diff --git a/.c8rc b/.c8rc index 040f2035d04c..cbba04c3f766 100644 --- a/.c8rc +++ b/.c8rc @@ -1,13 +1,5 @@ { - "include": [ - "bin/**/*.js", - "conf/**/*.js", - "lib/**/*.js" - ], - "reporter": [ - "lcov", - "text-summary", - "cobertura" - ], - "sourceMap": true + "include": ["bin/**/*.js", "conf/**/*.js", "lib/**/*.js"], + "reporter": ["lcov", "text-summary", "cobertura"], + "sourceMap": true } diff --git a/.codeclimate.yml b/.codeclimate.yml index 093f5ca853b9..5b432554c0c1 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,9 +1,9 @@ languages: - JavaScript: true + JavaScript: true exclude_paths: - - tests/** + - tests/** engines: - eslint: - enabled: true + eslint: + enabled: true diff --git a/.editorconfig b/.editorconfig index 646b08e97fca..a1434423c3e5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,16 +1,20 @@ root = true [*] -indent_style = space +indent_style = tab indent_size = 4 trim_trailing_whitespace = true end_of_line = lf insert_final_newline = true +[{*.{json,jsonc,json5},.c8rc}] +indent_style = space +indent_size = 2 + [docs/rules/linebreak-style.md] end_of_line = disabled -[{docs/rules/{indent.md,no-mixed-spaces-and-tabs.md}] +[docs/rules/{indent.md,no-mixed-spaces-and-tabs.md}] indent_style = disabled indent_size = disabled diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 7757f21bd2b5..000000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,200 +0,0 @@ -/* - * IMPORTANT! - * - * Any changes made to this file must also be made to eslint.config.js. - * - * Internally, ESLint is using the eslint.config.js file to lint itself. - * This file is needed too, because: - * - * 1. ESLint VS Code extension expects eslintrc config files to be - * present to work correctly. - * - * Once we no longer need to support both eslintrc and flat config, we will - * remove this file. - */ - - -"use strict"; - -const path = require("path"); - -const INTERNAL_FILES = { - CLI_ENGINE_PATTERN: "lib/cli-engine/**/*", - LINTER_PATTERN: "lib/linter/**/*", - RULE_TESTER_PATTERN: "lib/rule-tester/**/*", - RULES_PATTERN: "lib/rules/**/*", - SOURCE_CODE_PATTERN: "lib/source-code/**/*" -}; - -/** - * Resolve an absolute path or glob pattern. - * @param {string} pathOrPattern the path or glob pattern. - * @returns {string} The resolved path or glob pattern. - */ -function resolveAbsolutePath(pathOrPattern) { - return path.resolve(__dirname, pathOrPattern); -} - -/** - * Create an array of `no-restricted-require` entries for ESLint's core files. - * @param {string} [pattern] The glob pattern to create the entries for. - * @returns {Object[]} The array of `no-restricted-require` entries. - */ -function createInternalFilesPatterns(pattern = null) { - return Object.values(INTERNAL_FILES) - .filter(p => p !== pattern) - .map(p => ({ - name: [ - - // Disallow all children modules. - resolveAbsolutePath(p), - - // Allow the main `index.js` module. - `!${resolveAbsolutePath(p.replace(/\*\*\/\*$/u, "index.js"))}` - ] - })); -} - -module.exports = { - root: true, - plugins: [ - "eslint-plugin", - "internal-rules" - ], - extends: [ - "eslint/eslintrc" - ], - parserOptions: { - ecmaVersion: 2021 - }, - rules: { - "internal-rules/multiline-comment-style": "error" - }, - overrides: [ - { - files: ["tools/*.js", "docs/tools/*.js"], - rules: { - "no-console": "off", - "n/no-process-exit": "off" - } - }, - { - files: ["lib/rules/*", "tools/internal-rules/*"], - excludedFiles: ["index.js"], - extends: [ - "plugin:eslint-plugin/rules-recommended" - ], - rules: { - "eslint-plugin/prefer-placeholders": "error", - "eslint-plugin/prefer-replace-text": "error", - "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"], - "eslint-plugin/require-meta-docs-description": ["error", { pattern: "^(Enforce|Require|Disallow) .+[^. ]$" }], - "internal-rules/no-invalid-meta": "error" - } - }, - { - files: ["lib/rules/*"], - excludedFiles: ["index.js"], - rules: { - "eslint-plugin/require-meta-docs-url": ["error", { pattern: "https://eslint.org/docs/latest/rules/{{name}}" }] - } - }, - { - files: ["tests/lib/rules/*", "tests/tools/internal-rules/*"], - extends: [ - "plugin:eslint-plugin/tests-recommended" - ], - rules: { - "eslint-plugin/test-case-property-ordering": "error", - "eslint-plugin/test-case-shorthand-strings": "error" - } - }, - { - files: ["tests/**/*"], - env: { mocha: true }, - rules: { - "no-restricted-syntax": ["error", { - selector: "CallExpression[callee.object.name='assert'][callee.property.name='doesNotThrow']", - message: "`assert.doesNotThrow()` should be replaced with a comment next to the code." - }] - } - }, - - // Restrict relative path imports - { - files: ["lib/*"], - excludedFiles: ["lib/unsupported-api.js"], - rules: { - "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns() - ]] - } - }, - { - files: [INTERNAL_FILES.CLI_ENGINE_PATTERN], - rules: { - "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns(INTERNAL_FILES.CLI_ENGINE_PATTERN) - ]] - } - }, - { - files: [INTERNAL_FILES.LINTER_PATTERN], - rules: { - "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns(INTERNAL_FILES.LINTER_PATTERN), - "fs", - resolveAbsolutePath("lib/cli-engine/index.js"), - resolveAbsolutePath("lib/rule-tester/index.js") - ]] - } - }, - { - files: [INTERNAL_FILES.RULES_PATTERN], - rules: { - "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns(INTERNAL_FILES.RULES_PATTERN), - "fs", - resolveAbsolutePath("lib/cli-engine/index.js"), - resolveAbsolutePath("lib/linter/index.js"), - resolveAbsolutePath("lib/rule-tester/index.js"), - resolveAbsolutePath("lib/source-code/index.js") - ]] - } - }, - { - files: ["lib/shared/**/*"], - rules: { - "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns(), - resolveAbsolutePath("lib/cli-engine/index.js"), - resolveAbsolutePath("lib/linter/index.js"), - resolveAbsolutePath("lib/rule-tester/index.js"), - resolveAbsolutePath("lib/source-code/index.js") - ]] - } - }, - { - files: [INTERNAL_FILES.SOURCE_CODE_PATTERN], - rules: { - "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns(INTERNAL_FILES.SOURCE_CODE_PATTERN), - "fs", - resolveAbsolutePath("lib/cli-engine/index.js"), - resolveAbsolutePath("lib/linter/index.js"), - resolveAbsolutePath("lib/rule-tester/index.js"), - resolveAbsolutePath("lib/rules/index.js") - ]] - } - }, - { - files: [INTERNAL_FILES.RULE_TESTER_PATTERN], - rules: { - "n/no-restricted-require": ["error", [ - ...createInternalFilesPatterns(INTERNAL_FILES.RULE_TESTER_PATTERN), - resolveAbsolutePath("lib/cli-engine/index.js") - ]] - } - } - ] -}; diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000000..b2c5de714332 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# #19355 chore: formatted files with Prettier via trunk fmt +129882d2fdb4e7f597ed78eeadd86377f3d6b078 diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 101bbcd2c9d2..fd2be686c8d3 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -2,86 +2,96 @@ name: "\U0001F41E Report a problem" description: "Report an issue with ESLint or rules bundled with ESLint" title: "Bug: (fill in)" labels: - - bug - - "repro:needed" + - bug + - "repro:needed" body: -- type: markdown - attributes: - value: By opening an issue, you agree to abide by the [Open JS Foundation Code of Conduct](https://eslint.org/conduct). -- type: textarea - attributes: - label: Environment - description: | - Please tell us about how you're running ESLint (Run `npx eslint --env-info`.) - value: | - Node version: - npm version: - Local ESLint version: - Global ESLint version: - Operating System: - validations: - required: true -- type: dropdown - attributes: - label: What parser are you using? - description: | - Please keep in mind that some problems are parser-specific. - options: - - "Default (Espree)" - - "@typescript-eslint/parser" - - "@babel/eslint-parser" - - "vue-eslint-parser" - - "@angular-eslint/template-parser" - - Other - validations: - required: true -- type: textarea - attributes: - label: What did you do? - description: | - Please include a *minimal* reproduction case. If possible, include a link to a reproduction of the problem in the [ESLint demo](https://eslint.org/demo). Otherwise, include source code, configuration file(s), and any other information about how you're using ESLint. You can use Markdown in this field. - value: | -
- Configuration + - type: markdown + attributes: + value: By opening an issue, you agree to abide by the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). + - type: textarea + id: environment + attributes: + label: Environment + description: | + Please tell us about how you're running ESLint (Run `npx eslint --env-info`.) + value: | + Node version: + npm version: + Local ESLint version: + Global ESLint version: + Operating System: + validations: + required: true + - type: dropdown + id: parser + attributes: + label: What parser are you using? + description: | + Please keep in mind that some problems are parser-specific. + options: + - "Default (Espree)" + - "@typescript-eslint/parser" + - "@babel/eslint-parser" + - "vue-eslint-parser" + - "@angular-eslint/template-parser" + - Other + validations: + required: true + - type: textarea + id: description + attributes: + label: What did you do? + description: | + Please include a *minimal* reproduction case. If possible, include a link to a reproduction of the problem in the [ESLint demo](https://eslint.org/demo). Otherwise, include source code, configuration file(s), and any other information about how you're using ESLint. You can use Markdown in this field. + value: | +
+ Configuration - ``` - - ``` -
+ ```js + + ``` +
- ```js - - ``` - validations: - required: true -- type: textarea - attributes: - label: What did you expect to happen? - description: | - You can use Markdown in this field. - validations: - required: true -- type: textarea - attributes: - label: What actually happened? - description: | - Please copy-paste the actual ESLint output. You can use Markdown in this field. - validations: - required: true -- type: input - attributes: - label: Link to Minimal Reproducible Example - description: 'Link to a [playground](https://eslint.org/play), [StackBlitz](https://stackblitz.com), or GitHub repo with a minimal reproduction of the problem. **A minimal reproduction is required** so that others can help debug your issue. If a report is vague (e.g. just a generic error message) and has no reproduction, it may be auto-closed.' - placeholder: 'https://stackblitz.com/abcd1234' - validations: - required: true -- type: checkboxes - attributes: - label: Participation - options: - - label: I am willing to submit a pull request for this issue. - required: false -- type: textarea - attributes: - label: Additional comments - description: Is there anything else that's important for the team to know? + ```js + + ``` + validations: + required: true + - type: textarea + id: expectation + attributes: + label: What did you expect to happen? + description: | + You can use Markdown in this field. + validations: + required: true + - type: textarea + id: lint-output + attributes: + label: What actually happened? + description: | + Please copy-paste the actual ESLint output. You can use Markdown in this field. + validations: + required: true + - type: input + id: repro-url + attributes: + label: Link to Minimal Reproducible Example + description: "Link to a [playground](https://eslint.org/play), [StackBlitz](https://stackblitz.com), or GitHub repo with a minimal reproduction of the problem. **A minimal reproduction is required** so that others can help debug your issue. If a report is vague (e.g. just a generic error message) and has no reproduction, it may be auto-closed." + placeholder: "https://stackblitz.com/abcd1234" + validations: + required: true + - type: checkboxes + attributes: + label: Participation + options: + - label: I am willing to submit a pull request for this issue. + required: false + - type: markdown + attributes: + value: Please **do not** open a pull request until this issue has been accepted by the team. + - type: textarea + id: comments + attributes: + label: Additional comments + description: Is there anything else that's important for the team to know? diff --git a/.github/ISSUE_TEMPLATE/change.yml b/.github/ISSUE_TEMPLATE/change.yml index 675d5e4ca237..176b89065001 100644 --- a/.github/ISSUE_TEMPLATE/change.yml +++ b/.github/ISSUE_TEMPLATE/change.yml @@ -2,46 +2,49 @@ name: "\U0001F680 Request a change (not rule-related)" description: "Request a change that is not a bug fix, rule change, or new rule" title: "Change Request: (fill in)" labels: - - enhancement - - core + - enhancement + - core body: -- type: markdown - attributes: - value: By opening an issue, you agree to abide by the [Open JS Foundation Code of Conduct](https://eslint.org/conduct). -- type: input - attributes: - label: ESLint version - description: | - What version of ESLint are you currently using? (Run `npx eslint --version`.) - placeholder: | - e.g. v8.0.0 - validations: - required: true -- type: textarea - attributes: - label: What problem do you want to solve? - description: | - Please explain your use case in as much detail as possible. - placeholder: | - ESLint currently... - validations: - required: true -- type: textarea - attributes: - label: What do you think is the correct solution? - description: | - Please explain how you'd like to change ESLint to address the problem. - placeholder: | - I'd like ESLint to... - validations: - required: true -- type: checkboxes - attributes: - label: Participation - options: - - label: I am willing to submit a pull request for this change. - required: false -- type: textarea - attributes: - label: Additional comments - description: Is there anything else that's important for the team to know? + - type: markdown + attributes: + value: By opening an issue, you agree to abide by the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). + - type: input + attributes: + label: ESLint version + description: | + What version of ESLint are you currently using? (Run `npx eslint --version`.) + placeholder: | + e.g. v8.0.0 + validations: + required: true + - type: textarea + attributes: + label: What problem do you want to solve? + description: | + Please explain your use case in as much detail as possible. + placeholder: | + ESLint currently... + validations: + required: true + - type: textarea + attributes: + label: What do you think is the correct solution? + description: | + Please explain how you'd like to change ESLint to address the problem. + placeholder: | + I'd like ESLint to... + validations: + required: true + - type: checkboxes + attributes: + label: Participation + options: + - label: I am willing to submit a pull request for this change. + required: false + - type: markdown + attributes: + value: Please **do not** open a pull request until this issue has been accepted by the team. + - type: textarea + attributes: + label: Additional comments + description: Is there anything else that's important for the team to know? diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 1e20844c21fe..3dd494ac924e 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,11 +1,11 @@ blank_issues_enabled: false contact_links: - - name: đŸ—Ŗ Ask a Question, Discuss - url: https://github.com/eslint/eslint/discussions - about: Get help using ESLint - - name: 📝 Help with VS Code ESLint - url: https://github.com/microsoft/vscode-eslint/issues/ - about: Bugs and feature requests for the VS Code ESLint plugin - - name: Discord Server - url: https://eslint.org/chat - about: Talk with the team + - name: đŸ—Ŗ Ask a Question, Discuss + url: https://github.com/eslint/eslint/discussions + about: Get help using ESLint + - name: 📝 Help with VS Code ESLint + url: https://github.com/microsoft/vscode-eslint/issues/ + about: Bugs and feature requests for the VS Code ESLint plugin + - name: Discord Server + url: https://eslint.org/chat + about: Talk with the team diff --git a/.github/ISSUE_TEMPLATE/docs.yml b/.github/ISSUE_TEMPLATE/docs.yml index 88ed3c1440d5..b94e9ed4ba7b 100644 --- a/.github/ISSUE_TEMPLATE/docs.yml +++ b/.github/ISSUE_TEMPLATE/docs.yml @@ -2,45 +2,48 @@ name: "\U0001F4DD Docs" description: "Request an improvement to documentation" title: "Docs: (fill in)" labels: - - documentation + - documentation body: -- type: markdown - attributes: - value: By opening an issue, you agree to abide by the [Open JS Foundation Code of Conduct](https://eslint.org/conduct). -- type: textarea - attributes: - label: Docs page(s) - description: | - What page(s) are you suggesting be changed or created? - placeholder: | - e.g. https://eslint.org/docs/latest/use/getting-started - validations: - required: true -- type: textarea - attributes: - label: What documentation issue do you want to solve? - description: | - Please explain your issue in as much detail as possible. - placeholder: | - The ESLint docs currently... - validations: - required: true -- type: textarea - attributes: - label: What do you think is the correct solution? - description: | - Please explain how you'd like to change the ESLint docs to address the problem. - placeholder: | - I'd like the ESLint docs to... - validations: - required: true -- type: checkboxes - attributes: - label: Participation - options: - - label: I am willing to submit a pull request for this change. - required: false -- type: textarea - attributes: - label: Additional comments - description: Is there anything else that's important for the team to know? + - type: markdown + attributes: + value: By opening an issue, you agree to abide by the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). + - type: textarea + attributes: + label: Docs page(s) + description: | + What page(s) are you suggesting be changed or created? + placeholder: | + e.g. https://eslint.org/docs/latest/use/getting-started + validations: + required: true + - type: textarea + attributes: + label: What documentation issue do you want to solve? + description: | + Please explain your issue in as much detail as possible. + placeholder: | + The ESLint docs currently... + validations: + required: true + - type: textarea + attributes: + label: What do you think is the correct solution? + description: | + Please explain how you'd like to change the ESLint docs to address the problem. + placeholder: | + I'd like the ESLint docs to... + validations: + required: true + - type: checkboxes + attributes: + label: Participation + options: + - label: I am willing to submit a pull request for this change. + required: false + - type: markdown + attributes: + value: Please **do not** open a pull request until this issue has been accepted by the team. + - type: textarea + attributes: + label: Additional comments + description: Is there anything else that's important for the team to know? diff --git a/.github/ISSUE_TEMPLATE/new-rule.yml b/.github/ISSUE_TEMPLATE/new-rule.yml index 4c4463466eda..15fd1aad7a78 100644 --- a/.github/ISSUE_TEMPLATE/new-rule.yml +++ b/.github/ISSUE_TEMPLATE/new-rule.yml @@ -2,52 +2,55 @@ name: "\U0001F680 Propose a new core rule" description: "Propose a new rule to be added to the ESLint core" title: "New Rule: (fill in)" labels: - - rule - - feature + - rule + - feature body: -- type: markdown - attributes: - value: By opening an issue, you agree to abide by the [Open JS Foundation Code of Conduct](https://eslint.org/conduct). -- type: input - attributes: - label: Rule details - description: What should the new rule do? - validations: - required: true -- type: input - attributes: - label: Related ECMAScript feature - description: What new ECMAScript feature does this rule relate to? Note that we only accept new core rules related to new ECMAScript features. - validations: - required: true -- type: dropdown - attributes: - label: What type of rule is this? - options: - - Warns about a potential problem - - Suggests an alternate way of doing something - validations: - required: true -- type: textarea - attributes: - label: Example code - description: Please provide some example JavaScript code that this rule will warn about. This field will render as JavaScript. - render: js - validations: - required: true -- type: textarea - attributes: - label: Why should this rule be in the core instead of a plugin? - description: In general, we prefer that rules be implemented in plugins where they can be tailored to your specific use case. - validations: - required: true -- type: checkboxes - attributes: - label: Participation - options: - - label: I am willing to submit a pull request to implement this rule. - required: false -- type: textarea - attributes: - label: Additional comments - description: Is there anything else that's important for the team to know? + - type: markdown + attributes: + value: By opening an issue, you agree to abide by the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). + - type: input + attributes: + label: Rule details + description: What should the new rule do? + validations: + required: true + - type: input + attributes: + label: Related ECMAScript feature + description: What new ECMAScript feature does this rule relate to? Note that we only accept new core rules related to new ECMAScript features. + validations: + required: true + - type: dropdown + attributes: + label: What type of rule is this? + options: + - Warns about a potential problem + - Suggests an alternate way of doing something + validations: + required: true + - type: textarea + attributes: + label: Example code + description: Please provide some example JavaScript code that this rule will warn about. This field will render as JavaScript. + render: js + validations: + required: true + - type: textarea + attributes: + label: Why should this rule be in the core instead of a plugin? + description: In general, we prefer that rules be implemented in plugins where they can be tailored to your specific use case. + validations: + required: true + - type: checkboxes + attributes: + label: Participation + options: + - label: I am willing to submit a pull request to implement this rule. + required: false + - type: markdown + attributes: + value: Please **do not** open a pull request until this issue has been accepted by the team. + - type: textarea + attributes: + label: Additional comments + description: Is there anything else that's important for the team to know? diff --git a/.github/ISSUE_TEMPLATE/new-syntax.yml b/.github/ISSUE_TEMPLATE/new-syntax.yml index 467ff0f2b957..3d36d3f6c152 100644 --- a/.github/ISSUE_TEMPLATE/new-syntax.yml +++ b/.github/ISSUE_TEMPLATE/new-syntax.yml @@ -1,60 +1,63 @@ name: "\U0001F4DD Request for new syntax support" description: "Request new stage 4 syntax be supported." labels: - - core - - new syntax + - core + - new syntax body: -- type: markdown - attributes: - value: By opening an issue, you agree to abide by the [Open JS Foundation Code of Conduct](https://eslint.org/conduct). -- type: input - attributes: - label: Syntax name - description: What is the name of the syntax to implement? - placeholder: e.g. "class fields" - validations: - required: true -- type: textarea - attributes: - label: Syntax proposal URL - description: Please provide the TC39 URL for the syntax proposal. - placeholder: e.g. https://github.com/tc39/proposal-top-level-await - validations: - required: true -- type: textarea - attributes: - label: Example code - description: Please provide some example code for the new syntax. - render: js - validations: - required: true -- type: checkboxes - attributes: - label: Implementation Checklist - description: | - Please check off all items that have already been completed. Be sure to paste the pull request URLs next to each item so we can verify the work as done. - options: - - label: "Ecma262 update: " - required: false - - label: "ESTree update: " - required: false - - label: "Acorn update: " - required: false - - label: "`eslint-visitor-keys` update: " - required: false - - label: "`espree` update: " - required: false - - label: "`eslint-scope` update: " - required: false - - label: "`eslint` update: " - required: false -- type: checkboxes - attributes: - label: Participation - options: - - label: I am willing to submit a pull request for this change. - required: false -- type: textarea - attributes: - label: Additional comments - description: Is there anything else that's important for the team to know? + - type: markdown + attributes: + value: By opening an issue, you agree to abide by the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). + - type: input + attributes: + label: Syntax name + description: What is the name of the syntax to implement? + placeholder: e.g. "class fields" + validations: + required: true + - type: textarea + attributes: + label: Syntax proposal URL + description: Please provide the TC39 URL for the syntax proposal. + placeholder: e.g. https://github.com/tc39/proposal-top-level-await + validations: + required: true + - type: textarea + attributes: + label: Example code + description: Please provide some example code for the new syntax. + render: js + validations: + required: true + - type: checkboxes + attributes: + label: Implementation Checklist + description: | + Please check off all items that have already been completed. Be sure to paste the pull request URLs next to each item so we can verify the work as done. + options: + - label: "Ecma262 update: " + required: false + - label: "ESTree update: " + required: false + - label: "Acorn update: " + required: false + - label: "`eslint-visitor-keys` update: " + required: false + - label: "`espree` update: " + required: false + - label: "`eslint-scope` update: " + required: false + - label: "`eslint` update: " + required: false + - type: checkboxes + attributes: + label: Participation + options: + - label: I am willing to submit a pull request for this change. + required: false + - type: markdown + attributes: + value: Please **do not** open a pull request until this issue has been accepted by the team. + - type: textarea + attributes: + label: Additional comments + description: Is there anything else that's important for the team to know? diff --git a/.github/ISSUE_TEMPLATE/rule-change.yml b/.github/ISSUE_TEMPLATE/rule-change.yml index 2706a1180ca3..e85447fab9f2 100644 --- a/.github/ISSUE_TEMPLATE/rule-change.yml +++ b/.github/ISSUE_TEMPLATE/rule-change.yml @@ -2,60 +2,63 @@ name: "\U0001F4DD Request a rule change" description: "Request a change to an existing core rule" title: "Rule Change: (fill in)" labels: - - enhancement - - rule + - enhancement + - rule body: -- type: markdown - attributes: - value: By opening an issue, you agree to abide by the [Open JS Foundation Code of Conduct](https://eslint.org/conduct). -- type: input - attributes: - label: What rule do you want to change? - validations: - required: true -- type: dropdown - attributes: - label: What change do you want to make? - options: - - Generate more warnings - - Generate fewer warnings - - Implement autofix - - Implement suggestions - validations: - required: true -- type: dropdown - attributes: - label: How do you think the change should be implemented? - options: - - A new option - - A new default behavior - - Other - validations: - required: true -- type: textarea - attributes: - label: Example code - description: Please provide some example code that this change will affect. This field will render as JavaScript. - render: js - validations: - required: true -- type: textarea - attributes: - label: What does the rule currently do for this code? - validations: - required: true -- type: textarea - attributes: - label: What will the rule do after it's changed? - validations: - required: true -- type: checkboxes - attributes: - label: Participation - options: - - label: I am willing to submit a pull request to implement this change. - required: false -- type: textarea - attributes: - label: Additional comments - description: Is there anything else that's important for the team to know? + - type: markdown + attributes: + value: By opening an issue, you agree to abide by the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). + - type: input + attributes: + label: What rule do you want to change? + validations: + required: true + - type: dropdown + attributes: + label: What change do you want to make? + options: + - Generate more warnings + - Generate fewer warnings + - Implement autofix + - Implement suggestions + validations: + required: true + - type: dropdown + attributes: + label: How do you think the change should be implemented? + options: + - A new option + - A new default behavior + - Other + validations: + required: true + - type: textarea + attributes: + label: Example code + description: Please provide some example code that this change will affect. This field will render as JavaScript. + render: js + validations: + required: true + - type: textarea + attributes: + label: What does the rule currently do for this code? + validations: + required: true + - type: textarea + attributes: + label: What will the rule do after it's changed? + validations: + required: true + - type: checkboxes + attributes: + label: Participation + options: + - label: I am willing to submit a pull request to implement this change. + required: false + - type: markdown + attributes: + value: Please **do not** open a pull request until this issue has been accepted by the team. + - type: textarea + attributes: + label: Additional comments + description: Is there anything else that's important for the team to know? diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 35d241001913..5fd747b29061 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,7 @@ #### Prerequisites checklist diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000000..fd7a184cdd7b --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,88 @@ +# ESLint Project Knowledge + +## Project Structure + +- The ESLint project is organized into several directories: + - `bin`: Contains the command-line interface (CLI) files + - `conf`: Contains configuration data for ESLint + - `docs`: Contains the documentation website for ESLint + - `lib`: Contains the main source code for ESLint + - `messages`: Contains verbose error messages for certain errors in ESLint + - `packages`: Contains additional packages that are published separately + - `eslint-config-eslint`: Contains the ESLint configuration package + - `js`: Contains the `@eslint/js` package + - `templates`: Contains templates tools that generate files + - `tests`: Contains the test files for ESLint + - `tools`: Contains scripts for building, testing, and other tasks +- Test files mirror the structure of the source code files they're testing +- Configuration files are in the root directory, including `eslint.config.js`. + +## Source File Conventions + +- Source files follow a standard structure: + - Header with `@fileoverview` and `@author` + - Requirements section with necessary imports + - Optionally a type definitions section for importing types + - Optionally a helpers section with utility functions and constants + - Optionally an exports section where the file has its classes, functions, and constants to export + - For tools and scripts, a main section that executes code + +## Testing Conventions + +- Tests use Mocha for the testing framework +- Chai is used for assertions with `const assert = require("chai").assert` +- Test files follow a standard structure: + - Header with `@fileoverview` and `@author` + - Requirements section with necessary imports + - Optionally a helpers section with utility functions and constants + - Tests section with describe/it blocks +- Tests are organized in describe blocks for classes and methods +- Before running tests, objects like mock contexts/configs are set up +- Testing patterns include: + - Verifying object properties are set correctly + - Testing for expected behaviors and edge cases + - Validating error handling scenarios + - Testing deprecated methods for backward compatibility +- All new exported functions and public class members require writing new tests +- All bug fixes must have corresponding tests +- Never delete existing tests even if they are failing +- `npm test` command runs all tests in the `tests` directory +- `npx mocha ` command runs a specific test file + +## Rules + +- Rules are located in the `lib/rules` directory +- Documentation for rules is located in the `docs/src/rules` directory +- Each rule module exports an object with the following structure: + - `meta`: Contains metadata about the rule, including: + - `type`: The type of rule (e.g., "suggestion", "problem", "layout") + - `docs`: Documentation properties including description, recommended status, and URL + - `schema`: JSON Schema for rule configuration options + - `fixable`: Whether the rule provides auto-fixes (e.g., "code", "whitespace") + - `messages`: Message IDs and text templates for reporting + - `create`: A function that accepts a context object and returns an object with AST visitor methods + +## Rule Implementation Patterns + +- Rules use the visitor pattern to analyze JavaScript AST nodes +- Helper functions should be defined outside the `create` function to avoid recreating them on each execution +- Common utilities for working with ASTs are available in `./utils/ast-utils` +- Rules that need to fix code should implement a fixer function that returns corrections to apply +- Rules should avoid duplicate computations by factoring out common checks into helper functions +- The rule tester configuration now uses flat configuration format (`languageOptions` instead of `parserOptions`) + +## Rule Documentation + +- Documentation files use frontmatter with `title` and `rule_type` fields +- Rule documentation should include: + - A description of what the rule checks + - The rule details section explaining when the rule reports issues + - Examples of incorrect and correct code wrapped in ::: incorrect and ::: correct blocks + - A "When Not To Use It" section explaining when the rule might not be appropriate + - Optional version information and additional resources +- Code examples in the documentation should include the enabling comment (e.g., `/*eslint rule-name: "error"*/`) + +## Rules Registration + +- New rules must be added to the `lib/rules/index.js` file to be available in ESLint +- Rules are registered in an alphabetically sorted object using the `LazyLoadingRuleMap` for efficient loading diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 4c39a334be4f..da2359457948 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,8 @@ version: 2 updates: - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "weekly" - commit-message: - prefix: "ci" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "ci" diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 000000000000..1da4d3fa6555 --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,40 @@ +# Skipping this due to https://github.com/actions/labeler/issues/763 +#documentation: +#- any: +# - changed-files: +# - all-globs-to-all-files: ['docs/**', '!lib/rules/**'] + +rule: + - any: + - changed-files: + - any-glob-to-any-file: ["lib/rules/**"] + +cli: + - any: + - changed-files: + - any-glob-to-any-file: + [ + "lib/cli.js", + "lib/options.js", + "lib/cli-engine/**", + "lib/eslint/**", + ] + +core: + - any: + - changed-files: + - any-glob-to-any-file: + [ + "lib/{config,eslint,linter,rule-tester,source-code}/**", + "lib/api.js", + ] + +formatter: + - any: + - changed-files: + - any-glob-to-any-file: ["lib/cli-engine/formatters/**"] + +"github actions": + - any: + - changed-files: + - any-glob-to-any-file: [".github/workflows/**"] diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 8163f03867c8..ba90fa33a076 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,42 +1,45 @@ { - "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": [ - "config:recommended", - ":approveMajorUpdates", - ":semanticCommitScopeDisabled" - ], - "ignorePresets": [":semanticPrefixFixDepsChoreOthers"], - "labels": ["dependencies"], + $schema: "https://docs.renovatebot.com/renovate-schema.json", + extends: [ + "config:recommended", + ":approveMajorUpdates", + ":semanticCommitScopeDisabled", + ], + ignorePresets: [":semanticPrefixFixDepsChoreOthers"], + labels: ["dependencies"], + ignoreDeps: ["jiti-v2.0", "jiti-v2.1"], - // Wait well over npm's three day window for any new package as a precaution against malicious publishes - // https://docs.npmjs.com/policies/unpublish/#packages-published-less-than-72-hours-ago - "minimumReleaseAge": "7 days", + // Wait well over npm's three day window for any new package as a precaution against malicious publishes + // https://docs.npmjs.com/policies/unpublish/#packages-published-less-than-72-hours-ago + minimumReleaseAge: "7 days", - "packageRules": [ - { - "description": "Use the deps:actions label for github-action manager updates (this means Renovate's github-action manager).", - "addLabels": ["deps:actions"], - "matchManagers": ["github-actions"] - }, - { - "description": "Use the deps:npm label for npm manager packages (this means Renovate's npm manager).", - "addLabels": ["deps:npm"], - "matchManagers": ["npm"] - }, - { - "description": "Group Babel packages into a single PR.", - "groupName": "babel", - "matchPackagePrefixes": ["@babel", "babel-"] - }, - { - "description": "Group wdio packages into a single PR.", - "groupName": "wdio", - "matchPackagePrefixes": ["@wdio"] - }, - { - "description": "Group metascraper packages into a single PR.", - "groupName": "metascraper", - "matchPackagePrefixes": ["metascraper"] - } - ] + packageRules: [ + { + description: "Use the deps:actions label for github-action manager updates (this means Renovate's github-action manager).", + addLabels: ["deps:actions"], + matchManagers: ["github-actions"], + }, + { + description: "Use the deps:npm label for npm manager packages (this means Renovate's npm manager).", + addLabels: ["deps:npm"], + matchManagers: ["npm"], + }, + { + description: "Group Babel packages into a single PR.", + groupName: "babel", + matchPackagePrefixes: ["@babel", "babel-"], + }, + { + description: "Update ESLint packages together.", + groupName: "eslint", + matchPackagePrefixes: ["@eslint/"], + matchPackageNames: ["espree", "eslint-scope", "eslint-visitor-keys"], + minimumReleaseAge: null, // Don't wait for these packages + }, + { + description: "Group metascraper packages into a single PR.", + groupName: "metascraper", + matchPackagePrefixes: ["metascraper"], + }, + ], } diff --git a/.github/workflows/annotate_pr.yaml b/.github/workflows/annotate_pr.yaml new file mode 100644 index 000000000000..a3047eadb444 --- /dev/null +++ b/.github/workflows/annotate_pr.yaml @@ -0,0 +1,25 @@ +name: Annotate PR with trunk issues + +on: + workflow_run: + workflows: [Pull Request] + types: [completed] + +permissions: read-all + +jobs: + trunk_check: + name: Trunk Check Annotate + runs-on: ubuntu-latest + + permissions: + checks: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Trunk Check + uses: trunk-io/trunk-action@v1 + with: + post-annotations: true # only for fork PRs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff0f1953dccb..7129baca809b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,88 +1,151 @@ name: CI on: - push: - branches: [main] - pull_request: - branches: [main] + push: + branches: [main] + pull_request: + branches: [main] permissions: - contents: read + contents: read jobs: - verify_files: - name: Verify Files - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 'lts/*' - - name: Install Packages - run: npm install - - name: Lint Files - run: node Makefile lint - - name: Check Rule Files - run: node Makefile checkRuleFiles - - name: Check Licenses - run: node Makefile checkLicenses - - name: Install Docs Packages - working-directory: docs - run: npm install - - name: Stylelint Docs - working-directory: docs - run: npm run lint:scss - - name: Lint Docs JS Files - run: node Makefile lintDocsJS - - name: Check Rule Examples - run: node Makefile checkRuleExamples - - name: Build Docs Website - working-directory: docs - run: npm run build - - name: Validate internal links - working-directory: docs - run: npm run lint:links - - test_on_node: - name: Test - strategy: - matrix: - os: [ubuntu-latest] - node: [21.x, 20.x, 19.x, 18.x, 17.x, 16.x, 14.x, 12.x, "12.22.0"] - include: - - os: windows-latest - node: "lts/*" - - os: macOS-latest - node: "lts/*" - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node }} - - name: Install Packages - run: npm install - - name: Test - run: node Makefile mocha - - name: Fuzz Test - run: node Makefile fuzz - - test_on_browser: - name: Browser Test - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: '16' - - name: Install Packages - run: npm install - - name: Test - run: node Makefile wdio - - name: Fuzz Test - run: node Makefile fuzz - - uses: actions/upload-artifact@v3 - if: failure() - with: - name: logs - path: | - wdio-logs/*.log + verify_files: + name: Verify Files + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "lts/*" + - name: Install Packages + run: npm install + + - name: Install Docs Packages + working-directory: docs + run: npm install + + - name: Lint Files (eslint) + uses: trunk-io/trunk-action@v1 + with: + # Run on everything except the docs folder. + arguments: --ignore=docs/** --filter=eslint + check-mode: all + + - name: Lint Files (other) + uses: trunk-io/trunk-action@v1 + with: + # Run on everything except the docs folder. + arguments: --ignore=docs/** --filter=-eslint + + - name: Check Rule Files + run: node Makefile checkRuleFiles + + - name: Check Licenses + run: node Makefile checkLicenses + + - name: Lint Docs Files (eslint) + uses: trunk-io/trunk-action@v1 + with: + # Run only on the docs folder. + arguments: --ignore=** --ignore=!docs/** --filter=eslint + check-mode: all + + - name: Lint Docs Files (other) + uses: trunk-io/trunk-action@v1 + with: + # Run only on the docs folder. + arguments: --ignore=** --ignore=!docs/** --filter=-eslint + + - name: Check Rule Examples + run: node Makefile checkRuleExamples + + - name: Check Rule Types + run: npm run lint:rule-types + + - name: Lint Files, Dependencies, & Exports + run: npm run lint:unused + + test_on_node: + name: Test + strategy: + matrix: + os: [ubuntu-latest] + node: [24.x, 22.x, 20.x, 18.x, "18.18.0"] + NODE_OPTIONS: [""] + include: + - os: windows-latest + node: "lts/*" + - os: macOS-latest + node: "lts/*" + - os: ubuntu-latest + node: 24.x + + # `--experimental-strip-types` is enabled by default in Node.js 24.x. + # This additional environment is necessary only to test `--experimental-transform-types`, + # as it is not enabled by default in any Node.js version yet. + NODE_OPTIONS: "--experimental-transform-types" + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + - name: Install Packages + run: npm install + - name: Test + env: + NODE_OPTIONS: ${{ matrix.NODE_OPTIONS }} + run: node Makefile mocha + - name: Fuzz Test + run: node Makefile fuzz + - name: Test EMFILE Handling + run: npm run test:emfile + + test_on_browser: + name: Browser Test + runs-on: ubuntu-latest + env: + TERM: xterm-256color + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" # Should be the same as the version used on Netlify to build the ESLint Playground + - name: Install Packages + run: npm install + - name: Test + run: node Makefile cypress + - name: Fuzz Test + run: node Makefile fuzz + + test_types: + name: Test Types of ${{ matrix.package.name }} + runs-on: ubuntu-latest + + strategy: + matrix: + package: + [ + { name: eslint, directory: . }, + { + name: eslint-config-eslint, + directory: packages/eslint-config-eslint, + }, + { name: "@eslint/js", directory: packages/js }, + ] + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "lts/*" + - name: Install Packages + run: npm install + + - name: Install Packages for ${{ matrix.package.name }} + working-directory: ${{ matrix.package.directory }} + run: npm install + + - name: Test types for ${{ matrix.package.name }} + working-directory: ${{ matrix.package.directory }} + run: npm run test:types diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 637f06e2e517..b746a6fb07de 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -12,60 +12,62 @@ name: "CodeQL" on: - push: - branches: [main] - pull_request: - # The branches below must be a subset of the branches above - branches: [main] - schedule: - - cron: '28 17 * * 5' + push: + branches: [main] + pull_request: + # The branches below must be a subset of the branches above + branches: [main] + schedule: + - cron: "28 17 * * 5" jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write - strategy: - fail-fast: false - matrix: - language: [ 'javascript' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + strategy: + fail-fast: false + matrix: + language: ["javascript"] - steps: - - name: Checkout repository - uses: actions/checkout@v4 + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + steps: + - name: Checkout repository + uses: actions/checkout@v4 - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 - #- run: | - # make bootstrap - # make release + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/docs-ci.yml b/.github/workflows/docs-ci.yml new file mode 100644 index 000000000000..6728bfcf1a6e --- /dev/null +++ b/.github/workflows/docs-ci.yml @@ -0,0 +1,43 @@ +name: CI +on: + push: + branches: [main] + paths: + - "docs/**" + + pull_request: + branches: [main] + paths: + - "docs/**" + +permissions: + contents: read + +jobs: + verify_files: + name: Verify Docs Files + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "lts/*" + + - name: Install Docs Packages + working-directory: docs + run: npm install + + - name: Install Packages + run: npm install + + - name: Stylelint Docs + working-directory: docs + run: npm run lint:scss + + - name: Build Docs Website + working-directory: docs + run: npm run build + + - name: Validate internal links + working-directory: docs + run: npm run lint:links diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml new file mode 100644 index 000000000000..cdcd28f96746 --- /dev/null +++ b/.github/workflows/pr-labeler.yml @@ -0,0 +1,12 @@ +name: "Pull Request Labeler" +on: pull_request_target +jobs: + labeler: + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/labeler@v5 + with: + sync-labels: true diff --git a/.github/workflows/rebuild-docs-sites.yml b/.github/workflows/rebuild-docs-sites.yml new file mode 100644 index 000000000000..0bd3be8877ae --- /dev/null +++ b/.github/workflows/rebuild-docs-sites.yml @@ -0,0 +1,17 @@ +name: Rebuild Docs Sites + +on: + push: + branches: [main] + paths: + - "docs/src/_data/versions.json" + +jobs: + rebuild: + name: "Trigger rebuild on Netlify" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: > + jq -r '.items | map(.branch) | join(",")' docs/src/_data/versions.json + | xargs -I{LIST} curl -X POST -d {} "${{ secrets.NETLIFY_DOCS_BUILD_HOOK }}?trigger_branch={{LIST}}" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 9e7355d34b99..80cd1aa11c0e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -6,27 +6,28 @@ name: Mark stale issues and pull requests on: - schedule: - - cron: '31 22 * * *' + schedule: + - cron: "31 22 * * *" -jobs: - stale: +permissions: read-all - runs-on: ubuntu-latest - permissions: - issues: write - pull-requests: write +jobs: + stale: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write - steps: - - uses: actions/stale@v8 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - days-before-issue-stale: 30 - days-before-pr-stale: 10 - days-before-close: 7 - stale-issue-message: 'Oops! It looks like we lost track of this issue. What do we want to do here? This issue will auto-close in 7 days without an update.' - close-issue-message: 'This issue was auto-closed due to inactivity. While we wish we could keep responding to every issue, we unfortunately don''t have the bandwidth and need to focus on high-value issues.' - stale-pr-message: 'Hi everyone, it looks like we lost track of this pull request. Please review and see what the next steps are. This pull request will auto-close in 7 days without an update.' - close-pr-message: 'This pull request was auto-closed due to inactivity. While we wish we could keep working on every request, we unfortunately don''t have the bandwidth to continue here and need to focus on other things. You can resubmit this pull request if you would like to continue working on it.' - exempt-all-assignees: true - exempt-issue-labels: accepted + steps: + - uses: actions/stale@v9 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + days-before-issue-stale: 30 + days-before-pr-stale: 10 + days-before-close: 7 + stale-issue-message: "Oops! It looks like we lost track of this issue. What do we want to do here? This issue will auto-close in 7 days without an update." + close-issue-message: "This issue was auto-closed due to inactivity. While we wish we could keep responding to every issue, we unfortunately don't have the bandwidth and need to focus on high-value issues." + stale-pr-message: "Hi everyone, it looks like we lost track of this pull request. Please review and see what the next steps are. This pull request will auto-close in 7 days without an update." + close-pr-message: "This pull request was auto-closed due to inactivity. While we wish we could keep working on every request, we unfortunately don't have the bandwidth to continue here and need to focus on other things. You can resubmit this pull request if you would like to continue working on it." + exempt-all-assignees: true + exempt-issue-labels: accepted diff --git a/.github/workflows/types-integration.yml b/.github/workflows/types-integration.yml new file mode 100644 index 000000000000..09bbfb3636c9 --- /dev/null +++ b/.github/workflows/types-integration.yml @@ -0,0 +1,209 @@ +name: CI +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + webpack_plugin: + name: Types (eslint-webpack-plugin) + runs-on: ubuntu-latest + steps: + - name: Checkout eslint + uses: actions/checkout@v4 + with: + path: eslint + + - name: Checkout eslint-webpack-plugin + uses: actions/checkout@v4 + with: + repository: webpack-contrib/eslint-webpack-plugin + path: webpack + + - uses: actions/setup-node@v4 + with: + node-version: "lts/*" + + - name: Install Packages (eslint) + working-directory: eslint + run: npm install + + - name: Install Packages (eslint-webpack-plugin) + working-directory: webpack + run: | + npm install --no-package-lock ../eslint + + - name: Run TSC + working-directory: webpack + run: npm run lint:types + + neostandard: + name: Types (neostandard) + runs-on: ubuntu-latest + steps: + - name: Checkout eslint + uses: actions/checkout@v4 + with: + path: eslint + + - name: Checkout neostandard + uses: actions/checkout@v4 + with: + repository: neostandard/neostandard + path: neostandard + + - uses: actions/setup-node@v4 + with: + node-version: "lts/*" + + - name: Install Packages (eslint) + working-directory: eslint + run: npm install + + - name: Install Packages (neostandard) + working-directory: neostandard + run: | + npm install --no-package-lock ../eslint + + - name: Run TSC + working-directory: neostandard + run: npm run check:tsc + + eslint-flat-config-utils: + name: Types (eslint-flat-config-utils) + runs-on: ubuntu-latest + steps: + - name: Checkout eslint + uses: actions/checkout@v4 + with: + path: eslint + + - name: Checkout eslint-flat-config-utils + uses: actions/checkout@v4 + with: + repository: antfu/eslint-flat-config-utils + path: antfu + + - uses: actions/setup-node@v4 + with: + node-version: "lts/*" + + - name: Install Packages (eslint) + working-directory: eslint + run: npm install + + - name: Install Packages (eslint-flat-config-utils) + working-directory: antfu + run: | + npm install ../eslint + + - name: Run TSC + working-directory: antfu + run: npm run typecheck + + eslint-visitor-keys: + name: Types (eslint-visitor-keys) + runs-on: ubuntu-latest + steps: + - name: Checkout eslint + uses: actions/checkout@v4 + with: + path: eslint + + - name: Checkout eslint/js + uses: actions/checkout@v4 + with: + repository: eslint/js + path: eslint-js + + - uses: actions/setup-node@v4 + with: + node-version: "lts/*" + + - name: Install Packages (eslint) + working-directory: eslint + run: npm install + + - name: Update package.json eslint-visitor-keys + uses: restackio/update-json-file-action@2.1 + with: + file: eslint-js/packages/eslint-visitor-keys/package.json + fields: '{"scripts.prepare": "npm run build:cjs"}' + + - name: Install Packages (eslint/js) + working-directory: eslint-js + run: | + npm install ../eslint + + - name: Run TSC + working-directory: eslint-js + run: npm run build:types --workspace eslint-visitor-keys + + eslint_json: + name: Types (@eslint/json) + runs-on: ubuntu-latest + steps: + - name: Checkout eslint + uses: actions/checkout@v4 + with: + path: eslint + + - name: Checkout @eslint/json + uses: actions/checkout@v4 + with: + repository: eslint/json + path: json + + - uses: actions/setup-node@v4 + with: + node-version: "lts/*" + + - name: Install Packages (eslint) + working-directory: eslint + run: npm install + + - name: Install Packages (@eslint/json) + working-directory: json + run: | + npm install + npm run build + npm install ../eslint + + - name: Run TSC + working-directory: json + run: npm run test:types + + are-the-types-wrong: + name: Are the types wrong? + runs-on: ubuntu-latest + + strategy: + matrix: + package: + [ + { name: eslint, directory: . }, + { + name: eslint-config-eslint, + directory: packages/eslint-config-eslint, + }, + ] + + steps: + - name: Checkout ${{ matrix.package.name }} + uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "lts/*" + + - name: Install Packages + working-directory: ${{ matrix.package.directory }} + run: npm install + + - name: Check validity of type definitions + working-directory: ${{ matrix.package.directory }} + run: npm run lint:types diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml index 5e3fb97c48d3..f902d652919d 100644 --- a/.github/workflows/update-readme.yml +++ b/.github/workflows/update-readme.yml @@ -1,33 +1,33 @@ name: Data Fetch on: - schedule: - - cron: "0 8 * * *" # Every day at 1am PDT + schedule: + - cron: "0 8 * * *" # Every day at 1am PDT jobs: - build: - runs-on: ubuntu-latest - steps: - - name: Check out repo - uses: actions/checkout@v4 - with: - token: ${{ secrets.WORKFLOW_PUSH_BOT_TOKEN }} + build: + runs-on: ubuntu-latest + steps: + - name: Check out repo + uses: actions/checkout@v4 + with: + token: ${{ secrets.WORKFLOW_PUSH_BOT_TOKEN }} - - name: Set up Node.js - uses: actions/setup-node@v4 + - name: Set up Node.js + uses: actions/setup-node@v4 - - name: Install npm packages - run: npm install + - name: Install npm packages + run: npm install - - name: Update README with latest team and sponsor data - run: npm run build:readme + - name: Update README with latest team and sponsor data + run: npm run build:readme - - name: Setup Git - run: | - git config user.name "GitHub Actions Bot" - git config user.email "" - - - name: Save updated files - run: | - chmod +x ./tools/commit-readme.sh - ./tools/commit-readme.sh + - name: Setup Git + run: | + git config user.name "GitHub Actions Bot" + git config user.email "" + + - name: Save updated files + run: | + chmod +x ./tools/commit-readme.sh + ./tools/commit-readme.sh diff --git a/.gitignore b/.gitignore index 148181e07769..e71935e48d5b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,6 @@ test.js coverage/ build/ -logs -wdio-logs npm-debug.log yarn-error.log .pnpm-debug.log @@ -18,14 +16,15 @@ jsdoc/ .eslintcache .cache /packages/**/node_modules -/tools/internal-rules/node_modules /.vscode +*.code-workspace .sublimelinterrc .eslint-release-info.json .nyc_output /test-results.xml .temp-eslintcache /tests/fixtures/autofix-integration/temp.js +/tests/fixtures/suppressions/temp.js yarn.lock package-lock.json pnpm-lock.yaml @@ -33,3 +32,5 @@ pnpm-lock.yaml # Docs site _site /docs/src/assets/css + +.cursor diff --git a/.markdownlint.yml b/.markdownlint.yml index 79c1f0fbecf3..0a4841f824a7 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -1,19 +1,11 @@ default: true +extends: markdownlint/style/prettier # Exclusions for deliberate/widespread violations -MD002: false # First header should be a h1 header -MD004: # Unordered list style - style: asterisk -MD007: # Unordered list indentation - indent: 4 -MD013: false # Line length -MD019: false # Multiple spaces after hash on atx style header -MD021: false # Multiple spaces inside hashes on closed atx style header -MD024: false # Multiple headers with the same content -MD026: false # Trailing punctuation in header -MD029: false # Ordered list item prefix -MD030: false # Spaces after list markers -MD033: false # Allow inline HTML -MD041: false # First line in file should be a top level header -MD046: # Code block style +MD002: false # First header should be a h1 header +MD024: false # Multiple headers with the same content +MD026: false # Trailing punctuation in header +MD033: false # Allow inline HTML +MD041: false # First line in file should be a top level header +MD046: # Code block style style: fenced diff --git a/.markdownlintignore b/.markdownlintignore deleted file mode 100644 index 251c083e0566..000000000000 --- a/.markdownlintignore +++ /dev/null @@ -1,3 +0,0 @@ -CHANGELOG.md -node_modules -tmp diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index b06c585a34b0..48535b6c06ab 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -1,7 +1,7 @@ -- id: eslint - name: eslint - entry: eslint - description: "An AST-based pattern checker for JavaScript." - language: node - types: ['javascript'] - require_serial: false +- id: eslint + name: eslint + entry: eslint + description: "An AST-based pattern checker for JavaScript." + language: node + types: ["javascript"] + require_serial: false diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000000..899a095338fa --- /dev/null +++ b/.prettierignore @@ -0,0 +1,12 @@ +CHANGELOG.md +docs/package.json +docs/src/_data/rule_versions.json +docs/src/_data/rules.json +docs/src/_data/rules_meta.json +docs/src/_data/versions.json +docs/src/_includes +docs/src/use/formatters/html-formatter-example.html +docs/src/use/formatters/index.md +docs/src/rules/*.md +packages/js/src/configs/eslint-all.js +tests/fixtures diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 000000000000..7d6ef8e35a5b --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,15 @@ +{ + "useTabs": true, + "tabWidth": 4, + "arrowParens": "avoid", + + "overrides": [ + { + "files": ["*.{json,jsonc,json5}", ".c8rc"], + "options": { + "tabWidth": 2, + "useTabs": false + } + } + ] +} diff --git a/.trunk/.gitignore b/.trunk/.gitignore new file mode 100644 index 000000000000..15966d087ebc --- /dev/null +++ b/.trunk/.gitignore @@ -0,0 +1,9 @@ +*out +*logs +*actions +*notifications +*tools +plugins +user_trunk.yaml +user.yaml +tmp diff --git a/.trunk/configs/.shellcheckrc b/.trunk/configs/.shellcheckrc new file mode 100644 index 000000000000..8c7b1ada8a3e --- /dev/null +++ b/.trunk/configs/.shellcheckrc @@ -0,0 +1,7 @@ +enable=all +source-path=SCRIPTDIR +disable=SC2154 + +# If you're having issues with shellcheck following source, disable the errors via: +# disable=SC1090 +# disable=SC1091 diff --git a/.trunk/configs/svgo.config.js b/.trunk/configs/svgo.config.js new file mode 100644 index 000000000000..81cde54069ec --- /dev/null +++ b/.trunk/configs/svgo.config.js @@ -0,0 +1,16 @@ +"use strict"; + +module.exports = { + plugins: [ + { + name: "preset-default", + params: { + overrides: { + removeViewBox: false, // https://github.com/svg/svgo/issues/1128 + sortAttrs: true, + removeOffCanvasPaths: true, + }, + }, + }, + ], +}; diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml new file mode 100644 index 000000000000..7a12b9755ab6 --- /dev/null +++ b/.trunk/trunk.yaml @@ -0,0 +1,73 @@ +# This file controls the behavior of Trunk: https://docs.trunk.io/cli +# To learn more about the format of this file, see https://docs.trunk.io/reference/trunk-yaml +version: 0.1 +cli: + version: 1.22.12 + +repo: + trunk_remote_hint: github.com/eslint/eslint + +# Trunk provides extensibility via plugins. (https://docs.trunk.io/plugins) +plugins: + sources: + - id: trunk + uri: https://github.com/trunk-io/plugins + ref: v1.6.8 + +# Many linters and tools depend on runtimes - configure them here. (https://docs.trunk.io/runtimes) +runtimes: + enabled: + - go@1.21.0 + - node@18.18.0 + - python@3.10.8 +tools: + enabled: + - gh@2.70.0 + +# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration) +lint: + files: + - name: json + extensions: + - json + - jsonc + - json5 + - c8rc + definitions: + - name: eslint + files: [typescript, javascript, yaml, json] # Add YAML and JSON to default files. + hold_the_line: false + commands: + - name: lint + disable_upstream: true + run: node ${workspace}/bin/eslint.js --output-file ${tmpfile} --format json ${target} + - name: checkov + supported_platforms: [linux, macos] + - name: renovate + supported_platforms: [linux, macos] + enabled: + - taplo@0.9.3 + - eslint + - actionlint@1.7.7 + - checkov@3.2.405 + - markdownlint@0.44.0 + - oxipng@9.1.4 + - prettier@3.5.3 + - renovate@39.248.2 + - shellcheck@0.10.0 + - shfmt@3.6.0 + - svgo@3.3.2 + disabled: + - yamllint + - trufflehog # Requires the network to run. + ignore: + - linters: [markdownlint] + paths: + - CHANGELOG.md +actions: + disabled: + - trunk-announce + - trunk-check-pre-push + - trunk-fmt-pre-commit + enabled: + - trunk-upgrade-available diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ee57ce4b7b9..a92a2ca3b9e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,1320 @@ +v9.31.0 - July 11, 2025 + +* [`3ddd454`](https://github.com/eslint/eslint/commit/3ddd454c1c73294e5af7905d60d03fac162f1b3e) chore: upgrade to `@eslint/js@9.31.0` (#19935) (Francesco Trotta) +* [`d5054e5`](https://github.com/eslint/eslint/commit/d5054e5454a537e9ade238c768c262c6c592cbc1) chore: package.json update for @eslint/js release (Jenkins) +* [`0f4a378`](https://github.com/eslint/eslint/commit/0f4a3781fe7c11fad7b206c3c694655486ddd187) chore: update eslint (#19933) (renovate[bot]) +* [`664cb44`](https://github.com/eslint/eslint/commit/664cb44ab03785bd200a792607a7e20faa2d4b28) docs: Update README (GitHub Actions Bot) +* [`07fac6c`](https://github.com/eslint/eslint/commit/07fac6cafa0426b4d1ea12d9001f3955f19b286d) fix: retry on EMFILE when writing autofix results (#19926) (TKDev7) +* [`35cf44c`](https://github.com/eslint/eslint/commit/35cf44c22e36b1554486e7a75c870e86c10b83f8) feat: output full actual location in rule tester if different (#19904) (ST-DDT) +* [`40dbe2a`](https://github.com/eslint/eslint/commit/40dbe2a43f83d366e9026faec70293512fb61ca2) docs: fix mismatch between `globalIgnores()` code and text (#19914) (MaoShizhong) +* [`76c2340`](https://github.com/eslint/eslint/commit/76c2340c368f96db77439b5cd1df0196cc39bf3e) chore: bump mocha to v11 (#19917) (ëŖ¨ë°€LuMir) +* [`28cc7ab`](https://github.com/eslint/eslint/commit/28cc7abbb72b29b1cac6fc4253646a7839586064) fix: Remove incorrect RuleContext types (#19910) (Nicholas C. Zakas) +* [`a6a6325`](https://github.com/eslint/eslint/commit/a6a63259de6cb5642f69c7be429554bbcedca4c0) feat: support explicit resource management in `no-loop-func` (#19895) (Milos Djermanovic) +* [`4682cdc`](https://github.com/eslint/eslint/commit/4682cdc6960279ee17f23899fbab6f58d881eadf) feat: support explicit resource management in `no-undef-init` (#19894) (Milos Djermanovic) +* [`5848216`](https://github.com/eslint/eslint/commit/58482165eaf597cc5c58216a956c301ae87520b3) feat: support explicit resource management in `init-declarations` (#19893) (Milos Djermanovic) +* [`bb370b8`](https://github.com/eslint/eslint/commit/bb370b8e79f65ee32d9d89ecf249fb74a141ad22) feat: support explicit resource management in `no-const-assign` (#19892) (Milos Djermanovic) +* [`5a0069d`](https://github.com/eslint/eslint/commit/5a0069d60815246cf24e1c96125540792c2507ef) docs: Update README (GitHub Actions Bot) +* [`fef04b5`](https://github.com/eslint/eslint/commit/fef04b5c7fea99362d67b31b8e98cd4914020ed3) docs: Update working on issues info (#19902) (Nicholas C. Zakas) + +v9.30.1 - July 1, 2025 + +* [`b035f74`](https://github.com/eslint/eslint/commit/b035f747c6e6d1c7a299c90b0ed0b8109cf24a53) chore: upgrade to `@eslint/js@9.30.1` (#19906) (Francesco Trotta) +* [`b3dbc16`](https://github.com/eslint/eslint/commit/b3dbc16563cb7036d75edff9814e17053a645321) chore: package.json update for @eslint/js release (Jenkins) +* [`e91bb87`](https://github.com/eslint/eslint/commit/e91bb870f8c6e38baa508f18048cd2a2d04b8b9c) fix: allow separate default and named type imports (#19899) (xbinaryx) +* [`ab7c625`](https://github.com/eslint/eslint/commit/ab7c62598a9fca498e495d45029ae92fd5fb9bf3) docs: Update README (GitHub Actions Bot) +* [`dae1e5b`](https://github.com/eslint/eslint/commit/dae1e5bb27db0e846efbe3026210013b42817838) docs: update jsdoc's link (#19896) (JamesVanWaza) + +v9.30.0 - June 27, 2025 + +* [`2b6491c`](https://github.com/eslint/eslint/commit/2b6491cd4b8eec44d4a3f8dea1b71151e8dd0230) chore: upgrade to `@eslint/js@9.30.0` (#19889) (Francesco Trotta) +* [`5a5d526`](https://github.com/eslint/eslint/commit/5a5d5261037fdf84a91f2f22d3726d58572453f4) chore: package.json update for @eslint/js release (Jenkins) +* [`52a5fca`](https://github.com/eslint/eslint/commit/52a5fcaa4e0bb4e55c014c20ed47d6c93b107635) feat: Support `basePath` property in config objects (#19879) (Milos Djermanovic) +* [`6a0f164`](https://github.com/eslint/eslint/commit/6a0f164543bf8461d6a27a740c9e08aa77cbe42d) fix: handle `null` type `loc` in `getIndexFromLoc` method (#19862) (ëŖ¨ë°€LuMir) +* [`8662ed1`](https://github.com/eslint/eslint/commit/8662ed1f6debc358e22812b145e117aa4a907d78) docs: adopt eslint-stylistic sub packages related changes (#19887) (ntnyq) +* [`eaf8a41`](https://github.com/eslint/eslint/commit/eaf8a418af32b3190494e4a2284533353c28ccfa) chore: Correct typos in linter tests (#19878) (kilavvy) +* [`4ab4482`](https://github.com/eslint/eslint/commit/4ab44823df4d4b47d3650da949077a0551e7579e) feat: add `allowSeparateTypeImports` option to `no-duplicate-imports` (#19872) (sethamus) +* [`3fbcd70`](https://github.com/eslint/eslint/commit/3fbcd704a0b2aef2a6c1fc34d2bc4b35f6425067) fix: update error message for `no-restricted-properties` (#19855) (Tanuj Kanti) +* [`20158b0`](https://github.com/eslint/eslint/commit/20158b09db3430cf00b202ba8c25ce874bbaf00a) docs: typo in comment for unused variables handling (#19870) (leopardracer) +* [`ebfb5b4`](https://github.com/eslint/eslint/commit/ebfb5b46136c4d737c9783333e3057421d1a0bef) docs: Fixed Typo in configuration-files.md (#19873) (0-20) +* [`b8a7e7a`](https://github.com/eslint/eslint/commit/b8a7e7aeb5f0ed2e1670771ab4dda6fd723d96eb) feat: throw error when column is negative in `getIndexFromLoc` (#19831) (ëŖ¨ë°€LuMir) +* [`7ef4cf7`](https://github.com/eslint/eslint/commit/7ef4cf76610d42727a404e495ac6d47868cf5040) fix: remove unnecessary semicolon from fixes (#19857) (Francesco Trotta) +* [`7dabc38`](https://github.com/eslint/eslint/commit/7dabc38a8406d470fb2389eec2f0ad1ad214173e) fix: use `process.version` in `--env-info` (#19865) (TKDev7) +* [`4112fd0`](https://github.com/eslint/eslint/commit/4112fd09531092e9651e9981205bcd603dc56acf) docs: clarify that boolean is still allowed for rule `meta.deprecated` (#19866) (Bryan Mishkin) + +v9.29.0 - June 13, 2025 + +* [`5c114c9`](https://github.com/eslint/eslint/commit/5c114c962f29d0b33e6439e9ab0985014af06b9f) chore: upgrade @eslint/js@9.29.0 (#19851) (Milos Djermanovic) +* [`acf2201`](https://github.com/eslint/eslint/commit/acf2201a067d062e007b1b7b164b8e96fa1af50f) chore: package.json update for @eslint/js release (Jenkins) +* [`f686fcb`](https://github.com/eslint/eslint/commit/f686fcb51e47cf53b891ae595684afe8a0ef584d) feat: add `ecmaVersion: 2026`, parsing `using` and `await using` (#19832) (Milos Djermanovic) +* [`85c082c`](https://github.com/eslint/eslint/commit/85c082c54bd42ad818f5938b8fb1fb2aa0a1912f) fix: explicit matching behavior with negated patterns and arrays (#19845) (Milos Djermanovic) +* [`00e3e6a`](https://github.com/eslint/eslint/commit/00e3e6ad1357df7d46be51d3f305efecb90244a7) docs: add support for custom name parameter to `includeIgnoreFile` (#19795) (ëŖ¨ë°€LuMir) +* [`9bda4a9`](https://github.com/eslint/eslint/commit/9bda4a9bf18c9fef91cdd93921a0935ffcf9a9fc) fix: fix `LintOptions.filterCodeBlock` types (#19837) (ntnyq) +* [`a806994`](https://github.com/eslint/eslint/commit/a806994263e54e4bc1481736b1c0626c8b770808) refactor: Remove eslintrc from flat config functionality (#19833) (Nicholas C. Zakas) +* [`19cdd22`](https://github.com/eslint/eslint/commit/19cdd226bb5957f8f7e8cb4e92d38aafe47f8ff4) feat: prune suppressions for non-existent files (#19825) (TKDev7) +* [`b3d720f`](https://github.com/eslint/eslint/commit/b3d720f82f08022a33b10f0437111e7d270b8e3c) feat: add ES2025 globals (#19835) (fisker Cheung) +* [`677a283`](https://github.com/eslint/eslint/commit/677a2837a17320f54a8869682af128a2a7d77579) feat: add auto-accessor fields support to class-methods-use-this (#19789) (sethamus) +* [`3aed075`](https://github.com/eslint/eslint/commit/3aed0756ed3669ac27fc243c81fd82e3d0e6973b) docs: Update README (GitHub Actions Bot) +* [`7ab77a2`](https://github.com/eslint/eslint/commit/7ab77a2c7605126daaa7e7f7ab75b5c252677d12) fix: correct breaking deprecation of FlatConfig type (#19826) (Logicer) +* [`a2f888d`](https://github.com/eslint/eslint/commit/a2f888d679e2a44964da596a4158911819e1d31d) docs: enhance documentation with links and fix typos (#19761) (ëŖ¨ë°€LuMir) +* [`dbba058`](https://github.com/eslint/eslint/commit/dbba0589f5509223658b73de6eb721f659bcec47) feat: allow global type declaration in `no-var` (#19714) (Remco Haszing) +* [`152ed51`](https://github.com/eslint/eslint/commit/152ed51329d82c6e7375f41a105e01b31750e17f) test: switch to flat config mode in code path analysis tests (#19824) (Milos Djermanovic) +* [`b647239`](https://github.com/eslint/eslint/commit/b647239272931e0a947500b2f554fc8ccdf8adfd) chore: Update first-party dependencies faster with Renovate (#19822) (Nicholas C. Zakas) +* [`7abe42e`](https://github.com/eslint/eslint/commit/7abe42e2de931289e19e34e390d16936cf6faf64) refactor: SafeEmitter -> SourceCodeVisitor (#19708) (Nicholas C. Zakas) +* [`342bd29`](https://github.com/eslint/eslint/commit/342bd29e1a10a4b521ed0dbb6d889dcfc137e863) feat: ignore type annotations in no-restricted-globals (#19781) (sethamus) +* [`e392895`](https://github.com/eslint/eslint/commit/e39289596757702b6c8d747d5ab9c1a7820c108f) perf: improve time complexity of `getLocFromIndex` (#19782) (ëŖ¨ë°€LuMir) +* [`1ba3318`](https://github.com/eslint/eslint/commit/1ba33181ab300588a803434884c054ed003f0bbd) fix: add `language` and `dialects` to `no-use-before-define` (#19808) (Francesco Trotta) +* [`786bcd1`](https://github.com/eslint/eslint/commit/786bcd13652b90c5bd0c7201610b856ad1b87542) feat: add allowProperties option to no-restricted-properties (#19772) (sethamus) +* [`05b66d0`](https://github.com/eslint/eslint/commit/05b66d05bd68214f2fa1ab53fb2734c9d9e5348a) feat: add `sourceCode.isGlobalReference(node)` method (#19695) (Nitin Kumar) +* [`53c3235`](https://github.com/eslint/eslint/commit/53c3235ba1c90a85a44f0abd18998ccc4e0445bf) docs: update to clarify prompt usage (#19748) (Jennifer Davis) +* [`0ed289c`](https://github.com/eslint/eslint/commit/0ed289c5ceed1c10b599b22c8b9374a5a3a144dd) chore: remove accidentally committed file (#19807) (Francesco Trotta) + +v9.28.0 - May 30, 2025 + +* [`175b7b8`](https://github.com/eslint/eslint/commit/175b7b83fcdc8f3f84821510dd7e04d120402317) chore: upgrade to `@eslint/js@9.28.0` (#19802) (Francesco Trotta) +* [`844f5a6`](https://github.com/eslint/eslint/commit/844f5a69dc78ca38f856c137e061e8facc9d00ba) chore: package.json update for @eslint/js release (Jenkins) +* [`b0674be`](https://github.com/eslint/eslint/commit/b0674be94e4394401b4f668453a473572c321023) feat: Customization of serialization for languageOptions (#19760) (Nicholas C. Zakas) +* [`3ec2082`](https://github.com/eslint/eslint/commit/3ec208233f29c161aae8f99f9f091e371fe83a62) docs: Nested arrays in files config entry (#19799) (Nicholas C. Zakas) +* [`89a65b0`](https://github.com/eslint/eslint/commit/89a65b07f6171a860284b62d97c8b3edf312b98c) docs: clarify how config arrays can apply to subsets of files (#19788) (Shais Ch) +* [`2ba8a0d`](https://github.com/eslint/eslint/commit/2ba8a0d75c7a8e6aa4798275126698be40391d37) docs: Add description of meta.namespace to plugin docs (#19798) (Nicholas C. Zakas) +* [`eea3e7e`](https://github.com/eslint/eslint/commit/eea3e7eb1ca84f9e8870e1190d65d5235d9d8429) fix: Remove configured global variables from `GlobalScope#implicit` (#19779) (Milos Djermanovic) +* [`a95721f`](https://github.com/eslint/eslint/commit/a95721f1064fdbfe0e392b955ce3053a24551f80) feat: Add `--pass-on-unpruned-suppressions` CLI option (#19773) (Milos Djermanovic) +* [`a467de3`](https://github.com/eslint/eslint/commit/a467de39f6e509af95a7963904326635c1bf7116) fix: update context.report types (#19751) (Nitin Kumar) +* [`59dd7e6`](https://github.com/eslint/eslint/commit/59dd7e6b28507053bde985ea2311dca8ec0db681) docs: update `func-style` with examples (#19793) (Tanuj Kanti) +* [`62b1c1b`](https://github.com/eslint/eslint/commit/62b1c1bc7981798c3aec2dd430c200c797a25629) chore: update globals to v16 (#19791) (Nitin Kumar) +* [`bfd0e7a`](https://github.com/eslint/eslint/commit/bfd0e7a39535b3c1ddc742dfffa6bdcdc93079e2) feat: support TypeScript syntax in `no-use-before-define` (#19566) (Tanuj Kanti) +* [`68c61c0`](https://github.com/eslint/eslint/commit/68c61c093a885623e48f38026e3f3a05bfa403de) feat: support TS syntax in `no-shadow` (#19565) (Nitin Kumar) +* [`e8a1cb8`](https://github.com/eslint/eslint/commit/e8a1cb8f7fbc18efa589bfedea5326de636b4868) chore: ignore jiti-v2.0 & jiti-v2.1 for renovate (#19786) (Nitin Kumar) +* [`0f773ef`](https://github.com/eslint/eslint/commit/0f773ef248af0301a410fee11e1b22174100cf6a) feat: support TS syntax in `no-magic-numbers` (#19561) (Nitin Kumar) +* [`43d3975`](https://github.com/eslint/eslint/commit/43d39754b6d315954f46a70dbd53d1fa0eea1619) chore: Add Copilot Instructions file (#19753) (Nicholas C. Zakas) +* [`c4a6b60`](https://github.com/eslint/eslint/commit/c4a6b6051889b1cb668d4d2ae29e9c27c74993d6) feat: add allowTypeAnnotation to func-style (#19754) (sethamus) +* [`fd467bb`](https://github.com/eslint/eslint/commit/fd467bb892d735a4a8863beabd181a3f3152689a) fix: remove interopDefault to use jiti's default (#19697) (sethamus) +* [`2dfb5eb`](https://github.com/eslint/eslint/commit/2dfb5ebef4c14d552d10a6c7c2c2ce376e63654a) test: update `SourceCodeTraverser` tests (#19763) (Milos Djermanovic) +* [`b03ad17`](https://github.com/eslint/eslint/commit/b03ad176f158afdd921f0af5126c398012b10559) feat: add TypeScript support to `prefer-arrow-callback` (#19678) (Tanuj Kanti) +* [`e9129e0`](https://github.com/eslint/eslint/commit/e9129e0799d068c377d63d59a0a800e7d1fea8dd) docs: add global scope's `implicit` field to Scope Manager docs (#19770) (Milos Djermanovic) +* [`bc3c331`](https://github.com/eslint/eslint/commit/bc3c3313ce2719062805b6849d29f9a375cf23f2) feat: ignore overloaded function declarations in func-style rule (#19755) (sethamus) +* [`5bc21f9`](https://github.com/eslint/eslint/commit/5bc21f9e8e00f9e49442d1b6520b307ce94f3518) chore: add `*.code-workspace` to `.gitignore` (#19771) (ëŖ¨ë°€LuMir) +* [`72d16e3`](https://github.com/eslint/eslint/commit/72d16e3066aac2f1c74f4150ba43dfa8cf532584) fix: avoid false positive in `no-unassigned-vars` for declare module (#19746) (Azat S.) +* [`f4fa40e`](https://github.com/eslint/eslint/commit/f4fa40eb4bd6f4dba3b2e7fff259d0780ef6becf) refactor: NodeEventGenerator -> SourceCodeTraverser (#19679) (Nicholas C. Zakas) +* [`81c3c93`](https://github.com/eslint/eslint/commit/81c3c936266474c2081f310098084bd0eb1768d2) fix: curly types (#19750) (Eli) +* [`52f5b7a`](https://github.com/eslint/eslint/commit/52f5b7a0af48a2f143f0bccfd4e036025b08280d) docs: fix minor typos and add links (#19743) (ëŖ¨ë°€LuMir) +* [`0f49329`](https://github.com/eslint/eslint/commit/0f49329b4a7f91714f2cd1e9ce532d32202c47f4) refactor: use a service to emit warnings (#19725) (Francesco Trotta) +* [`20a9e59`](https://github.com/eslint/eslint/commit/20a9e59438fde3642ab058cc55ee1b9fa02b6391) chore: update dependency shelljs to ^0.10.0 (#19740) (renovate[bot]) +* [`00716a3`](https://github.com/eslint/eslint/commit/00716a339ede24ed5a76aceed833f38a6c4e8d3a) docs: upfront recommend against using the no-return-await rule (#19727) (Mike DiDomizio) + +v9.27.0 - May 16, 2025 + +* [`f8f1560`](https://github.com/eslint/eslint/commit/f8f1560de633aaf24a7099f89cbbfed12a762a32) chore: upgrade @eslint/js@9.27.0 (#19739) (Milos Djermanovic) +* [`ecaef73`](https://github.com/eslint/eslint/commit/ecaef7351f9f3220aa57409bf98db3e55b07a02a) chore: package.json update for @eslint/js release (Jenkins) +* [`25de550`](https://github.com/eslint/eslint/commit/25de55055d420d7c8b794ae5fdaeb67947c613d9) docs: Update description of frozen rules to mention TypeScript (#19736) (Nicholas C. Zakas) +* [`bd5def6`](https://github.com/eslint/eslint/commit/bd5def66d1a3f9bad7da3547b5dff6003e67d9d3) docs: Clean up configuration files docs (#19735) (Nicholas C. Zakas) +* [`d71e37f`](https://github.com/eslint/eslint/commit/d71e37f450f4ae115ec394615e21523685f0d370) feat: Allow flags to be set in ESLINT_FLAGS env variable (#19717) (Nicholas C. Zakas) +* [`5687ce7`](https://github.com/eslint/eslint/commit/5687ce7055d30e2d5ef800b3d5c3096c3fc42c0e) fix: correct mismatched removed rules (#19734) (ëŖ¨ë°€LuMir) +* [`596fdc6`](https://github.com/eslint/eslint/commit/596fdc62047dff863e990c3246b32da97ae9a14e) chore: update dependency @arethetypeswrong/cli to ^0.18.0 (#19732) (renovate[bot]) +* [`ba456e0`](https://github.com/eslint/eslint/commit/ba456e000e104fd7f2dbd27eebbd4f35e6c18934) feat: Externalize MCP server (#19699) (Nicholas C. Zakas) +* [`dc5ed33`](https://github.com/eslint/eslint/commit/dc5ed337fd18cb59801e4afaf394f6b84057b601) fix: correct types and tighten type definitions in `SourceCode` class (#19731) (ëŖ¨ë°€LuMir) +* [`4d0c60d`](https://github.com/eslint/eslint/commit/4d0c60d0738cb32c12e4ea132caa6fab6d5ed0a7) docs: Add Neovim to editor integrations (#19729) (Maria JosÊ Solano) +* [`f791da0`](https://github.com/eslint/eslint/commit/f791da040189ada1b1ec15856557b939ffcd978b) chore: remove unbalanced curly brace from `.editorconfig` (#19730) (Maria JosÊ Solano) +* [`e86edee`](https://github.com/eslint/eslint/commit/e86edee0918107e4e41e908fe59c937b83f00d4e) refactor: Consolidate Config helpers (#19675) (Nicholas C. Zakas) +* [`07c1a7e`](https://github.com/eslint/eslint/commit/07c1a7e839ec61bd706c651428606ea5955b2bb0) feat: add `allowRegexCharacters` to `no-useless-escape` (#19705) (sethamus) +* [`cf36352`](https://github.com/eslint/eslint/commit/cf3635299e09570b7472286f25dacd8ab24e0517) chore: remove shared types (#19718) (Francesco Trotta) +* [`f60f276`](https://github.com/eslint/eslint/commit/f60f2764971a33e252be13e560dccf21f554dbf1) refactor: Easier RuleContext creation (#19709) (Nicholas C. Zakas) +* [`71317eb`](https://github.com/eslint/eslint/commit/71317ebeaf1c542114e4fcda99ee26115d8e4a27) docs: Update README (GitHub Actions Bot) +* [`de1b5de`](https://github.com/eslint/eslint/commit/de1b5deba069f770140f3a7dba2702c1016dcc2a) fix: correct `service` property name in `Linter.ESLintParseResult` type (#19713) (Francesco Trotta) +* [`58a171e`](https://github.com/eslint/eslint/commit/58a171e8f0dcc1e599ac22bf8c386abacdbee424) chore: update dependency @eslint/plugin-kit to ^0.3.1 (#19712) (renovate[bot]) +* [`3a075a2`](https://github.com/eslint/eslint/commit/3a075a29cfb43ef08711c2e433fb6f218855886d) chore: update dependency @eslint/core to ^0.14.0 (#19715) (renovate[bot]) +* [`60c3e2c`](https://github.com/eslint/eslint/commit/60c3e2cf9256f3676b7934e26ff178aaf19c9e97) fix: sort keys in eslint-suppressions.json to avoid git churn (#19711) (Ron Waldon-Howe) +* [`4c289e6`](https://github.com/eslint/eslint/commit/4c289e685e6cf87331f4b1e6afe34a4feb8e6cc8) docs: Update README (GitHub Actions Bot) +* [`9da90ca`](https://github.com/eslint/eslint/commit/9da90ca3c163adb23a9cc52421f59dedfce34fc9) fix: add `allowReserved` to `Linter.ParserOptions` type (#19710) (Francesco Trotta) +* [`7bc6c71`](https://github.com/eslint/eslint/commit/7bc6c71ca350fa37531291e1d704be6ed408c5dc) feat: add no-unassigned-vars rule (#19618) (Jacob Bandes-Storch) +* [`ee40364`](https://github.com/eslint/eslint/commit/ee4036429758cdaf7f77c52f1c2b74b5a2bb7b66) feat: convert no-array-constructor suggestions to autofixes (#19621) (sethamus) +* [`fbb8be9`](https://github.com/eslint/eslint/commit/fbb8be9256dc7613fa0b87e87974714284b78a94) fix: add `info` to `ESLint.DeprecatedRuleUse` type (#19701) (Francesco Trotta) +* [`f0f0d46`](https://github.com/eslint/eslint/commit/f0f0d46ab2f87e439642abd84b6948b447b66349) docs: clarify that unused suppressions cause non-zero exit code (#19698) (Milos Djermanovic) +* [`44bac9d`](https://github.com/eslint/eslint/commit/44bac9d15c4e0ca099d0b0d85e601f3b55d4e167) ci: run tests in Node.js 24 (#19702) (Francesco Trotta) +* [`32957cd`](https://github.com/eslint/eslint/commit/32957cde72196c7e41741db311786d881c1613a1) feat: support TS syntax in `max-params` (#19557) (Nitin Kumar) +* [`35304dd`](https://github.com/eslint/eslint/commit/35304dd2b0d8a4b640b9a25ae27ebdcb5e124cde) chore: add missing `funding` field to packages (#19684) (ëŖ¨ë°€LuMir) +* [`8ed3273`](https://github.com/eslint/eslint/commit/8ed32734cc22988173f99fd0703d50f94c60feb8) docs: fix internal usages of `ConfigData` type (#19688) (Francesco Trotta) +* [`f305beb`](https://github.com/eslint/eslint/commit/f305beb82c51215ad48c5c860f02be1b34bcce32) test: mock `process.emitWarning` to prevent output disruption (#19687) (Francesco Trotta) +* [`eb316a8`](https://github.com/eslint/eslint/commit/eb316a83a49347ab47ae965ff95f81dd620d074c) docs: add `fmt` and `check` sections to `Package.json Conventions` (#19686) (ëŖ¨ë°€LuMir) +* [`a3a2559`](https://github.com/eslint/eslint/commit/a3a255924866b94ef8d604e91636547600edec56) docs: fix wording in Combine Configs (#19685) (Milos Djermanovic) +* [`c8d17e1`](https://github.com/eslint/eslint/commit/c8d17e11dc63909e693eaed5b5ccc50e698ac3b3) docs: Update README (GitHub Actions Bot) + +v9.26.0 - May 2, 2025 + +* [`5b247c8`](https://github.com/eslint/eslint/commit/5b247c859f1b653297a9b9135d92a59742a669cc) chore: upgrade to `@eslint/js@9.26.0` (#19681) (Francesco Trotta) +* [`d6fa4ac`](https://github.com/eslint/eslint/commit/d6fa4ac031c2fe24fb778e84940393fbda3ddf77) chore: package.json update for @eslint/js release (Jenkins) +* [`e9754e7`](https://github.com/eslint/eslint/commit/e9754e7433edf665602ceba4f7f8fbca559c974f) feat: add reportGlobalThis to no-shadow-restricted-names (#19670) (sethamus) +* [`0fa2b7a`](https://github.com/eslint/eslint/commit/0fa2b7a3666f1eedcc091446dc860037c9bafa5c) feat: add suggestions for `eqeqeq` rule (#19640) (Nitin Kumar) +* [`dd98d63`](https://github.com/eslint/eslint/commit/dd98d63f09c9324124734206d904d31d433a7c92) docs: Update README (GitHub Actions Bot) +* [`96e84de`](https://github.com/eslint/eslint/commit/96e84de55ad17c96e5b6f2dece75145542505469) fix: check cache file existence before deletion (#19648) (sethamus) +* [`c25e858`](https://github.com/eslint/eslint/commit/c25e858d2d7e9bd3e53dcb32c9af5251d6f0569e) docs: Update README (GitHub Actions Bot) +* [`0958690`](https://github.com/eslint/eslint/commit/09586905be394c05839996a5ea812adfac44d320) chore: disambiguate internal types `LanguageOptions` and `Rule` (#19669) (Francesco Trotta) +* [`dcbdcc9`](https://github.com/eslint/eslint/commit/dcbdcc9c6be628240269b41f7bb576dde1e6f5b3) feat: Add MCP server (#19592) (Nicholas C. Zakas) +* [`b2397e9`](https://github.com/eslint/eslint/commit/b2397e9bef5ca7faf7e100ecebc20e457bf0b588) docs: Update README (GitHub Actions Bot) +* [`d683aeb`](https://github.com/eslint/eslint/commit/d683aebc8e0792e4f80bd1488c705c90f22c317e) fix: don't crash on tests with circular references in `RuleTester` (#19664) (Milos Djermanovic) +* [`f1c858e`](https://github.com/eslint/eslint/commit/f1c858e3c1e9712ef398588bf5ed68bc19fad3f2) chore: fix internal type references to `Plugin` and `Rule` (#19665) (Francesco Trotta) +* [`9736d5d`](https://github.com/eslint/eslint/commit/9736d5d15870c9185da7d140becb9a15aa69057d) fix: add `namespace` to `Plugin.meta` type (#19661) (Milos Djermanovic) +* [`40dd299`](https://github.com/eslint/eslint/commit/40dd2998cedddb75e0514b2c5cc855293c85da41) refactor: One-shot ESQuery selector analysis (#19652) (Nicholas C. Zakas) +* [`addd0a6`](https://github.com/eslint/eslint/commit/addd0a6a62d1b89dc7ab49cbd08c5a6af3e7da29) docs: fix formatting of unordered lists in Markdown (#19660) (Milos Djermanovic) +* [`a21b38d`](https://github.com/eslint/eslint/commit/a21b38db0276ab3373c95ebc7b1ef1910b79dfe6) docs: Update README (GitHub Actions Bot) +* [`c0721a7`](https://github.com/eslint/eslint/commit/c0721a7f34264da0a32ade8432511eeda4a2c1b9) docs: fix double space in command (#19657) (CamWass) +* [`1cfd702`](https://github.com/eslint/eslint/commit/1cfd7024226cd9c42ceb75732f79e3bc36e8305c) chore: update dependency @eslint/json to ^0.12.0 (#19656) (renovate[bot]) +* [`2dfd83e`](https://github.com/eslint/eslint/commit/2dfd83ef4ee054f748732581c422508c45d6f1bf) feat: add `ignoreDirectives` option in `no-unused-expressions` (#19645) (sethamus) +* [`17bae69`](https://github.com/eslint/eslint/commit/17bae69e02fff6f26487a3cbd9c3c3218088949c) fix: update `RuleTester.run()` type (#19634) (Nitin Kumar) + +v9.25.1 - April 21, 2025 + +* [`1f2b057`](https://github.com/eslint/eslint/commit/1f2b057ddcbef4340f78d1314456935054b8d93f) chore: upgrade @eslint/js@9.25.1 (#19642) (Milos Djermanovic) +* [`771317f`](https://github.com/eslint/eslint/commit/771317fa937a07277201f7155e9b835e6a5658f9) chore: package.json update for @eslint/js release (Jenkins) +* [`cdc8e8c`](https://github.com/eslint/eslint/commit/cdc8e8c950ddfe1f9d462ea138ad7866da0394da) fix: revert directive detection in no-unused-expressions (#19639) (sethamus) + +v9.25.0 - April 18, 2025 + +* [`88dc196`](https://github.com/eslint/eslint/commit/88dc1965a4f53babec36e0f5bd450dd02467acde) chore: upgrade @eslint/js@9.25.0 (#19636) (Milos Djermanovic) +* [`345288d`](https://github.com/eslint/eslint/commit/345288d7b270e8c122e922bfa31f219aedc4e63b) chore: package.json update for @eslint/js release (Jenkins) +* [`910bd13`](https://github.com/eslint/eslint/commit/910bd13c4cb49001f2a9f172229360771b857585) fix: `nodeTypeKey` not being used in `NodeEventGenerator` (#19631) (StyleShit) +* [`ca7a735`](https://github.com/eslint/eslint/commit/ca7a735dde44120111d56e36ce93ba750b3c3c86) docs: update `no-undef-init` when not to use section (#19624) (Tanuj Kanti) +* [`affe6be`](https://github.com/eslint/eslint/commit/affe6be0181422a51875a2ad40eb5152d94fc254) chore: upgrade trunk (#19628) (sethamus) +* [`1b870c9`](https://github.com/eslint/eslint/commit/1b870c9da4b3aa28f4a6f4f62e0437b743344994) docs: use `eslint-config-xo` in the getting started guide (#19629) (Nitin Kumar) +* [`5d4af16`](https://github.com/eslint/eslint/commit/5d4af16ab170306862dd0c33894044e59e03d041) docs: add types for multiple rule options (#19616) (Tanuj Kanti) +* [`e8f8d57`](https://github.com/eslint/eslint/commit/e8f8d57bd6c0d95f9f25db8c5b3ff72de42488b7) docs: Update README (GitHub Actions Bot) +* [`a40348f`](https://github.com/eslint/eslint/commit/a40348f1f67a6c3da320682d683589f91d7e6f7b) docs: no-use-before-define tweaks (#19622) (Kirk Waiblinger) +* [`0ba3ae3`](https://github.com/eslint/eslint/commit/0ba3ae3e5a2425560baf771c05e7c69c63a1983c) docs: Update README (GitHub Actions Bot) +* [`865dbfe`](https://github.com/eslint/eslint/commit/865dbfed6cbade8a22756965be256da317801937) docs: ensure "learn more" deprecation links point to useful resource (#19590) (Kirk Waiblinger) +* [`dcd95aa`](https://github.com/eslint/eslint/commit/dcd95aafa33a95c8102834af85129f6f398fe394) feat: support TypeScript syntax in no-empty-function rule (#19551) (sethamus) +* [`77d6d5b`](https://github.com/eslint/eslint/commit/77d6d5bc4923012aee34b0a7c3d971f017d65555) feat: support TS syntax in `no-unused-expressions` (#19564) (Sweta Tanwar) +* [`90228e5`](https://github.com/eslint/eslint/commit/90228e5d57672579cf82bede29880532c2cb8ca9) feat: support `JSRuleDefinition` type (#19604) (ëŖ¨ë°€LuMir) +* [`f80b746`](https://github.com/eslint/eslint/commit/f80b746d850021d253c01bb0eae466a701e63055) docs: add known limitations for no-self-compare (#19612) (Nitin Kumar) +* [`59ba6b7`](https://github.com/eslint/eslint/commit/59ba6b73789835813ab3002c651a7217dd30a8cc) feat: add allowObjects option to no-restricted-properties (#19607) (sethamus) +* [`db650a0`](https://github.com/eslint/eslint/commit/db650a036baf502c7366a7da633d4cd00719394e) feat: support TypeScript syntax in `no-invalid-this` rule (#19532) (Tanuj Kanti) +* [`dd20cf2`](https://github.com/eslint/eslint/commit/dd20cf274e285f09f230638184c997c44912485f) test: fix `no-loop-func` test with duplicate variable reports (#19610) (Milos Djermanovic) +* [`9535cff`](https://github.com/eslint/eslint/commit/9535cffe7b66abe850d90258e702279afabce7fe) feat: support TS syntax in `no-loop-func` (#19559) (Nitin Kumar) +* [`bd05397`](https://github.com/eslint/eslint/commit/bd05397ef68bb23a6148aeb70088d7167f201bf7) chore: upgrade `@eslint/*` dependencies (#19606) (Milos Djermanovic) +* [`22ea18b`](https://github.com/eslint/eslint/commit/22ea18b8babe4d60af7b3518b24d1ec31bf09605) chore: replace invalid `int` type with `number` inside JSDocs. (#19597) (Arya Emami) +* [`865aed6`](https://github.com/eslint/eslint/commit/865aed629318ca1e86e7d371fac49d7de4e7e8a8) docs: Update README (GitHub Actions Bot) + +v9.24.0 - April 4, 2025 + +* [`ef67420`](https://github.com/eslint/eslint/commit/ef6742091d49fc1809ad933f1daeff7124f57e93) chore: upgrade @eslint/js@9.24.0 (#19602) (Milos Djermanovic) +* [`4946847`](https://github.com/eslint/eslint/commit/4946847bb675ee26c3a52bfe3bca63a0dc5e5c61) chore: package.json update for @eslint/js release (Jenkins) +* [`f857820`](https://github.com/eslint/eslint/commit/f8578206cc9b9fcd03dc5311f8a2d96b7b3359a5) docs: update documentation for `--experimental-strip-types` (#19594) (Nikolas SchrÃļter) +* [`803e4af`](https://github.com/eslint/eslint/commit/803e4af48e7fc3a2051e8c384f30fe4a318520e3) docs: simplify gitignore path handling in includeIgnoreFile section (#19596) (Thomas Broyer) +* [`6d979cc`](https://github.com/eslint/eslint/commit/6d979ccc183454e616bc82a598db5402e9d63dcf) docs: Update README (GitHub Actions Bot) +* [`b23d1c5`](https://github.com/eslint/eslint/commit/b23d1c5f0297c5e2e9a4ff70533f3c0bdbfc34b8) fix: deduplicate variable names in no-loop-func error messages (#19595) (Nitin Kumar) +* [`556c25b`](https://github.com/eslint/eslint/commit/556c25bbadd640ba9465ca6ec152f10959591666) feat: support loading TS config files using `--experimental-strip-types` (#19401) (Arya Emami) +* [`82177e4`](https://github.com/eslint/eslint/commit/82177e4108d6b3e63ece6072d923c0a2c08907bf) docs: Update README (GitHub Actions Bot) +* [`a995acb`](https://github.com/eslint/eslint/commit/a995acbe32471ce8c20cbf9f48b4f3e1d8bc2229) chore: correct 'flter'/'filter' typo in package script (#19587) (Josh Goldberg ✨) +* [`72650ac`](https://github.com/eslint/eslint/commit/72650acdb715fc25c675dc6368877b0e3f8d3885) feat: support TS syntax in `init-declarations` (#19540) (Nitin Kumar) +* [`03fb0bc`](https://github.com/eslint/eslint/commit/03fb0bca2be41597fcea7c0e84456bbaf2e5acca) feat: normalize patterns to handle "./" prefix in files and ignores (#19568) (Pixel998) +* [`b9a5efa`](https://github.com/eslint/eslint/commit/b9a5efa937046f2d3f97e6caabb67a4bc182c983) test: skip symlink test on Windows (#19503) (fisker Cheung) +* [`46eea6d`](https://github.com/eslint/eslint/commit/46eea6d1cbed41d020cb76841ebba30710b0afd0) chore: remove `Rule` & `FormatterFunction` from `shared/types.js` (#19556) (Nitin Kumar) +* [`fb8cdb8`](https://github.com/eslint/eslint/commit/fb8cdb842edcc035969e14b7b7e3ee372304f2d7) fix: use `any[]` type for `context.options` (#19584) (Francesco Trotta) +* [`071dcd3`](https://github.com/eslint/eslint/commit/071dcd3a8e34aeeb52d0b9c23c2c4a1e58b45858) feat: support TS syntax in `no-dupe-class-members` (#19558) (Nitin Kumar) +* [`e849dc0`](https://github.com/eslint/eslint/commit/e849dc01286cde5b6e2f0e04bf36928710633715) docs: replace existing var with const (#19578) (Sweta Tanwar) +* [`bdcc91d`](https://github.com/eslint/eslint/commit/bdcc91d5b61ad1b3e55044767362548c906f5462) chore: modify .editorconfig to keep parity with prettier config (#19577) (Sweta Tanwar) +* [`7790d83`](https://github.com/eslint/eslint/commit/7790d8305a8cef7cc95c331205d59d6b3c2b4e2e) chore: fix some typos in comment (#19576) (todaymoon) +* [`cd72bcc`](https://github.com/eslint/eslint/commit/cd72bcc0c8d81fbf47ff3c8fe05ae48e1d862246) feat: Introduce a way to suppress violations (#19159) (Iacovos Constantinou) +* [`2a81578`](https://github.com/eslint/eslint/commit/2a81578ac179b1eeb1484fddee31913ed99042a2) feat: support TS syntax in `no-loss-of-precision` (#19560) (Nitin Kumar) +* [`366e369`](https://github.com/eslint/eslint/commit/366e3694afd85ab6605adf4fee4dfa1316be8b74) build: re-enable Prettier formatting for `package.json` files (#19569) (Francesco Trotta) +* [`30ae4ed`](https://github.com/eslint/eslint/commit/30ae4ed093d19e9950d09c2ab57f43d3564e31c9) feat: add new options to class-methods-use-this (#19527) (sethamus) +* [`b79ade6`](https://github.com/eslint/eslint/commit/b79ade6c1e0765457637f7ddaa52a39eed3aad38) feat: support TypeScript syntax in `no-array-constructor` (#19493) (Tanuj Kanti) +* [`0c65c62`](https://github.com/eslint/eslint/commit/0c65c628022ff3ce40598c1a6ce95728e7eda317) docs: don't pass filename when linting rule examples (#19571) (Milos Djermanovic) +* [`76064a6`](https://github.com/eslint/eslint/commit/76064a632438533bbb90e253ec72d172e948d200) test: ignore `package-lock.json` for `eslint-webpack-plugin` (#19572) (Francesco Trotta) +* [`6be36c9`](https://github.com/eslint/eslint/commit/6be36c99432ecdc72e33b6fb3293cf28f66ab78d) docs: Update custom-rules.md code example of fixer (#19555) (Yifan Pan) + +v9.23.0 - March 21, 2025 + +* [`0ac8ea4`](https://github.com/eslint/eslint/commit/0ac8ea45350fa5819694a3775641e94b1da3282b) chore: update dependencies for v9.23.0 release (#19554) (Francesco Trotta) +* [`20591c4`](https://github.com/eslint/eslint/commit/20591c49ff27435b1555111a929a6966febc249f) chore: package.json update for @eslint/js release (Jenkins) +* [`901344f`](https://github.com/eslint/eslint/commit/901344f9441c746dfa82261a0d00ff6ef35bcdf1) chore: update dependency @eslint/json to ^0.11.0 (#19552) (renovate[bot]) +* [`557a0d2`](https://github.com/eslint/eslint/commit/557a0d23755f8af4f2aaab751805c7ba6496fc21) feat: support TypeScript syntax in no-useless-constructor (#19535) (Josh Goldberg ✨) +* [`2357edd`](https://github.com/eslint/eslint/commit/2357edd09beca1c3f70c92df23f2f99b9ebc7a70) build: exclude autogenerated files from Prettier formatting (#19548) (Francesco Trotta) +* [`5405939`](https://github.com/eslint/eslint/commit/5405939efcfe6a038a7c89354eae9c39c8ff21e3) docs: show red underlines in TypeScript examples in rules docs (#19547) (Milos Djermanovic) +* [`48b53d6`](https://github.com/eslint/eslint/commit/48b53d6e79945b4f5f66aa2073c2d51ff7896c7c) docs: replace var with const in examples (#19539) (Nitin Kumar) +* [`0e20aa7`](https://github.com/eslint/eslint/commit/0e20aa72fec53b16a21c42ac9e82969efa8f94d2) fix: move deprecated `RuleContext` methods to subtype (#19531) (Francesco Trotta) +* [`5228383`](https://github.com/eslint/eslint/commit/5228383e3e5c77c7dd07fc9d17b9a57c2ee5bb48) chore: fix update-readme formatting (#19544) (Milos Djermanovic) +* [`c39d7db`](https://github.com/eslint/eslint/commit/c39d7db7142ebdb8174da00358b80094eaad39c1) docs: Update README (GitHub Actions Bot) +* [`a4f8760`](https://github.com/eslint/eslint/commit/a4f87604f4d8d53cb2efbd19aa067606dd1c409e) docs: revert accidental changes (#19542) (Francesco Trotta) +* [`5439525`](https://github.com/eslint/eslint/commit/5439525925dc26b387cc6cebf0b01f42464b4ab0) chore: format JSON files in Trunk (#19541) (Francesco Trotta) +* [`75adc99`](https://github.com/eslint/eslint/commit/75adc99eab2878e58fc88f0d4b1b6f9091455914) chore: enabled Prettier in Trunk (#19354) (Josh Goldberg ✨) +* [`2395168`](https://github.com/eslint/eslint/commit/239516856fbf61828f5ac2c8b45e245103c41c04) chore: added .git-blame-ignore-revs for Prettier via trunk fmt (#19538) (Josh Goldberg ✨) +* [`129882d`](https://github.com/eslint/eslint/commit/129882d2fdb4e7f597ed78eeadd86377f3d6b078) chore: formatted files with Prettier via trunk fmt (#19355) (Josh Goldberg ✨) +* [`1738dbc`](https://github.com/eslint/eslint/commit/1738dbc36ce556745c230d3592e7f1aa673a1430) chore: temporarily disable prettier in trunk (#19537) (Josh Goldberg ✨) +* [`8320241`](https://github.com/eslint/eslint/commit/83202412a1ceefd3eba4b97cc9dbe99ab70d59a2) feat: support TypeScript syntax in `default-param-last` (#19431) (Josh Goldberg ✨) +* [`280128f`](https://github.com/eslint/eslint/commit/280128f73def56479e32e7d40879fff05b7f44a2) docs: add copy button (#19512) (xbinaryx) +* [`833c4a3`](https://github.com/eslint/eslint/commit/833c4a301d4f7d21583d520d20d8a6724171733f) feat: defineConfig() supports "flat/" config prefix (#19533) (Nicholas C. Zakas) +* [`cc3bd00`](https://github.com/eslint/eslint/commit/cc3bd00795708c4d7c06a6103983245cc9d9845b) fix: reporting variable used in catch block in `no-useless-assignment` (#19423) (Tanuj Kanti) +* [`cd83eaa`](https://github.com/eslint/eslint/commit/cd83eaa761b4acd9a43fd3888a12ea08483c3366) docs: replace `var` with `const` in examples (#19530) (Nitin Kumar) +* [`7ff0cde`](https://github.com/eslint/eslint/commit/7ff0cde23014909997dd493de890463d8b09205e) docs: Update README (GitHub Actions Bot) +* [`996cfb9`](https://github.com/eslint/eslint/commit/996cfb9771734cb462b02a73c4aa87555854a05e) docs: migrate sass to module system (#19518) (xbinaryx) +* [`dc854fd`](https://github.com/eslint/eslint/commit/dc854fdd2634cdec575ae5fc508edd838056f006) chore: update dependency shelljs to ^0.9.0 (#19524) (renovate[bot]) +* [`4a0df16`](https://github.com/eslint/eslint/commit/4a0df16f1ba7bed02d15c561119623199ea2ace0) feat: circular autofix/conflicting rules detection (#19514) (Milos Djermanovic) +* [`5d57496`](https://github.com/eslint/eslint/commit/5d574963b71529abbb84fbc4861230a050434664) chore: fix some comments (#19525) (jimmycathy) +* [`17cb958`](https://github.com/eslint/eslint/commit/17cb9586a706e75adee09b2388deea77a6ca8f14) docs: replace `var` with `let` and `const` in rule examples (#19515) (Tanuj Kanti) +* [`83e24f5`](https://github.com/eslint/eslint/commit/83e24f5be4d5723b5f79512b46ab68bc97a23247) docs: Replace var with let or const (#19511) (Jenna Toff) +* [`a59d0c0`](https://github.com/eslint/eslint/commit/a59d0c06b5a28ae5149eae6d10fa9f4968963b01) docs: Update docs for defineConfig (#19505) (Nicholas C. Zakas) +* [`d46ff83`](https://github.com/eslint/eslint/commit/d46ff832195aa841224a21086afda9d98be45ad6) fix: `no-dupe-keys` false positive with proto setter (#19508) (Milos Djermanovic) +* [`e732773`](https://github.com/eslint/eslint/commit/e7327736b92686e02721461ac9ccf6e65e0badac) fix: navigation of search results on pressing Enter (#19502) (Tanuj Kanti) +* [`fe92927`](https://github.com/eslint/eslint/commit/fe929270f33493d1a77be0f25a95d97817440c49) docs: `require-unicode-regexp` add note for `i` flag and `\w` (#19510) (Chaemin-Lim) +* [`f4e9c5f`](https://github.com/eslint/eslint/commit/f4e9c5fda9f8bcd36f1afe3706da60554cd07c48) fix: allow `RuleTester` to test files inside `node_modules/` (#19499) (fisker Cheung) +* [`9c5c6ee`](https://github.com/eslint/eslint/commit/9c5c6ee7734c6a5918a5983d4f2bd971ca3225a8) test: fix an assertion failure (#19500) (fisker Cheung) +* [`be56a68`](https://github.com/eslint/eslint/commit/be56a685bf1aadbf59d99d43e71c00802bc9ba27) feat: support TypeScript syntax in `class-methods-use-this` (#19498) (Josh Goldberg ✨) +* [`7a699a6`](https://github.com/eslint/eslint/commit/7a699a6b2616c24fe58df1265f6148b406a17e41) chore: remove formatting-related lint rules internally (#19473) (Josh Goldberg ✨) +* [`c99db89`](https://github.com/eslint/eslint/commit/c99db89141f1601abe6f9d398a4b6c126e3a0bdb) test: replace WebdriverIO with Cypress (#19465) (Pixel998) + +v9.22.0 - March 7, 2025 + +* [`97f788b`](https://github.com/eslint/eslint/commit/97f788b02e5742445887b4499a6dba9abb879a79) chore: upgrade @eslint/js@9.22.0 (#19489) (Milos Djermanovic) +* [`eed409a`](https://github.com/eslint/eslint/commit/eed409a64bfe2ae1123086aaf1652c8124e49b7c) chore: package.json update for @eslint/js release (Jenkins) +* [`f9a56d3`](https://github.com/eslint/eslint/commit/f9a56d337881300c94093e38804ba929ee09f7e9) chore: upgrade eslint-scope@8.3.0 (#19488) (Milos Djermanovic) +* [`7ddb095`](https://github.com/eslint/eslint/commit/7ddb095419203d0efc883a6b3fdd3ac20128400a) feat: Export defineConfig, globalIgnores (#19487) (Nicholas C. Zakas) +* [`86c5f37`](https://github.com/eslint/eslint/commit/86c5f37bc7300157d9f19b633197135d2a7a2645) docs: Update README (GitHub Actions Bot) +* [`19c0127`](https://github.com/eslint/eslint/commit/19c0127e79c37dba8d389733024be7326e540767) fix: improve message for `no-console` suggestions (#19483) (Francesco Trotta) +* [`fbdeff0`](https://github.com/eslint/eslint/commit/fbdeff08f3bf4edd7f686af91d9ac0ed6e295080) docs: Update README (GitHub Actions Bot) +* [`c9e8510`](https://github.com/eslint/eslint/commit/c9e85105300069f4aa60526ca2de4d67d1eebe1d) docs: generate deprecation notice in TSDoc comments from rule metadata (#19461) (Francesco Trotta) +* [`2f386ad`](https://github.com/eslint/eslint/commit/2f386ad203a672832c91e72c285a25bd64d48d9d) docs: replace `var` with `const` in rule examples (#19469) (Tanuj Kanti) +* [`0e688e3`](https://github.com/eslint/eslint/commit/0e688e3a0d53bad991d2b4ae3bda926cc29bd54b) docs: Update README (GitHub Actions Bot) +* [`49e624f`](https://github.com/eslint/eslint/commit/49e624f4a02e03762232cce2047febb36b1bcf4c) fix: improve error message for falsy parsed JS AST (#19458) (Josh Goldberg ✨) +* [`06b596d`](https://github.com/eslint/eslint/commit/06b596d221a2b4af644824bd10e0194e7237b6f2) docs: Restore the carrot to the position where the search input was lost (#19459) (Amaresh S M) + +v9.21.0 - February 21, 2025 + +* [`a8c9a9f`](https://github.com/eslint/eslint/commit/a8c9a9f1b30db08094b145dd79921ae302b6ae24) chore: update `@eslint/eslintrc` and `@eslint/js` (#19453) (Francesco Trotta) +* [`265e0cf`](https://github.com/eslint/eslint/commit/265e0cf6d03df44f0e65ce5bcb0bac382189486a) chore: package.json update for @eslint/js release (Jenkins) +* [`418717f`](https://github.com/eslint/eslint/commit/418717f1150bb794c40014eca60c9116de2b0488) feat: introduce new deprecated types for rules (#19238) (fnx) +* [`3401b85`](https://github.com/eslint/eslint/commit/3401b85faaf75629900b7592433169fc00d8b224) test: add test for `Rule.ReportDescriptor` type (#19449) (Francesco Trotta) +* [`e497aa7`](https://github.com/eslint/eslint/commit/e497aa75f5441406985d417303081944f24acf6f) chore: update rewrite dependencies (#19448) (Francesco Trotta) +* [`c5561ea`](https://github.com/eslint/eslint/commit/c5561ea7fcc9d48f7c8017f51fb64fcdf13ff832) docs: Update README (GitHub Actions Bot) +* [`db5340d`](https://github.com/eslint/eslint/commit/db5340d57bff6b6e3a148f0f2bb56c7da6614ec0) fix: update missing plugin message template (#19445) (Milos Djermanovic) +* [`d8ffdd4`](https://github.com/eslint/eslint/commit/d8ffdd4e51ac46cef51b4118aa3d97195b38de63) fix: do not exit process on rule crash (#19436) (Francesco Trotta) +* [`dab5478`](https://github.com/eslint/eslint/commit/dab5478e8628447dbf9eaaa8b6f36d7ca253ed48) chore: better error message for missing plugin in config (#19402) (Tanuj Kanti) +* [`80b0485`](https://github.com/eslint/eslint/commit/80b048535e1d951692e838fe502fb0edb72c837f) docs: replace `var` with `let` and `const` in rule example (#19434) (Tanuj Kanti) +* [`ebfe2eb`](https://github.com/eslint/eslint/commit/ebfe2ebc3d8b8f2d84caf309b2fc6bc8fd66fc22) chore: set js language for bug report issue config block (#19439) (Josh Goldberg ✨) +* [`f67d5e8`](https://github.com/eslint/eslint/commit/f67d5e875324a9d899598b11807a9c7624021432) docs: Update README (GitHub Actions Bot) +* [`75afc61`](https://github.com/eslint/eslint/commit/75afc61ff89c8c38a31877d1302584af9266f6d3) docs: Update README (GitHub Actions Bot) +* [`5fd211d`](https://github.com/eslint/eslint/commit/5fd211d00b6f0fc58cf587196a432325b7b88ec2) test: processors can return subpaths (#19425) (Milos Djermanovic) +* [`0636cab`](https://github.com/eslint/eslint/commit/0636caba7dd7c77c1845a69257bda68d5287a097) docs: Update Eleventy from v2 to v3 (#19415) (Amaresh S M) +* [`5c5b802`](https://github.com/eslint/eslint/commit/5c5b8025d3e2a2a796909bdf7866fdce2a2f334c) feat: Add `--ext` CLI option (#19405) (Milos Djermanovic) +* [`dd7d930`](https://github.com/eslint/eslint/commit/dd7d93063418a9a9064a0e7cb7f556f5b8b6e96b) docs: Update README (GitHub Actions Bot) + +v9.20.1 - February 11, 2025 + +* [`fe3ccb2`](https://github.com/eslint/eslint/commit/fe3ccb2ff43a9f20a7801c679f7d41f6a7ed3ddc) docs: allow typing in search box while dropdown is open (#19424) (Amaresh S M) +* [`274f054`](https://github.com/eslint/eslint/commit/274f054f19f5f490d9496c6eee4bcd8620d2f4be) fix: fix `RuleContext` type (#19417) (Francesco Trotta) +* [`93c78a5`](https://github.com/eslint/eslint/commit/93c78a5c58edb7ead9bff87c874d2ff9b824ec04) docs: Add instructions for pnpm compat (#19422) (Nicholas C. Zakas) +* [`b476a93`](https://github.com/eslint/eslint/commit/b476a930bb3a6d644c482747d985f5da0d89e1e9) docs: Fix Keyboard Navigation for Search Results (#19416) (Amaresh S M) +* [`ccb60c0`](https://github.com/eslint/eslint/commit/ccb60c0b1452e73750e3734c9cd7c7b12c473827) docs: Update README (GitHub Actions Bot) + +v9.20.0 - February 7, 2025 + +* [`979097a`](https://github.com/eslint/eslint/commit/979097a3b4c656e2d9faabd4a52010d6647911f6) chore: upgrade @eslint/js@9.20.0 (#19412) (Francesco Trotta) +* [`031734e`](https://github.com/eslint/eslint/commit/031734efcb27e0d800da7ec32f5d5dae55f80564) chore: package.json update for @eslint/js release (Jenkins) +* [`91d4d9f`](https://github.com/eslint/eslint/commit/91d4d9f62095e302c71595cc04c47073f366315c) fix: Bring types in sync with @eslint/core (#19157) (Nicholas C. Zakas) +* [`b7012c8`](https://github.com/eslint/eslint/commit/b7012c85f3c0f683baeffb6d856faf86c4d41702) docs: rewrite examples with var using let and const (#19407) (Mueez Javaid Hashmi) +* [`d4c47c3`](https://github.com/eslint/eslint/commit/d4c47c3738f2bf53b4f6a1cf505861b35875ac5f) test: avoid empty config warning in test output (#19408) (Milos Djermanovic) +* [`e89a54a`](https://github.com/eslint/eslint/commit/e89a54a3090f3503daf5e36b02b0035c993e3fd1) feat: change behavior of inactive flags (#19386) (Milos Djermanovic) +* [`fa25c7a`](https://github.com/eslint/eslint/commit/fa25c7a79edee280f275fbc35b83bcde906d1480) fix: Emit warning when empty config file is used (#19399) (Nicholas C. Zakas) +* [`6406376`](https://github.com/eslint/eslint/commit/64063765afa5bf29855d996ccabfaa93b19bd458) docs: Update README (GitHub Actions Bot) +* [`350f2b9`](https://github.com/eslint/eslint/commit/350f2b9349bc8d2230cd953c14b77071f2961f47) docs: rewrite some examples with var using let and const (#19404) (Mueez Javaid Hashmi) +* [`93c325a`](https://github.com/eslint/eslint/commit/93c325a7a841d0fe4b5bf79efdec832e7c8f805f) docs: rewrite examples with var using let and const (#19398) (Mueez Javaid Hashmi) +* [`56ff404`](https://github.com/eslint/eslint/commit/56ff4048e053374db39201e7e880bde4c930e19f) docs: replace var with let or const in rules docs (#19396) (Daniel Harbrueger) +* [`4053226`](https://github.com/eslint/eslint/commit/4053226996bbdec1ffdef8af1b9d7f5aa4b11b86) docs: change `sourceType` in `no-eval` examples (#19393) (Milos Djermanovic) +* [`1324af0`](https://github.com/eslint/eslint/commit/1324af027986d655848ee1a9dcb89a527917ea3e) docs: replace var with let and const in rules docs (#19392) (Daniel Harbrueger) +* [`8b87e00`](https://github.com/eslint/eslint/commit/8b87e007bb2ba59b73061d22ef34baffb5656b79) docs: replace `var` with `const` and `let` in rules (#19389) (Tanuj Kanti) +* [`31a9fd0`](https://github.com/eslint/eslint/commit/31a9fd03d23aecf2b1e0c9b3df27554aff245723) fix: Clearer error message for wrong plugin format (#19380) (Nicholas C. Zakas) +* [`61d99e3`](https://github.com/eslint/eslint/commit/61d99e38f248f4d9abc09d970c4eebddd1af86ca) fix: Better error message for unserializable parser (#19384) (Nicholas C. Zakas) +* [`758c66b`](https://github.com/eslint/eslint/commit/758c66bc8d83cd4eda9639b0745f0d0fb70f04f4) docs: Explain what frozen rules mean (#19382) (Nicholas C. Zakas) +* [`67dd82a`](https://github.com/eslint/eslint/commit/67dd82ab88d784b6f36e471b6a5c6f64e37f9485) chore: update dependency @eslint/json to ^0.10.0 (#19387) (renovate[bot]) +* [`db1b9a6`](https://github.com/eslint/eslint/commit/db1b9a66e387e573f45885687dfefc04ab2877fe) fix: Ensure module scope is checked for references in `consistent-this` (#19383) (Nicholas C. Zakas) +* [`8bcd820`](https://github.com/eslint/eslint/commit/8bcd820f37f2361e4f7261a9876f52d21bd9de8f) fix: `arrow-body-style` crash with single-token body (#19379) (Milos Djermanovic) +* [`15ac0e1`](https://github.com/eslint/eslint/commit/15ac0e182486f32d63171a310050383e15767697) chore: add permissions: read-all to stale.yml workflow (#19374) (Josh Goldberg ✨) +* [`0ef8bb8`](https://github.com/eslint/eslint/commit/0ef8bb859c988e558683c2d8bd9c9606f22e456c) docs: additional checks for rule examples (#19358) (Milos Djermanovic) +* [`58ab2f6`](https://github.com/eslint/eslint/commit/58ab2f69d2d4cf9b49bf3fd303795040ec761ebd) docs: fix order of installation steps in getting started (#19326) (Tanuj Kanti) + +v9.19.0 - January 24, 2025 + +* [`9b9cb05`](https://github.com/eslint/eslint/commit/9b9cb0584867916d50aa2e9590b132e2ef8ca59c) chore: upgrade @eslint/js@9.19.0 (#19371) (Milos Djermanovic) +* [`58560e7`](https://github.com/eslint/eslint/commit/58560e70bb4dcb305343fcd7c893ac56a404f674) chore: package.json update for @eslint/js release (Jenkins) +* [`cfea9ab`](https://github.com/eslint/eslint/commit/cfea9abe0e27cf2ce1d27232b8c70555397e141b) docs: Clarify overrideConfig option (#19370) (Nicholas C. Zakas) +* [`2b84f66`](https://github.com/eslint/eslint/commit/2b84f666cd7474bb061e2f12205af57f5dbb89d6) docs: Update README (#19362) (Nicholas C. Zakas) +* [`044f93c`](https://github.com/eslint/eslint/commit/044f93cbbe71a45130156004509083814e2b9669) docs: clarify frozen rule description (#19351) (Pavel) +* [`797ee7c`](https://github.com/eslint/eslint/commit/797ee7c0d669678b90c5d5742228bc7b24353f79) docs: fix Bluesky links (#19368) (Milos Djermanovic) +* [`81a9c0e`](https://github.com/eslint/eslint/commit/81a9c0ebc33dd33765711296f827c4448c80163d) docs: Update README (GitHub Actions Bot) +* [`093fb3d`](https://github.com/eslint/eslint/commit/093fb3d40286588c2c425b738426ebfe5d142a63) docs: replace `var` with `let` and `const` in rule examples (#19365) (Tanuj Kanti) +* [`417de32`](https://github.com/eslint/eslint/commit/417de3298527e4f257e1ae7b02e1df9db3c9ed33) docs: replace var with const in rule examples (#19352) (jj) +* [`17f2aae`](https://github.com/eslint/eslint/commit/17f2aaec16d5afbb0d219bce6ae01d7b15d74828) docs: update getting-started config to match default generated config (#19308) (0xDev) +* [`aae6717`](https://github.com/eslint/eslint/commit/aae67172ab9631b4267fc03f64d3c3d6d1fcda73) fix: sync rule type header comments automatically (#19276) (Francesco Trotta) +* [`8a0a5a8`](https://github.com/eslint/eslint/commit/8a0a5a8851f72982327c2aa3a41403963f025771) docs: better `global ignores` instruction (#19297) (Jacopo Marrone) +* [`2089707`](https://github.com/eslint/eslint/commit/20897070913418078d8f1ea9a877d223650dff73) test: fix failing test in Node.js v22.13.0 (#19345) (Francesco Trotta) +* [`6671a2c`](https://github.com/eslint/eslint/commit/6671a2cd8ccc710fefbccad9a813c3bea5f76c68) docs: Update README (GitHub Actions Bot) +* [`1637b8e`](https://github.com/eslint/eslint/commit/1637b8e87df5c7f58ab71e0e159f4b96c998e070) feat: add `--report-unused-inline-configs` (#19201) (Josh Goldberg ✨) +* [`e39d3f2`](https://github.com/eslint/eslint/commit/e39d3f22ff793db42e1f1fc3808cbb12fc513118) docs: fix divider for rule category (#19264) (Tanuj Kanti) +* [`e0cf53f`](https://github.com/eslint/eslint/commit/e0cf53f80a4b127524e0badc8999d5d1a247143f) docs: fix search result box position for small screens (#19328) (Tanuj Kanti) +* [`f92a680`](https://github.com/eslint/eslint/commit/f92a6803a10c66cf77408b2bf29c17bcd63b1049) docs: replace var with let or const in rule examples (#19331) (Ravi Teja Kolla) +* [`b04b84b`](https://github.com/eslint/eslint/commit/b04b84bc17d4aaaea1326cb08196593624db02a2) docs: revert accidental changes in TS config files docs (#19336) (Francesco Trotta) + +v9.18.0 - January 10, 2025 + +* [`c52be85`](https://github.com/eslint/eslint/commit/c52be85c4a916f70807377e1a486adb3a5857347) chore: upgrade to `@eslint/js@9.18.0` (#19330) (Francesco Trotta) +* [`362099c`](https://github.com/eslint/eslint/commit/362099c580992b2602316fc417ce3e595b96f28c) chore: package.json update for @eslint/js release (Jenkins) +* [`9486141`](https://github.com/eslint/eslint/commit/94861418f1573e4e1cbdd0174413d19054553294) deps: upgrade `@eslint/core` and `@eslint/plugin-kit` (#19329) (Francesco Trotta) +* [`d9c23c5`](https://github.com/eslint/eslint/commit/d9c23c55be52a431141f38561c14140ee8b15686) docs: replace `var` with `const` in rule examples (#19325) (Tanuj Kanti) +* [`8e1a898`](https://github.com/eslint/eslint/commit/8e1a898411fd16c73332d7a2dd28aff9bac8da01) docs: add tabs to cli code blocks (#18784) (Jay) +* [`f3aeefb`](https://github.com/eslint/eslint/commit/f3aeefbd6547c25d78819ab7e77cf36a2c26611c) docs: rewrite using let and const in rule examples (#19320) (PoloSpark) +* [`0b680b3`](https://github.com/eslint/eslint/commit/0b680b3cc19c1e8d79ab94e7160051177c4adfe7) docs: Update README (GitHub Actions Bot) +* [`98c86a9`](https://github.com/eslint/eslint/commit/98c86a99f7657a2f15ea30a251523446b10a7cad) docs: `Edit this page` button link to different branches (#19228) (Tanuj Kanti) +* [`6947901`](https://github.com/eslint/eslint/commit/6947901d14b18dbb2db259c9769bd8ac4cd04c3c) docs: remove hardcoded edit link (#19323) (Milos Djermanovic) +* [`03f2f44`](https://github.com/eslint/eslint/commit/03f2f442a9a8bec15e89786980c07be5980cdac5) docs: rewrite var with const in rules examples (#19317) (Thiago) +* [`26c3003`](https://github.com/eslint/eslint/commit/26c3003bfca2f7d98950446fdf5b3978d17a3a60) docs: Clarify dangers of eslint:all (#19318) (Nicholas C. Zakas) +* [`c038257`](https://github.com/eslint/eslint/commit/c03825730d277405c357388d62ed48b3973083ba) docs: add `eqeqeq` in related rules to `no-eq-null` (#19310) (ëŖ¨ë°€LuMir) +* [`89c8fc5`](https://github.com/eslint/eslint/commit/89c8fc54c977ac457d3b5525a87cec1c51e72e23) docs: rewrite examples with var using let and const (#19315) (Amaresh S M) +* [`495aa49`](https://github.com/eslint/eslint/commit/495aa499a7390f99b763cba8f2b8312e3eecfe0d) chore: extract package `name` from `package.json` for public interface (#19314) (ëŖ¨ë°€LuMir) +* [`db574c4`](https://github.com/eslint/eslint/commit/db574c4d380e2d25b6111a06bd15caa83f75bb2d) docs: add missing backticks to `no-void` (#19313) (ëŖ¨ë°€LuMir) +* [`8d943c3`](https://github.com/eslint/eslint/commit/8d943c335c528a6a6a631dcbd98506238240ecfb) docs: add missing backticks to `default-case-last` (#19311) (ëŖ¨ë°€LuMir) +* [`36ef8bb`](https://github.com/eslint/eslint/commit/36ef8bbeab495ef2598a4b1f52e32b4cb50be5e2) docs: rewrite examples with var using let and const (#19298) (Amaresh S M) +* [`1610c9e`](https://github.com/eslint/eslint/commit/1610c9ee1479f23b1bc5a6853d0b42b83dacdb7f) docs: add missing backticks to `no-else-return` (#19309) (ëŖ¨ë°€LuMir) +* [`df409d8`](https://github.com/eslint/eslint/commit/df409d8f76555c7baa4353d678d5fc460454a4d7) docs: Update README (GitHub Actions Bot) +* [`e84e6e2`](https://github.com/eslint/eslint/commit/e84e6e269c4aefc84952e17a1f967697b02b7ad2) feat: Report allowed methods for `no-console` rule (#19306) (Anna Bocharova) +* [`2e84213`](https://github.com/eslint/eslint/commit/2e842138e689ee5623552e885c3a5ac1b0c2bfcf) docs: Fix Horizontal Scroll Overflow in Rule Description on Mobile View (#19304) (Amaresh S M) +* [`6e7361b`](https://github.com/eslint/eslint/commit/6e7361bb6ae93c87fccdf2219379c7793517f17a) docs: replace `var` with `let` and `const` in rule example (#19302) (Tanuj Kanti) +* [`069af5e`](https://github.com/eslint/eslint/commit/069af5e9ac43c7f33bd2a30abce3d5d94f504465) docs: rewrite `var` using `const` in rule examples (#19303) (Kim GyeonWon) +* [`064e35d`](https://github.com/eslint/eslint/commit/064e35de95339cfedcad467c3c9871d5ff70c1a7) docs: remove 'I hope to' comments from scope-manager-interface (#19300) (Josh Goldberg ✨) +* [`8e00305`](https://github.com/eslint/eslint/commit/8e003056a805468b07bcf4edba83a90a932fb520) docs: replace `var` with `const` in rule examples (#19299) (Tanuj Kanti) +* [`a559009`](https://github.com/eslint/eslint/commit/a559009f51ad9f081bae5252bb2b7a6e23c54767) docs: Add warning about extending core rules (#19295) (Nicholas C. Zakas) +* [`0bfdf6c`](https://github.com/eslint/eslint/commit/0bfdf6caaf3e1553c67a77da900245879c730ad3) docs: Update README (GitHub Actions Bot) +* [`ce0b9ff`](https://github.com/eslint/eslint/commit/ce0b9ff04242f61c8c49fc1ce164eb45eb3c459a) docs: add navigation link for `code explorer` (#19285) (Tanuj Kanti) +* [`e255cc9`](https://github.com/eslint/eslint/commit/e255cc98abef202929112378bfe133f260f2ac9d) docs: add bluesky icon to footer (#19290) (Tanuj Kanti) +* [`5d64851`](https://github.com/eslint/eslint/commit/5d64851955f410f31c159a7097f6cc7d4a01d6a1) docs: remove outdated info about environments (#19296) (Francesco Trotta) +* [`eec01f0`](https://github.com/eslint/eslint/commit/eec01f04ae1c44f7c9a8c6afec59dd72f5a57600) docs: switch rule examples config format to `languageOptions` (#19277) (Milos Djermanovic) +* [`b36ca0a`](https://github.com/eslint/eslint/commit/b36ca0a490829c579358ec7193bde35275000e04) docs: Fixing Focus Order by Rearranging Element Sequence (#19241) (Amaresh S M) +* [`d122c8a`](https://github.com/eslint/eslint/commit/d122c8a756bb8e232ef7c25cca6dcae645094835) docs: add missing backticks to `sort-imports` (#19282) (ëŖ¨ë°€LuMir) +* [`0367a70`](https://github.com/eslint/eslint/commit/0367a70a43346f1b9df8be75d38f98f9cfe4007c) docs: update custom parser docs (#19288) (Francesco Trotta) +* [`da768d4`](https://github.com/eslint/eslint/commit/da768d4541c4c30bfc33640a07a8d8a485520b18) fix: correct `overrideConfigFile` type (#19289) (Francesco Trotta) +* [`8c07ebb`](https://github.com/eslint/eslint/commit/8c07ebb9004309f8691f972d554e8bbb3eb517bc) docs: add `border-radius` to `hX:target` selector styles (#19270) (ëŖ¨ë°€LuMir) +* [`eff7c57`](https://github.com/eslint/eslint/commit/eff7c5721c101975a03e7906905f1fe2c9538df0) docs: add limitation section in `no-loop-func` (#19287) (Tanuj Kanti) +* [`8efc2d0`](https://github.com/eslint/eslint/commit/8efc2d0c92dab6099f34c1479cd80bdc5cd1b07b) feat: unflag TypeScript config files (#19266) (Francesco Trotta) +* [`87a9352`](https://github.com/eslint/eslint/commit/87a9352c621e7cd1d5bb77b3c08df7837363ea12) feat: check imports and class names in `no-shadow-restricted-names` (#19272) (Milos Djermanovic) +* [`5db226f`](https://github.com/eslint/eslint/commit/5db226f4da9ad7d53a4505a90290b68d4036c082) docs: add missing backticks in various parts of the documentation (#19269) (ëŖ¨ë°€LuMir) +* [`789edbb`](https://github.com/eslint/eslint/commit/789edbbae5aeeefc8fee94cd653b0b5f3e2ae3eb) docs: Update README (GitHub Actions Bot) +* [`613c06a`](https://github.com/eslint/eslint/commit/613c06a2c341758739473409a2331074884ec7f8) docs: mark rules that are frozen with â„ī¸ (#19231) (Amaresh S M) +* [`43172ec`](https://github.com/eslint/eslint/commit/43172ecbd449c13a503cb39539e31106179f5d80) docs: Update README (GitHub Actions Bot) +* [`ac8b3c4`](https://github.com/eslint/eslint/commit/ac8b3c4ca9f7b84f84356137cf23a1ba6dfecf11) docs: fix description of `overrideConfigFile` option (#19262) (Milos Djermanovic) +* [`6fe0e72`](https://github.com/eslint/eslint/commit/6fe0e7244a7e88458ea7fdcebc43794c03793c4b) chore: update dependency @eslint/json to ^0.9.0 (#19263) (renovate[bot]) +* [`bbb9b46`](https://github.com/eslint/eslint/commit/bbb9b46c20662019e98df85dedde9b68719afa1f) docs: Update README (GitHub Actions Bot) +* [`995b492`](https://github.com/eslint/eslint/commit/995b49231a3f0ccddb941663175ce4fead9c9432) docs: fix inconsistent divider in rule categories box (#19249) (Tanuj Kanti) +* [`f76d05d`](https://github.com/eslint/eslint/commit/f76d05da6e745adbea574c32b334638c7ba3c0c8) docs: Refactor search result handling with better event listener cleanup (#19252) (Amaresh S M) +* [`c5f3d7d`](https://github.com/eslint/eslint/commit/c5f3d7dab303468ae33ccfec61bba75a816f832c) docs: Update README (GitHub Actions Bot) + +v9.17.0 - December 13, 2024 + +* [`cc243c9`](https://github.com/eslint/eslint/commit/cc243c948226a585f01d3e68b4cedbabcc5e0e40) chore: upgrade to `@eslint/js@9.17.0` (#19242) (Francesco Trotta) +* [`84c5787`](https://github.com/eslint/eslint/commit/84c57877801da729265cf9ce11d325c0be8c82b1) chore: package.json update for @eslint/js release (Jenkins) +* [`eed91d1`](https://github.com/eslint/eslint/commit/eed91d12d4d265bd32905dd1fbf8a6d5dbcdb54a) feat: add suggestions to `no-unused-vars` (#18352) (Tanuj Kanti) +* [`4c4f53b`](https://github.com/eslint/eslint/commit/4c4f53b8c961dd6aed6c0cdca303018d805a59fe) chore: add missing backticks to `flags.js` (#19226) (ëŖ¨ë°€LuMir) +* [`3c22d2a`](https://github.com/eslint/eslint/commit/3c22d2accedd7b0bc381a4eee2c3db4df657b236) docs: update `yoda` to `Yoda` in `yoda.md` for consistency (#19230) (ëŖ¨ë°€LuMir) +* [`e0a2203`](https://github.com/eslint/eslint/commit/e0a220355f447b3674b758776344959ce746b5e3) docs: add missing backticks to `no-sequences` (#19233) (ëŖ¨ë°€LuMir) +* [`4cc4881`](https://github.com/eslint/eslint/commit/4cc48812cdfd686304b5b3b71ea70cd7f2d8389a) docs: Update README (GitHub Actions Bot) +* [`3db6fdf`](https://github.com/eslint/eslint/commit/3db6fdf885b17d25103e3cddc31fea56542e064d) docs: [no-await-in-loop] expand on benefits and inapplicability (#19211) (Kirk Waiblinger) +* [`67d683d`](https://github.com/eslint/eslint/commit/67d683df29d873002299c70736dacea731b69f5d) fix: fix crash when `message.fix` is nullish (#19168) (ntnyq) +* [`bf2a4f6`](https://github.com/eslint/eslint/commit/bf2a4f686bb387711e269b08f13771e4208113f0) docs: add missing backticks to `func-style` (#19227) (ëŖ¨ë°€LuMir) +* [`4b3132c`](https://github.com/eslint/eslint/commit/4b3132c3f55db6b51665c4c42bb762d00e266262) chore: update dependency eslint-plugin-expect-type to ^0.6.0 (#19221) (renovate[bot]) +* [`9bf2204`](https://github.com/eslint/eslint/commit/9bf220464a594d44744fd35d688c61159366b8ea) chore: add type definitions for the `eslint-config-eslint` package (#19050) (Arya Emami) +* [`ba098bd`](https://github.com/eslint/eslint/commit/ba098bd03c9943007ec77d628dc59f7eaf60f871) docs: add missing header to `prefer-spread` (#19224) (ëŖ¨ë°€LuMir) +* [`b607ae6`](https://github.com/eslint/eslint/commit/b607ae64913ca2b6450a74344ab0ad548e314915) docs: update description of `no-param-reassign` (#19220) (ëŖ¨ë°€LuMir) +* [`1eb424d`](https://github.com/eslint/eslint/commit/1eb424de558fba301eaef9a7fce256539b48dee3) docs: add missing backticks to `prefer-destructuring` (#19223) (ëŖ¨ë°€LuMir) +* [`85998d1`](https://github.com/eslint/eslint/commit/85998d14051c7a0c5b7b6da8cfda13dc1fc7c153) docs: add missing backticks to `no-unneeded-ternary` (#19222) (ëŖ¨ë°€LuMir) +* [`ee8c220`](https://github.com/eslint/eslint/commit/ee8c2200a19dd55aa5068b6cd336a2aec7c52ad3) chore: fix incorrect `name` property in `integration-tutorial-code` (#19218) (ëŖ¨ë°€LuMir) +* [`b75b32c`](https://github.com/eslint/eslint/commit/b75b32c091f0742788ecf232f52d9e9427008526) docs: add missing backticks to `no-new-func` (#19219) (ëŖ¨ë°€LuMir) +* [`a7700bc`](https://github.com/eslint/eslint/commit/a7700bcc9c566b3e348a72f3e6a4a6f5ac3345a4) docs: add missing backticks to `id-length` (#19217) (ëŖ¨ë°€LuMir) +* [`c618707`](https://github.com/eslint/eslint/commit/c61870715e5bc1cb51c45a8efd72f392502807b8) fix: ignore vars with non-identifier references in no-useless-assignment (#19200) (YeonJuan) +* [`e2bb429`](https://github.com/eslint/eslint/commit/e2bb429974ae397c1c1cc495fa7630441c9da61a) docs: add missing backticks to `complexity.md` (#19214) (ëŖ¨ë°€LuMir) +* [`045d716`](https://github.com/eslint/eslint/commit/045d716b92276720961e950e6a259ef40e8e07ea) docs: add missing `)` to `id-denylist` (#19213) (ëŖ¨ë°€LuMir) +* [`7fe4114`](https://github.com/eslint/eslint/commit/7fe4114be2e714506fd406ea4474430ea3de0f93) docs: Update README (GitHub Actions Bot) +* [`c743ba6`](https://github.com/eslint/eslint/commit/c743ba6402a27130f8b7700ae0816b087e20085d) docs: add CSS language to `no-restricted-syntax` (#19208) (Milos Djermanovic) +* [`cca801d`](https://github.com/eslint/eslint/commit/cca801dd17931cfd913456569b41f6132d0366aa) chore: Upgrade cross-spawn to 7.0.6 (#19185) (folortin) +* [`1416d70`](https://github.com/eslint/eslint/commit/1416d70d4358a57e99e810dd25ac0e6263924c02) docs: add missing backticks to `eqeqeq` (#19207) (ëŖ¨ë°€LuMir) +* [`b950c1b`](https://github.com/eslint/eslint/commit/b950c1b7db28cc569a53b6677b71a40f1fe3bf98) docs: add missing backticks to `prefer-object-spread` (#19206) (ëŖ¨ë°€LuMir) +* [`8a941cb`](https://github.com/eslint/eslint/commit/8a941cb776d0872236c33027bcff6337739a6ddb) docs: update docs and `description` of `require-unicode-regexp` (#19205) (ëŖ¨ë°€LuMir) +* [`cbab228`](https://github.com/eslint/eslint/commit/cbab2281ddd583ddddfb55151babbd9ea59eb078) docs: Update README (GitHub Actions Bot) +* [`f2257ce`](https://github.com/eslint/eslint/commit/f2257ce41278dd85170d4d102969738bcabd5903) docs: update comments and `description` of `no-script-url` (#19203) (ëŖ¨ë°€LuMir) +* [`365f0f4`](https://github.com/eslint/eslint/commit/365f0f4df7045c42de0f9624c488d62cf7f6cece) docs: add missing backtick to `default-case-last` (#19202) (ëŖ¨ë°€LuMir) +* [`e6b84f5`](https://github.com/eslint/eslint/commit/e6b84f535e84050b0c63ae304eb17f9d181ac299) docs: add missing punctuation in document (#19161) (ëŖ¨ë°€LuMir) +* [`c88708e`](https://github.com/eslint/eslint/commit/c88708e0571b62ee5d6c6168373e4204ec75b931) docs: replace quote with backtick in `description` of `for-direction` (#19199) (ëŖ¨ë°€LuMir) +* [`a76f233`](https://github.com/eslint/eslint/commit/a76f233a67abebf861efc0dd06cde2187abbd273) docs: use higher contrast color tokens for code comments (#19187) (Josh Goldberg ✨) +* [`db19502`](https://github.com/eslint/eslint/commit/db195024978044b7457d5d551299f96f6b60caed) docs: Update README (GitHub Actions Bot) + +v9.16.0 - November 29, 2024 + +* [`feb703b`](https://github.com/eslint/eslint/commit/feb703b3dc198cda03fb69c75a31d56d999b9d2e) chore: upgrade to `@eslint/js@9.16.0` (#19195) (Francesco Trotta) +* [`df9bf95`](https://github.com/eslint/eslint/commit/df9bf9519a302e284700ad300463ecdf2ebf9f25) chore: package.json update for @eslint/js release (Jenkins) +* [`9eefc8f`](https://github.com/eslint/eslint/commit/9eefc8f813b5c31f49fbbd9a36f439b365bea180) docs: fix typos in `use-isnan` (#19190) (ëŖ¨ë°€LuMir) +* [`0c8cea8`](https://github.com/eslint/eslint/commit/0c8cea8c803962a4358032fde5c117a1e9c41ca0) docs: switch the order of words in `no-unreachable` (#19189) (ëŖ¨ë°€LuMir) +* [`0c19417`](https://github.com/eslint/eslint/commit/0c19417c644a29b5113d3a2b94ce00640117574b) docs: add missing backtick to `no-async-promise-executor` (#19188) (ëŖ¨ë°€LuMir) +* [`8df9276`](https://github.com/eslint/eslint/commit/8df927646cadaa70263914c62f2f76fccb8c46fd) docs: add backtick in `-0` in `description` of `no-compare-neg-zero` (#19186) (ëŖ¨ë°€LuMir) +* [`7e16e3f`](https://github.com/eslint/eslint/commit/7e16e3fb8594e361b3e121d2d4059dc26e30c407) docs: fix `caseSensitive` option's title of `sort-keys` (#19183) (Tanuj Kanti) +* [`f831893`](https://github.com/eslint/eslint/commit/f831893b6e2951f56ce8b9ff12e4a16913b72b47) chore: add type for `ignoreComputedKeys` option of `sort-keys` (#19184) (Tanuj Kanti) +* [`8f70eb1`](https://github.com/eslint/eslint/commit/8f70eb142cce025e7040d016a959eff0f51eb672) feat: Add `ignoreComputedKeys` option in `sort-keys` rule (#19162) (Milos Djermanovic) +* [`0c6b842`](https://github.com/eslint/eslint/commit/0c6b84212144da3238693fa56500b02bd4a9f05a) docs: fix typos in `migration-guide.md` (#19180) (ëŖ¨ë°€LuMir) +* [`353266e`](https://github.com/eslint/eslint/commit/353266edf827d4e63e9efef321f5d128748bc74d) docs: fix a typo in `debug.md` (#19179) (ëŖ¨ë°€LuMir) +* [`3afb8a1`](https://github.com/eslint/eslint/commit/3afb8a1dcf12ad12df480db014042a51403ff672) chore: update dependency @eslint/json to ^0.8.0 (#19177) (Milos Djermanovic) +* [`5ff318a`](https://github.com/eslint/eslint/commit/5ff318a528e3f6b8b9c6a62ea949d66ebb7f0716) docs: delete unnecessary horizontal rule(`---`) in `nodejs-api` (#19175) (ëŖ¨ë°€LuMir) +* [`576bcc5`](https://github.com/eslint/eslint/commit/576bcc5461c0c00c30dfceec9abcddb99e559c74) docs: mark more rules as handled by TypeScript (#19164) (Tanuj Kanti) +* [`1f77c53`](https://github.com/eslint/eslint/commit/1f77c53b12d00403b88a0e02c8d2432278abcf52) chore: add `repository.directory` property to `package.json` (#19165) (ëŖ¨ë°€LuMir) +* [`d460594`](https://github.com/eslint/eslint/commit/d46059410a0e02b98067aa31975c25fd8d0d1c2b) chore: update dependency @arethetypeswrong/cli to ^0.17.0 (#19147) (renovate[bot]) +* [`45cd4ea`](https://github.com/eslint/eslint/commit/45cd4ead9c4fc354a2542b806ec82afb67cb54fc) refactor: update default options in rules (#19136) (Milos Djermanovic) +* [`742d054`](https://github.com/eslint/eslint/commit/742d054ac1124d4e53c84234dd6960d4e272d490) docs: note that `no-restricted-syntax` can be used with any language (#19148) (Milos Djermanovic) + +v9.15.0 - November 15, 2024 + +* [`2967d91`](https://github.com/eslint/eslint/commit/2967d91037ad670ea3a67fdb9c171b60d9af138b) chore: upgrade @eslint/js@9.15.0 (#19133) (Milos Djermanovic) +* [`b441bee`](https://github.com/eslint/eslint/commit/b441bee6ad9807fef614bd071e6bd3e8b3307b2d) chore: package.json update for @eslint/js release (Jenkins) +* [`7d6bf4a`](https://github.com/eslint/eslint/commit/7d6bf4a250f97d8ff1e2606e3d769e016a32f45b) chore: upgrade @eslint/core@0.9.0 (#19131) (Milos Djermanovic) +* [`01557ce`](https://github.com/eslint/eslint/commit/01557cec24203be72222858a3912da0a474ac75c) feat: Implement Language#normalizeLanguageOptions() (#19104) (Nicholas C. Zakas) +* [`902e707`](https://github.com/eslint/eslint/commit/902e70713de1ab67ede9ef8a3836fd2d09a759e5) chore: upgrade @eslint/plugin-kit@0.2.3 (#19130) (Milos Djermanovic) +* [`2edc0e2`](https://github.com/eslint/eslint/commit/2edc0e2bdc40c4a6da8d526c82c0b6c582bae419) feat: add meta.defaultOptions (#17656) (Josh Goldberg ✨) +* [`fd33f13`](https://github.com/eslint/eslint/commit/fd33f1315ac59b1b3828dbab8e1e056a1585eff0) fix: update types for `no-restricted-imports` rule (#19060) (Nitin Kumar) +* [`5ff6c1d`](https://github.com/eslint/eslint/commit/5ff6c1dd09f32b56c05ab97f328741fc8ffb1f64) chore: bump cross-spawn (#19125) (Ian Bobinac) +* [`d927920`](https://github.com/eslint/eslint/commit/d9279202e7d15452e44adf38451d33d4aaad3bd4) docs: fix styles in no-js mode (#18916) (Tanuj Kanti) +* [`bdec50e`](https://github.com/eslint/eslint/commit/bdec50e91baf9d5eefa07d97d2059fdebb53cdaa) feat: fix `no-useless-computed-key` false negative with `__proto__` (#19123) (Milos Djermanovic) +* [`09bc2a8`](https://github.com/eslint/eslint/commit/09bc2a88c00aa9a93c7de505795fc4e85b2e6357) docs: Update README (GitHub Actions Bot) +* [`bd35098`](https://github.com/eslint/eslint/commit/bd35098f5b949ecb83e8c7e287524b28b2a3dd71) fix: switch away from Node.js node:assert and AssertionError (#19082) (Josh Goldberg ✨) +* [`39089c8`](https://github.com/eslint/eslint/commit/39089c80a7af09494fce86a6574bf012cbe46d10) docs: add `no-useless-computed-key` examples with object patterns (#19109) (Milos Djermanovic) +* [`895c60f`](https://github.com/eslint/eslint/commit/895c60f7fe09f59df1e9490006220d3ec4b9d5b0) docs: add missing messageId property and suggestion properties (#19122) (fnx) +* [`cceccc7`](https://github.com/eslint/eslint/commit/cceccc771631011e04b37122b990205f0e8b6925) chore: update dependency @eslint/json to ^0.6.0 (#19117) (renovate[bot]) +* [`0da3f73`](https://github.com/eslint/eslint/commit/0da3f732fe1776f8f79dac829b2cab4cedd4b6d8) chore: update algolia referrer (#19114) (Strek) +* [`9db5b15`](https://github.com/eslint/eslint/commit/9db5b152c325a930130d49ca967013471c3ba0dc) fix: unsafe report for `no-lonely-if` (#19087) (Abraham Guo) +* [`68fa497`](https://github.com/eslint/eslint/commit/68fa497f6a11f1738dce85bb2bdd7a5f8b9f5d6d) fix: ignore files on a different drive on Windows (#19069) (Francesco Trotta) +* [`4f08332`](https://github.com/eslint/eslint/commit/4f08332ac03e51002f8de6da5c5a362608205437) ci: unpin `trunk-io/trunk-action` (#19108) (Francesco Trotta) +* [`3087c9e`](https://github.com/eslint/eslint/commit/3087c9e95094cad1354aca2e4ae48c7bd2381184) feat: add `meta` object to `@eslint/js` plugin (#19095) (Francesco Trotta) +* [`298625e`](https://github.com/eslint/eslint/commit/298625eb65dc00bfa0a877ea46faada22021c23e) docs: Change CLI -c to use flat config (#19103) (Nicholas C. Zakas) +* [`4ce625a`](https://github.com/eslint/eslint/commit/4ce625a230778a41b856162df9ebcc57c25cc103) fix: upgrade @humanwhocodes/retry@0.4.1 to avoid debug logs (#19102) (Milos Djermanovic) +* [`522d8a3`](https://github.com/eslint/eslint/commit/522d8a32f326c52886c531f43cf6a1ff15af8286) docs: add deprecation on `indent`, `quotes` and `semi` rule types (#19090) (Marco Pasqualetti) +* [`6b75683`](https://github.com/eslint/eslint/commit/6b75683b47c346faaeb6c1dac8e168d64338c7b3) perf: optimize `text-table` by replacing regex with `trimEnd` (#19061) (Nitin Kumar) + +v9.14.0 - November 1, 2024 + +* [`f36cb16`](https://github.com/eslint/eslint/commit/f36cb1649a85028fb3999ee2056ee467a907c061) chore: upgrade @eslint/js@9.14.0 (#19086) (Milos Djermanovic) +* [`28be447`](https://github.com/eslint/eslint/commit/28be4471f6eb61b4304ae3d17ea7eeacc6364bbe) chore: package.json update for @eslint/js release (Jenkins) +* [`24d0172`](https://github.com/eslint/eslint/commit/24d0172bbfb92cac663cb1631bd04e7539262066) fix: enable retry concurrency limit for readFile() (#19077) (Nicholas C. Zakas) +* [`3fa009f`](https://github.com/eslint/eslint/commit/3fa009f25992d3d305437205be0ca145a0fb53f4) feat: add support for Import Attributes and RegExp Modifiers (#19076) (Milos Djermanovic) +* [`b0faee3`](https://github.com/eslint/eslint/commit/b0faee30e007a89bd7bdbc22a70223fabb99a541) feat: add types for the `@eslint/js` package (#19010) (Nitin Kumar) +* [`151c965`](https://github.com/eslint/eslint/commit/151c965aec1c46000ac7dfc67a1c04802112aafc) docs: update `context.languageOptions.parser` description (#19084) (Nitin Kumar) +* [`dc34f94`](https://github.com/eslint/eslint/commit/dc34f94a2ed25b37ac4aafcabed7bfae582db77e) docs: Update README (GitHub Actions Bot) +* [`f48a2a0`](https://github.com/eslint/eslint/commit/f48a2a0e9bf4a659b9af5e70e873fb631430c1ba) test: add `no-invalid-regexp` tests with RegExp Modifiers (#19075) (Milos Djermanovic) +* [`37c9177`](https://github.com/eslint/eslint/commit/37c9177aa07296a7a794c4b4ef5333e16fa22415) build: update `@wdio/*` dependencies (#19068) (Francesco Trotta) +* [`b442067`](https://github.com/eslint/eslint/commit/b44206725247d30b10cd58859c388949f5489087) fix: Don't crash when directory is deleted during traversal. (#19067) (Nicholas C. Zakas) +* [`35a8858`](https://github.com/eslint/eslint/commit/35a8858d62cb050fa0b56702e55c94ffaaf6956d) build: exclude flawed dendency versions (#19065) (Francesco Trotta) +* [`425202e`](https://github.com/eslint/eslint/commit/425202ed49a1372c1719d4e7b48d0fbdda8af9fa) perf: Fix caching in config loaders (#19042) (Milos Djermanovic) +* [`3d44b3c`](https://github.com/eslint/eslint/commit/3d44b3c4751e4c44c32b879b65a723faee9c1c29) ci: run tests in Node.js 23 (#19055) (Francesco Trotta) +* [`f16e846`](https://github.com/eslint/eslint/commit/f16e846ac004bc32e52cd3991d14d7a89374bbb5) docs: Update README (GitHub Actions Bot) +* [`ee0a77e`](https://github.com/eslint/eslint/commit/ee0a77ea3caa5838bab704b54a577eefbed58f68) docs: change link from @types/eslint to lib/types (#19049) (Karl Horky) +* [`d474443`](https://github.com/eslint/eslint/commit/d474443109762f3b92811df0411965cf64f595c2) fix: avoid call stack overflow while processing globs (#19035) (Livia Medeiros) +* [`7259627`](https://github.com/eslint/eslint/commit/725962731538eaa38d5d78b9e82ce3fccc9762d0) test: ensure tmp directory cleanup in `check-emfile-handling.js` (#19036) (Livia Medeiros) +* [`50f03a1`](https://github.com/eslint/eslint/commit/50f03a119e6827c03b1d6c86d3aa1f4820b609e8) docs: Clarify global ignores in config migration guide (#19032) (Milos Djermanovic) + +v9.13.0 - October 18, 2024 + +* [`68d2d9d`](https://github.com/eslint/eslint/commit/68d2d9dfd63401b6a9b413f11ac2c4b583e4897a) chore: upgrade to `@eslint/js@9.13.0` and `@eslint/core@^0.7.0` (#19034) (Francesco Trotta) +* [`2211f0a`](https://github.com/eslint/eslint/commit/2211f0aeb350f55e1fa71d4df93d46bc1795789d) chore: package.json update for @eslint/js release (Jenkins) +* [`381c32b`](https://github.com/eslint/eslint/commit/381c32b6975fa3208c62ca2b1052eb87182ed731) feat: Allow languages to provide `defaultLanguageOptions` (#19003) (Milos Djermanovic) +* [`78836d4`](https://github.com/eslint/eslint/commit/78836d40ebd3881e527075a991da4cbb0ff9adfc) fix: update the `complexity` rule type (#19027) (Nitin Kumar) +* [`c7abaef`](https://github.com/eslint/eslint/commit/c7abaef5332caf4b6251c9550a81a9c29bf324fd) perf: using Node.js compile cache (#19012) (唯į„ļ) +* [`bf723bd`](https://github.com/eslint/eslint/commit/bf723bd0f948dbfef57f9b34ff894e7603aeaf88) feat: Improve eslintrc warning message (#19023) (Milos Djermanovic) +* [`1d7c077`](https://github.com/eslint/eslint/commit/1d7c077145d070aa7754018b29b038ce2e0c8b0e) chore: add pkg.type "commonjs" (#19011) (唯į„ļ) +* [`abdbfa8`](https://github.com/eslint/eslint/commit/abdbfa83907712d0d44a35aeed4e0ea7bf106740) docs: mark `LintMessage#nodeType` as deprecated (#19019) (Nitin Kumar) +* [`468e3bd`](https://github.com/eslint/eslint/commit/468e3bdadfdf5f197a44efd6c8dc5cf2b241f964) test: fix `ESLint` tests (#19021) (Francesco Trotta) +* [`19e68d3`](https://github.com/eslint/eslint/commit/19e68d3f3a86cf23e5c98eaf8736eeaa33f194f4) docs: update deprecated rules type definitions (#19018) (Nitin Kumar) +* [`1def4cd`](https://github.com/eslint/eslint/commit/1def4cdfab1f067c5089df8b36242cdf912b0eb6) feat: drop support for jiti v1.21 (#18996) (Francesco Trotta) +* [`7dd402d`](https://github.com/eslint/eslint/commit/7dd402d33226d821a17b22c4753ce9c40fc909bd) docs: Update examples of passing multiple values to a CLI option (#19006) (Milos Djermanovic) +* [`064c8b6`](https://github.com/eslint/eslint/commit/064c8b612e2e4b773d6b25867f2045e3ceaa9d66) fix: update rule types (#18925) (Nitin Kumar) +* [`f879be2`](https://github.com/eslint/eslint/commit/f879be240ca7ddf485b700df0eb93985ccb1db45) feat: export `ESLint.defaultConfig` (#18983) (Nitin Kumar) +* [`5dcbc51`](https://github.com/eslint/eslint/commit/5dcbc519b729698be651bdbddb39da774cb70bf1) docs: Add example with side-effect imports to no-restricted-imports (#18997) (Milos Djermanovic) +* [`ed4635f`](https://github.com/eslint/eslint/commit/ed4635fa0e4fb91705223a7d9c230b6e9a87cd4c) ci: upgrade knip@5.32.0 (#18992) (Milos Djermanovic) +* [`efad767`](https://github.com/eslint/eslint/commit/efad76732170a9a7db2e056a8d9a0cf503448c48) chore: remove unused ignore dependency (#18993) (Amaresh S M) +* [`1ee87ca`](https://github.com/eslint/eslint/commit/1ee87ca1c50018947e76c29e78da9aaf711f53a2) docs: Update README (GitHub Actions Bot) +* [`2c3dbdc`](https://github.com/eslint/eslint/commit/2c3dbdc2319fcf2f65c2de13f9064c5a315be890) docs: Use prerendered sponsors for README (#18988) (Milos Djermanovic) + +v9.12.0 - October 4, 2024 + +* [`555aafd`](https://github.com/eslint/eslint/commit/555aafd06f6dddc743acff06111dc72dd8ea4c4e) chore: upgrade to `@eslint/js@9.12.0` (#18987) (Francesco Trotta) +* [`873ae60`](https://github.com/eslint/eslint/commit/873ae608c15a0a386f022076b5aab6112b56b59b) chore: package.json update for @eslint/js release (Jenkins) +* [`ea380ca`](https://github.com/eslint/eslint/commit/ea380cac6f598c86b25a2726c2783636c4169957) fix: Upgrade retry to avoid EMFILE errors (#18986) (Nicholas C. Zakas) +* [`d0a5414`](https://github.com/eslint/eslint/commit/d0a5414c30421e5dbe313790502dbf13b9330fef) refactor: replace strip-ansi with native module (#18982) (Cristopher) +* [`b827029`](https://github.com/eslint/eslint/commit/b8270299abe777bb80a065d537aa1d4be74be705) chore: Enable JSON5 linting (#18979) (Milos Djermanovic) +* [`ecbd522`](https://github.com/eslint/eslint/commit/ecbd52291d7c118b77016c6bf1c60b7d263c44f0) docs: Mention code explorer (#18978) (Nicholas C. Zakas) +* [`7ea4ecc`](https://github.com/eslint/eslint/commit/7ea4ecc6e3320a74c960cb78acc94c0140d15f55) docs: Clarifying the Use of Meta Objects (#18697) (Amaresh S M) +* [`d3e4b2e`](https://github.com/eslint/eslint/commit/d3e4b2ea4a8f76d4d49345c242f013f49635274f) docs: Clarify how to exclude `.js` files (#18976) (Milos Djermanovic) +* [`5a6a053`](https://github.com/eslint/eslint/commit/5a6a05321ca34480c780be8c2cb7946e4c299001) feat: update to `jiti` v2 (#18954) (Arya Emami) +* [`57232ff`](https://github.com/eslint/eslint/commit/57232ff3d50412586df094f052b47adb38f8d9ae) docs: Mention plugin-kit in language docs (#18973) (Nicholas C. Zakas) +* [`b80ed00`](https://github.com/eslint/eslint/commit/b80ed007cefee086db1ff17cde9f7dd6690459f0) docs: Update README (GitHub Actions Bot) +* [`cb69ab3`](https://github.com/eslint/eslint/commit/cb69ab374c149eb725b2fc5a8f0ff33fd7268a46) docs: Update README (GitHub Actions Bot) +* [`7fb0d95`](https://github.com/eslint/eslint/commit/7fb0d957c102be499d5358a74928e0ea93913371) docs: Update README (GitHub Actions Bot) +* [`fdd6319`](https://github.com/eslint/eslint/commit/fdd631964aee250bc5520770bc1fc3f2f2872813) fix: Issues with type definitions (#18940) (Arya Emami) +* [`8f55ca2`](https://github.com/eslint/eslint/commit/8f55ca22d94c1b0ff3be323b97949edef8d880b0) chore: Upgrade espree, eslint-visitor-keys, eslint-scope (#18962) (Nicholas C. Zakas) +* [`17a07fb`](https://github.com/eslint/eslint/commit/17a07fb548ecce24b88e8b2b07491c24ed1111a9) feat: Hooks for test cases (RuleTester) (#18771) (Anna Bocharova) +* [`493348a`](https://github.com/eslint/eslint/commit/493348a9a5dcca29d7fbbe13c67ce13a7a38413b) docs: Update README (GitHub Actions Bot) +* [`87a582c`](https://github.com/eslint/eslint/commit/87a582c8b537d133c140781aa9e3ff0201a3c10f) docs: fix typo in `id-match` rule (#18944) (Jay) +* [`2ff0e51`](https://github.com/eslint/eslint/commit/2ff0e51cedaab967b7ce383437f64b4a6df8608d) feat: Implement alternate config lookup (#18742) (Nicholas C. Zakas) +* [`2d17453`](https://github.com/eslint/eslint/commit/2d174532ae96bcaecf6fd7de78755164378b3a2d) feat: Implement modified cyclomatic complexity (#18896) (Dmitry Pashkevich) +* [`c1a2725`](https://github.com/eslint/eslint/commit/c1a2725e9c776d6845d94c866c7f7b1fe0315090) chore: update dependency mocha to ^10.7.3 (#18945) (Milos Djermanovic) + +v9.11.1 - September 23, 2024 + +* [`df4a859`](https://github.com/eslint/eslint/commit/df4a859b4cd578a3535a488a665a6e858289f455) chore: upgrade @eslint/js@9.11.1 (#18943) (Milos Djermanovic) +* [`36d8095`](https://github.com/eslint/eslint/commit/36d8095308d8973aa38bb9568121822776a5199d) chore: package.json update for @eslint/js release (Jenkins) +* [`20fd916`](https://github.com/eslint/eslint/commit/20fd91689f2a89643a6f67e900a53552d47ddfe5) fix: add `@eslint/core`, `@types/estree`, & `@types/json-schema` deps (#18938) (Nitin Kumar) +* [`3eff709`](https://github.com/eslint/eslint/commit/3eff70963772e9faad4a865aaa4cf3d7dbe700a0) docs: replace deprecated `Linter.FlatConfig` type with `Linter.Config` (#18941) (Carlos Meira) +* [`2738322`](https://github.com/eslint/eslint/commit/27383226b8c5ead6b7cafc017a8ca12a1512a301) fix: add missing types for `require-atomic-updates` rule (#18937) (KristÃŗf PoduszlÃŗ) +* [`d71ff30`](https://github.com/eslint/eslint/commit/d71ff3068c3134171346c91f4095dd5908d9c897) fix: add missing types for `object-shorthand` rule (#18935) (KristÃŗf PoduszlÃŗ) +* [`561cadc`](https://github.com/eslint/eslint/commit/561cadc54376fd0a5cc1446c3cd76bfbb6b3ce9d) fix: add missing types for `no-unsafe-negation` rule (#18932) (KristÃŗf PoduszlÃŗ) +* [`8843656`](https://github.com/eslint/eslint/commit/8843656f9b161d97d9dc78db01413029621e266d) fix: add missing types for `no-underscore-dangle` rule (#18931) (KristÃŗf PoduszlÃŗ) +* [`92cde5c`](https://github.com/eslint/eslint/commit/92cde5c6da43b6017657e4c596421e3347f3dbc4) fix: add missing types for `no-shadow` rule (#18930) (KristÃŗf PoduszlÃŗ) +* [`b3cbe11`](https://github.com/eslint/eslint/commit/b3cbe11a9216d1edabd5b85d6f274ca84574bce6) fix: add missing types for `no-sequences` rule (#18929) (KristÃŗf PoduszlÃŗ) +* [`976f77f`](https://github.com/eslint/eslint/commit/976f77f7f6da591b715b1ce2592f09c2f1160153) fix: add missing types for `no-unused-expressions` rule (#18933) (KristÃŗf PoduszlÃŗ) + +v9.11.0 - September 20, 2024 + +* [`ca21a64`](https://github.com/eslint/eslint/commit/ca21a64ed0f59adb9dadcef2fc8f7248879edbd3) chore: upgrade @eslint/js@9.11.0 (#18927) (Milos Djermanovic) +* [`a10f90a`](https://github.com/eslint/eslint/commit/a10f90af35aea9ac555b1f33106fbba1027d774e) chore: package.json update for @eslint/js release (Jenkins) +* [`5e5f39b`](https://github.com/eslint/eslint/commit/5e5f39b82535f59780ce4be56d01fd1466029c25) fix: add missing types for `no-restricted-exports` rule (#18914) (KristÃŗf PoduszlÃŗ) +* [`e4e5709`](https://github.com/eslint/eslint/commit/e4e570952249d1c4fde59c79a0f49a38490b72c9) docs: correct `prefer-object-has-own` type definition comment (#18924) (Nitin Kumar) +* [`8f630eb`](https://github.com/eslint/eslint/commit/8f630eb5794ef9fe38e0b8f034287650def634bd) fix: add missing types for `no-param-reassign` options (#18906) (KristÃŗf PoduszlÃŗ) +* [`d715781`](https://github.com/eslint/eslint/commit/d71578124f14d6da3fa5ab5cc391bb6c9ac3ffcf) fix: add missing types for `no-extra-boolean-cast` options (#18902) (KristÃŗf PoduszlÃŗ) +* [`e4e02cc`](https://github.com/eslint/eslint/commit/e4e02cc6938f38ad5028bb8ad82f52460a18dea5) refactor: Extract processor logic into ProcessorService (#18818) (Nicholas C. Zakas) +* [`ec30c73`](https://github.com/eslint/eslint/commit/ec30c7349e0bc2c37465a036e8c7ea3318ac2328) feat: add "eslint/universal" to export `Linter` (#18883) (唯į„ļ) +* [`c591da6`](https://github.com/eslint/eslint/commit/c591da68d4a96aa28df68f4eff7641f42af82b15) feat: Add language to types (#18917) (Nicholas C. Zakas) +* [`91cbd18`](https://github.com/eslint/eslint/commit/91cbd18c70dee2ef73de8d8e43f2c744fd173934) docs: add unicode abbreviations in no-irregular-whitespace rule (#18894) (Alix Royere) +* [`959d360`](https://github.com/eslint/eslint/commit/959d360be597d3112b10590018cd52f1d98712d6) build: Support updates to previous major versions (#18871) (Milos Djermanovic) +* [`6d4484d`](https://github.com/eslint/eslint/commit/6d4484d9c19e4132f3dee948174a543dbbb5d30f) chore: updates for v8.57.1 release (Jenkins) +* [`492eb8f`](https://github.com/eslint/eslint/commit/492eb8f34ebbc5c9d1dbfcf4dd06b8dde8d1df74) feat: limit the name given to `ImportSpecifier` in `id-length` (#18861) (Tanuj Kanti) +* [`2de5742`](https://github.com/eslint/eslint/commit/2de5742682ec45e24dca9ca7faaa45330497fca9) fix: add missing types for `no-misleading-character-class` options (#18905) (KristÃŗf PoduszlÃŗ) +* [`c153084`](https://github.com/eslint/eslint/commit/c153084250673b31bed46e3fe6af7a65b4ce8d6f) fix: add missing types for `no-implicit-coercion` options (#18903) (KristÃŗf PoduszlÃŗ) +* [`19c6856`](https://github.com/eslint/eslint/commit/19c685608d134d9120a129cc80c0ba7f8f016aa3) feat: Add `no-useless-constructor` suggestion (#18799) (Jordan Thomson) +* [`fa11b2e`](https://github.com/eslint/eslint/commit/fa11b2ede6e5dc1f55dfe4b9b65d9760828900e8) fix: add missing types for `no-empty-function` options (#18901) (KristÃŗf PoduszlÃŗ) +* [`a0deed1`](https://github.com/eslint/eslint/commit/a0deed122a9676fab07b903c8d16fbf60b92eadf) fix: add missing types for `camelcase` options (#18897) (KristÃŗf PoduszlÃŗ) +* [`71f37c5`](https://github.com/eslint/eslint/commit/71f37c5bf04afb704232d312cc6c72c957d1c14e) refactor: use optional chaining when validating config rules (#18893) (lucasrmendonca) +* [`2c2805f`](https://github.com/eslint/eslint/commit/2c2805f8ee0fb1f27f3e442de248f45e5a98a067) chore: Add PR note to all templates (#18892) (Nicholas C. Zakas) +* [`7b852ce`](https://github.com/eslint/eslint/commit/7b852ce59e6ed56931c080aa46ab548fa57feffc) refactor: use `Directive` class from `@eslint/plugin-kit` (#18884) (Milos Djermanovic) +* [`a48f8c2`](https://github.com/eslint/eslint/commit/a48f8c29b58c27d87dbf202d55a5770d678d37d6) feat: add type `FormatterFunction`, update `LoadedFormatter` (#18872) (Francesco Trotta) +* [`d594ddd`](https://github.com/eslint/eslint/commit/d594ddd2cc9b0c251291ea12fbd14ccd2ee32ac7) chore: update dependency @eslint/core to ^0.6.0 (#18863) (renovate[bot]) +* [`59cfc0f`](https://github.com/eslint/eslint/commit/59cfc0f1b3bbb62260602579f79bd1c36ab5a00f) docs: clarify `resultsMeta` in `LoadedFormatter` type (#18881) (Milos Djermanovic) +* [`78b2421`](https://github.com/eslint/eslint/commit/78b2421e28f29206fe120ae1b03804b1b79e6324) chore: Update change.yml (#18882) (Nicholas C. Zakas) +* [`a416f0a`](https://github.com/eslint/eslint/commit/a416f0a270e922c86e8571e94a30fc87d72fa873) chore: enable `$ExpectType` comments in .ts files (#18869) (Francesco Trotta) +* [`adcc50d`](https://github.com/eslint/eslint/commit/adcc50dbf1fb98c0884f841e2a627796a4490373) docs: Update README (GitHub Actions Bot) +* [`4edac1a`](https://github.com/eslint/eslint/commit/4edac1a325a832804f76602736a86217b40f69ac) docs: Update README (GitHub Actions Bot) + +v8.57.1 - September 16, 2024 + +* [`140ec45`](https://github.com/eslint/eslint/commit/140ec4569fda5a974b6964242b0b2991828a5567) chore: upgrade @eslint/js@8.57.1 (#18913) (Milos Djermanovic) +* [`bcdfc04`](https://github.com/eslint/eslint/commit/bcdfc04a69c53dbf1fc3d38603fe0a796bf2274d) chore: package.json update for @eslint/js release (Jenkins) +* [`3f6ce8d`](https://github.com/eslint/eslint/commit/3f6ce8d6b74aba0d645448e898f271825eeb9630) chore: pin vite-plugin-commonjs@0.10.1 (#18910) (Milos Djermanovic) +* [`a19072f`](https://github.com/eslint/eslint/commit/a19072f9f17ea8266bc66193e5f8a4bf1368835d) fix: add logic to handle fixTypes in the lintText() method (#18900) (Francesco Trotta) +* [`04c7188`](https://github.com/eslint/eslint/commit/04c718865b75a95ebfc4d429b8c9fad773228624) fix: Don't lint same file multiple times (#18899) (Francesco Trotta) +* [`87ec3c4`](https://github.com/eslint/eslint/commit/87ec3c49dd23ab8892bc19aae711292d03a73483) fix: do not throw when defining a global named `__defineSetter__` (#18898) (Francesco Trotta) +* [`60a1267`](https://github.com/eslint/eslint/commit/60a12676878c3fe0623c3b93e7565f003daac5f0) fix: Provide helpful error message for nullish configs (#18889) (Milos Djermanovic) +* [`35d366a`](https://github.com/eslint/eslint/commit/35d366aed6e8ab0cfa8f9c9bac4656e3784c11f6) build: Support updates to previous major versions (#18870) (Milos Djermanovic) +* [`a0dea8e`](https://github.com/eslint/eslint/commit/a0dea8ee01cc4c1b65927562afd3a46418573a02) fix: allow `name` in global ignores, fix `--no-ignore` for non-global (#18875) (Milos Djermanovic) +* [`3836bb4`](https://github.com/eslint/eslint/commit/3836bb48d3f12058ec36c2edf2ca1b50eb1c923b) fix: do not crash on error in `fs.walk` filter (#18886) (Milos Djermanovic) +* [`2dec349`](https://github.com/eslint/eslint/commit/2dec349199df4cba1554172ad38163cc09ad0a52) fix: skip processor code blocks that match only universal patterns (#18880) (Milos Djermanovic) +* [`6a5add4`](https://github.com/eslint/eslint/commit/6a5add41e80941c7253b56b02815ac316e583006) docs: v8.x Add EOL banner (#18744) (Amaresh S M) +* [`b034575`](https://github.com/eslint/eslint/commit/b034575978e3bb57e2edca0d2dc547c7a3abc928) docs: v8.x add version support page to the dropdown (#18731) (Amaresh S M) +* [`760ef7d`](https://github.com/eslint/eslint/commit/760ef7d9dbd7b615ccbdc20f02cbc05dbabbada8) docs: v8.x add version support page in the side navbar (#18740) (Amaresh S M) +* [`428b7ea`](https://github.com/eslint/eslint/commit/428b7ea0a9c086b7d8afa0adb629b09d7347d41d) docs: Add Powered by Algolia label to the search (#18658) (Amaresh S M) +* [`9f07549`](https://github.com/eslint/eslint/commit/9f0754979527d05cd0abb2ea7ab1c3563fb4a361) chore: ignore `/docs/v8.x` in link checker (#18660) (Milos Djermanovic) +* [`c68c07f`](https://github.com/eslint/eslint/commit/c68c07ff44c180952e93c6f2c860079db6291b29) docs: version selectors synchronization (#18265) (Milos Djermanovic) + +v9.10.0 - September 6, 2024 + +* [`24c3ff7`](https://github.com/eslint/eslint/commit/24c3ff7d0c0bd8b98994e04f0870cbec94c5801d) chore: upgrade to @eslint/js@9.10.0 (#18866) (Francesco Trotta) +* [`1ebdde1`](https://github.com/eslint/eslint/commit/1ebdde1cf2793b12c2e9417ce428ae3326ce8ea3) chore: package.json update for @eslint/js release (Jenkins) +* [`301b90d`](https://github.com/eslint/eslint/commit/301b90df0c032c62d00ba377fefadc5c99b55bf4) feat: Add types (#18854) (Nicholas C. Zakas) +* [`bee0e7a`](https://github.com/eslint/eslint/commit/bee0e7a5f55717d029258c99864df356c9745692) docs: update README (#18865) (Milos Djermanovic) +* [`bcf0df5`](https://github.com/eslint/eslint/commit/bcf0df55c2c151d018083dd126e55dfdb62a3e78) feat: limit namespace import identifier in id-length rule (#18849) (ChaedongIm) +* [`45c18e1`](https://github.com/eslint/eslint/commit/45c18e108efd23b4ed2d6bb55e4e2f92620d7f09) feat: add `requireFlag` option to `require-unicode-regexp` rule (#18836) (Brett Zamir) +* [`5d80b59`](https://github.com/eslint/eslint/commit/5d80b5952056edf1a17bf4bfae30270fc7e7a9bd) docs: specify that `ruleId` can be `null` in custom formatter docs (#18857) (Milos Djermanovic) +* [`156b1c3`](https://github.com/eslint/eslint/commit/156b1c3037b616ff13eced5652f94784ebbe0e89) docs: Update README (GitHub Actions Bot) +* [`e8fc5bd`](https://github.com/eslint/eslint/commit/e8fc5bd3daae5aa52f90466236f467a075a10c91) chore: update dependency @eslint/core to ^0.5.0 (#18848) (renovate[bot]) +* [`343f992`](https://github.com/eslint/eslint/commit/343f99216096f1db955766870e35d92d5a121448) refactor: don't use `node.value` when removing unused directives (#18835) (Milos Djermanovic) +* [`183b459`](https://github.com/eslint/eslint/commit/183b459b72be5c1e359985b1584f73421dfb1484) feat: add error message for duplicate flags in `no-invalid-regexp` (#18837) (Tanuj Kanti) +* [`f6fdef9`](https://github.com/eslint/eslint/commit/f6fdef9df4b1d4d07bb84c38d56ab2595fbb7e11) docs: Update README (GitHub Actions Bot) +* [`c69b406`](https://github.com/eslint/eslint/commit/c69b40667a288bed1290b34b37387dc671295bb0) feat: report duplicate allowed flags in `no-invalid-regexp` (#18754) (Tanuj Kanti) +* [`a20c870`](https://github.com/eslint/eslint/commit/a20c870744824943e213e08ca52514ac10882fdb) docs: Update README (GitHub Actions Bot) +* [`90e699b`](https://github.com/eslint/eslint/commit/90e699bd9d76139ed0aeb3894839b2d4856b4a72) docs: Update README (GitHub Actions Bot) +* [`3db18b0`](https://github.com/eslint/eslint/commit/3db18b0b6733aa1d083bf55967735e5ff4195c6c) refactor: Extract FileContext into class (#18831) (Nicholas C. Zakas) +* [`931d650`](https://github.com/eslint/eslint/commit/931d650b3754c4323a19f6d259a96a5098c7c3eb) refactor: Use @eslint/plugin-kit (#18822) (Nicholas C. Zakas) +* [`ed5cf0c`](https://github.com/eslint/eslint/commit/ed5cf0c6a91032ca51a867e619c9dc7bc70ea554) chore: update dependency @eslint/json to ^0.4.0 (#18829) (Milos Djermanovic) +* [`d1f0831`](https://github.com/eslint/eslint/commit/d1f0831bac173fe3e6e81ff95c5abdbf95b02b65) chore: added missing ids (#18817) (Strek) +* [`ec92813`](https://github.com/eslint/eslint/commit/ec928136576572a6841d238b7e41ac976d27c687) refactor: Config class (#18763) (Nicholas C. Zakas) + +v9.9.1 - August 23, 2024 + +* [`b0c34d0`](https://github.com/eslint/eslint/commit/b0c34d04b1ac1e56609209db2f9b18a6c05a198d) chore: upgrade to @eslint/js@9.9.1 (#18809) (Francesco Trotta) +* [`cd5a0da`](https://github.com/eslint/eslint/commit/cd5a0daa24b7ab019c42d64da478c84cc4d32c34) chore: package.json update for @eslint/js release (Jenkins) +* [`4840930`](https://github.com/eslint/eslint/commit/4840930b9d8b6aa3578fe234180425e9060ceeca) docs: Update README with version support and clean up content (#18804) (Nicholas C. Zakas) +* [`f61f40d`](https://github.com/eslint/eslint/commit/f61f40d8a68b27ad1ff96c019ac41d4e958961a4) docs: Update globals examples (#18805) (Nicholas C. Zakas) +* [`e112642`](https://github.com/eslint/eslint/commit/e1126423db08a29a6cdf39626110fd29186785f0) refactor: Extract parsing logic from Linter (#18790) (Nicholas C. Zakas) +* [`241fcea`](https://github.com/eslint/eslint/commit/241fcea48abe1c63f22b31be4bd75b6039768a85) docs: Use and define languages (#18795) (Nicholas C. Zakas) +* [`0f68a85`](https://github.com/eslint/eslint/commit/0f68a851db4db4eb6ff537345e7d6c26434950f1) chore: use eslint-plugin-yml on yaml files only (#18801) (Milos Djermanovic) +* [`5dbdd63`](https://github.com/eslint/eslint/commit/5dbdd63dc83428447e25f1fc1d05d8a69e3b006a) docs: eslint-plugin-markdown -> @eslint/markdown (#18797) (Nicholas C. Zakas) +* [`c6c8ddd`](https://github.com/eslint/eslint/commit/c6c8ddd3130bbfec98ef817e4647faf19b34c85c) docs: update links to eslint-visitor-keys repo (#18796) (Francesco Trotta) +* [`f8d1b3c`](https://github.com/eslint/eslint/commit/f8d1b3c2324cdada4fe1d8799f4f517c1585a001) chore: update dependencies for browser tests (#18794) (Christian Bromann) +* [`aed2624`](https://github.com/eslint/eslint/commit/aed262407918406c19d43b8d54070fa93508782b) chore: update dependency @eslint/config-array to ^0.18.0 (#18788) (renovate[bot]) +* [`5c29128`](https://github.com/eslint/eslint/commit/5c291283dc29dcfdae585d9878e0fb8ab0d68c43) chore: update dependency @eslint/core to ^0.4.0 (#18789) (renovate[bot]) +* [`5d66fb2`](https://github.com/eslint/eslint/commit/5d66fb2b53ded440180feef526b1211673c40e88) chore: migrate linting workflow to use trunk check meta-linter (#18643) (Chris Clearwater) +* [`f981d05`](https://github.com/eslint/eslint/commit/f981d054ed935ef9844b6f76d4ce90ebb345b66f) docs: Update README (GitHub Actions Bot) +* [`b516974`](https://github.com/eslint/eslint/commit/b516974713ada28c75f1e21599fc0cec13a8b321) docs: update links to `eslint/js` repo (#18781) (Francesco Trotta) +* [`fb7a3f5`](https://github.com/eslint/eslint/commit/fb7a3f5df5f661bcd96e483558da66eafeb4b954) docs: update note for package managers (#18779) (Jay) +* [`bf96855`](https://github.com/eslint/eslint/commit/bf96855d7c181648cb0a0e8faf77d707ddd4725f) chore: add ids to github issue templates (#18775) (Strek) +* [`9bde90c`](https://github.com/eslint/eslint/commit/9bde90c2edb6800c7f6428c5550ff00fff44ab02) fix: add logic to handle `fixTypes` in `lintText()` (#18736) (Amaresh S M) + +v9.9.0 - August 9, 2024 + +* [`461b2c3`](https://github.com/eslint/eslint/commit/461b2c35786dc5fd5e146f370bdcafd32938386f) chore: upgrade to `@eslint/js@9.9.0` (#18765) (Francesco Trotta) +* [`59dba1b`](https://github.com/eslint/eslint/commit/59dba1b3404391f5d968be578f0205569d5d41b2) chore: package.json update for @eslint/js release (Jenkins) +* [`fea8563`](https://github.com/eslint/eslint/commit/fea8563d3372a663aa7a1a676290c34cfb8452ba) chore: update dependency @eslint/core to ^0.3.0 (#18724) (renovate[bot]) +* [`41d0206`](https://github.com/eslint/eslint/commit/41d02066935b987d2e3b13a08680cc74d7067986) feat: Add support for TS config files (#18134) (Arya Emami) +* [`aac191e`](https://github.com/eslint/eslint/commit/aac191e6701495666c264f71fc440207ea19251f) chore: update dependency @eslint/json to ^0.3.0 (#18760) (renovate[bot]) +* [`9fe068c`](https://github.com/eslint/eslint/commit/9fe068c60db466277a785434496a8f90a9090bed) docs: how to author plugins with configs that extend other configs (#18753) (Alec Gibson) +* [`b97fa05`](https://github.com/eslint/eslint/commit/b97fa051375d1a4592faf251c783691d0b0b9ab9) chore: update wdio dependencies for more stable tests (#18759) (Christian Bromann) +* [`3a4eaf9`](https://github.com/eslint/eslint/commit/3a4eaf921543b1cd5d1df4ea9dec02fab396af2a) feat: add suggestion to `require-await` to remove `async` keyword (#18716) (Dave) +* [`48117b2`](https://github.com/eslint/eslint/commit/48117b27e98639ffe7e78a230bfad9a93039fb7f) docs: add version support page in the side navbar (#18738) (Amaresh S M) +* [`fec2951`](https://github.com/eslint/eslint/commit/fec2951d58c704c57bea7e89ffde119e4dc621e3) docs: add version support page to the dropdown (#18730) (Amaresh S M) +* [`38a0661`](https://github.com/eslint/eslint/commit/38a0661872dd6f1db2f53501895c58e8cf4e8064) docs: Fix typo (#18735) (Zaina Al Habash) +* [`3c32a9e`](https://github.com/eslint/eslint/commit/3c32a9e23c270d83bd8b2649e78aabb76992928e) docs: Update yarn command for creating ESLint config (#18739) (Temitope Ogunleye) +* [`f9ac978`](https://github.com/eslint/eslint/commit/f9ac978de629c9a702febcf478a743c5ab11fcf6) docs: Update README (GitHub Actions Bot) + +v9.8.0 - July 26, 2024 + +* [`deee448`](https://github.com/eslint/eslint/commit/deee4480def929cfa7f5b75f315d84f23eaba592) chore: upgrade to `@eslint/js@9.8.0` (#18720) (Francesco Trotta) +* [`4aaf2b3`](https://github.com/eslint/eslint/commit/4aaf2b39ba3659aff0c769de4ccefa3d5379ff93) chore: package.json update for @eslint/js release (Jenkins) +* [`8e1a627`](https://github.com/eslint/eslint/commit/8e1a627a6784380ca7e7670e336bbe9630da2da1) chore: update dependency @eslint/core to ^0.2.0 (#18700) (renovate[bot]) +* [`13d0bd3`](https://github.com/eslint/eslint/commit/13d0bd371eb8eb4aa1601c8727212a62ab923d0e) feat: Add and use SourceCode#getLoc/getRange (#18703) (Nicholas C. Zakas) +* [`282df1a`](https://github.com/eslint/eslint/commit/282df1aef3c3e62f2617c6c2944944510f287a07) docs: Add system theme option (#18617) (Amaresh S M) +* [`ab0ff27`](https://github.com/eslint/eslint/commit/ab0ff2755d6950d7e7fb92944771c1c30f933e02) fix: Throw error when invalid flags passed (#18705) (Nicholas C. Zakas) +* [`70dc803`](https://github.com/eslint/eslint/commit/70dc80337328338f3811040e3f1a1bd5674ecbd2) fix: `basePath` directory can never be ignored (#18711) (Milos Djermanovic) +* [`53b1ff0`](https://github.com/eslint/eslint/commit/53b1ff047948e36682fade502c949f4e371e53cd) docs: Debug config docs (#18698) (Nicholas C. Zakas) +* [`4514424`](https://github.com/eslint/eslint/commit/45144243f3b5762bd8e19e41749a7d330a723ada) build: Enable JSON linting (#18681) (Nicholas C. Zakas) +* [`a7016a5`](https://github.com/eslint/eslint/commit/a7016a50d88011f279d52b9355a5662e561c414c) docs: fix search input stylings (#18682) (Amaresh S M) + +v9.7.0 - July 12, 2024 + +* [`793b718`](https://github.com/eslint/eslint/commit/793b7180119e7e440d685defb2ee01597574ef1e) chore: upgrade @eslint/js@9.7.0 (#18680) (Francesco Trotta) +* [`7ed6f9a`](https://github.com/eslint/eslint/commit/7ed6f9a4db702bbad941422f456451a8dba7a450) chore: package.json update for @eslint/js release (Jenkins) +* [`14e9f81`](https://github.com/eslint/eslint/commit/14e9f81ccdb51d2b915b68f442d48ced0a691646) fix: destructuring in catch clause in `no-unused-vars` (#18636) (Francesco Trotta) +* [`7bcda76`](https://github.com/eslint/eslint/commit/7bcda760369c44d0f1131fccaaf1ccfed5af85f1) refactor: Add type references (#18652) (Nicholas C. Zakas) +* [`51bf57c`](https://github.com/eslint/eslint/commit/51bf57c493a65baeee3a935f2d0e52e27271fb48) chore: add tech sponsors through actions (#18624) (Strek) +* [`9f416db`](https://github.com/eslint/eslint/commit/9f416db680ad01716a769296085bf3eb93f76424) docs: Add Powered by Algolia label to the search. (#18633) (Amaresh S M) +* [`6320732`](https://github.com/eslint/eslint/commit/6320732c3e2a52a220552e348108c53c60f9ef7a) refactor: don't use `parent` property in `NodeEventGenerator` (#18653) (Milos Djermanovic) +* [`7bd9839`](https://github.com/eslint/eslint/commit/7bd98398f112da020eddcda2c26cf4cc563af004) feat: add support for es2025 duplicate named capturing groups (#18630) (Yosuke Ota) +* [`1381394`](https://github.com/eslint/eslint/commit/1381394a75b5902ce588455765a3919e2f138a7a) feat: add `regex` option in `no-restricted-imports` (#18622) (Nitin Kumar) +* [`9e6d640`](https://github.com/eslint/eslint/commit/9e6d6405c3ee774c2e716a3453ede9696ced1be7) refactor: move "Parsing error" prefix adding to Linter (#18650) (Milos Djermanovic) +* [`c8d26cb`](https://github.com/eslint/eslint/commit/c8d26cb4a2f9d89bfc1914167d3e9f1d3314ffe7) docs: Open JS Foundation -> OpenJS Foundation (#18649) (Milos Djermanovic) +* [`6e79ac7`](https://github.com/eslint/eslint/commit/6e79ac76f44b34c24a3e92c20713fbafe1dcbae2) docs: `loadESLint` does not support option `cwd` (#18641) (Francesco Trotta) + +v9.6.0 - June 28, 2024 + +* [`b15ee30`](https://github.com/eslint/eslint/commit/b15ee302742e280e8cd019b49e7b50a4f3b88bc0) chore: upgrade @eslint/js@9.6.0 (#18632) (Milos Djermanovic) +* [`d655503`](https://github.com/eslint/eslint/commit/d655503b1fc97acfb4e7c61b3d9b557733c189b7) chore: package.json update for @eslint/js release (Jenkins) +* [`1613e2e`](https://github.com/eslint/eslint/commit/1613e2e586423ec7871617aec4dce5c433f0e9f0) fix: Allow escaping characters in config patterns on Windows (#18628) (Milos Djermanovic) +* [`13dbecd`](https://github.com/eslint/eslint/commit/13dbecdea749abf51951ce61662eec2621a4b9af) docs: Limit search to just docs (#18627) (Nicholas C. Zakas) +* [`7c78ad9`](https://github.com/eslint/eslint/commit/7c78ad9d9f896354d557f24e2d37710cf79a27bf) refactor: Use language.visitorKeys and check for non-JS SourceCode (#18625) (Nicholas C. Zakas) +* [`e2b16e2`](https://github.com/eslint/eslint/commit/e2b16e2b72606162dce3d804bc80186b6c5ec0f9) feat: Implement feature flags (#18516) (Nicholas C. Zakas) +* [`69ff64e`](https://github.com/eslint/eslint/commit/69ff64e638c0a56628afbc271dda5c963724aca4) refactor: Return value of applyInlineConfig() (#18623) (Nicholas C. Zakas) +* [`375227f`](https://github.com/eslint/eslint/commit/375227f94da3c1c4ff6c61a29b272889fa48ca79) docs: Update getting-started.md - add pnpm to init eslint config (#18599) (Kostiantyn Ochenash) +* [`44915bb`](https://github.com/eslint/eslint/commit/44915bb95dfa21f946021d77b3b361e7e9b140e0) docs: Update README (GitHub Actions Bot) +* [`d2d06f7`](https://github.com/eslint/eslint/commit/d2d06f7a70d9b96b125ecf2de8951bea549db4da) refactor: use `/` separator when adjusting `ignorePatterns` on Windows (#18613) (Milos Djermanovic) +* [`21d3766`](https://github.com/eslint/eslint/commit/21d3766c3f4efd981d3cc294c2c82c8014815e6e) fix: `no-unused-vars` include caught errors pattern in report message (#18609) (Kirk Waiblinger) +* [`6421973`](https://github.com/eslint/eslint/commit/642197346bf02d277c2014144537aa21ab57dc59) refactor: fix disable directives for languages with 0-based lines (#18605) (Milos Djermanovic) +* [`d7a7736`](https://github.com/eslint/eslint/commit/d7a7736937981befc5dfd68ce512f1a6ebf93e68) fix: improve `no-unused-vars` message on unused caught errors (#18608) (Kirk Waiblinger) +* [`0a13539`](https://github.com/eslint/eslint/commit/0a135395aca72461eb8b4c6f0866290bcf59916e) refactor: Allow optional methods for languages (#18604) (Nicholas C. Zakas) +* [`f9e95d2`](https://github.com/eslint/eslint/commit/f9e95d2d06c0a7017417a3de4929b14d1008c63c) fix: correct locations of invalid `/* eslint */` comments (#18593) (Milos Djermanovic) +* [`8824aa1`](https://github.com/eslint/eslint/commit/8824aa1469ffc572c5e5c1765d1b6da113dfba19) feat: add `ecmaVersion: 2025`, parsing duplicate named capturing groups (#18596) (Milos Djermanovic) +* [`c7ddee0`](https://github.com/eslint/eslint/commit/c7ddee0d089e4db7be3f1a09f1a5731dd90b81b1) chore: make internal-rules not being a package (#18601) (Milos Djermanovic) +* [`3379164`](https://github.com/eslint/eslint/commit/3379164e8b0cee57caf7da34226982075ebef51a) chore: remove `.eslintrc.js` (#18011) (唯į„ļ) +* [`d0c3a32`](https://github.com/eslint/eslint/commit/d0c3a322fbcc2f70cfcd9d5010efef721245c382) chore: update knip (with webdriver-io plugin) (#18594) (Lars Kappert) +* [`d50db7b`](https://github.com/eslint/eslint/commit/d50db7bcb4c19c0631ab80b120249ecf155824ce) docs: Update vscode-eslint info (#18595) (Nicholas C. Zakas) + +v9.5.0 - June 14, 2024 + +* [`f588160`](https://github.com/eslint/eslint/commit/f588160c2f9996c9c62b787f1fe678f71740ec43) chore: upgrade @eslint/js@9.5.0 (#18591) (Milos Djermanovic) +* [`5890841`](https://github.com/eslint/eslint/commit/58908415c3e9e7924d39a2ff96573f7677ddb806) chore: package.json update for @eslint/js release (Jenkins) +* [`455f7fd`](https://github.com/eslint/eslint/commit/455f7fd1662069e9e0f4dc912ecda72962679fbe) docs: add section about including `.gitignore` files (#18590) (Milos Djermanovic) +* [`e9f4ccd`](https://github.com/eslint/eslint/commit/e9f4ccd8a182801e08d96d4246df10246ea82a58) chore: remove unused eslint-disable directive (#18589) (Milos Djermanovic) +* [`721eafe`](https://github.com/eslint/eslint/commit/721eafeae45b33b95addf385c23eca1e2f8017d0) docs: update info about universal `files` patterns (#18587) (Francesco Trotta) +* [`4b23ffd`](https://github.com/eslint/eslint/commit/4b23ffd6454cfb1a269430f5fe28e7d1c37b9d3e) refactor: Move JS parsing logic into JS language (#18448) (Nicholas C. Zakas) +* [`6880286`](https://github.com/eslint/eslint/commit/6880286e17375b08323512f38ea59fed440a4fb5) fix: treat `*` as a universal pattern (#18586) (Milos Djermanovic) +* [`8127127`](https://github.com/eslint/eslint/commit/8127127386180a2882bb1b75a8fbc7ffda78dce1) docs: Update README (GitHub Actions Bot) +* [`b2d256c`](https://github.com/eslint/eslint/commit/b2d256c7356838f908c4a5762d6dc64b41bbce5d) feat: `no-sparse-arrays` report on "comma" instead of the whole array (#18579) (fisker Cheung) +* [`1495b93`](https://github.com/eslint/eslint/commit/1495b93d6fac4d7b6c9efa24c46b613f47feb1d4) chore: update WebdriverIO packages (#18558) (Christian Bromann) +* [`cea7ede`](https://github.com/eslint/eslint/commit/cea7ede4618d789180d37ee12a57939b30a5c4ee) chore: add website donate link instead of opencollective (#18582) (Strek) +* [`55c2a66`](https://github.com/eslint/eslint/commit/55c2a6621cc403f2fc11eb4ad762eadc70a54874) docs: Update README (GitHub Actions Bot) +* [`eb76282`](https://github.com/eslint/eslint/commit/eb76282e0a2db8aa10a3d5659f5f9237d9729121) docs: Update README (GitHub Actions Bot) +* [`ff6e96e`](https://github.com/eslint/eslint/commit/ff6e96ec30862a4eb77a201551ec8c618335bfc2) docs: `baseConfig` and `overrideConfig` can be arrays (#18571) (Milos Djermanovic) +* [`7fbe211`](https://github.com/eslint/eslint/commit/7fbe211427432aba5fa972252b9b6b5cf9866624) fix: message template for all files ignored (#18564) (Milos Djermanovic) +* [`ec94880`](https://github.com/eslint/eslint/commit/ec948803c99ab1b001f093c7a2c412945fbb385f) chore: package.json update for eslint-config-eslint release (Jenkins) +* [`d2d83e0`](https://github.com/eslint/eslint/commit/d2d83e045ad03f024d1679275708054d789ebe20) docs: Add mention of eslint-transforms to v9 migration guide (#18566) (Nicholas C. Zakas) +* [`6912586`](https://github.com/eslint/eslint/commit/69125865b058c08ded162d4395d606dd22acb77d) chore: extract formatting rules into separate config (#18560) (Milos Djermanovic) +* [`9ce6832`](https://github.com/eslint/eslint/commit/9ce6832578d5798b591f490a8609c87235e881c7) docs: add callout box for unintuitive behavior (#18567) (Ben McCann) +* [`b8db99c`](https://github.com/eslint/eslint/commit/b8db99c575c75edc9b42e6333e1b0aa7d26d9a01) docs: Add VS Code info to config migration guide (#18555) (Nicholas C. Zakas) +* [`518a35c`](https://github.com/eslint/eslint/commit/518a35c8fa9161522cbe9066d48e6c6fcd8aadf3) docs: Mention config migrator (#18561) (Nicholas C. Zakas) +* [`469cb36`](https://github.com/eslint/eslint/commit/469cb363f87564bafb8e628e738e01b53f4d6911) fix: Don't lint the same file multiple times (#18552) (Milos Djermanovic) +* [`9738f7e`](https://github.com/eslint/eslint/commit/9738f7e9dee49a9a3a7b8bfce87eb236ede6f572) ci: fix CLI flags for c8, raise thresholds (#18554) (Francesco Trotta) +* [`eb440fc`](https://github.com/eslint/eslint/commit/eb440fcf16bd2f62d58b7aa9bbaf546cd94e9918) docs: specifying files with arbitrary or no extension (#18539) (Francesco Trotta) +* [`38c159e`](https://github.com/eslint/eslint/commit/38c159e7dda812ce6dfdbf8c5b78db7cdd676c62) docs: Provide example of reading package.json for plugins meta (#18530) (Nicholas C. Zakas) +* [`c6de7bb`](https://github.com/eslint/eslint/commit/c6de7bba57054efd4620e0630c23e2c63b1927b2) chore: update dependency markdownlint-cli to ^0.41.0 (#18538) (renovate[bot]) +* [`5cff638`](https://github.com/eslint/eslint/commit/5cff638c03183204d09eb0a7a8bd2e032630db17) fix: improve message for ignored files without a matching config (#18404) (Francesco Trotta) +* [`d16a659`](https://github.com/eslint/eslint/commit/d16a6599cad35726f62eb230bb95af463611c6c6) docs: add link to migration guide for `--ext` CLI option (#18537) (Milos Djermanovic) +* [`73408de`](https://github.com/eslint/eslint/commit/73408de08dbe1873bf6b5564533c0d81134cfeee) docs: add link to configuration file docs before examples (#18535) (Milos Djermanovic) +* [`2c8fd34`](https://github.com/eslint/eslint/commit/2c8fd34bf1471efbd6e616b50d4e25ea858a6989) ci: pin @wdio/browser-runner v8.36.0 (#18540) (唯į„ļ) + +v9.4.0 - May 31, 2024 + +* [`010dd2e`](https://github.com/eslint/eslint/commit/010dd2ef50456a1ba5892152192b6c9d9d5fd470) chore: upgrade to `@eslint/js@9.4.0` (#18534) (Francesco Trotta) +* [`5e1b5dc`](https://github.com/eslint/eslint/commit/5e1b5dc9a3d839737125571c8fd4e239d81608de) chore: package.json update for @eslint/js release (Jenkins) +* [`d7ab6f5`](https://github.com/eslint/eslint/commit/d7ab6f589d39c64bc5daaef4be3a972032f04c05) docs: update theme when when `prefers-color-scheme` changes (#18510) (Nitin Kumar) +* [`594145f`](https://github.com/eslint/eslint/commit/594145f493d913e2b7e25a27accf33c44e1d4687) refactor: switch to `@eslint/config-array` (#18527) (Francesco Trotta) +* [`525fdff`](https://github.com/eslint/eslint/commit/525fdffde4cb34010bc503f6d54855b3f9d07811) docs: fix components files (#18519) (Tanuj Kanti) +* [`89a4a0a`](https://github.com/eslint/eslint/commit/89a4a0a260b8eb11487fe3d5d4d80f4630933eb3) feat: ignore IIFE's in the `no-loop-func` rule (#17528) (Nitin Kumar) +* [`80747d2`](https://github.com/eslint/eslint/commit/80747d23dec69b30ea2c3620a1198f7d06b012b8) docs: refactor `prefer-destructuring` rule (#18472) (Tanuj Kanti) +* [`f6534d1`](https://github.com/eslint/eslint/commit/f6534d14033e04f6c7c88a1f0c44a8077148ec6b) fix: skip processor code blocks that match only universal patterns (#18507) (Milos Djermanovic) +* [`7226ebd`](https://github.com/eslint/eslint/commit/7226ebd69df04a4cc5fe546641f3443b60ec47e9) fix: allow implicit undefined return in `no-constructor-return` (#18515) (Ali Rezvani) +* [`f06e0b5`](https://github.com/eslint/eslint/commit/f06e0b5f51ae1aad8957d27aa0ea4d6d0ad51455) docs: clarify func-style (#18477) (Cameron Steffen) +* [`389744b`](https://github.com/eslint/eslint/commit/389744be255717c507fafc158746e579ac08d77e) fix: use `@eslint/config-inspector@latest` (#18483) (唯į„ļ) +* [`70118a5`](https://github.com/eslint/eslint/commit/70118a5b11860fce364028d3c515393b6a586aae) fix: `func-style` false positive with arrow functions and `super` (#18473) (Milos Djermanovic) + +v9.3.0 - May 17, 2024 + +* [`58e2719`](https://github.com/eslint/eslint/commit/58e271924aeb8ac2b8864845cd787ef3f9239939) chore: update dependencies for v9.3.0 release (#18469) (Francesco Trotta) +* [`b681ecb`](https://github.com/eslint/eslint/commit/b681ecbdf0882cbb7902682a9d35c1e76ac76c30) chore: package.json update for @eslint/js release (Jenkins) +* [`8db0eff`](https://github.com/eslint/eslint/commit/8db0eff4ba89b45f439c27ba1202ed056ae92e83) fix: Improve config error messages (#18457) (Nicholas C. Zakas) +* [`ceada8c`](https://github.com/eslint/eslint/commit/ceada8c702d4903d6872f46a25d68b672d2c6289) docs: explain how to use "tsc waiting" label (#18466) (Francesco Trotta) +* [`b32153c`](https://github.com/eslint/eslint/commit/b32153c97317c6fc593c2abbf6ae994519d473b4) feat: add `overrides.namedExports` to `func-style` rule (#18444) (Percy Ma) +* [`06f1d1c`](https://github.com/eslint/eslint/commit/06f1d1cd874dfc40a6651b08d766f6522a67b3f0) chore: update dependency @humanwhocodes/retry to ^0.3.0 (#18463) (renovate[bot]) +* [`5c28d9a`](https://github.com/eslint/eslint/commit/5c28d9a367e1608e097c491f40b8afd0730a8b9e) fix: don't remove comments between key and value in object-shorthand (#18442) (Kuba Jastrzębski) +* [`62e686c`](https://github.com/eslint/eslint/commit/62e686c5e90411fed2b5561be5688d7faf64d791) docs: Add troubleshooting info for plugin compatibility (#18451) (Nicholas C. Zakas) +* [`e17e1c0`](https://github.com/eslint/eslint/commit/e17e1c0dd5d5dc5a4cae5888116913f6555b1f1e) docs: Update README (GitHub Actions Bot) +* [`39fb0ee`](https://github.com/eslint/eslint/commit/39fb0ee9cd33f952707294e67f194d414261a571) fix: object-shorthand loses type parameters when auto-fixing (#18438) (dalaoshu) +* [`b67eba4`](https://github.com/eslint/eslint/commit/b67eba4514026ef7e489798fd883beb678817a46) feat: add `restrictedNamedExportsPattern` to `no-restricted-exports` (#18431) (Akul Srivastava) +* [`2465a1e`](https://github.com/eslint/eslint/commit/2465a1e3f3b78f302f64e62e5f0d851626b81b3c) docs: Update README (GitHub Actions Bot) +* [`d23574c`](https://github.com/eslint/eslint/commit/d23574c5c0275c8b3714a7a6d3e8bf2108af60f1) docs: Clarify usage of `no-unreachable` with TypeScript (#18445) (benj-dobs) +* [`1db9bae`](https://github.com/eslint/eslint/commit/1db9bae944b69945e3b05f76754cced16ae83838) docs: Fix typos (#18443) (Frieder Bluemle) +* [`069aa68`](https://github.com/eslint/eslint/commit/069aa680c78b8516b9a1b568519f1d01e74fb2a2) feat: add option `allowEscape` to `no-misleading-character-class` rule (#18208) (Francesco Trotta) +* [`7065196`](https://github.com/eslint/eslint/commit/70651968beb0f907c9689c2477721c0b991acc4a) docs: Update README (GitHub Actions Bot) +* [`05ef92d`](https://github.com/eslint/eslint/commit/05ef92dd15949014c0735125c89b7bd70dec58c8) feat: deprecate `multiline-comment-style` & `line-comment-position` (#18435) (唯į„ļ) +* [`a63ed72`](https://github.com/eslint/eslint/commit/a63ed722a64040d2be90f36e45f1f5060a9fe28e) refactor: Use `node:` protocol for built-in Node.js modules (#18434) (Milos Djermanovic) +* [`04e7c6e`](https://github.com/eslint/eslint/commit/04e7c6e0a24bd2d7691ae641e2dc0e6d538dcdfd) docs: update deprecation notice of `no-return-await` (#18433) (Tanuj Kanti) +* [`e763512`](https://github.com/eslint/eslint/commit/e7635126f36145b47fe5d135ab258af43b2715c9) docs: Link global ignores section in config object property list (#18430) (MaoShizhong) +* [`37eba48`](https://github.com/eslint/eslint/commit/37eba48d6f2d3c99c5ecf2fc3967e428a6051dbb) fix: don't crash when `fs.readFile` returns promise from another realm (#18416) (Milos Djermanovic) +* [`040700a`](https://github.com/eslint/eslint/commit/040700a7a19726bb9568fc190bff95e88fb87269) chore: update dependency markdownlint-cli to ^0.40.0 (#18425) (renovate[bot]) +* [`f47847c`](https://github.com/eslint/eslint/commit/f47847c1b45ef1ac5f05f3a37f5f8c46b860c57f) chore: update actions/stale action to v9 (#18426) (renovate[bot]) +* [`c18ad25`](https://github.com/eslint/eslint/commit/c18ad252c280443e85f788c70ce597e1941f8ff5) chore: update actions/upload-artifact action to v4 (#18427) (renovate[bot]) +* [`27e3060`](https://github.com/eslint/eslint/commit/27e3060f7519d84501a11218343c34df4947b303) chore: Disable documentation label (#18423) (Nicholas C. Zakas) +* [`ac7f718`](https://github.com/eslint/eslint/commit/ac7f718de66131187302387fc26907c4c93196f9) docs: reflect release of v9 in config migration guide (#18412) (Peter Briggs) +* [`db0b174`](https://github.com/eslint/eslint/commit/db0b174c3ace60e29585bfc3520727c44cefcfc5) feat: add `enforceForInnerExpressions` option to `no-extra-boolean-cast` (#18222) (Kirk Waiblinger) +* [`0de0909`](https://github.com/eslint/eslint/commit/0de0909e001191a3464077d37e8c0b3f67e9a1cb) docs: fix grammar in configuration file resolution (#18419) (Mike McCready) + +v9.2.0 - May 3, 2024 + +* [`b346605`](https://github.com/eslint/eslint/commit/b3466052802a1586560ad56a8128d603284d58c2) chore: upgrade @eslint/js@9.2.0 (#18413) (Milos Djermanovic) +* [`c4c18e0`](https://github.com/eslint/eslint/commit/c4c18e05fc866b73218dbe58b760546f39a2a620) chore: package.json update for @eslint/js release (Jenkins) +* [`284722c`](https://github.com/eslint/eslint/commit/284722ca8375c9a9e4f741bfdd78e765542da61f) chore: package.json update for eslint-config-eslint release (Jenkins) +* [`0f5df50`](https://github.com/eslint/eslint/commit/0f5df509a4bc00cff2c62b90fab184bdf0231322) docs: Update README (GitHub Actions Bot) +* [`347d44f`](https://github.com/eslint/eslint/commit/347d44f96b3d9d690e4f7380029e8a5a60b2fdc7) chore: remove eslintrc export from eslint-config-eslint (#18400) (Milos Djermanovic) +* [`8485d76`](https://github.com/eslint/eslint/commit/8485d76134bdbd29230780fadc284c482cd1d963) feat: `no-case-declarations` add suggestions (#18388) (Josh Goldberg ✨) +* [`a498f35`](https://github.com/eslint/eslint/commit/a498f35cef4df9c9f5387fafafaf482d913d5765) feat: update Unicode letter detection in capitalized-comments rule (#18375) (Francesco Trotta) +* [`1579ce0`](https://github.com/eslint/eslint/commit/1579ce05cbb523cb5b04ff77fab06ba1ecd18dce) docs: update wording regarding indirect eval (#18394) (Kirk Waiblinger) +* [`f316e20`](https://github.com/eslint/eslint/commit/f316e2009a8aa902fa447a49b6b5e560848f0711) ci: run tests in Node.js 22 (#18393) (Francesco Trotta) +* [`eeec413`](https://github.com/eslint/eslint/commit/eeec41346738afb491958fdbf0bcf45a302ca1b7) fix: do not throw when defining a global named __defineSetter__ (#18364) (唯į„ļ) +* [`f12a02c`](https://github.com/eslint/eslint/commit/f12a02c5749d31beefe46d2753a0d68b56f2281d) docs: update to eslint v9 in custom-rule-tutorial (#18383) (唯į„ļ) + +v9.1.1 - April 22, 2024 + +* [`a26b402`](https://github.com/eslint/eslint/commit/a26b40279f283853717236b44602b27b57f0b627) fix: use @eslint/create-config latest (#18373) (唯į„ļ) + +v9.1.0 - April 19, 2024 + +* [`d9a2983`](https://github.com/eslint/eslint/commit/d9a2983e1301599117cf554aa6a9bd44b84f2e55) chore: upgrade @eslint/js to v9.1.1 (#18367) (Francesco Trotta) +* [`03068f1`](https://github.com/eslint/eslint/commit/03068f13c0e3e6b34b8ca63628cfc79dd256feac) feat: Provide helpful error message for nullish configs (#18357) (Nicholas C. Zakas) +* [`50d406d`](https://github.com/eslint/eslint/commit/50d406d68c0304370fa47d156a407258b68dfa1b) chore: package.json update for @eslint/js release (Jenkins) +* [`8d18958`](https://github.com/eslint/eslint/commit/8d189586d60f9beda7be8cdefd4156c023c4fdde) fix: Remove name from eslint/js packages (#18368) (Nicholas C. Zakas) +* [`155c71c`](https://github.com/eslint/eslint/commit/155c71c210aaa7235ddadabb067813d8b1c76f65) chore: package.json update for @eslint/js release (Jenkins) +* [`594eb0e`](https://github.com/eslint/eslint/commit/594eb0e5c2b14a418d686c33d2d40fb439888b70) fix: do not crash on error in `fs.walk` filter (#18295) (Francesco Trotta) +* [`751b518`](https://github.com/eslint/eslint/commit/751b518f02b1e9f4f0cb4a4007ffacb1be2246af) feat: replace dependency graphemer with `Intl.Segmenter` (#18110) (Francesco Trotta) +* [`fb50077`](https://github.com/eslint/eslint/commit/fb50077fec497fbf01d754fc75aa22cff43ef066) docs: include notes about globals in migration-guide (#18356) (Gabriel Rohden) +* [`4d11e56`](https://github.com/eslint/eslint/commit/4d11e567baff575146fd267b3765ab2c788aa1e5) feat: add `name` to eslint configs (#18289) (唯į„ļ) +* [`1cbe1f6`](https://github.com/eslint/eslint/commit/1cbe1f6d38272784307c260f2375ab30e68716e8) feat: allow `while(true)` in `no-constant-condition` (#18286) (Tanuj Kanti) +* [`0588fc5`](https://github.com/eslint/eslint/commit/0588fc5ecb87fddd70e1848e417ba712b48473c3) refactor: Move directive gathering to SourceCode (#18328) (Nicholas C. Zakas) +* [`0d8cf63`](https://github.com/eslint/eslint/commit/0d8cf6350ce3dc417d6e23922e6d4ad03952aaaa) fix: EMFILE errors (#18313) (Nicholas C. Zakas) +* [`e1ac0b5`](https://github.com/eslint/eslint/commit/e1ac0b5c035bfdff7be08b69e89e1470a7becac3) fix: --inspect-config only for flat config and respect -c (#18306) (Nicholas C. Zakas) +* [`09675e1`](https://github.com/eslint/eslint/commit/09675e153169d4d0f4a85a95007dcd17d34d70c7) fix: `--no-ignore` should not apply to non-global ignores (#18334) (Milos Djermanovic) +* [`9048e21`](https://github.com/eslint/eslint/commit/9048e2184c19799bb9b8a5908345d4ce05020c41) chore: lint `docs/src/_data` js files (#18335) (Milos Djermanovic) +* [`4820790`](https://github.com/eslint/eslint/commit/48207908a8291916a124af60e02d0327276f8957) chore: upgrade globals@15.0.0 dev dependency (#18332) (Milos Djermanovic) +* [`698d9ff`](https://github.com/eslint/eslint/commit/698d9ff2c9c4e24836d69358b93d42c356eb853b) chore: upgrade jsdoc & unicorn plugins in eslint-config-eslint (#18333) (Milos Djermanovic) +* [`71c771f`](https://github.com/eslint/eslint/commit/71c771fb390cf178220d06fd7316033a385128a9) docs: Fix missing accessible name for scroll-to-top link (#18329) (GermÃĄn FreixinÃŗs) +* [`0db676f`](https://github.com/eslint/eslint/commit/0db676f9c64d2622ada86b653136d2bda4f0eee0) feat: add `Intl` in es6 globals (#18318) (唯į„ļ) +* [`200fd4e`](https://github.com/eslint/eslint/commit/200fd4e3223d1ad22dca3dc79aa6eaa860fefe32) docs: indicate eslintrc mode for `.eslintignore` (#18285) (Francesco Trotta) +* [`32c08cf`](https://github.com/eslint/eslint/commit/32c08cf66536e595e93284500b0b8d702e30cfd8) chore: drop Node < 18 and use @eslint/js v9 in eslint-config-eslint (#18323) (Milos Djermanovic) +* [`16b6a8b`](https://github.com/eslint/eslint/commit/16b6a8b469d2e0ba6d904b9e858711590568b246) docs: Update README (GitHub Actions Bot) +* [`a76fb55`](https://github.com/eslint/eslint/commit/a76fb55004ea095c68dde134ca7db0212c93c86e) chore: @eslint-community/eslint-plugin-eslint-comments v4.3.0 (#18319) (Milos Djermanovic) +* [`df5f8a9`](https://github.com/eslint/eslint/commit/df5f8a9bc1042c13f1969c9fbd8c72eee0662daa) docs: `paths` and `patterns` difference in `no-restricted-imports` (#18273) (Tanuj Kanti) +* [`c537d76`](https://github.com/eslint/eslint/commit/c537d76327586616b7ca5d00e76eaf6c76e6bcd2) docs: update `npm init @eslint/config` generated file names (#18298) (唯į„ļ) +* [`78e45b1`](https://github.com/eslint/eslint/commit/78e45b1d8d6b673ced233ca82b9ff1dddcdd1fec) chore: eslint-plugin-eslint-plugin v6.0.0 (#18316) (唯į„ļ) +* [`36103a5`](https://github.com/eslint/eslint/commit/36103a52432fffa20b90f2c6960757e6b9dc778f) chore: eslint-plugin-n v17.0.0 (#18315) (唯į„ļ) +* [`e1e305d`](https://github.com/eslint/eslint/commit/e1e305defaab98605d79c81d67ee5a48558c458a) docs: fix `linebreak-style` examples (#18262) (Francesco Trotta) +* [`113f51e`](https://github.com/eslint/eslint/commit/113f51ec4e52d3082a74b9682239a6e28d1a70ee) docs: Mention package.json config support dropped (#18305) (Nicholas C. Zakas) +* [`1fa6622`](https://github.com/eslint/eslint/commit/1fa66220ad130eeb69cfa0207d3896b7bb09c576) build: do not use `--force` flag to install dependencies (#18284) (Francesco Trotta) +* [`5c35321`](https://github.com/eslint/eslint/commit/5c353215e05818e17e83192acbb4d3730c716afa) docs: add eslintrc-only note to `--rulesdir` (#18281) (Adam Lui åˆ˜åą•éš) + +v9.0.0 - April 5, 2024 + +* [`19f9a89`](https://github.com/eslint/eslint/commit/19f9a8926bd7888ab4a813ae323ad3c332fd5d5c) chore: Update dependencies for v9.0.0 (#18275) (Nicholas C. Zakas) +* [`7c957f2`](https://github.com/eslint/eslint/commit/7c957f295dcd97286016cfb3c121dbae72f26a91) chore: package.json update for @eslint/js release (Jenkins) +* [`d73a33c`](https://github.com/eslint/eslint/commit/d73a33caddc34ab1eb62039f0f661a338836147c) chore: ignore `/docs/v8.x` in link checker (#18274) (Milos Djermanovic) +* [`d54a412`](https://github.com/eslint/eslint/commit/d54a41200483b7dd90531841a48a1f3a91f172fe) feat: Add --inspect-config CLI flag (#18270) (Nicholas C. Zakas) +* [`e151050`](https://github.com/eslint/eslint/commit/e151050e64b57f156c32f6d0d1f20dce08b5a610) docs: update get-started to the new `@eslint/create-config` (#18217) (唯į„ļ) +* [`610c148`](https://github.com/eslint/eslint/commit/610c1486dc54a095667822113eb08062a1aad2b7) fix: Support `using` declarations in no-lone-blocks (#18269) (Kirk Waiblinger) +* [`44a81c6`](https://github.com/eslint/eslint/commit/44a81c6151c58a3f4c1f6bb2927b0996f81c2daa) chore: upgrade knip (#18272) (Lars Kappert) +* [`94178ad`](https://github.com/eslint/eslint/commit/94178ad5cf4cfa1c8664dd8ac878790e72c90d8c) docs: mention about `name` field in flat config (#18252) (Anthony Fu) +* [`1765c24`](https://github.com/eslint/eslint/commit/1765c24df2f48ab1c1565177b8c6dbef63acf977) docs: add Troubleshooting page (#18181) (Josh Goldberg ✨) +* [`e80b60c`](https://github.com/eslint/eslint/commit/e80b60c342f59db998afefd856b31159a527886a) chore: remove code for testing version selectors (#18266) (Milos Djermanovic) +* [`96607d0`](https://github.com/eslint/eslint/commit/96607d0581845fab19f832cd435547f9da960733) docs: version selectors synchronization (#18260) (Milos Djermanovic) +* [`e508800`](https://github.com/eslint/eslint/commit/e508800658d0a71356ccc8b94a30e06140fc8858) fix: rule tester ignore irrelevant test case properties (#18235) (fnx) +* [`a129acb`](https://github.com/eslint/eslint/commit/a129acba0bd2d44480b56fd96c3d5444e850ba5b) fix: flat config name on ignores object (#18258) (Nicholas C. Zakas) +* [`97ce45b`](https://github.com/eslint/eslint/commit/97ce45bcdaf2320efd59bb7974e0c8e073aab672) feat: Add `reportUsedIgnorePattern` option to `no-unused-vars` rule (#17662) (Pearce Ropion) +* [`651ec91`](https://github.com/eslint/eslint/commit/651ec9122d0bd8dd08082098bd1e1a24892983f2) docs: remove `/* eslint-env */` comments from rule examples (#18249) (Milos Djermanovic) +* [`950c4f1`](https://github.com/eslint/eslint/commit/950c4f11c6797de56a5b056affd0c74211840957) docs: Update README (GitHub Actions Bot) +* [`3e9fcea`](https://github.com/eslint/eslint/commit/3e9fcea3808af83bda1e610aa2d33fb92135b5de) feat: Show config names in error messages (#18256) (Nicholas C. Zakas) +* [`b7cf3bd`](https://github.com/eslint/eslint/commit/b7cf3bd29f25a0bab4102a51029bf47c50f406b5) fix!: correct `camelcase` rule schema for `allow` option (#18232) (eMerzh) +* [`12f5746`](https://github.com/eslint/eslint/commit/12f574628f2adbe1bfed07aafecf5152b5fc3f4d) docs: add info about dot files and dir in flat config (#18239) (Tanuj Kanti) +* [`b93f408`](https://github.com/eslint/eslint/commit/b93f4085c105117a1081b249bd50c0831127fab3) docs: update shared settings example (#18251) (Tanuj Kanti) +* [`26384d3`](https://github.com/eslint/eslint/commit/26384d3367e11bd4909a3330b72741742897fa1f) docs: fix `ecmaVersion` in one example, add checks (#18241) (Milos Djermanovic) +* [`7747097`](https://github.com/eslint/eslint/commit/77470973a0c2cae8ce07a456f2ad95896bc8d1d3) docs: Update PR review process (#18233) (Nicholas C. Zakas) +* [`b07d427`](https://github.com/eslint/eslint/commit/b07d427826f81c2bdb683d04879093c687479edf) docs: fix typo (#18246) (Kirill Gavrilov) +* [`a98babc`](https://github.com/eslint/eslint/commit/a98babcda227649b2299d10e3f887241099406f7) chore: add npm script to run WebdriverIO test (#18238) (Francesco Trotta) +* [`9b7bd3b`](https://github.com/eslint/eslint/commit/9b7bd3be066ac1f72fa35c4d31a1b178c7e2b683) chore: update dependency markdownlint to ^0.34.0 (#18237) (renovate[bot]) +* [`778082d`](https://github.com/eslint/eslint/commit/778082d4fa5e2fc97549c9e5acaecc488ef928f5) docs: add Glossary page (#18187) (Josh Goldberg ✨) +* [`dadc5bf`](https://github.com/eslint/eslint/commit/dadc5bf843a7181b9724a261c7ac0486091207aa) fix: `constructor-super` false positives with loops (#18226) (Milos Djermanovic) +* [`de40874`](https://github.com/eslint/eslint/commit/de408743b5c3fc25ebd7ef5fb11ab49ab4d06c36) feat: Rule Performance Statistics for flat ESLint (#17850) (Mara Kiefer) +* [`d85c436`](https://github.com/eslint/eslint/commit/d85c436353d566d261798c51dadb8ed50def1a7d) feat: use-isnan report NaN in `indexOf` and `lastIndexOf` with fromIndex (#18225) (Tanuj Kanti) +* [`b185eb9`](https://github.com/eslint/eslint/commit/b185eb97ec60319cc39023e8615959dd598919ae) 9.0.0-rc.0 (Jenkins) +* [`26010c2`](https://github.com/eslint/eslint/commit/26010c209d2657cd401bf2550ba4f276cb318f7d) Build: changelog update for 9.0.0-rc.0 (Jenkins) +* [`297416d`](https://github.com/eslint/eslint/commit/297416d2b41f5880554d052328aa36cd79ceb051) chore: package.json update for eslint-9.0.0-rc.0 (#18223) (Francesco Trotta) +* [`d363c51`](https://github.com/eslint/eslint/commit/d363c51b177e085b011c7fde1c5a5a09b3db9cdb) chore: package.json update for @eslint/js release (Jenkins) +* [`239a7e2`](https://github.com/eslint/eslint/commit/239a7e27209a6b861d634b3ef245ebbb805793a3) docs: Clarify the description of `sort-imports` options (#18198) (gyeongwoo park) +* [`09bd7fe`](https://github.com/eslint/eslint/commit/09bd7fe09ad255a263286e90accafbe2bf04ccfc) feat!: move AST traversal into SourceCode (#18167) (Nicholas C. Zakas) +* [`b91f9dc`](https://github.com/eslint/eslint/commit/b91f9dc072f17f5ea79803deb86cf002d031b4cf) build: fix TypeError in prism-eslint-hooks.js (#18209) (Francesco Trotta) +* [`4769c86`](https://github.com/eslint/eslint/commit/4769c86cc16e0b54294c0a394a1ec7ed88fc334f) docs: fix incorrect example in `no-lone-blocks` (#18215) (Tanuj Kanti) +* [`1b841bb`](https://github.com/eslint/eslint/commit/1b841bb04ac642c5ee84d1e44be3e53317579526) chore: fix some comments (#18213) (avoidaway) +* [`b8fb572`](https://github.com/eslint/eslint/commit/b8fb57256103b908712302ccd508f464eff1c9dc) feat: add `reportUnusedFallthroughComment` option to no-fallthrough rule (#18188) (Kirk Waiblinger) +* [`ae8103d`](https://github.com/eslint/eslint/commit/ae8103de69c12c6e71644a1de9589644e6767d15) fix: load plugins in the CLI in flat config mode (#18185) (Francesco Trotta) +* [`5251327`](https://github.com/eslint/eslint/commit/5251327711a2d7083e3c629cb8e48d9d1e809add) docs: Update README (GitHub Actions Bot) +* [`29c3595`](https://github.com/eslint/eslint/commit/29c359599c2ddd168084a2c8cbca626c51d0dc13) chore: remove repetitive words (#18193) (cuithon) +* [`1dc8618`](https://github.com/eslint/eslint/commit/1dc861897e8b47280e878d609c13c9e41892f427) docs: Update README (GitHub Actions Bot) +* [`acc2e06`](https://github.com/eslint/eslint/commit/acc2e06edd55eaab58530d891c0a572c1f0ec453) chore: Introduce Knip (#18005) (Lars Kappert) +* [`ba89c73`](https://github.com/eslint/eslint/commit/ba89c73261f7fd1b6cdd50cfaeb8f4ce36101757) 9.0.0-beta.2 (Jenkins) +* [`d7ec0d1`](https://github.com/eslint/eslint/commit/d7ec0d1fbdbafa139d090ffd8b42d33bd4aa46f8) Build: changelog update for 9.0.0-beta.2 (Jenkins) +* [`7509276`](https://github.com/eslint/eslint/commit/75092764db117252067558bd3fbbf0c66ac081b7) chore: upgrade @eslint/js@9.0.0-beta.2 (#18180) (Milos Djermanovic) +* [`96087b3`](https://github.com/eslint/eslint/commit/96087b33dc10311bba83e22cc968919c358a0188) chore: package.json update for @eslint/js release (Jenkins) +* [`ba1c1bb`](https://github.com/eslint/eslint/commit/ba1c1bbc6ba9d57a83d04f450566337d3c3b0448) docs: Update README (GitHub Actions Bot) +* [`337cdf9`](https://github.com/eslint/eslint/commit/337cdf9f7ad939df7bc55c23d953e12d847b6ecc) docs: Explain limitations of RuleTester fix testing (#18175) (Nicholas C. Zakas) +* [`c7abd89`](https://github.com/eslint/eslint/commit/c7abd8936193a87be274174c47d6775e6220e354) docs: Explain Node.js version support (#18176) (Nicholas C. Zakas) +* [`925afa2`](https://github.com/eslint/eslint/commit/925afa2b0c882f77f6b4411bdca3cb8ad6934b56) chore: Remove some uses of `lodash.merge` (#18179) (Milos Djermanovic) +* [`1c173dc`](https://github.com/eslint/eslint/commit/1c173dc1f3d36a28cb2543e93675c2fbdb6fa9f1) feat: add `ignoreClassWithStaticInitBlock` option to `no-unused-vars` (#18170) (Tanuj Kanti) +* [`d961eeb`](https://github.com/eslint/eslint/commit/d961eeb855b6dd9118a78165e358e454eb1d090d) docs: show red underlines in examples in rules docs (#18041) (Yosuke Ota) +* [`558274a`](https://github.com/eslint/eslint/commit/558274abbd25ef269f4994cf258b2e44afbad548) docs: Update README (GitHub Actions Bot) +* [`2908b9b`](https://github.com/eslint/eslint/commit/2908b9b96ab7a25fe8044a1755030b18186a75b0) docs: Update release documentation (#18174) (Nicholas C. Zakas) +* [`a451b32`](https://github.com/eslint/eslint/commit/a451b32b33535a57b4b7e24291f30760f65460ba) feat: make `no-misleading-character-class` report more granular errors (#18082) (Francesco Trotta) +* [`972ef15`](https://github.com/eslint/eslint/commit/972ef155a94ad2cc85db7d209ad869869222c14c) chore: remove invalid type in @eslint/js (#18164) (Nitin Kumar) +* [`1f1260e`](https://github.com/eslint/eslint/commit/1f1260e863f53e2a5891163485a67c55d41993aa) docs: replace HackerOne link with GitHub advisory (#18165) (Francesco Trotta) +* [`79a95eb`](https://github.com/eslint/eslint/commit/79a95eb7da7fe657b6448c225d4f8ac31117456a) feat!: disallow multiple configuration comments for same rule (#18157) (Milos Djermanovic) +* [`e37153f`](https://github.com/eslint/eslint/commit/e37153f71f173e8667273d6298bef81e0d33f9ba) fix: improve error message for invalid rule config (#18147) (Nitin Kumar) +* [`c49ed63`](https://github.com/eslint/eslint/commit/c49ed63265fc8e0cccea404810a4c5075d396a15) feat: update complexity rule for optional chaining & default values (#18152) (Mathias Schreck) +* [`e5ef3cd`](https://github.com/eslint/eslint/commit/e5ef3cd6953bb40108556e0465653898ffed8420) docs: add inline cases condition in `no-fallthrough` (#18158) (Tanuj Kanti) +* [`af6e170`](https://github.com/eslint/eslint/commit/af6e17081fa6c343474959712e7a4a20f8b304e2) fix: stop linting files after an error (#18155) (Francesco Trotta) +* [`450d0f0`](https://github.com/eslint/eslint/commit/450d0f044023843b1790bd497dfca45dcbdb41e4) docs: fix `ignore` option docs (#18154) (Francesco Trotta) +* [`11144a2`](https://github.com/eslint/eslint/commit/11144a2671b2404b293f656be111221557f3390f) feat: `no-restricted-imports` option added `allowImportNames` (#16196) (M Pater) +* [`491a1d1`](https://github.com/eslint/eslint/commit/491a1d16a8dbcbe2f0cc82ce7bef580229d09b86) 9.0.0-beta.1 (Jenkins) +* [`fd9c0a9`](https://github.com/eslint/eslint/commit/fd9c0a9f0e50da617fe1f2e60ba3df0276a7f06b) Build: changelog update for 9.0.0-beta.1 (Jenkins) +* [`32ffdd1`](https://github.com/eslint/eslint/commit/32ffdd181aa673ccc596f714d10a2f879ec622a7) chore: upgrade @eslint/js@9.0.0-beta.1 (#18146) (Milos Djermanovic) +* [`e41425b`](https://github.com/eslint/eslint/commit/e41425b5c3b4c885f2679a3663bd081911a8b570) chore: package.json update for @eslint/js release (Jenkins) +* [`bb3b9c6`](https://github.com/eslint/eslint/commit/bb3b9c68fe714bb8aa305be5f019a7a42f4374ee) chore: upgrade @eslint/eslintrc@3.0.2 (#18145) (Milos Djermanovic) +* [`c9f2f33`](https://github.com/eslint/eslint/commit/c9f2f3343e7c197e5e962c68ef202d6a1646866e) build: changelog update for 8.57.0 (#18144) (Milos Djermanovic) +* [`5fe095c`](https://github.com/eslint/eslint/commit/5fe095cf718b063dc5e58089b0a6cbcd53da7925) docs: show v8.57.0 as latest version in dropdown (#18142) (Milos Djermanovic) +* [`0cb4914`](https://github.com/eslint/eslint/commit/0cb4914ef93cd572ba368d390b1cf0b93f578a9d) fix: validate options when comment with just severity enables rule (#18133) (Milos Djermanovic) +* [`7db5bb2`](https://github.com/eslint/eslint/commit/7db5bb270f95d1472de0bfed0e33ed5ab294942e) docs: Show prerelease version in dropdown (#18135) (Nicholas C. Zakas) +* [`e462524`](https://github.com/eslint/eslint/commit/e462524cc318ffacecd266e6fe1038945a0b02e9) chore: upgrade eslint-release@3.2.2 (#18138) (Milos Djermanovic) +* [`8e13a6b`](https://github.com/eslint/eslint/commit/8e13a6beb587e624cc95ae16eefe503ad024b11b) chore: fix spelling mistake in README.md (#18128) (Will Eastcott) +* [`66f52e2`](https://github.com/eslint/eslint/commit/66f52e276c31487424bcf54e490c4ac7ef70f77f) chore: remove unused tools rule-types.json, update-rule-types.js (#18125) (Josh Goldberg ✨) +* [`bf0c7ef`](https://github.com/eslint/eslint/commit/bf0c7effdba51c48b929d06ce1965408a912dc77) ci: fix sync-labels value of pr-labeler (#18124) (Tanuj Kanti) +* [`cace6d0`](https://github.com/eslint/eslint/commit/cace6d0a3afa5c84b18abee4ef8c598125143461) ci: add PR labeler action (#18109) (Nitin Kumar) +* [`73a5f06`](https://github.com/eslint/eslint/commit/73a5f0641b43e169247b0000f44a366ee6bbc4f2) docs: Update README (GitHub Actions Bot) +* [`74124c2`](https://github.com/eslint/eslint/commit/74124c20287fac1995c3f4e553f0723c066f311d) feat: add suggestions to `use-isnan` in `indexOf` & `lastIndexOf` calls (#18063) (StyleShit) +* [`1a65d3e`](https://github.com/eslint/eslint/commit/1a65d3e4a6ee16e3f607d69b998a08c3fed505ca) chore: export `base` config from `eslint-config-eslint` (#18119) (Milos Djermanovic) +* [`f95cd27`](https://github.com/eslint/eslint/commit/f95cd27679eef228173e27e170429c9710c939b3) docs: Disallow multiple rule configuration comments in the same example (#18116) (Milos Djermanovic) +* [`9aa4df3`](https://github.com/eslint/eslint/commit/9aa4df3f4d85960eee72923f3b9bfc88e62f04fb) refactor: remove `globals` dependency (#18115) (Milos Djermanovic) +* [`d8068ec`](https://github.com/eslint/eslint/commit/d8068ec70fac050e900dc400510a4ad673e17633) docs: Update link for schema examples (#18112) (Svetlana) +* [`428dbdb`](https://github.com/eslint/eslint/commit/428dbdbef367e17edef7ba648fba0d37c860be9c) 9.0.0-beta.0 (Jenkins) +* [`1bbc495`](https://github.com/eslint/eslint/commit/1bbc495aecbd3e4a4aaf54d7c489191809c1b65b) Build: changelog update for 9.0.0-beta.0 (Jenkins) +* [`e40d1d7`](https://github.com/eslint/eslint/commit/e40d1d74a5b9788cbec195f4e602b50249f26659) chore: upgrade @eslint/js@9.0.0-beta.0 (#18108) (Milos Djermanovic) +* [`9870f93`](https://github.com/eslint/eslint/commit/9870f93e714edefb410fccae1e9924a3c1972a2e) chore: package.json update for @eslint/js release (Jenkins) +* [`2c62e79`](https://github.com/eslint/eslint/commit/2c62e797a433e5fc298b976872a89c594f88bb19) chore: upgrade @eslint/eslintrc@3.0.1 (#18107) (Milos Djermanovic) +* [`81f0294`](https://github.com/eslint/eslint/commit/81f0294e651928b49eb49495b90b54376073a790) chore: upgrade espree@10.0.1 (#18106) (Milos Djermanovic) +* [`5e2b292`](https://github.com/eslint/eslint/commit/5e2b2922aa65bda54b0966d1bf71acda82b3047c) chore: upgrade eslint-visitor-keys@4.0.0 (#18105) (Milos Djermanovic) +* [`9163646`](https://github.com/eslint/eslint/commit/916364692bae6a93c10b5d48fc1e9de1677d0d09) feat!: Rule Tester checks for missing placeholder data in the message (#18073) (fnx) +* [`53f0f47`](https://github.com/eslint/eslint/commit/53f0f47badffa1b04ec2836f2ae599f4fc464da2) feat: Add loadESLint() API method for v9 (#18097) (Nicholas C. Zakas) +* [`f1c7e6f`](https://github.com/eslint/eslint/commit/f1c7e6fc8ea77fcdae4ad1f8fe1cd104a281d2e9) docs: Switch to Ethical Ads (#18090) (Strek) +* [`15c143f`](https://github.com/eslint/eslint/commit/15c143f96ef164943fd3d39b5ad79d9a4a40de8f) docs: JS Foundation -> OpenJS Foundation in PR template (#18092) (Nicholas C. Zakas) +* [`c4d26fd`](https://github.com/eslint/eslint/commit/c4d26fd3d1f59c1c0f2266664887ad18692039f3) fix: `use-isnan` doesn't report on `SequenceExpression`s (#18059) (StyleShit) +* [`6ea339e`](https://github.com/eslint/eslint/commit/6ea339e658d29791528ab26aabd86f1683cab6c3) docs: add stricter rule test validations to v9 migration guide (#18085) (Milos Djermanovic) +* [`ce838ad`](https://github.com/eslint/eslint/commit/ce838adc3b673e52a151f36da0eedf5876977514) chore: replace dependency npm-run-all with npm-run-all2 ^5.0.0 (#18045) (renovate[bot]) +* [`3c816f1`](https://github.com/eslint/eslint/commit/3c816f193eecace5efc6166efa2852a829175ef8) docs: use relative link from CLI to core concepts (#18083) (Milos Djermanovic) +* [`54df731`](https://github.com/eslint/eslint/commit/54df731174d2528170560d1f765e1336eca0a8bd) chore: update dependency markdownlint-cli to ^0.39.0 (#18084) (renovate[bot]) +* [`9458735`](https://github.com/eslint/eslint/commit/9458735381269d12b24f76e1b2b6fda1bc5a509b) docs: fix malformed `eslint` config comments in rule examples (#18078) (Francesco Trotta) +* [`07a1ada`](https://github.com/eslint/eslint/commit/07a1ada7166b76c7af6186f4c5e5de8b8532edba) docs: link from `--fix` CLI doc to the relevant core concept (#18080) (Bryan Mishkin) +* [`8f06a60`](https://github.com/eslint/eslint/commit/8f06a606845f40aaf0fea1fd83d5930747c5acec) chore: update dependency shelljs to ^0.8.5 (#18079) (Francesco Trotta) +* [`b844324`](https://github.com/eslint/eslint/commit/b844324e4e8f511c9985a96c7aca063269df9570) docs: Update team responsibilities (#18048) (Nicholas C. Zakas) +* [`aadfb60`](https://github.com/eslint/eslint/commit/aadfb609f1b847e492fc3b28ced62f830fe7f294) docs: document languageOptions and other v9 changes for context (#18074) (fnx) +* [`3c4d51d`](https://github.com/eslint/eslint/commit/3c4d51d55fa5435ab18b6bf46f6b97df0f480ae7) feat!: default for `enforceForClassMembers` in `no-useless-computed-key` (#18054) (Francesco Trotta) +* [`47e60f8`](https://github.com/eslint/eslint/commit/47e60f85e0c3f275207bb4be9b5947166a190477) feat!: Stricter rule test validations (#17654) (fnx) +* [`1a94589`](https://github.com/eslint/eslint/commit/1a945890105d307541dcbff15f6438c19b476ade) feat!: `no-unused-vars` default caughtErrors to 'all' (#18043) (Josh Goldberg ✨) +* [`857e242`](https://github.com/eslint/eslint/commit/857e242584227181ecb8af79fc6bc236b9975228) docs: tweak explanation for meta.docs rule properties (#18057) (Bryan Mishkin) +* [`10485e8`](https://github.com/eslint/eslint/commit/10485e8b961d045514bc1e34227cf09867a6c4b7) docs: recommend messageId over message for reporting rule violations (#18050) (Bryan Mishkin) +* [`98b5ab4`](https://github.com/eslint/eslint/commit/98b5ab406bac6279eadd84e8a5fd5a01fc586ff1) docs: Update README (GitHub Actions Bot) +* [`93ffe30`](https://github.com/eslint/eslint/commit/93ffe30da5e2127e336c1c22e69e09ec0558a8e6) chore: update dependency file-entry-cache to v8 (#17903) (renovate[bot]) +* [`505fbf4`](https://github.com/eslint/eslint/commit/505fbf4b35c14332bffb0c838cce4843a00fad68) docs: update `no-restricted-imports` rule (#18015) (Tanuj Kanti) +* [`2d11d46`](https://github.com/eslint/eslint/commit/2d11d46e890a9f1b5f639b8ee034ffa9bd453e42) feat: add suggestions to `use-isnan` in binary expressions (#17996) (StyleShit) +* [`c25b4af`](https://github.com/eslint/eslint/commit/c25b4aff1fe35e5bd9d4fcdbb45b739b6d253828) docs: Update README (GitHub Actions Bot) +* [`fd1e2f3`](https://github.com/eslint/eslint/commit/fd1e2f346307f7711bf0f206b4d09656d15a7e1a) 9.0.0-alpha.2 (Jenkins) +* [`96f8877`](https://github.com/eslint/eslint/commit/96f8877de7dd3d92ac5afb77c92d821002d24929) Build: changelog update for 9.0.0-alpha.2 (Jenkins) +* [`6ffdcbb`](https://github.com/eslint/eslint/commit/6ffdcbb8c51956054d3f81c5ce446c15dcd51a6f) chore: upgrade @eslint/js@9.0.0-alpha.2 (#18038) (Milos Djermanovic) +* [`2c12715`](https://github.com/eslint/eslint/commit/2c1271528e88d0c3c6a92eeee902001f1703d5c9) chore: package.json update for @eslint/js release (Jenkins) +* [`cc74c4d`](https://github.com/eslint/eslint/commit/cc74c4da99368b97494b924dbea1cb6e87adec53) chore: upgrade espree@10.0.0 (#18037) (Milos Djermanovic) +* [`26093c7`](https://github.com/eslint/eslint/commit/26093c76903310d12f21e24e73d97c0d2ac1f359) feat: fix false negatives in `no-this-before-super` (#17762) (Yosuke Ota) +* [`57089cb`](https://github.com/eslint/eslint/commit/57089cb5166acf8b8bdba8a8dbeb0a129f841478) feat!: no-restricted-imports allow multiple config entries for same path (#18021) (Milos Djermanovic) +* [`33d1ab0`](https://github.com/eslint/eslint/commit/33d1ab0b6ea5fcebca7284026d2396df41b06566) docs: add more examples to flat config ignores docs (#18020) (Milos Djermanovic) +* [`e6eebca`](https://github.com/eslint/eslint/commit/e6eebca90750ef5c7c99d4fe3658553cf737dab8) docs: Update sort-keys options properties count (#18025) (LB (Ben Johnston)) +* [`dfb68b6`](https://github.com/eslint/eslint/commit/dfb68b63ce6e8df6ffe81bd843e650c5b017dce9) chore: use Node.js 20 for docs sites (#18026) (Milos Djermanovic) +* [`8c1b8dd`](https://github.com/eslint/eslint/commit/8c1b8dda169920c4e3b99f6548f9c872d65ee426) test: add more tests for ignoring files and directories (#18018) (Milos Djermanovic) +* [`60b966b`](https://github.com/eslint/eslint/commit/60b966b6861da11617ddc15487bd7a51c584c596) chore: update dependency @eslint/js to v9.0.0-alpha.1 (#18014) (renovate[bot]) +* [`5471e43`](https://github.com/eslint/eslint/commit/5471e435d12bf5add9869d81534b147e445a2368) feat: convert unsafe autofixes to suggestions in `no-implicit-coercion` (#17985) (GÃŧrgÃŧn DayÄąoğlu) +* [`2e1d549`](https://github.com/eslint/eslint/commit/2e1d54960051b59e1c731fa44c2ef843290b1335) feat!: detect duplicate test cases (#17955) (Bryan Mishkin) +* [`1fedfd2`](https://github.com/eslint/eslint/commit/1fedfd28a46d86b2fbcf06a2328befafd6535a88) docs: Improve flat config ignores docs (#17997) (Nicholas C. Zakas) +* [`e3051be`](https://github.com/eslint/eslint/commit/e3051be6366b00e1571e702023a351177d24e443) feat: emit warning when `.eslintignore` file is detected (#17952) (Nitin Kumar) +* [`38b9b06`](https://github.com/eslint/eslint/commit/38b9b06695f88c70441dd15ae5d97ffd8088be23) docs: update valid-typeof rule (#18001) (Tanuj Kanti) +* [`39076fb`](https://github.com/eslint/eslint/commit/39076fb5e4c7fa10b305d510f489aff34a5f5d99) fix: handle absolute file paths in `RuleTester` (#17989) (Nitin Kumar) +* [`b4abfea`](https://github.com/eslint/eslint/commit/b4abfea4c1703a50f1ce639e3207ad342a56f79d) docs: Update note about ECMAScript support (#17991) (Francesco Trotta) +* [`c893bc0`](https://github.com/eslint/eslint/commit/c893bc0bdf1bca256fbab6190358e5f922683249) chore: update `markdownlint` to `v0.33.0` (#17995) (Nitin Kumar) +* [`6788873`](https://github.com/eslint/eslint/commit/6788873328a7f974d5e45c0be06ca0c7dd409acd) docs: Update release blog post template (#17994) (Nicholas C. Zakas) +* [`1f37442`](https://github.com/eslint/eslint/commit/1f3744278433006042b8d5f4e9e1e488b2bbb011) docs: Add sections on non-npm plugin configuration (#17984) (Nicholas C. Zakas) +* [`bbf2b21`](https://github.com/eslint/eslint/commit/bbf2b214473606329a5dbcbe022079f4048923a8) 9.0.0-alpha.1 (Jenkins) +* [`52d5e7a`](https://github.com/eslint/eslint/commit/52d5e7a41d37a1a6d9aa1dffba3b688573800536) Build: changelog update for 9.0.0-alpha.1 (Jenkins) +* [`c5e50ee`](https://github.com/eslint/eslint/commit/c5e50ee65cf22871770b1d4d438b9056c577f646) chore: package.json update for @eslint/js release (Jenkins) +* [`1bf2520`](https://github.com/eslint/eslint/commit/1bf2520c4166aa55596417bf44c567555bc65fba) chore: Split Docs CI from core CI (#17897) (Nicholas C. Zakas) +* [`6d11f3d`](https://github.com/eslint/eslint/commit/6d11f3dac1b76188d7fda6e772e89b5c3945ac4d) fix: Ensure config keys are printed for config errors (#17980) (Nicholas C. Zakas) +* [`320787e`](https://github.com/eslint/eslint/commit/320787e661beb979cf063d0f8333654f94ef9efd) chore: delete relative-module-resolver.js (#17981) (Francesco Trotta) +* [`96307da`](https://github.com/eslint/eslint/commit/96307da837c407c9a1275124b65ca29c07ffd5e4) docs: migration guide entry for `no-inner-declarations` (#17977) (Tanuj Kanti) +* [`40be60e`](https://github.com/eslint/eslint/commit/40be60e0186cdde76219df4e8e628125df2912d8) docs: Update README (GitHub Actions Bot) +* [`a630edd`](https://github.com/eslint/eslint/commit/a630edd809894dc38752705bb5954d847987f031) feat: maintain latest ecma version in ESLint (#17958) (Milos Djermanovic) +* [`701f1af`](https://github.com/eslint/eslint/commit/701f1afbee34e458b56d2dfa36d9153d6aebea3a) feat!: no-inner-declaration new default behaviour and option (#17885) (Tanuj Kanti) +* [`b4e0503`](https://github.com/eslint/eslint/commit/b4e0503a56beea1222be266cc6b186d89410d1f2) feat: add `no-useless-assignment` rule (#17625) (Yosuke Ota) +* [`806f708`](https://github.com/eslint/eslint/commit/806f70878e787f2c56aaa42a3e7adb61bc015278) fix: `no-misleading-character-class` edge cases with granular errors (#17970) (Milos Djermanovic) +* [`287c4b7`](https://github.com/eslint/eslint/commit/287c4b7d498746b43392ee4fecd6904a9cd4b30b) feat: `no-misleading-character-class` granular errors (#17515) (Josh Goldberg ✨) +* [`d31c180`](https://github.com/eslint/eslint/commit/d31c180312260d1a286cc8162907b6a33368edc9) docs: fix number of code-path events on custom rules page (#17969) (Richard Hunter) +* [`1529ab2`](https://github.com/eslint/eslint/commit/1529ab288ec815b2690864e04dd6d0a1f0b537c6) docs: reorder entries in v9 migration guide (#17967) (Milos Djermanovic) +* [`bde5105`](https://github.com/eslint/eslint/commit/bde51055530d4a71bd9f48c90ed7de9c0b767d86) fix!: handle `--output-file` for empty output when saving to disk (#17957) (Nitin Kumar) +* [`9507525`](https://github.com/eslint/eslint/commit/95075251fb3ce35aaf7eadbd1d0a737106c13ec6) docs: Explain how to combine configs (#17947) (Nicholas C. Zakas) +* [`7c78576`](https://github.com/eslint/eslint/commit/7c785769fd177176966de7f6c1153480f7405000) docs: Add more removed `context` methods to migrate to v9 guide (#17951) (Milos Djermanovic) +* [`07107a5`](https://github.com/eslint/eslint/commit/07107a5904c2580243971c8ad7f26a04738b712e) fix!: upgrade eslint-scope@8.0.0 (#17942) (Milos Djermanovic) +* [`3ee0f6c`](https://github.com/eslint/eslint/commit/3ee0f6ca5d756da647e4e76bf3daa82a5905a792) fix!: no-unused-vars `varsIgnorePattern` behavior with catch arguments (#17932) (Tanuj Kanti) +* [`4926f33`](https://github.com/eslint/eslint/commit/4926f33b96faf07a64aceec5f1f4882f4faaf4b5) refactor: use `Object.hasOwn()` (#17948) (Milos Djermanovic) +* [`df200e1`](https://github.com/eslint/eslint/commit/df200e147705eb62f94b99c170554327259c65d4) refactor: use `Array.prototype.at()` to get last elements (#17949) (Milos Djermanovic) +* [`51f8bc8`](https://github.com/eslint/eslint/commit/51f8bc836bf0b13dad3a897ae84259bcdaed2431) fix!: configuration comments with just severity should retain options (#17945) (Milos Djermanovic) +* [`3a877d6`](https://github.com/eslint/eslint/commit/3a877d68d0151679f8bf1cabc39746778754b3dd) docs: Update removed CLI flags migration (#17939) (Nicholas C. Zakas) +* [`750b8df`](https://github.com/eslint/eslint/commit/750b8dff6df02a500e12cb78390fd14814c82e5b) chore: update dependency glob to v10 (#17917) (renovate[bot]) +* [`c2bf27d`](https://github.com/eslint/eslint/commit/c2bf27def29ef1ca7f5bfe20c1306bf78087ea29) build: update docs files when publishing prereleases (#17940) (Milos Djermanovic) +* [`d191bdd`](https://github.com/eslint/eslint/commit/d191bdd67214c33e65bd605e616ca7cc947fd045) feat!: Remove CodePath#currentSegments (#17936) (Milos Djermanovic) +* [`4a9cd1e`](https://github.com/eslint/eslint/commit/4a9cd1ea1cd0c115b98d07d1b6018ca918a9c73f) docs: Update Linter API for v9 (#17937) (Milos Djermanovic) +* [`74794f5`](https://github.com/eslint/eslint/commit/74794f53a6bc88b67653c737f858cfdf35b1c73d) chore: removed unused eslintrc modules (#17938) (Milos Djermanovic) +* [`10ed29c`](https://github.com/eslint/eslint/commit/10ed29c0c4505dbac3bb05b0e3d61f329b99f747) chore: remove unused dependency rimraf (#17934) (Francesco Trotta) +* [`2a8eea8`](https://github.com/eslint/eslint/commit/2a8eea8e5847f4103d90d667a2b08edf9795545f) docs: update docs for v9.0.0-alpha.0 (#17929) (Milos Djermanovic) +* [`903ee60`](https://github.com/eslint/eslint/commit/903ee60ea910aee344df7edb66874f80e4b6ed31) ci: use `--force` flag when installing eslint (#17921) (Milos Djermanovic) +* [`73a841a`](https://github.com/eslint/eslint/commit/73a841a7dff809e6cf7bb9a37f073d168eabd45f) 9.0.0-alpha.0 (Jenkins) +* [`e91d85d`](https://github.com/eslint/eslint/commit/e91d85db76c7bd8a5998f7ff52d2cc844d0e953e) Build: changelog update for 9.0.0-alpha.0 (Jenkins) +* [`7f0ba51`](https://github.com/eslint/eslint/commit/7f0ba51bcef3e6fbf972ceb20403238f0e1f0ea9) docs: show `NEXT` in version selectors (#17911) (Milos Djermanovic) +* [`17fedc1`](https://github.com/eslint/eslint/commit/17fedc17e9e6e39ad986d917fb4e9e4835c50482) chore: upgrade @eslint/js@9.0.0-alpha.0 (#17928) (Milos Djermanovic) +* [`cb89ef3`](https://github.com/eslint/eslint/commit/cb89ef373fffbed991f4e099cb255a7c116889f9) chore: package.json update for @eslint/js release (Jenkins) +* [`0a7911e`](https://github.com/eslint/eslint/commit/0a7911e09adf2aca4d93c81f4be1cd80db7dd735) docs: add flat config default to v9 migration guide (#17927) (Milos Djermanovic) +* [`94f8065`](https://github.com/eslint/eslint/commit/94f80652aca302e2715ea51c10c3a1010786b751) docs: Add CLI updates to migrate to v9 guide (#17924) (Nicholas C. Zakas) +* [`16187f2`](https://github.com/eslint/eslint/commit/16187f23c6e5aaed3b50ff551a66f758893d5422) docs: Add exported and string config notes to migrate to v9 guide (#17926) (Nicholas C. Zakas) +* [`3ae50cc`](https://github.com/eslint/eslint/commit/3ae50cc788c3cdd209e642573e3c831dd86fa0cd) docs: Add RuleTester changes to migrate to v9 guide (#17923) (Nicholas C. Zakas) +* [`0831b58`](https://github.com/eslint/eslint/commit/0831b58fe6fb5778c92aeb4cefa9ecedbbfbf48b) docs: add rule changes to v9 migration guide (#17925) (Milos Djermanovic) +* [`946ae00`](https://github.com/eslint/eslint/commit/946ae00457265eb298eb169d6d48ca7ec71b9eef) feat!: FlatRuleTester -> RuleTester (#17922) (Nicholas C. Zakas) +* [`f182114`](https://github.com/eslint/eslint/commit/f182114144ae0bb7187de34a1661f31fb70f1357) fix: deep merge behavior in flat config (#17906) (Francesco Trotta) +* [`037abfc`](https://github.com/eslint/eslint/commit/037abfc21f264fca3a910c4a5cd23d1bf6826c3d) docs: update API docs (#17919) (Milos Djermanovic) +* [`baff28c`](https://github.com/eslint/eslint/commit/baff28ce8f167f564471f1d70d6e9c4b0cb1a508) feat!: remove `no-inner-declarations` from `eslint:recommended` (#17920) (Milos Djermanovic) +* [`f6f4a45`](https://github.com/eslint/eslint/commit/f6f4a45680039f720a2fccd5f445deaf45babb3d) chore: drop structuredClone polyfill for v9 (#17915) (Kevin Gibbons) +* [`afc3c03`](https://github.com/eslint/eslint/commit/afc3c038ed3132a99659604624cc24e702eec45a) docs: add function-style and `meta.schema` changes to v9 migration guide (#17912) (Milos Djermanovic) +* [`cadfbcd`](https://github.com/eslint/eslint/commit/cadfbcd468737fc9447243edd1d15058efb6d3d8) feat!: Rename FlatESLint to ESLint (#17914) (Nicholas C. Zakas) +* [`412dcbb`](https://github.com/eslint/eslint/commit/412dcbb672b5a5859f96753afa7cb87291135a1b) chore: upgrade eslint-plugin-n@16.6.0 (#17916) (Milos Djermanovic) +* [`8792464`](https://github.com/eslint/eslint/commit/8792464ee7956af82dab582ca9ee59da596a608e) feat: Enable eslint.config.mjs and eslint.config.cjs (#17909) (Nicholas C. Zakas) +* [`24ce927`](https://github.com/eslint/eslint/commit/24ce9276d472b85541c4b01db488c789f33fd234) feat: warn by default for unused disable directives (#17879) (Bryan Mishkin) +* [`02a8baf`](https://github.com/eslint/eslint/commit/02a8baf9f2ef7b309c7d45564a79ed5d2153057f) chore: Rename files with underscores (#17910) (Nicholas C. Zakas) +* [`d1018fc`](https://github.com/eslint/eslint/commit/d1018fc5e59db0495aa4a7f501c9d3f831981f35) feat!: skip running warnings in --quiet mode (#17274) (Maddy Miller) +* [`fb81b1c`](https://github.com/eslint/eslint/commit/fb81b1cb78d2692a87fd3591fdc0f96b0c95e760) feat!: Set default `schema: []`, drop support for function-style rules (#17792) (Milos Djermanovic) +* [`1da0723`](https://github.com/eslint/eslint/commit/1da0723695d080008b22f30c8b5c86fe386c6242) docs: update `eslint:recommended` section in Migrate to v9.x (#17908) (Milos Djermanovic) +* [`f55881f`](https://github.com/eslint/eslint/commit/f55881f492d10e9c759e459ba6bade1be3dad84b) docs: remove configuration-files-new.md (#17907) (Milos Djermanovic) +* [`63ae191`](https://github.com/eslint/eslint/commit/63ae191070569a9118b5972c90a98633b0a336e1) docs: Migrate to v9.0.0 (#17905) (Nicholas C. Zakas) +* [`e708496`](https://github.com/eslint/eslint/commit/e7084963c73f3cbaae5d569b4a2bee1509dd8cef) docs: Switch to flat config by default (#17840) (Nicholas C. Zakas) +* [`fdf0424`](https://github.com/eslint/eslint/commit/fdf0424c5c08c058479a6cd7676be6985e0f400f) docs: Update Create a Plugin for flat config (#17826) (Nicholas C. Zakas) +* [`0b21e1f`](https://github.com/eslint/eslint/commit/0b21e1fd67d94f907d007a7a9707a3ae1cc08575) feat!: add two more cases to `no-implicit-coercion` (#17832) (GÃŧrgÃŧn DayÄąoğlu) +* [`e6a91bd`](https://github.com/eslint/eslint/commit/e6a91bdf401e3b765f2b712e447154e4a2419fbc) docs: Switch shareable config docs to use flat config (#17827) (Nicholas C. Zakas) +* [`c0f5d91`](https://github.com/eslint/eslint/commit/c0f5d913b0f07de332dfcecf6052f1e64bf3d2fb) chore: remove creating an unused instance of Linter in tests (#17902) (Milos Djermanovic) +* [`2916c63`](https://github.com/eslint/eslint/commit/2916c63046603e0cdc578d3c2eef8fca5b2e8847) feat!: Switch Linter to flat config by default (#17851) (Nicholas C. Zakas) +* [`3826cdf`](https://github.com/eslint/eslint/commit/3826cdf89294d079be037a9ab30b7506077b26ac) chore: use jsdoc/no-multi-asterisks with allowWhitespace: true (#17900) (Percy Ma) +* [`a9a17b3`](https://github.com/eslint/eslint/commit/a9a17b3f1cb6b6c609bda86a618ac5ff631285d2) chore: fix getting scope in tests (#17899) (Milos Djermanovic) +* [`200518e`](https://github.com/eslint/eslint/commit/200518eb6d42de4c3b0c6ef190fc09a95718297e) fix!: Parsing 'exported' comment using parseListConfig (#17675) (amondev) +* [`3831fb7`](https://github.com/eslint/eslint/commit/3831fb78daa3da296b71823f61f8e3a4556ff7d3) docs: updated examples of `max-lines` rule (#17898) (Tanuj Kanti) +* [`bdd6ba1`](https://github.com/eslint/eslint/commit/bdd6ba138645dba0442bb0ed2ee73049df56f38d) feat!: Remove valid-jsdoc and require-jsdoc (#17694) (Nicholas C. Zakas) +* [`595a1f6`](https://github.com/eslint/eslint/commit/595a1f689edb5250d8398af13c3e4bd19d284d92) test: ensure that CLI tests run with FlatESLint (#17884) (Francesco Trotta) +* [`12be307`](https://github.com/eslint/eslint/commit/12be3071d014814149e8e6d602f5c192178ca771) fix!: Behavior of CLI when no arguments are passed (#17644) (Nicholas C. Zakas) +* [`cd1ac20`](https://github.com/eslint/eslint/commit/cd1ac2041f48f2b6d743ebf671d0279a70de6eea) docs: Update README (GitHub Actions Bot) +* [`8fe8c56`](https://github.com/eslint/eslint/commit/8fe8c5626b98840d6a8580004f6ceffeff56264f) feat!: Update shouldUseFlatConfig and CLI so flat config is default (#17748) (Nicholas C. Zakas) +* [`c7eca43`](https://github.com/eslint/eslint/commit/c7eca43202be98f6ff253b46c9a38602eeb92ea0) chore: update dependency markdownlint-cli to ^0.38.0 (#17865) (renovate[bot]) +* [`60dea3e`](https://github.com/eslint/eslint/commit/60dea3e3abd6c0b6aab25437b2d0501b0d30b70c) feat!: deprecate no-new-symbol, recommend no-new-native-nonconstructor (#17710) (Francesco Trotta) +* [`5aa9c49`](https://github.com/eslint/eslint/commit/5aa9c499da48b2d3187270d5d8ece71ad7521f56) feat!: check for parsing errors in suggestion fixes (#16639) (Bryan Mishkin) +* [`b3e0bb0`](https://github.com/eslint/eslint/commit/b3e0bb03cc814e78b06a1acc4e5347b4c90d72bf) feat!: assert suggestion messages are unique in rule testers (#17532) (Josh Goldberg ✨) +* [`e563c52`](https://github.com/eslint/eslint/commit/e563c52e35d25f726d423cc3b1dffcd80027fd99) feat!: `no-invalid-regexp` make allowConstructorFlags case-sensitive (#17533) (Josh Goldberg ✨) +* [`e5f02c7`](https://github.com/eslint/eslint/commit/e5f02c70084c4f80900c0875b08f665e1f030af2) fix!: no-sequences rule schema correction (#17878) (MHO) +* [`6ee3e9e`](https://github.com/eslint/eslint/commit/6ee3e9eb5df7bdfdaa1746214793ed511112be76) feat!: Update `eslint:recommended` configuration (#17716) (Milos Djermanovic) +* [`c2cf85a`](https://github.com/eslint/eslint/commit/c2cf85a7447777e6b499cbb5c49de919bb5c817f) feat!: drop support for string configurations in flat config array (#17717) (Milos Djermanovic) +* [`c314fd6`](https://github.com/eslint/eslint/commit/c314fd612587c42cfbe6acbe286629c4178be3f7) feat!: Remove `SourceCode#getComments()` (#17715) (Milos Djermanovic) +* [`ae78ff1`](https://github.com/eslint/eslint/commit/ae78ff16558a1a2ca07b2b9cd294157d1bdcce2e) feat!: Remove deprecated context methods (#17698) (Nicholas C. Zakas) +* [`f71c328`](https://github.com/eslint/eslint/commit/f71c328e2786e2d73f168e43c7f96de172484a49) feat!: Swap FlatESLint-ESLint, FlatRuleTester-RuleTester in API (#17823) (Nicholas C. Zakas) +* [`5304da0`](https://github.com/eslint/eslint/commit/5304da03d94dc8cb19060e2efc9206784c4cec0e) feat!: remove formatters except html, json(-with-metadata), and stylish (#17531) (Josh Goldberg ✨) +* [`e1e827f`](https://github.com/eslint/eslint/commit/e1e827ffcbd73faa40dbac3b97529452e9c67108) feat!: Require Node.js `^18.18.0 || ^20.9.0 || >=21.1.0` (#17725) (Milos Djermanovic) +* [`b577e8a`](https://github.com/eslint/eslint/commit/b577e8a55750c5e842074f62f1babb1836c4571c) fix: allow circular references in config (#17752) (Francesco Trotta) +* [`cc0c9f7`](https://github.com/eslint/eslint/commit/cc0c9f707aa9da7965b98151868b3c249c7f8f30) ci: bump github/codeql-action from 2 to 3 (#17873) (dependabot[bot]) + +v9.0.0-rc.0 - March 22, 2024 + +* [`297416d`](https://github.com/eslint/eslint/commit/297416d2b41f5880554d052328aa36cd79ceb051) chore: package.json update for eslint-9.0.0-rc.0 (#18223) (Francesco Trotta) +* [`d363c51`](https://github.com/eslint/eslint/commit/d363c51b177e085b011c7fde1c5a5a09b3db9cdb) chore: package.json update for @eslint/js release (Jenkins) +* [`239a7e2`](https://github.com/eslint/eslint/commit/239a7e27209a6b861d634b3ef245ebbb805793a3) docs: Clarify the description of `sort-imports` options (#18198) (gyeongwoo park) +* [`09bd7fe`](https://github.com/eslint/eslint/commit/09bd7fe09ad255a263286e90accafbe2bf04ccfc) feat!: move AST traversal into SourceCode (#18167) (Nicholas C. Zakas) +* [`b91f9dc`](https://github.com/eslint/eslint/commit/b91f9dc072f17f5ea79803deb86cf002d031b4cf) build: fix TypeError in prism-eslint-hooks.js (#18209) (Francesco Trotta) +* [`4769c86`](https://github.com/eslint/eslint/commit/4769c86cc16e0b54294c0a394a1ec7ed88fc334f) docs: fix incorrect example in `no-lone-blocks` (#18215) (Tanuj Kanti) +* [`1b841bb`](https://github.com/eslint/eslint/commit/1b841bb04ac642c5ee84d1e44be3e53317579526) chore: fix some comments (#18213) (avoidaway) +* [`b8fb572`](https://github.com/eslint/eslint/commit/b8fb57256103b908712302ccd508f464eff1c9dc) feat: add `reportUnusedFallthroughComment` option to no-fallthrough rule (#18188) (Kirk Waiblinger) +* [`ae8103d`](https://github.com/eslint/eslint/commit/ae8103de69c12c6e71644a1de9589644e6767d15) fix: load plugins in the CLI in flat config mode (#18185) (Francesco Trotta) +* [`5251327`](https://github.com/eslint/eslint/commit/5251327711a2d7083e3c629cb8e48d9d1e809add) docs: Update README (GitHub Actions Bot) +* [`29c3595`](https://github.com/eslint/eslint/commit/29c359599c2ddd168084a2c8cbca626c51d0dc13) chore: remove repetitive words (#18193) (cuithon) +* [`1dc8618`](https://github.com/eslint/eslint/commit/1dc861897e8b47280e878d609c13c9e41892f427) docs: Update README (GitHub Actions Bot) +* [`acc2e06`](https://github.com/eslint/eslint/commit/acc2e06edd55eaab58530d891c0a572c1f0ec453) chore: Introduce Knip (#18005) (Lars Kappert) + +v9.0.0-beta.2 - March 8, 2024 + +* [`7509276`](https://github.com/eslint/eslint/commit/75092764db117252067558bd3fbbf0c66ac081b7) chore: upgrade @eslint/js@9.0.0-beta.2 (#18180) (Milos Djermanovic) +* [`96087b3`](https://github.com/eslint/eslint/commit/96087b33dc10311bba83e22cc968919c358a0188) chore: package.json update for @eslint/js release (Jenkins) +* [`ba1c1bb`](https://github.com/eslint/eslint/commit/ba1c1bbc6ba9d57a83d04f450566337d3c3b0448) docs: Update README (GitHub Actions Bot) +* [`337cdf9`](https://github.com/eslint/eslint/commit/337cdf9f7ad939df7bc55c23d953e12d847b6ecc) docs: Explain limitations of RuleTester fix testing (#18175) (Nicholas C. Zakas) +* [`c7abd89`](https://github.com/eslint/eslint/commit/c7abd8936193a87be274174c47d6775e6220e354) docs: Explain Node.js version support (#18176) (Nicholas C. Zakas) +* [`925afa2`](https://github.com/eslint/eslint/commit/925afa2b0c882f77f6b4411bdca3cb8ad6934b56) chore: Remove some uses of `lodash.merge` (#18179) (Milos Djermanovic) +* [`1c173dc`](https://github.com/eslint/eslint/commit/1c173dc1f3d36a28cb2543e93675c2fbdb6fa9f1) feat: add `ignoreClassWithStaticInitBlock` option to `no-unused-vars` (#18170) (Tanuj Kanti) +* [`d961eeb`](https://github.com/eslint/eslint/commit/d961eeb855b6dd9118a78165e358e454eb1d090d) docs: show red underlines in examples in rules docs (#18041) (Yosuke Ota) +* [`558274a`](https://github.com/eslint/eslint/commit/558274abbd25ef269f4994cf258b2e44afbad548) docs: Update README (GitHub Actions Bot) +* [`2908b9b`](https://github.com/eslint/eslint/commit/2908b9b96ab7a25fe8044a1755030b18186a75b0) docs: Update release documentation (#18174) (Nicholas C. Zakas) +* [`a451b32`](https://github.com/eslint/eslint/commit/a451b32b33535a57b4b7e24291f30760f65460ba) feat: make `no-misleading-character-class` report more granular errors (#18082) (Francesco Trotta) +* [`972ef15`](https://github.com/eslint/eslint/commit/972ef155a94ad2cc85db7d209ad869869222c14c) chore: remove invalid type in @eslint/js (#18164) (Nitin Kumar) +* [`1f1260e`](https://github.com/eslint/eslint/commit/1f1260e863f53e2a5891163485a67c55d41993aa) docs: replace HackerOne link with GitHub advisory (#18165) (Francesco Trotta) +* [`79a95eb`](https://github.com/eslint/eslint/commit/79a95eb7da7fe657b6448c225d4f8ac31117456a) feat!: disallow multiple configuration comments for same rule (#18157) (Milos Djermanovic) +* [`e37153f`](https://github.com/eslint/eslint/commit/e37153f71f173e8667273d6298bef81e0d33f9ba) fix: improve error message for invalid rule config (#18147) (Nitin Kumar) +* [`c49ed63`](https://github.com/eslint/eslint/commit/c49ed63265fc8e0cccea404810a4c5075d396a15) feat: update complexity rule for optional chaining & default values (#18152) (Mathias Schreck) +* [`e5ef3cd`](https://github.com/eslint/eslint/commit/e5ef3cd6953bb40108556e0465653898ffed8420) docs: add inline cases condition in `no-fallthrough` (#18158) (Tanuj Kanti) +* [`af6e170`](https://github.com/eslint/eslint/commit/af6e17081fa6c343474959712e7a4a20f8b304e2) fix: stop linting files after an error (#18155) (Francesco Trotta) +* [`450d0f0`](https://github.com/eslint/eslint/commit/450d0f044023843b1790bd497dfca45dcbdb41e4) docs: fix `ignore` option docs (#18154) (Francesco Trotta) +* [`11144a2`](https://github.com/eslint/eslint/commit/11144a2671b2404b293f656be111221557f3390f) feat: `no-restricted-imports` option added `allowImportNames` (#16196) (M Pater) + +v9.0.0-beta.1 - February 23, 2024 + +* [`32ffdd1`](https://github.com/eslint/eslint/commit/32ffdd181aa673ccc596f714d10a2f879ec622a7) chore: upgrade @eslint/js@9.0.0-beta.1 (#18146) (Milos Djermanovic) +* [`e41425b`](https://github.com/eslint/eslint/commit/e41425b5c3b4c885f2679a3663bd081911a8b570) chore: package.json update for @eslint/js release (Jenkins) +* [`bb3b9c6`](https://github.com/eslint/eslint/commit/bb3b9c68fe714bb8aa305be5f019a7a42f4374ee) chore: upgrade @eslint/eslintrc@3.0.2 (#18145) (Milos Djermanovic) +* [`c9f2f33`](https://github.com/eslint/eslint/commit/c9f2f3343e7c197e5e962c68ef202d6a1646866e) build: changelog update for 8.57.0 (#18144) (Milos Djermanovic) +* [`5fe095c`](https://github.com/eslint/eslint/commit/5fe095cf718b063dc5e58089b0a6cbcd53da7925) docs: show v8.57.0 as latest version in dropdown (#18142) (Milos Djermanovic) +* [`0cb4914`](https://github.com/eslint/eslint/commit/0cb4914ef93cd572ba368d390b1cf0b93f578a9d) fix: validate options when comment with just severity enables rule (#18133) (Milos Djermanovic) +* [`7db5bb2`](https://github.com/eslint/eslint/commit/7db5bb270f95d1472de0bfed0e33ed5ab294942e) docs: Show prerelease version in dropdown (#18135) (Nicholas C. Zakas) +* [`e462524`](https://github.com/eslint/eslint/commit/e462524cc318ffacecd266e6fe1038945a0b02e9) chore: upgrade eslint-release@3.2.2 (#18138) (Milos Djermanovic) +* [`8e13a6b`](https://github.com/eslint/eslint/commit/8e13a6beb587e624cc95ae16eefe503ad024b11b) chore: fix spelling mistake in README.md (#18128) (Will Eastcott) +* [`66f52e2`](https://github.com/eslint/eslint/commit/66f52e276c31487424bcf54e490c4ac7ef70f77f) chore: remove unused tools rule-types.json, update-rule-types.js (#18125) (Josh Goldberg ✨) +* [`bf0c7ef`](https://github.com/eslint/eslint/commit/bf0c7effdba51c48b929d06ce1965408a912dc77) ci: fix sync-labels value of pr-labeler (#18124) (Tanuj Kanti) +* [`cace6d0`](https://github.com/eslint/eslint/commit/cace6d0a3afa5c84b18abee4ef8c598125143461) ci: add PR labeler action (#18109) (Nitin Kumar) +* [`73a5f06`](https://github.com/eslint/eslint/commit/73a5f0641b43e169247b0000f44a366ee6bbc4f2) docs: Update README (GitHub Actions Bot) +* [`74124c2`](https://github.com/eslint/eslint/commit/74124c20287fac1995c3f4e553f0723c066f311d) feat: add suggestions to `use-isnan` in `indexOf` & `lastIndexOf` calls (#18063) (StyleShit) +* [`1a65d3e`](https://github.com/eslint/eslint/commit/1a65d3e4a6ee16e3f607d69b998a08c3fed505ca) chore: export `base` config from `eslint-config-eslint` (#18119) (Milos Djermanovic) +* [`f95cd27`](https://github.com/eslint/eslint/commit/f95cd27679eef228173e27e170429c9710c939b3) docs: Disallow multiple rule configuration comments in the same example (#18116) (Milos Djermanovic) +* [`9aa4df3`](https://github.com/eslint/eslint/commit/9aa4df3f4d85960eee72923f3b9bfc88e62f04fb) refactor: remove `globals` dependency (#18115) (Milos Djermanovic) +* [`d8068ec`](https://github.com/eslint/eslint/commit/d8068ec70fac050e900dc400510a4ad673e17633) docs: Update link for schema examples (#18112) (Svetlana) + +v8.57.0 - February 23, 2024 + +* [`1813aec`](https://github.com/eslint/eslint/commit/1813aecc4660582b0678cf32ba466eb9674266c4) chore: upgrade @eslint/js@8.57.0 (#18143) (Milos Djermanovic) +* [`5c356bb`](https://github.com/eslint/eslint/commit/5c356bb0c6f53c570224f8e9f02c4baca8fc6d2f) chore: package.json update for @eslint/js release (Jenkins) +* [`84922d0`](https://github.com/eslint/eslint/commit/84922d0bfa10689a34a447ab8e55975ff1c1c708) docs: Show prerelease version in dropdown (#18139) (Nicholas C. Zakas) +* [`1120b9b`](https://github.com/eslint/eslint/commit/1120b9b7b97f10f059d8b7ede19de2572f892366) feat: Add loadESLint() API method for v8 (#18098) (Nicholas C. Zakas) +* [`5b8c363`](https://github.com/eslint/eslint/commit/5b8c3636a3d7536535a6878eca0e5b773e4829d4) docs: Switch to Ethical Ads (#18117) (Milos Djermanovic) +* [`2196d97`](https://github.com/eslint/eslint/commit/2196d97094ba94d6d750828879a29538d1600de5) fix: handle absolute file paths in `FlatRuleTester` (#18064) (Nitin Kumar) +* [`f4a1fe2`](https://github.com/eslint/eslint/commit/f4a1fe2e45aa1089fe775290bf530de82f34bf16) test: add more tests for ignoring files and directories (#18068) (Nitin Kumar) +* [`69dd1d1`](https://github.com/eslint/eslint/commit/69dd1d1387b7b53617548d1f9f2c149f179e6e17) fix: Ensure config keys are printed for config errors (#18067) (Nitin Kumar) +* [`9852a31`](https://github.com/eslint/eslint/commit/9852a31edcf054bd5d15753ef18e2ad3216b1b71) fix: deep merge behavior in flat config (#18065) (Nitin Kumar) +* [`dca7d0f`](https://github.com/eslint/eslint/commit/dca7d0f1c262bc72310147bcefe1d04ecf60acbc) feat: Enable `eslint.config.mjs` and `eslint.config.cjs` (#18066) (Nitin Kumar) +* [`4c7e9b0`](https://github.com/eslint/eslint/commit/4c7e9b0b539ba879ac1799e81f3b6add2eed4b2f) fix: allow circular references in config (#18056) (Milos Djermanovic) +* [`77dbfd9`](https://github.com/eslint/eslint/commit/77dbfd9887b201a46fc68631cbde50c08e1a8dbf) docs: show NEXT in version selectors (#18052) (Milos Djermanovic) +* [`42c0aef`](https://github.com/eslint/eslint/commit/42c0aefaf6ea8b998b1c6db61906a79c046d301a) ci: Enable CI for `v8.x` branch (#18047) (Milos Djermanovic) + +v9.0.0-beta.0 - February 9, 2024 + +* [`e40d1d7`](https://github.com/eslint/eslint/commit/e40d1d74a5b9788cbec195f4e602b50249f26659) chore: upgrade @eslint/js@9.0.0-beta.0 (#18108) (Milos Djermanovic) +* [`9870f93`](https://github.com/eslint/eslint/commit/9870f93e714edefb410fccae1e9924a3c1972a2e) chore: package.json update for @eslint/js release (Jenkins) +* [`2c62e79`](https://github.com/eslint/eslint/commit/2c62e797a433e5fc298b976872a89c594f88bb19) chore: upgrade @eslint/eslintrc@3.0.1 (#18107) (Milos Djermanovic) +* [`81f0294`](https://github.com/eslint/eslint/commit/81f0294e651928b49eb49495b90b54376073a790) chore: upgrade espree@10.0.1 (#18106) (Milos Djermanovic) +* [`5e2b292`](https://github.com/eslint/eslint/commit/5e2b2922aa65bda54b0966d1bf71acda82b3047c) chore: upgrade eslint-visitor-keys@4.0.0 (#18105) (Milos Djermanovic) +* [`9163646`](https://github.com/eslint/eslint/commit/916364692bae6a93c10b5d48fc1e9de1677d0d09) feat!: Rule Tester checks for missing placeholder data in the message (#18073) (fnx) +* [`53f0f47`](https://github.com/eslint/eslint/commit/53f0f47badffa1b04ec2836f2ae599f4fc464da2) feat: Add loadESLint() API method for v9 (#18097) (Nicholas C. Zakas) +* [`f1c7e6f`](https://github.com/eslint/eslint/commit/f1c7e6fc8ea77fcdae4ad1f8fe1cd104a281d2e9) docs: Switch to Ethical Ads (#18090) (Strek) +* [`15c143f`](https://github.com/eslint/eslint/commit/15c143f96ef164943fd3d39b5ad79d9a4a40de8f) docs: JS Foundation -> OpenJS Foundation in PR template (#18092) (Nicholas C. Zakas) +* [`c4d26fd`](https://github.com/eslint/eslint/commit/c4d26fd3d1f59c1c0f2266664887ad18692039f3) fix: `use-isnan` doesn't report on `SequenceExpression`s (#18059) (StyleShit) +* [`6ea339e`](https://github.com/eslint/eslint/commit/6ea339e658d29791528ab26aabd86f1683cab6c3) docs: add stricter rule test validations to v9 migration guide (#18085) (Milos Djermanovic) +* [`ce838ad`](https://github.com/eslint/eslint/commit/ce838adc3b673e52a151f36da0eedf5876977514) chore: replace dependency npm-run-all with npm-run-all2 ^5.0.0 (#18045) (renovate[bot]) +* [`3c816f1`](https://github.com/eslint/eslint/commit/3c816f193eecace5efc6166efa2852a829175ef8) docs: use relative link from CLI to core concepts (#18083) (Milos Djermanovic) +* [`54df731`](https://github.com/eslint/eslint/commit/54df731174d2528170560d1f765e1336eca0a8bd) chore: update dependency markdownlint-cli to ^0.39.0 (#18084) (renovate[bot]) +* [`9458735`](https://github.com/eslint/eslint/commit/9458735381269d12b24f76e1b2b6fda1bc5a509b) docs: fix malformed `eslint` config comments in rule examples (#18078) (Francesco Trotta) +* [`07a1ada`](https://github.com/eslint/eslint/commit/07a1ada7166b76c7af6186f4c5e5de8b8532edba) docs: link from `--fix` CLI doc to the relevant core concept (#18080) (Bryan Mishkin) +* [`8f06a60`](https://github.com/eslint/eslint/commit/8f06a606845f40aaf0fea1fd83d5930747c5acec) chore: update dependency shelljs to ^0.8.5 (#18079) (Francesco Trotta) +* [`b844324`](https://github.com/eslint/eslint/commit/b844324e4e8f511c9985a96c7aca063269df9570) docs: Update team responsibilities (#18048) (Nicholas C. Zakas) +* [`aadfb60`](https://github.com/eslint/eslint/commit/aadfb609f1b847e492fc3b28ced62f830fe7f294) docs: document languageOptions and other v9 changes for context (#18074) (fnx) +* [`3c4d51d`](https://github.com/eslint/eslint/commit/3c4d51d55fa5435ab18b6bf46f6b97df0f480ae7) feat!: default for `enforceForClassMembers` in `no-useless-computed-key` (#18054) (Francesco Trotta) +* [`47e60f8`](https://github.com/eslint/eslint/commit/47e60f85e0c3f275207bb4be9b5947166a190477) feat!: Stricter rule test validations (#17654) (fnx) +* [`1a94589`](https://github.com/eslint/eslint/commit/1a945890105d307541dcbff15f6438c19b476ade) feat!: `no-unused-vars` default caughtErrors to 'all' (#18043) (Josh Goldberg ✨) +* [`857e242`](https://github.com/eslint/eslint/commit/857e242584227181ecb8af79fc6bc236b9975228) docs: tweak explanation for meta.docs rule properties (#18057) (Bryan Mishkin) +* [`10485e8`](https://github.com/eslint/eslint/commit/10485e8b961d045514bc1e34227cf09867a6c4b7) docs: recommend messageId over message for reporting rule violations (#18050) (Bryan Mishkin) +* [`98b5ab4`](https://github.com/eslint/eslint/commit/98b5ab406bac6279eadd84e8a5fd5a01fc586ff1) docs: Update README (GitHub Actions Bot) +* [`93ffe30`](https://github.com/eslint/eslint/commit/93ffe30da5e2127e336c1c22e69e09ec0558a8e6) chore: update dependency file-entry-cache to v8 (#17903) (renovate[bot]) +* [`505fbf4`](https://github.com/eslint/eslint/commit/505fbf4b35c14332bffb0c838cce4843a00fad68) docs: update `no-restricted-imports` rule (#18015) (Tanuj Kanti) +* [`2d11d46`](https://github.com/eslint/eslint/commit/2d11d46e890a9f1b5f639b8ee034ffa9bd453e42) feat: add suggestions to `use-isnan` in binary expressions (#17996) (StyleShit) +* [`c25b4af`](https://github.com/eslint/eslint/commit/c25b4aff1fe35e5bd9d4fcdbb45b739b6d253828) docs: Update README (GitHub Actions Bot) + +v9.0.0-alpha.2 - January 26, 2024 + +* [`6ffdcbb`](https://github.com/eslint/eslint/commit/6ffdcbb8c51956054d3f81c5ce446c15dcd51a6f) chore: upgrade @eslint/js@9.0.0-alpha.2 (#18038) (Milos Djermanovic) +* [`2c12715`](https://github.com/eslint/eslint/commit/2c1271528e88d0c3c6a92eeee902001f1703d5c9) chore: package.json update for @eslint/js release (Jenkins) +* [`cc74c4d`](https://github.com/eslint/eslint/commit/cc74c4da99368b97494b924dbea1cb6e87adec53) chore: upgrade espree@10.0.0 (#18037) (Milos Djermanovic) +* [`26093c7`](https://github.com/eslint/eslint/commit/26093c76903310d12f21e24e73d97c0d2ac1f359) feat: fix false negatives in `no-this-before-super` (#17762) (Yosuke Ota) +* [`57089cb`](https://github.com/eslint/eslint/commit/57089cb5166acf8b8bdba8a8dbeb0a129f841478) feat!: no-restricted-imports allow multiple config entries for same path (#18021) (Milos Djermanovic) +* [`33d1ab0`](https://github.com/eslint/eslint/commit/33d1ab0b6ea5fcebca7284026d2396df41b06566) docs: add more examples to flat config ignores docs (#18020) (Milos Djermanovic) +* [`e6eebca`](https://github.com/eslint/eslint/commit/e6eebca90750ef5c7c99d4fe3658553cf737dab8) docs: Update sort-keys options properties count (#18025) (LB (Ben Johnston)) +* [`dfb68b6`](https://github.com/eslint/eslint/commit/dfb68b63ce6e8df6ffe81bd843e650c5b017dce9) chore: use Node.js 20 for docs sites (#18026) (Milos Djermanovic) +* [`8c1b8dd`](https://github.com/eslint/eslint/commit/8c1b8dda169920c4e3b99f6548f9c872d65ee426) test: add more tests for ignoring files and directories (#18018) (Milos Djermanovic) +* [`60b966b`](https://github.com/eslint/eslint/commit/60b966b6861da11617ddc15487bd7a51c584c596) chore: update dependency @eslint/js to v9.0.0-alpha.1 (#18014) (renovate[bot]) +* [`5471e43`](https://github.com/eslint/eslint/commit/5471e435d12bf5add9869d81534b147e445a2368) feat: convert unsafe autofixes to suggestions in `no-implicit-coercion` (#17985) (GÃŧrgÃŧn DayÄąoğlu) +* [`2e1d549`](https://github.com/eslint/eslint/commit/2e1d54960051b59e1c731fa44c2ef843290b1335) feat!: detect duplicate test cases (#17955) (Bryan Mishkin) +* [`1fedfd2`](https://github.com/eslint/eslint/commit/1fedfd28a46d86b2fbcf06a2328befafd6535a88) docs: Improve flat config ignores docs (#17997) (Nicholas C. Zakas) +* [`e3051be`](https://github.com/eslint/eslint/commit/e3051be6366b00e1571e702023a351177d24e443) feat: emit warning when `.eslintignore` file is detected (#17952) (Nitin Kumar) +* [`38b9b06`](https://github.com/eslint/eslint/commit/38b9b06695f88c70441dd15ae5d97ffd8088be23) docs: update valid-typeof rule (#18001) (Tanuj Kanti) +* [`39076fb`](https://github.com/eslint/eslint/commit/39076fb5e4c7fa10b305d510f489aff34a5f5d99) fix: handle absolute file paths in `RuleTester` (#17989) (Nitin Kumar) +* [`b4abfea`](https://github.com/eslint/eslint/commit/b4abfea4c1703a50f1ce639e3207ad342a56f79d) docs: Update note about ECMAScript support (#17991) (Francesco Trotta) +* [`c893bc0`](https://github.com/eslint/eslint/commit/c893bc0bdf1bca256fbab6190358e5f922683249) chore: update `markdownlint` to `v0.33.0` (#17995) (Nitin Kumar) +* [`6788873`](https://github.com/eslint/eslint/commit/6788873328a7f974d5e45c0be06ca0c7dd409acd) docs: Update release blog post template (#17994) (Nicholas C. Zakas) +* [`1f37442`](https://github.com/eslint/eslint/commit/1f3744278433006042b8d5f4e9e1e488b2bbb011) docs: Add sections on non-npm plugin configuration (#17984) (Nicholas C. Zakas) + +v9.0.0-alpha.1 - January 12, 2024 + +* [`c5e50ee`](https://github.com/eslint/eslint/commit/c5e50ee65cf22871770b1d4d438b9056c577f646) chore: package.json update for @eslint/js release (Jenkins) +* [`1bf2520`](https://github.com/eslint/eslint/commit/1bf2520c4166aa55596417bf44c567555bc65fba) chore: Split Docs CI from core CI (#17897) (Nicholas C. Zakas) +* [`6d11f3d`](https://github.com/eslint/eslint/commit/6d11f3dac1b76188d7fda6e772e89b5c3945ac4d) fix: Ensure config keys are printed for config errors (#17980) (Nicholas C. Zakas) +* [`320787e`](https://github.com/eslint/eslint/commit/320787e661beb979cf063d0f8333654f94ef9efd) chore: delete relative-module-resolver.js (#17981) (Francesco Trotta) +* [`96307da`](https://github.com/eslint/eslint/commit/96307da837c407c9a1275124b65ca29c07ffd5e4) docs: migration guide entry for `no-inner-declarations` (#17977) (Tanuj Kanti) +* [`40be60e`](https://github.com/eslint/eslint/commit/40be60e0186cdde76219df4e8e628125df2912d8) docs: Update README (GitHub Actions Bot) +* [`a630edd`](https://github.com/eslint/eslint/commit/a630edd809894dc38752705bb5954d847987f031) feat: maintain latest ecma version in ESLint (#17958) (Milos Djermanovic) +* [`701f1af`](https://github.com/eslint/eslint/commit/701f1afbee34e458b56d2dfa36d9153d6aebea3a) feat!: no-inner-declaration new default behaviour and option (#17885) (Tanuj Kanti) +* [`b4e0503`](https://github.com/eslint/eslint/commit/b4e0503a56beea1222be266cc6b186d89410d1f2) feat: add `no-useless-assignment` rule (#17625) (Yosuke Ota) +* [`806f708`](https://github.com/eslint/eslint/commit/806f70878e787f2c56aaa42a3e7adb61bc015278) fix: `no-misleading-character-class` edge cases with granular errors (#17970) (Milos Djermanovic) +* [`287c4b7`](https://github.com/eslint/eslint/commit/287c4b7d498746b43392ee4fecd6904a9cd4b30b) feat: `no-misleading-character-class` granular errors (#17515) (Josh Goldberg ✨) +* [`d31c180`](https://github.com/eslint/eslint/commit/d31c180312260d1a286cc8162907b6a33368edc9) docs: fix number of code-path events on custom rules page (#17969) (Richard Hunter) +* [`1529ab2`](https://github.com/eslint/eslint/commit/1529ab288ec815b2690864e04dd6d0a1f0b537c6) docs: reorder entries in v9 migration guide (#17967) (Milos Djermanovic) +* [`bde5105`](https://github.com/eslint/eslint/commit/bde51055530d4a71bd9f48c90ed7de9c0b767d86) fix!: handle `--output-file` for empty output when saving to disk (#17957) (Nitin Kumar) +* [`9507525`](https://github.com/eslint/eslint/commit/95075251fb3ce35aaf7eadbd1d0a737106c13ec6) docs: Explain how to combine configs (#17947) (Nicholas C. Zakas) +* [`7c78576`](https://github.com/eslint/eslint/commit/7c785769fd177176966de7f6c1153480f7405000) docs: Add more removed `context` methods to migrate to v9 guide (#17951) (Milos Djermanovic) +* [`07107a5`](https://github.com/eslint/eslint/commit/07107a5904c2580243971c8ad7f26a04738b712e) fix!: upgrade eslint-scope@8.0.0 (#17942) (Milos Djermanovic) +* [`3ee0f6c`](https://github.com/eslint/eslint/commit/3ee0f6ca5d756da647e4e76bf3daa82a5905a792) fix!: no-unused-vars `varsIgnorePattern` behavior with catch arguments (#17932) (Tanuj Kanti) +* [`4926f33`](https://github.com/eslint/eslint/commit/4926f33b96faf07a64aceec5f1f4882f4faaf4b5) refactor: use `Object.hasOwn()` (#17948) (Milos Djermanovic) +* [`df200e1`](https://github.com/eslint/eslint/commit/df200e147705eb62f94b99c170554327259c65d4) refactor: use `Array.prototype.at()` to get last elements (#17949) (Milos Djermanovic) +* [`51f8bc8`](https://github.com/eslint/eslint/commit/51f8bc836bf0b13dad3a897ae84259bcdaed2431) fix!: configuration comments with just severity should retain options (#17945) (Milos Djermanovic) +* [`3a877d6`](https://github.com/eslint/eslint/commit/3a877d68d0151679f8bf1cabc39746778754b3dd) docs: Update removed CLI flags migration (#17939) (Nicholas C. Zakas) +* [`750b8df`](https://github.com/eslint/eslint/commit/750b8dff6df02a500e12cb78390fd14814c82e5b) chore: update dependency glob to v10 (#17917) (renovate[bot]) +* [`c2bf27d`](https://github.com/eslint/eslint/commit/c2bf27def29ef1ca7f5bfe20c1306bf78087ea29) build: update docs files when publishing prereleases (#17940) (Milos Djermanovic) +* [`d191bdd`](https://github.com/eslint/eslint/commit/d191bdd67214c33e65bd605e616ca7cc947fd045) feat!: Remove CodePath#currentSegments (#17936) (Milos Djermanovic) +* [`4a9cd1e`](https://github.com/eslint/eslint/commit/4a9cd1ea1cd0c115b98d07d1b6018ca918a9c73f) docs: Update Linter API for v9 (#17937) (Milos Djermanovic) +* [`74794f5`](https://github.com/eslint/eslint/commit/74794f53a6bc88b67653c737f858cfdf35b1c73d) chore: removed unused eslintrc modules (#17938) (Milos Djermanovic) +* [`10ed29c`](https://github.com/eslint/eslint/commit/10ed29c0c4505dbac3bb05b0e3d61f329b99f747) chore: remove unused dependency rimraf (#17934) (Francesco Trotta) +* [`2a8eea8`](https://github.com/eslint/eslint/commit/2a8eea8e5847f4103d90d667a2b08edf9795545f) docs: update docs for v9.0.0-alpha.0 (#17929) (Milos Djermanovic) +* [`903ee60`](https://github.com/eslint/eslint/commit/903ee60ea910aee344df7edb66874f80e4b6ed31) ci: use `--force` flag when installing eslint (#17921) (Milos Djermanovic) + +v9.0.0-alpha.0 - December 29, 2023 + +* [`7f0ba51`](https://github.com/eslint/eslint/commit/7f0ba51bcef3e6fbf972ceb20403238f0e1f0ea9) docs: show `NEXT` in version selectors (#17911) (Milos Djermanovic) +* [`17fedc1`](https://github.com/eslint/eslint/commit/17fedc17e9e6e39ad986d917fb4e9e4835c50482) chore: upgrade @eslint/js@9.0.0-alpha.0 (#17928) (Milos Djermanovic) +* [`cb89ef3`](https://github.com/eslint/eslint/commit/cb89ef373fffbed991f4e099cb255a7c116889f9) chore: package.json update for @eslint/js release (Jenkins) +* [`0a7911e`](https://github.com/eslint/eslint/commit/0a7911e09adf2aca4d93c81f4be1cd80db7dd735) docs: add flat config default to v9 migration guide (#17927) (Milos Djermanovic) +* [`94f8065`](https://github.com/eslint/eslint/commit/94f80652aca302e2715ea51c10c3a1010786b751) docs: Add CLI updates to migrate to v9 guide (#17924) (Nicholas C. Zakas) +* [`16187f2`](https://github.com/eslint/eslint/commit/16187f23c6e5aaed3b50ff551a66f758893d5422) docs: Add exported and string config notes to migrate to v9 guide (#17926) (Nicholas C. Zakas) +* [`3ae50cc`](https://github.com/eslint/eslint/commit/3ae50cc788c3cdd209e642573e3c831dd86fa0cd) docs: Add RuleTester changes to migrate to v9 guide (#17923) (Nicholas C. Zakas) +* [`0831b58`](https://github.com/eslint/eslint/commit/0831b58fe6fb5778c92aeb4cefa9ecedbbfbf48b) docs: add rule changes to v9 migration guide (#17925) (Milos Djermanovic) +* [`946ae00`](https://github.com/eslint/eslint/commit/946ae00457265eb298eb169d6d48ca7ec71b9eef) feat!: FlatRuleTester -> RuleTester (#17922) (Nicholas C. Zakas) +* [`f182114`](https://github.com/eslint/eslint/commit/f182114144ae0bb7187de34a1661f31fb70f1357) fix: deep merge behavior in flat config (#17906) (Francesco Trotta) +* [`037abfc`](https://github.com/eslint/eslint/commit/037abfc21f264fca3a910c4a5cd23d1bf6826c3d) docs: update API docs (#17919) (Milos Djermanovic) +* [`baff28c`](https://github.com/eslint/eslint/commit/baff28ce8f167f564471f1d70d6e9c4b0cb1a508) feat!: remove `no-inner-declarations` from `eslint:recommended` (#17920) (Milos Djermanovic) +* [`f6f4a45`](https://github.com/eslint/eslint/commit/f6f4a45680039f720a2fccd5f445deaf45babb3d) chore: drop structuredClone polyfill for v9 (#17915) (Kevin Gibbons) +* [`afc3c03`](https://github.com/eslint/eslint/commit/afc3c038ed3132a99659604624cc24e702eec45a) docs: add function-style and `meta.schema` changes to v9 migration guide (#17912) (Milos Djermanovic) +* [`cadfbcd`](https://github.com/eslint/eslint/commit/cadfbcd468737fc9447243edd1d15058efb6d3d8) feat!: Rename FlatESLint to ESLint (#17914) (Nicholas C. Zakas) +* [`412dcbb`](https://github.com/eslint/eslint/commit/412dcbb672b5a5859f96753afa7cb87291135a1b) chore: upgrade eslint-plugin-n@16.6.0 (#17916) (Milos Djermanovic) +* [`8792464`](https://github.com/eslint/eslint/commit/8792464ee7956af82dab582ca9ee59da596a608e) feat: Enable eslint.config.mjs and eslint.config.cjs (#17909) (Nicholas C. Zakas) +* [`24ce927`](https://github.com/eslint/eslint/commit/24ce9276d472b85541c4b01db488c789f33fd234) feat: warn by default for unused disable directives (#17879) (Bryan Mishkin) +* [`02a8baf`](https://github.com/eslint/eslint/commit/02a8baf9f2ef7b309c7d45564a79ed5d2153057f) chore: Rename files with underscores (#17910) (Nicholas C. Zakas) +* [`d1018fc`](https://github.com/eslint/eslint/commit/d1018fc5e59db0495aa4a7f501c9d3f831981f35) feat!: skip running warnings in --quiet mode (#17274) (Maddy Miller) +* [`fb81b1c`](https://github.com/eslint/eslint/commit/fb81b1cb78d2692a87fd3591fdc0f96b0c95e760) feat!: Set default `schema: []`, drop support for function-style rules (#17792) (Milos Djermanovic) +* [`1da0723`](https://github.com/eslint/eslint/commit/1da0723695d080008b22f30c8b5c86fe386c6242) docs: update `eslint:recommended` section in Migrate to v9.x (#17908) (Milos Djermanovic) +* [`f55881f`](https://github.com/eslint/eslint/commit/f55881f492d10e9c759e459ba6bade1be3dad84b) docs: remove configuration-files-new.md (#17907) (Milos Djermanovic) +* [`63ae191`](https://github.com/eslint/eslint/commit/63ae191070569a9118b5972c90a98633b0a336e1) docs: Migrate to v9.0.0 (#17905) (Nicholas C. Zakas) +* [`e708496`](https://github.com/eslint/eslint/commit/e7084963c73f3cbaae5d569b4a2bee1509dd8cef) docs: Switch to flat config by default (#17840) (Nicholas C. Zakas) +* [`fdf0424`](https://github.com/eslint/eslint/commit/fdf0424c5c08c058479a6cd7676be6985e0f400f) docs: Update Create a Plugin for flat config (#17826) (Nicholas C. Zakas) +* [`0b21e1f`](https://github.com/eslint/eslint/commit/0b21e1fd67d94f907d007a7a9707a3ae1cc08575) feat!: add two more cases to `no-implicit-coercion` (#17832) (GÃŧrgÃŧn DayÄąoğlu) +* [`e6a91bd`](https://github.com/eslint/eslint/commit/e6a91bdf401e3b765f2b712e447154e4a2419fbc) docs: Switch shareable config docs to use flat config (#17827) (Nicholas C. Zakas) +* [`c0f5d91`](https://github.com/eslint/eslint/commit/c0f5d913b0f07de332dfcecf6052f1e64bf3d2fb) chore: remove creating an unused instance of Linter in tests (#17902) (Milos Djermanovic) +* [`2916c63`](https://github.com/eslint/eslint/commit/2916c63046603e0cdc578d3c2eef8fca5b2e8847) feat!: Switch Linter to flat config by default (#17851) (Nicholas C. Zakas) +* [`3826cdf`](https://github.com/eslint/eslint/commit/3826cdf89294d079be037a9ab30b7506077b26ac) chore: use jsdoc/no-multi-asterisks with allowWhitespace: true (#17900) (Percy Ma) +* [`a9a17b3`](https://github.com/eslint/eslint/commit/a9a17b3f1cb6b6c609bda86a618ac5ff631285d2) chore: fix getting scope in tests (#17899) (Milos Djermanovic) +* [`200518e`](https://github.com/eslint/eslint/commit/200518eb6d42de4c3b0c6ef190fc09a95718297e) fix!: Parsing 'exported' comment using parseListConfig (#17675) (amondev) +* [`3831fb7`](https://github.com/eslint/eslint/commit/3831fb78daa3da296b71823f61f8e3a4556ff7d3) docs: updated examples of `max-lines` rule (#17898) (Tanuj Kanti) +* [`bdd6ba1`](https://github.com/eslint/eslint/commit/bdd6ba138645dba0442bb0ed2ee73049df56f38d) feat!: Remove valid-jsdoc and require-jsdoc (#17694) (Nicholas C. Zakas) +* [`595a1f6`](https://github.com/eslint/eslint/commit/595a1f689edb5250d8398af13c3e4bd19d284d92) test: ensure that CLI tests run with FlatESLint (#17884) (Francesco Trotta) +* [`12be307`](https://github.com/eslint/eslint/commit/12be3071d014814149e8e6d602f5c192178ca771) fix!: Behavior of CLI when no arguments are passed (#17644) (Nicholas C. Zakas) +* [`cd1ac20`](https://github.com/eslint/eslint/commit/cd1ac2041f48f2b6d743ebf671d0279a70de6eea) docs: Update README (GitHub Actions Bot) +* [`8fe8c56`](https://github.com/eslint/eslint/commit/8fe8c5626b98840d6a8580004f6ceffeff56264f) feat!: Update shouldUseFlatConfig and CLI so flat config is default (#17748) (Nicholas C. Zakas) +* [`c7eca43`](https://github.com/eslint/eslint/commit/c7eca43202be98f6ff253b46c9a38602eeb92ea0) chore: update dependency markdownlint-cli to ^0.38.0 (#17865) (renovate[bot]) +* [`60dea3e`](https://github.com/eslint/eslint/commit/60dea3e3abd6c0b6aab25437b2d0501b0d30b70c) feat!: deprecate no-new-symbol, recommend no-new-native-nonconstructor (#17710) (Francesco Trotta) +* [`5aa9c49`](https://github.com/eslint/eslint/commit/5aa9c499da48b2d3187270d5d8ece71ad7521f56) feat!: check for parsing errors in suggestion fixes (#16639) (Bryan Mishkin) +* [`b3e0bb0`](https://github.com/eslint/eslint/commit/b3e0bb03cc814e78b06a1acc4e5347b4c90d72bf) feat!: assert suggestion messages are unique in rule testers (#17532) (Josh Goldberg ✨) +* [`e563c52`](https://github.com/eslint/eslint/commit/e563c52e35d25f726d423cc3b1dffcd80027fd99) feat!: `no-invalid-regexp` make allowConstructorFlags case-sensitive (#17533) (Josh Goldberg ✨) +* [`e5f02c7`](https://github.com/eslint/eslint/commit/e5f02c70084c4f80900c0875b08f665e1f030af2) fix!: no-sequences rule schema correction (#17878) (MHO) +* [`6ee3e9e`](https://github.com/eslint/eslint/commit/6ee3e9eb5df7bdfdaa1746214793ed511112be76) feat!: Update `eslint:recommended` configuration (#17716) (Milos Djermanovic) +* [`c2cf85a`](https://github.com/eslint/eslint/commit/c2cf85a7447777e6b499cbb5c49de919bb5c817f) feat!: drop support for string configurations in flat config array (#17717) (Milos Djermanovic) +* [`c314fd6`](https://github.com/eslint/eslint/commit/c314fd612587c42cfbe6acbe286629c4178be3f7) feat!: Remove `SourceCode#getComments()` (#17715) (Milos Djermanovic) +* [`ae78ff1`](https://github.com/eslint/eslint/commit/ae78ff16558a1a2ca07b2b9cd294157d1bdcce2e) feat!: Remove deprecated context methods (#17698) (Nicholas C. Zakas) +* [`f71c328`](https://github.com/eslint/eslint/commit/f71c328e2786e2d73f168e43c7f96de172484a49) feat!: Swap FlatESLint-ESLint, FlatRuleTester-RuleTester in API (#17823) (Nicholas C. Zakas) +* [`5304da0`](https://github.com/eslint/eslint/commit/5304da03d94dc8cb19060e2efc9206784c4cec0e) feat!: remove formatters except html, json(-with-metadata), and stylish (#17531) (Josh Goldberg ✨) +* [`e1e827f`](https://github.com/eslint/eslint/commit/e1e827ffcbd73faa40dbac3b97529452e9c67108) feat!: Require Node.js `^18.18.0 || ^20.9.0 || >=21.1.0` (#17725) (Milos Djermanovic) +* [`b577e8a`](https://github.com/eslint/eslint/commit/b577e8a55750c5e842074f62f1babb1836c4571c) fix: allow circular references in config (#17752) (Francesco Trotta) +* [`cc0c9f7`](https://github.com/eslint/eslint/commit/cc0c9f707aa9da7965b98151868b3c249c7f8f30) ci: bump github/codeql-action from 2 to 3 (#17873) (dependabot[bot]) + v8.56.0 - December 15, 2023 * [`ba6af85`](https://github.com/eslint/eslint/commit/ba6af85c7d8ba55d37f8663aee949d148e441c1a) chore: upgrade @eslint/js@8.56.0 (#17864) (Milos Djermanovic) @@ -2660,7 +3977,7 @@ v6.0.0-rc.0 - June 9, 2019 * [`87451f4`](https://github.com/eslint/eslint/commit/87451f4779bc4c0ec874042b6854920f947ee258) Fix: no-octal should report NonOctalDecimalIntegerLiteral (fixes #11794) (#11805) (Milos Djermanovic) * [`e4ab053`](https://github.com/eslint/eslint/commit/e4ab0531c4e44c23494c6a802aa2329d15ac90e5) Update: support "bigint" in valid-typeof rule (#11802) (Colin Ihrig) * [`e0fafc8`](https://github.com/eslint/eslint/commit/e0fafc8ef59a80a6137f4170bbe46582d6fbcafc) Chore: removes unnecessary assignment in loop (#11780) (Dimitri Mitropoulos) -* [`20908a3`](https://github.com/eslint/eslint/commit/20908a38f489c285abf8fceef4d9d13bf8b87f22) Docs: removed '>' prefix from from docs/working-with-rules (#11818) (Alok Takshak) +* [`20908a3`](https://github.com/eslint/eslint/commit/20908a38f489c285abf8fceef4d9d13bf8b87f22) Docs: removed '>' prefix from docs/working-with-rules (#11818) (Alok Takshak) * [`1c43eef`](https://github.com/eslint/eslint/commit/1c43eef605a9cf02a157881424ea62dcae747f69) Sponsors: Sync README with website (ESLint Jenkins) * [`21f3131`](https://github.com/eslint/eslint/commit/21f3131aa1636afa8e5c01053e0e870f968425b1) Fix: `overrides` handle relative paths as expected (fixes #11577) (#11799) (Toru Nagashima) * [`5509cdf`](https://github.com/eslint/eslint/commit/5509cdfa1b3d575248eef89a935f79c82e3f3071) Fix: fails the test case if autofix made syntax error (fixes #11615) (#11798) (Toru Nagashima) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c3c3a2a2e025..374b1fdca564 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,22 +4,25 @@ Please be sure to read the contribution guidelines before making or requesting a ## Code of Conduct -This project adheres to the [Open JS Foundation Code of Conduct](https://eslint.org/conduct). We kindly request that you read over our code of conduct before contributing. +This project adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). We kindly request that you read over our code of conduct before contributing. ## Filing Issues Before filing an issue, please be sure to read the guidelines for what you're reporting: -* [Report Bugs](https://eslint.org/docs/latest/contribute/report-bugs) -* [Propose a New Rule](https://eslint.org/docs/latest/contribute/propose-new-rule) -* [Propose a Rule Change](https://eslint.org/docs/latest/contribute/propose-rule-change) -* [Request a Change](https://eslint.org/docs/latest/contribute/request-change) +- [Report Bugs](https://eslint.org/docs/latest/contribute/report-bugs) +- [Propose a New Rule](https://eslint.org/docs/latest/contribute/propose-new-rule) +- [Propose a Rule Change](https://eslint.org/docs/latest/contribute/propose-rule-change) +- [Request a Change](https://eslint.org/docs/latest/contribute/request-change) -To report a security vulnerability in ESLint, please use our [HackerOne program](https://hackerone.com/eslint). +To report a security vulnerability in ESLint, please use our [create an advisory form](https://github.com/eslint/eslint/security/advisories/new) on GitHub. ## Contributing Code -In order to submit code or documentation to an ESLint project, you’ll be asked to sign our CLA when you send your first pull request. (Read more about the Open JS Foundation CLA process at .) Also, please read over the [Pull Request Guidelines](https://eslint.org/docs/latest/contribute/pull-requests). +In order to submit code or documentation to an ESLint project, you’ll be asked to sign our CLA when you send your first pull request. (Read more about the OpenJS Foundation CLA process at .) Also, please read: + +- [Working on issues](https://eslint.org/docs/latest/contribute/work-on-issue) +- [Pull Request Guidelines](https://eslint.org/docs/latest/contribute/pull-requests) ## Full Documentation diff --git a/Makefile.js b/Makefile.js index 5e5352895bc8..2b96713d4b3b 100644 --- a/Makefile.js +++ b/Makefile.js @@ -11,20 +11,18 @@ //------------------------------------------------------------------------------ const checker = require("npm-license"), - ReleaseOps = require("eslint-release"), - fs = require("fs"), - glob = require("glob"), - marked = require("marked"), - matter = require("gray-matter"), - os = require("os"), - path = require("path"), - semver = require("semver"), - ejs = require("ejs"), - loadPerf = require("load-perf"), - yaml = require("js-yaml"), - ignore = require("ignore"), - { CLIEngine } = require("./lib/cli-engine"), - builtinRules = require("./lib/rules/index"); + ReleaseOps = require("eslint-release"), + fs = require("node:fs"), + glob = require("glob"), + marked = require("marked"), + matter = require("gray-matter"), + os = require("node:os"), + path = require("node:path"), + semver = require("semver"), + ejs = require("ejs"), + loadPerf = require("load-perf"), + { CLIEngine } = require("./lib/cli-engine"), + builtinRules = require("./lib/rules/index"); require("shelljs/make"); /* global target -- global.target is declared in `shelljs/make.js` */ @@ -33,7 +31,17 @@ require("shelljs/make"); * @see https://github.com/shelljs/shelljs/blob/124d3349af42cb794ae8f78fc9b0b538109f7ca7/make.js#L4 * @see https://github.com/DefinitelyTyped/DefinitelyTyped/blob/3aa2d09b6408380598cfb802743b07e1edb725f3/types/shelljs/make.d.ts#L8-L11 */ -const { cat, cd, echo, exec, exit, find, ls, mkdir, pwd, test } = require("shelljs"); +const { + cat, + cd, + echo, + exec, + exit, + find, + mkdir, + pwd, + test, +} = require("shelljs"); //------------------------------------------------------------------------------ // Settings @@ -48,81 +56,72 @@ const { cat, cd, echo, exec, exit, find, ls, mkdir, pwd, test } = require("shell const PERF_MULTIPLIER = 13e6; const OPEN_SOURCE_LICENSES = [ - /MIT/u, /BSD/u, /Apache/u, /ISC/u, /WTF/u, /Public Domain/u, /LGPL/u, /Python/u + /MIT/u, + /BSD/u, + /Apache/u, + /ISC/u, + /WTF/u, + /Public Domain/u, + /LGPL/u, + /Python/u, + /BlueOak/u, ]; +const MAIN_GIT_BRANCH = "main"; + //------------------------------------------------------------------------------ // Data //------------------------------------------------------------------------------ const NODE = "node ", // intentional extra space - NODE_MODULES = "./node_modules/", - TEMP_DIR = "./tmp/", - DEBUG_DIR = "./debug/", - BUILD_DIR = "build", - SITE_DIR = "../eslint.org", - DOCS_DIR = "./docs", - DOCS_SRC_DIR = path.join(DOCS_DIR, "src"), - DOCS_DATA_DIR = path.join(DOCS_SRC_DIR, "_data"), - PERF_TMP_DIR = path.join(TEMP_DIR, "eslint", "performance"), - - // Utilities - intentional extra space at the end of each string - MOCHA = `${NODE_MODULES}mocha/bin/_mocha `, - ESLINT = `${NODE} bin/eslint.js `, - - // Files - RULE_FILES = glob.sync("lib/rules/*.js").filter(filePath => path.basename(filePath) !== "index.js"), - JSON_FILES = find("conf/").filter(fileType("json")), - MARKDOWNLINT_IGNORE_INSTANCE = ignore().add(fs.readFileSync(path.join(__dirname, ".markdownlintignore"), "utf-8")), - MARKDOWN_FILES_ARRAY = MARKDOWNLINT_IGNORE_INSTANCE.filter(find("docs/").concat(ls(".")).filter(fileType("md"))), - TEST_FILES = "\"tests/{bin,conf,lib,tools}/**/*.js\"", - PERF_ESLINTRC = path.join(PERF_TMP_DIR, "eslint.config.js"), - PERF_MULTIFILES_TARGET_DIR = path.join(PERF_TMP_DIR, "eslint"), - - /* - * glob arguments with Windows separator `\` don't work: - * https://github.com/eslint/eslint/issues/16259 - */ - PERF_MULTIFILES_TARGETS = `"${TEMP_DIR}eslint/performance/eslint/{lib,tests/lib}/**/*.js"`, - - // Settings - MOCHA_TIMEOUT = parseInt(process.env.ESLINT_MOCHA_TIMEOUT, 10) || 10000; + NODE_MODULES = "./node_modules/", + TEMP_DIR = "./tmp/", + DEBUG_DIR = "./debug/", + BUILD_DIR = "build", + SITE_DIR = "../eslint.org", + DOCS_DIR = "./docs", + DOCS_SRC_DIR = path.join(DOCS_DIR, "src"), + DOCS_DATA_DIR = path.join(DOCS_SRC_DIR, "_data"), + PERF_TMP_DIR = path.join(TEMP_DIR, "eslint", "performance"), + // Utilities - intentional extra space at the end of each string + MOCHA = `${NODE_MODULES}mocha/bin/_mocha `, + ESLINT = `${NODE} bin/eslint.js `, + // Files + RULE_FILES = glob + .sync("lib/rules/*.js") + .filter(filePath => path.basename(filePath) !== "index.js"), + TEST_FILES = '"tests/{bin,conf,lib,tools}/**/*.js"', + PERF_ESLINTRC = path.join(PERF_TMP_DIR, "eslint.config.js"), + PERF_MULTIFILES_TARGET_DIR = path.join(PERF_TMP_DIR, "eslint"), + CHANGELOG_FILE = "./CHANGELOG.md", + VERSIONS_FILE = "./docs/src/_data/versions.json", + /* + * glob arguments with Windows separator `\` don't work: + * https://github.com/eslint/eslint/issues/16259 + */ + PERF_MULTIFILES_TARGETS = `"${TEMP_DIR}eslint/performance/eslint/{lib,tests/lib}/**/*.js"`, + // Settings + MOCHA_TIMEOUT = parseInt(process.env.ESLINT_MOCHA_TIMEOUT, 10) || 10000; //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -/** - * Simple JSON file validation that relies on ES JSON parser. - * @param {string} filePath Path to JSON. - * @throws Error If file contents is invalid JSON. - * @returns {undefined} - */ -function validateJsonFile(filePath) { - const contents = fs.readFileSync(filePath, "utf8"); - - JSON.parse(contents); -} - -/** - * Generates a function that matches files with a particular extension. - * @param {string} extension The file extension (i.e. "js") - * @returns {Function} The function to pass into a filter method. - * @private - */ -function fileType(extension) { - return function(filename) { - return filename.slice(filename.lastIndexOf(".") + 1) === extension; - }; -} - /** * Executes a command and returns the output instead of printing it to stdout. * @param {string} cmd The command string to execute. * @returns {string} The result of the executed command. */ function execSilent(cmd) { - return exec(cmd, { silent: true }).stdout; + return exec(cmd, { silent: true }).stdout; +} + +/** + * Gets name of the currently checked out Git branch. + * @returns {string} Name of the currently checked out Git branch. + */ +function getCurrentGitBranch() { + return execSilent("git branch --show-current").trim(); } /** @@ -133,30 +132,40 @@ function execSilent(cmd) { * @private */ function generateBlogPost(releaseInfo, prereleaseMajorVersion) { - const ruleList = RULE_FILES - - // Strip the .js extension - .map(ruleFileName => path.basename(ruleFileName, ".js")) - - /* - * Sort by length descending. This ensures that rule names which are substrings of other rule names are not - * matched incorrectly. For example, the string "no-undefined" should get matched with the `no-undefined` rule, - * instead of getting matched with the `no-undef` rule followed by the string "ined". - */ - .sort((ruleA, ruleB) => ruleB.length - ruleA.length); - - const renderContext = Object.assign({ prereleaseMajorVersion, ruleList }, releaseInfo); - - const output = ejs.render(cat("./templates/blogpost.md.ejs"), renderContext), - now = new Date(), - month = now.getMonth() + 1, - day = now.getDate(), - filename = path.join(SITE_DIR, `src/content/blog/${now.getFullYear()}-${ - month < 10 ? `0${month}` : month}-${ - day < 10 ? `0${day}` : day}-eslint-v${ - releaseInfo.version}-released.md`); - - output.to(filename); + const ruleList = RULE_FILES + + // Strip the .js extension + .map(ruleFileName => path.basename(ruleFileName, ".js")) + + /* + * Sort by length descending. This ensures that rule names which are substrings of other rule names are not + * matched incorrectly. For example, the string "no-undefined" should get matched with the `no-undefined` rule, + * instead of getting matched with the `no-undef` rule followed by the string "ined". + */ + .sort((ruleA, ruleB) => ruleB.length - ruleA.length); + + const renderContext = Object.assign( + { prereleaseMajorVersion, ruleList }, + releaseInfo, + ); + + const output = ejs.render( + cat("./templates/blogpost.md.ejs"), + renderContext, + ), + now = new Date(), + month = now.getMonth() + 1, + day = now.getDate(), + filename = path.join( + SITE_DIR, + `src/content/blog/${now.getFullYear()}-${ + month < 10 ? `0${month}` : month + }-${day < 10 ? `0${day}` : day}-eslint-v${ + releaseInfo.version + }-released.md`, + ); + + output.to(filename); } /** @@ -165,17 +174,20 @@ function generateBlogPost(releaseInfo, prereleaseMajorVersion) { * @returns {void} */ function generateFormatterExamples(formatterInfo) { - const output = ejs.render(cat("./templates/formatter-examples.md.ejs"), formatterInfo); - const outputDir = path.join(DOCS_SRC_DIR, "use/formatters/"), - filename = path.join(outputDir, "index.md"), - htmlFilename = path.join(outputDir, "html-formatter-example.html"); - - if (!test("-d", outputDir)) { - mkdir(outputDir); - } - - output.to(filename); - formatterInfo.formatterResults.html.result.to(htmlFilename); + const output = ejs.render( + cat("./templates/formatter-examples.md.ejs"), + formatterInfo, + ); + const outputDir = path.join(DOCS_SRC_DIR, "use/formatters/"), + filename = path.join(outputDir, "index.md"), + htmlFilename = path.join(outputDir, "html-formatter-example.html"); + + if (!test("-d", outputDir)) { + mkdir(outputDir); + } + + output.to(filename); + formatterInfo.formatterResults.html.result.to(htmlFilename); } /** @@ -183,62 +195,64 @@ function generateFormatterExamples(formatterInfo) { * @returns {void} */ function generateRuleIndexPage() { - const docsSiteOutputFile = path.join(DOCS_DATA_DIR, "rules.json"), - docsSiteMetaOutputFile = path.join(DOCS_DATA_DIR, "rules_meta.json"), - ruleTypes = "conf/rule-type-list.json", - ruleTypesData = JSON.parse(cat(path.resolve(ruleTypes))); - - const meta = {}; - - RULE_FILES - .map(filename => [filename, path.basename(filename, ".js")]) - .sort((a, b) => a[1].localeCompare(b[1])) - .forEach(pair => { - const filename = pair[0]; - const basename = pair[1]; - const rule = require(path.resolve(filename)); - - /* - * Eleventy interprets the {{ }} in messages as being variables, - * which can cause an error if there's syntax it doesn't expect. - * Because we don't use this info in the website anyway, it's safer - * to just remove it. - * - * Also removing the schema because we don't need it. - */ - meta[basename] = { - ...rule.meta, - schema: void 0, - messages: void 0 - }; - - if (rule.meta.deprecated) { - ruleTypesData.deprecated.push({ - name: basename, - replacedBy: rule.meta.replacedBy || [], - fixable: !!rule.meta.fixable, - hasSuggestions: !!rule.meta.hasSuggestions - }); - } else { - const output = { - name: basename, - description: rule.meta.docs.description, - recommended: rule.meta.docs.recommended || false, - fixable: !!rule.meta.fixable, - hasSuggestions: !!rule.meta.hasSuggestions - }, - ruleType = ruleTypesData.types[rule.meta.type]; - - ruleType.push(output); - } - }); - - ruleTypesData.types = Object.fromEntries( - Object.entries(ruleTypesData.types).filter(([, value]) => value && value.length > 0) - ); - - JSON.stringify(ruleTypesData, null, 4).to(docsSiteOutputFile); - JSON.stringify(meta, null, 4).to(docsSiteMetaOutputFile); + const docsSiteOutputFile = path.join(DOCS_DATA_DIR, "rules.json"), + docsSiteMetaOutputFile = path.join(DOCS_DATA_DIR, "rules_meta.json"), + ruleTypes = "conf/rule-type-list.json", + ruleTypesData = JSON.parse(cat(path.resolve(ruleTypes))); + + const meta = {}; + + RULE_FILES.map(filename => [filename, path.basename(filename, ".js")]) + .sort((a, b) => a[1].localeCompare(b[1])) + .forEach(pair => { + const filename = pair[0]; + const basename = pair[1]; + const rule = require(path.resolve(filename)); + + /* + * Eleventy interprets the {{ }} in messages as being variables, + * which can cause an error if there's syntax it doesn't expect. + * Because we don't use this info in the website anyway, it's safer + * to just remove it. + * + * Also removing the schema because we don't need it. + */ + meta[basename] = { + ...rule.meta, + schema: void 0, + messages: void 0, + }; + + if (rule.meta.deprecated) { + ruleTypesData.deprecated.push({ + name: basename, + replacedBy: rule.meta.deprecated.replacedBy ?? [], + fixable: !!rule.meta.fixable, + hasSuggestions: !!rule.meta.hasSuggestions, + }); + } else { + const output = { + name: basename, + description: rule.meta.docs.description, + recommended: rule.meta.docs.recommended || false, + fixable: !!rule.meta.fixable, + frozen: !!rule.meta.docs.frozen, + hasSuggestions: !!rule.meta.hasSuggestions, + }, + ruleType = ruleTypesData.types[rule.meta.type]; + + ruleType.push(output); + } + }); + + ruleTypesData.types = Object.fromEntries( + Object.entries(ruleTypesData.types).filter( + ([, value]) => value && value.length > 0, + ), + ); + + JSON.stringify(ruleTypesData, null, 4).to(docsSiteOutputFile); + JSON.stringify(meta, null, 4).to(docsSiteMetaOutputFile); } /** @@ -248,14 +262,14 @@ function generateRuleIndexPage() { * @returns {void} */ function commitSiteToGit(tag) { - const currentDir = pwd(); - - cd(SITE_DIR); - exec("git add -A ."); - exec(`git commit -m "Added release blog post for ${tag}"`); - exec(`git tag ${tag}`); - exec("git fetch origin && git rebase origin/main"); - cd(currentDir); + const currentDir = pwd(); + + cd(SITE_DIR); + exec("git add -A ."); + exec(`git commit -m "Added release blog post for ${tag}"`); + exec(`git tag ${tag}`); + exec("git fetch origin && git rebase origin/main"); + cd(currentDir); } /** @@ -264,75 +278,149 @@ function commitSiteToGit(tag) { * @returns {void} */ function publishSite() { - const currentDir = pwd(); + const currentDir = pwd(); - cd(SITE_DIR); - exec("git push origin HEAD --tags"); - cd(currentDir); + cd(SITE_DIR); + exec("git push origin HEAD --tags"); + cd(currentDir); } /** - * Updates the changelog, bumps the version number in package.json, creates a local git commit and tag, - * and generates the site in an adjacent `website` folder. - * @returns {void} + * Determines whether the given version is a prerelease. + * @param {string} version The version to check. + * @returns {boolean} `true` if it is a prerelease, `false` otherwise. */ -function generateRelease() { - ReleaseOps.generateRelease(); - const releaseInfo = JSON.parse(cat(".eslint-release-info.json")); - - echo("Generating site"); - target.gensite(); - generateBlogPost(releaseInfo); - commitSiteToGit(`v${releaseInfo.version}`); - - echo("Updating version in docs package"); - const docsPackagePath = path.join(__dirname, "docs", "package.json"); - const docsPackage = require(docsPackagePath); +function isPreRelease(version) { + return /[a-z]/u.test(version); +} - docsPackage.version = releaseInfo.version; - fs.writeFileSync(docsPackagePath, `${JSON.stringify(docsPackage, null, 4)}\n`); +/** + * Updates docs/src/_data/versions.json + * @param {string} oldVersion Current version. + * @param {string} newVersion New version to be released. + * @returns {void} + */ +function updateVersions(oldVersion, newVersion) { + echo("Updating ESLint versions list in docs package"); + + const filePath = path.join( + __dirname, + "docs", + "src", + "_data", + "versions.json", + ); + const data = require(filePath); + const { items } = data; + + const isOldVersionPrerelease = isPreRelease(oldVersion); + const isNewVersionPrerelease = isPreRelease(newVersion); + + if (isOldVersionPrerelease) { + if (isNewVersionPrerelease) { + // prerelease -> prerelease. Just update the version. + items.find(item => item.branch === "next").version = newVersion; + } else { + // prerelease -> release. First, update the item for the previous latest version + const latestVersionItem = items.find( + item => item.branch === "latest", + ); + const latestVersion = latestVersionItem.version; + const versionBranch = `v${latestVersion.slice(0, latestVersion.indexOf("."))}.x`; // "v8.x", "v9.x", "v10.x" ... + + latestVersionItem.branch = versionBranch; + latestVersionItem.path = `/docs/${versionBranch}/`; + + // Then, replace the item for the prerelease with a new item for the new latest version + items.splice( + items.findIndex(item => item.branch === "next"), + 1, + { + version: newVersion, + branch: "latest", + path: "/docs/latest/", + }, + ); + } + } else { + if (isNewVersionPrerelease) { + // release -> prerelease. Insert an item for the prerelease. + items.splice(1, 0, { + version: newVersion, + branch: "next", + path: "/docs/next/", + }); + } else { + // release -> release. Just update the version. + items.find(item => item.branch === "latest").version = newVersion; + } + } + + fs.writeFileSync(filePath, `${JSON.stringify(data, null, 4)}\n`); +} - echo("Updating commit with docs data"); - exec("git add docs/ && git commit --amend --no-edit"); - exec(`git tag -a -f v${releaseInfo.version} -m ${releaseInfo.version}`); +/** + * Updates TSDoc header comments of all rule types. + * @returns {void} + */ +function updateRuleTypeHeaders() { + const { execFileSync } = require("node:child_process"); + + // We don't need the stack trace of execFileSync if the command fails. + try { + execFileSync(process.execPath, ["tools/update-rule-type-headers.js"], { + stdio: "inherit", + }); + } catch { + exit(1); + } } /** * Updates the changelog, bumps the version number in package.json, creates a local git commit and tag, * and generates the site in an adjacent `website` folder. - * @param {string} prereleaseId The prerelease identifier (alpha, beta, etc.) + * @param {Object} options Release options. + * @param {string} [options.prereleaseId] The prerelease identifier (alpha, beta, etc.). If `undefined`, this is + * a regular release. + * @param {string} options.packageTag Tag that should be added to the package submitted to the npm registry. * @returns {void} */ -function generatePrerelease(prereleaseId) { - ReleaseOps.generateRelease(prereleaseId); - const releaseInfo = JSON.parse(cat(".eslint-release-info.json")); - const nextMajor = semver.inc(releaseInfo.version, "major"); - - echo("Generating site"); - - // always write docs into the next major directory (so 2.0.0-alpha.0 writes to 2.0.0) - target.gensite(nextMajor); - - /* - * Premajor release should have identical "next major version". - * Preminor and prepatch release will not. - * 5.0.0-alpha.0 --> next major = 5, current major = 5 - * 4.4.0-alpha.0 --> next major = 5, current major = 4 - * 4.0.1-alpha.0 --> next major = 5, current major = 4 - */ - if (semver.major(releaseInfo.version) === semver.major(nextMajor)) { - - /* - * This prerelease is for a major release (not preminor/prepatch). - * Blog post generation logic needs to be aware of this (as well as - * know what the next major version is actually supposed to be). - */ - generateBlogPost(releaseInfo, nextMajor); - } else { - generateBlogPost(releaseInfo); - } - - commitSiteToGit(`v${releaseInfo.version}`); +function generateRelease({ prereleaseId, packageTag }) { + echo(`Current Git branch: ${getCurrentGitBranch()}`); + + const oldVersion = require("./package.json").version; + + ReleaseOps.generateRelease(prereleaseId, packageTag); + const releaseInfo = JSON.parse(cat(".eslint-release-info.json")); + + echo("Generating site"); + target.gensite(); + generateBlogPost( + releaseInfo, + prereleaseId ? semver.inc(releaseInfo.version, "major") : void 0, + ); + commitSiteToGit(`v${releaseInfo.version}`); + + echo("Updating version in docs package"); + const docsPackagePath = path.join(__dirname, "docs", "package.json"); + const docsPackage = require(docsPackagePath); + + docsPackage.version = releaseInfo.version; + fs.writeFileSync( + docsPackagePath, + `${JSON.stringify(docsPackage, null, 4)}\n`, + ); + + if (getCurrentGitBranch() === MAIN_GIT_BRANCH) { + updateVersions(oldVersion, releaseInfo.version); + } + + echo("Updating rule type header comments"); + updateRuleTypeHeaders(); + + echo("Updating commit with docs data and rule types"); + exec("git add lib/types/rules.d.ts docs/ && git commit --amend --no-edit"); + exec(`git tag -a -f v${releaseInfo.version} -m ${releaseInfo.version}`); } /** @@ -341,21 +429,45 @@ function generatePrerelease(prereleaseId) { * @returns {void} */ function publishRelease() { - ReleaseOps.publishRelease(); - const releaseInfo = JSON.parse(cat(".eslint-release-info.json")); - const isPreRelease = /[a-z]/u.test(releaseInfo.version); - - /* - * for a pre-release, push to the "next" branch to trigger docs deploy - * for a release, push to the "latest" branch to trigger docs deploy - */ - if (isPreRelease) { - exec("git push origin HEAD:next -f"); - } else { - exec("git push origin HEAD:latest -f"); - } + ReleaseOps.publishRelease(); + const releaseInfo = JSON.parse(cat(".eslint-release-info.json")); + + const docsSiteBranch = + releaseInfo.packageTag === "maintenance" + ? `v${semver.major(releaseInfo.version)}.x` + : releaseInfo.packageTag; // "latest" or "next" + + echo(`Updating docs site branch: ${docsSiteBranch}`); + exec(`git push origin HEAD:${docsSiteBranch} -f`); - publishSite(); + publishSite(); + + // Update changelog and list of versions on the main branch + if (getCurrentGitBranch() !== MAIN_GIT_BRANCH) { + echo(`Updating changelog and versions on branch: ${MAIN_GIT_BRANCH}`); + + exec(`git checkout ${MAIN_GIT_BRANCH} --force`); + + fs.writeFileSync( + CHANGELOG_FILE, + `${releaseInfo.markdownChangelog}${cat(CHANGELOG_FILE)}`, + ); + + const versions = JSON.parse(cat(VERSIONS_FILE)); + + versions.items.find(({ branch }) => branch === docsSiteBranch).version = + releaseInfo.version; + fs.writeFileSync( + VERSIONS_FILE, + `${JSON.stringify(versions, null, 4)}\n`, + ); + + exec(`git add ${CHANGELOG_FILE} ${VERSIONS_FILE}`); + exec( + `git commit -m "chore: updates for v${releaseInfo.version} release"`, + ); + exec("git push origin HEAD"); + } } /** @@ -364,7 +476,7 @@ function publishRelease() { * @returns {Array} The separated lines. */ function splitCommandResultToLines(result) { - return result.trim().split("\n"); + return result.trim().split("\n"); } /** @@ -373,10 +485,10 @@ function splitCommandResultToLines(result) { * @returns {string} The commit sha. */ function getFirstCommitOfFile(filePath) { - let commits = execSilent(`git rev-list HEAD -- ${filePath}`); + let commits = execSilent(`git rev-list HEAD -- ${filePath}`); - commits = splitCommandResultToLines(commits); - return commits[commits.length - 1].trim(); + commits = splitCommandResultToLines(commits); + return commits.at(-1).trim(); } /** @@ -385,18 +497,20 @@ function getFirstCommitOfFile(filePath) { * @returns {string} The tag name. */ function getFirstVersionOfFile(filePath) { - const firstCommit = getFirstCommitOfFile(filePath); - let tags = execSilent(`git tag --contains ${firstCommit}`); - - tags = splitCommandResultToLines(tags); - return tags.reduce((list, version) => { - const validatedVersion = semver.valid(version.trim()); - - if (validatedVersion) { - list.push(validatedVersion); - } - return list; - }, []).sort(semver.compare)[0]; + const firstCommit = getFirstCommitOfFile(filePath); + let tags = execSilent(`git tag --contains ${firstCommit}`); + + tags = splitCommandResultToLines(tags); + return tags + .reduce((list, version) => { + const validatedVersion = semver.valid(version.trim()); + + if (validatedVersion) { + list.push(validatedVersion); + } + return list; + }, []) + .sort(semver.compare)[0]; } /** @@ -405,9 +519,9 @@ function getFirstVersionOfFile(filePath) { * @returns {string} The commit sha. */ function getCommitDeletingFile(filePath) { - const commits = execSilent(`git rev-list HEAD -- ${filePath}`); + const commits = execSilent(`git rev-list HEAD -- ${filePath}`); - return splitCommandResultToLines(commits)[0]; + return splitCommandResultToLines(commits)[0]; } /** @@ -416,36 +530,13 @@ function getCommitDeletingFile(filePath) { * @returns {string} The version number. */ function getFirstVersionOfDeletion(filePath) { - const deletionCommit = getCommitDeletingFile(filePath), - tags = execSilent(`git tag --contains ${deletionCommit}`); - - return splitCommandResultToLines(tags) - .map(version => semver.valid(version.trim())) - .filter(version => version) - .sort(semver.compare)[0]; -} + const deletionCommit = getCommitDeletingFile(filePath), + tags = execSilent(`git tag --contains ${deletionCommit}`); -/** - * Lints Markdown files. - * @param {Array} files Array of file names to lint. - * @returns {Object} exec-style exit code object. - * @private - */ -function lintMarkdown(files) { - const markdownlint = require("markdownlint"); - const config = yaml.load(fs.readFileSync(path.join(__dirname, "./.markdownlint.yml"), "utf8")), - result = markdownlint.sync({ - files, - config, - resultVersion: 1 - }), - resultString = result.toString(), - returnCode = resultString ? 1 : 0; - - if (resultString) { - console.error(resultString); - } - return { code: returnCode }; + return splitCommandResultToLines(tags) + .map(version => semver.valid(version.trim())) + .filter(version => version) + .sort(semver.compare)[0]; } /** @@ -453,56 +544,63 @@ function lintMarkdown(files) { * @returns {Object} Output from each formatter */ function getFormatterResults() { - const stripAnsi = require("strip-ansi"); - const formattersMetadata = require("./lib/cli-engine/formatters/formatters-meta.json"); - - const formatterFiles = fs.readdirSync("./lib/cli-engine/formatters/").filter(fileName => !fileName.includes("formatters-meta.json")), - rules = { - "no-else-return": "warn", - indent: ["warn", 4], - "space-unary-ops": "error", - semi: ["warn", "always"], - "consistent-return": "error" - }, - cli = new CLIEngine({ - useEslintrc: false, - baseConfig: { extends: "eslint:recommended" }, - rules - }), - codeString = [ - "function addOne(i) {", - " if (i != NaN) {", - " return i ++", - " } else {", - " return", - " }", - "};" - ].join("\n"), - rawMessages = cli.executeOnText(codeString, "fullOfProblems.js", true), - rulesMap = cli.getRules(), - rulesMeta = {}; - - Object.keys(rules).forEach(ruleId => { - rulesMeta[ruleId] = rulesMap.get(ruleId).meta; - }); - - return formatterFiles.reduce((data, filename) => { - const fileExt = path.extname(filename), - name = path.basename(filename, fileExt); - - if (fileExt === ".js") { - const formattedOutput = cli.getFormatter(name)( - rawMessages.results, - { rulesMeta } - ); - - data.formatterResults[name] = { - result: stripAnsi(formattedOutput), - description: formattersMetadata.find(formatter => formatter.name === name).description - }; - } - return data; - }, { formatterResults: {} }); + const util = require("node:util"); + const formattersMetadata = require("./lib/cli-engine/formatters/formatters-meta.json"); + + const formatterFiles = fs + .readdirSync("./lib/cli-engine/formatters/") + .filter(fileName => !fileName.includes("formatters-meta.json")), + rules = { + "no-else-return": "warn", + indent: ["warn", 4], + "space-unary-ops": "error", + semi: ["warn", "always"], + "consistent-return": "error", + }, + cli = new CLIEngine({ + useEslintrc: false, + baseConfig: { extends: "eslint:recommended" }, + rules, + }), + codeString = [ + "function addOne(i) {", + " if (i != NaN) {", + " return i ++", + " } else {", + " return", + " }", + "};", + ].join("\n"), + rawMessages = cli.executeOnText(codeString, "fullOfProblems.js", true), + rulesMap = cli.getRules(), + rulesMeta = {}; + + Object.keys(rules).forEach(ruleId => { + rulesMeta[ruleId] = rulesMap.get(ruleId).meta; + }); + + return formatterFiles.reduce( + (data, filename) => { + const fileExt = path.extname(filename), + name = path.basename(filename, fileExt); + + if (fileExt === ".js") { + const formattedOutput = cli.getFormatter(name)( + rawMessages.results, + { rulesMeta }, + ); + + data.formatterResults[name] = { + result: util.stripVTControlCharacters(formattedOutput), + description: formattersMetadata.find( + formatter => formatter.name === name, + ).description, + }; + } + return data; + }, + { formatterResults: {} }, + ); } /** @@ -511,415 +609,404 @@ function getFormatterResults() { * @returns {string} The executable path */ function getBinFile(command) { - return path.join("node_modules", ".bin", command); + return path.join("node_modules", ".bin", command); } //------------------------------------------------------------------------------ // Tasks //------------------------------------------------------------------------------ -target.lint = function([fix = false] = []) { - let errors = 0, - lastReturn; - - /* - * In order to successfully lint JavaScript files in the `docs` directory, dependencies declared in `docs/package.json` - * would have to be installed in `docs/node_modules`. In particular, eslint-plugin-node rules examine `docs/node_modules` - * when analyzing `require()` calls from CJS modules in the `docs` directory. Since our release process does not run `npm install` - * in the `docs` directory, linting would fail and break the release. Also, working on the main `eslint` package does not require - * installing dependencies declared in `docs/package.json`, so most contributors will not have `docs/node_modules` locally. - * Therefore, we add `--ignore-pattern "docs/**"` to exclude linting the `docs` directory from this command. - * There is a separate command `target.lintDocsJS` for linting JavaScript files in the `docs` directory. - */ - echo("Validating JavaScript files"); - lastReturn = exec(`${ESLINT}${fix ? "--fix" : ""} . --ignore-pattern "docs/**"`); - if (lastReturn.code !== 0) { - errors++; - } - - echo("Validating JSON Files"); - JSON_FILES.forEach(validateJsonFile); - - echo("Validating Markdown Files"); - lastReturn = lintMarkdown(MARKDOWN_FILES_ARRAY); - if (lastReturn.code !== 0) { - errors++; - } - - if (errors) { - exit(1); - } -}; - -target.lintDocsJS = function([fix = false] = []) { - let errors = 0; - - echo("Validating JavaScript files in the docs directory"); - const lastReturn = exec(`${ESLINT}${fix ? "--fix" : ""} docs`); - - if (lastReturn.code !== 0) { - errors++; - } - - if (errors) { - exit(1); - } -}; - -target.fuzz = function({ amount = 1000, fuzzBrokenAutofixes = false } = {}) { - const fuzzerRunner = require("./tools/fuzzer-runner"); - const fuzzResults = fuzzerRunner.run({ amount, fuzzBrokenAutofixes }); - - if (fuzzResults.length) { - - const uniqueStackTraceCount = new Set(fuzzResults.map(result => result.error)).size; - - echo(`The fuzzer reported ${fuzzResults.length} error${fuzzResults.length === 1 ? "" : "s"} with a total of ${uniqueStackTraceCount} unique stack trace${uniqueStackTraceCount === 1 ? "" : "s"}.`); - - const formattedResults = JSON.stringify({ results: fuzzResults }, null, 4); - - if (process.env.CI) { - echo("More details can be found below."); - echo(formattedResults); - } else { - if (!test("-d", DEBUG_DIR)) { - mkdir(DEBUG_DIR); - } - - let fuzzLogPath; - let fileSuffix = 0; - - // To avoid overwriting any existing fuzzer log files, append a numeric suffix to the end of the filename. - do { - fuzzLogPath = path.join(DEBUG_DIR, `fuzzer-log-${fileSuffix}.json`); - fileSuffix++; - } while (test("-f", fuzzLogPath)); - - formattedResults.to(fuzzLogPath); - - // TODO: (not-an-aardvark) Create a better way to isolate and test individual fuzzer errors from the log file - echo(`More details can be found in ${fuzzLogPath}.`); - } - - exit(1); - } +target.fuzz = function ({ amount = 1000, fuzzBrokenAutofixes = false } = {}) { + const { run } = require("./tools/fuzzer-runner"); + const fuzzResults = run({ amount, fuzzBrokenAutofixes }); + + if (fuzzResults.length) { + const uniqueStackTraceCount = new Set( + fuzzResults.map(result => result.error), + ).size; + + echo( + `The fuzzer reported ${fuzzResults.length} error${fuzzResults.length === 1 ? "" : "s"} with a total of ${uniqueStackTraceCount} unique stack trace${uniqueStackTraceCount === 1 ? "" : "s"}.`, + ); + + const formattedResults = JSON.stringify( + { results: fuzzResults }, + null, + 4, + ); + + if (process.env.CI) { + echo("More details can be found below."); + echo(formattedResults); + } else { + if (!test("-d", DEBUG_DIR)) { + mkdir(DEBUG_DIR); + } + + let fuzzLogPath; + let fileSuffix = 0; + + // To avoid overwriting any existing fuzzer log files, append a numeric suffix to the end of the filename. + do { + fuzzLogPath = path.join( + DEBUG_DIR, + `fuzzer-log-${fileSuffix}.json`, + ); + fileSuffix++; + } while (test("-f", fuzzLogPath)); + + formattedResults.to(fuzzLogPath); + + // TODO: (not-an-aardvark) Create a better way to isolate and test individual fuzzer errors from the log file + echo(`More details can be found in ${fuzzLogPath}.`); + } + + exit(1); + } }; target.mocha = () => { - let errors = 0, - lastReturn; - - echo("Running unit tests"); - - lastReturn = exec(`${getBinFile("c8")} -- ${MOCHA} --forbid-only -R progress -t ${MOCHA_TIMEOUT} -c ${TEST_FILES}`); - if (lastReturn.code !== 0) { - errors++; - } - - lastReturn = exec(`${getBinFile("c8")} check-coverage --statement 98 --branch 97 --function 98 --lines 98`); - if (lastReturn.code !== 0) { - errors++; - } - - if (errors) { - exit(1); - } + let errors = 0, + lastReturn; + + echo("Running unit tests"); + + lastReturn = exec( + `${getBinFile("c8")} -- ${MOCHA} --forbid-only -R progress -t ${MOCHA_TIMEOUT} -c ${TEST_FILES}`, + ); + if (lastReturn.code !== 0) { + errors++; + } + + lastReturn = exec( + `${getBinFile("c8")} check-coverage --statements 99 --branches 98 --functions 99 --lines 99`, + ); + if (lastReturn.code !== 0) { + errors++; + } + + if (errors) { + exit(1); + } }; -target.wdio = () => { - echo("Running unit tests on browsers"); - target.webpack("production"); - const lastReturn = exec(`${getBinFile("wdio")} run wdio.conf.js`); +target.cypress = () => { + echo("Running unit tests on browsers"); + target.webpack("production"); + const lastReturn = exec(`${getBinFile("cypress")} run --no-runner-ui`); - if (lastReturn.code !== 0) { - exit(1); - } + if (lastReturn.code !== 0) { + exit(1); + } }; -target.test = function() { - target.checkRuleFiles(); - target.mocha(); - - // target.wdio(); // Temporarily disabled due to problems on Jenkins - - target.fuzz({ amount: 150, fuzzBrokenAutofixes: false }); - target.checkLicenses(); +target.test = function () { + target.checkRuleFiles(); + target.mocha(); + target.fuzz({ amount: 150, fuzzBrokenAutofixes: false }); + target.checkLicenses(); }; -target.gensite = function() { - echo("Generating documentation"); - - const DOCS_RULES_DIR = path.join(DOCS_SRC_DIR, "rules"); - const RULE_VERSIONS_FILE = path.join(DOCS_SRC_DIR, "_data/rule_versions.json"); - - // Set up rule version information - let versions = test("-f", RULE_VERSIONS_FILE) ? JSON.parse(cat(RULE_VERSIONS_FILE)) : {}; - - if (!versions.added) { - versions = { - added: versions, - removed: {} - }; - } - - // 1. Update rule meta data by checking rule docs - important to catch removed rules - echo("> Updating rule version meta data (Step 1)"); - const ruleDocsFiles = find(DOCS_RULES_DIR); - - ruleDocsFiles.forEach((filename, i) => { - if (test("-f", filename) && path.extname(filename) === ".md") { - - echo(`> Updating rule version meta data (Step 1: ${i + 1}/${ruleDocsFiles.length}): ${filename}`); - - const baseName = path.basename(filename, ".md"), - sourceBaseName = `${baseName}.js`, - sourcePath = path.join("lib/rules", sourceBaseName); - - if (!versions.added[baseName]) { - versions.added[baseName] = getFirstVersionOfFile(sourcePath); - } - - if (!versions.removed[baseName] && !test("-f", sourcePath)) { - versions.removed[baseName] = getFirstVersionOfDeletion(sourcePath); - } - - } - }); - - JSON.stringify(versions, null, 4).to(RULE_VERSIONS_FILE); - - // 2. Generate rules index page meta data - echo("> Generating the rules index page (Step 2)"); - generateRuleIndexPage(); - - // 3. Create Example Formatter Output Page - echo("> Creating the formatter examples (Step 3)"); - generateFormatterExamples(getFormatterResults()); - - echo("Done generating documentation"); +target.gensite = function () { + echo("Generating documentation"); + + const DOCS_RULES_DIR = path.join(DOCS_SRC_DIR, "rules"); + const RULE_VERSIONS_FILE = path.join( + DOCS_SRC_DIR, + "_data/rule_versions.json", + ); + + // Set up rule version information + let versions = test("-f", RULE_VERSIONS_FILE) + ? JSON.parse(cat(RULE_VERSIONS_FILE)) + : {}; + + if (!versions.added) { + versions = { + added: versions, + removed: {}, + }; + } + + // 1. Update rule meta data by checking rule docs - important to catch removed rules + echo("> Updating rule version meta data (Step 1)"); + const ruleDocsFiles = find(DOCS_RULES_DIR); + + ruleDocsFiles.forEach((filename, i) => { + if (test("-f", filename) && path.extname(filename) === ".md") { + echo( + `> Updating rule version meta data (Step 1: ${i + 1}/${ruleDocsFiles.length}): ${filename}`, + ); + + const baseName = path.basename(filename, ".md"), + sourceBaseName = `${baseName}.js`, + sourcePath = path.join("lib/rules", sourceBaseName); + + if (!versions.added[baseName]) { + versions.added[baseName] = getFirstVersionOfFile(sourcePath); + } + + if (!versions.removed[baseName] && !test("-f", sourcePath)) { + versions.removed[baseName] = + getFirstVersionOfDeletion(sourcePath); + } + } + }); + + JSON.stringify(versions, null, 4).to(RULE_VERSIONS_FILE); + + // 2. Generate rules index page meta data + echo("> Generating the rules index page (Step 2)"); + generateRuleIndexPage(); + + // 3. Create Example Formatter Output Page + echo("> Creating the formatter examples (Step 3)"); + generateFormatterExamples(getFormatterResults()); + + echo("Done generating documentation"); }; target.generateRuleIndexPage = generateRuleIndexPage; -target.webpack = function(mode = "none") { - exec(`${getBinFile("webpack")} --mode=${mode} --output-path=${BUILD_DIR}`); +target.webpack = function (mode = "none") { + exec(`${getBinFile("webpack")} --mode=${mode} --output-path=${BUILD_DIR}`); }; -target.checkRuleFiles = function() { - - echo("Validating rules"); - - const ruleTypes = require("./tools/rule-types.json"); - let errors = 0; - - RULE_FILES.forEach(filename => { - const basename = path.basename(filename, ".js"); - const docFilename = `docs/src/rules/${basename}.md`; - const docText = cat(docFilename); - const docTextWithoutFrontmatter = matter(String(docText)).content; - const docMarkdown = marked.lexer(docTextWithoutFrontmatter, { gfm: true, silent: false }); - const ruleCode = cat(filename); - const knownHeaders = ["Rule Details", "Options", "Environments", "Examples", "Known Limitations", "When Not To Use It", "Compatibility"]; - - /** - * Check if basename is present in rule-types.json file. - * @returns {boolean} true if present - * @private - */ - function isInRuleTypes() { - return Object.prototype.hasOwnProperty.call(ruleTypes, basename); - } - - /** - * Check if id is present in title - * @param {string} id id to check for - * @returns {boolean} true if present - * @private - * @todo Will remove this check when the main heading is automatically generated from rule metadata. - */ - function hasIdInTitle(id) { - return new RegExp(`title: ${id}`, "u").test(docText); - } - - /** - * Check if all H2 headers are known and in the expected order - * Only H2 headers are checked as H1 and H3 are variable and/or rule specific. - * @returns {boolean} true if all headers are known and in the right order - */ - function hasKnownHeaders() { - const headers = docMarkdown.filter(token => token.type === "heading" && token.depth === 2).map(header => header.text); - - for (const header of headers) { - if (!knownHeaders.includes(header)) { - return false; - } - } - - /* - * Check only the subset of used headers for the correct order - */ - const presentHeaders = knownHeaders.filter(header => headers.includes(header)); - - for (let i = 0; i < presentHeaders.length; ++i) { - if (presentHeaders[i] !== headers[i]) { - return false; - } - } - - return true; - } - - /** - * Check if deprecated information is in rule code and README.md. - * @returns {boolean} true if present - * @private - */ - function hasDeprecatedInfo() { - const deprecatedTagRegExp = /@deprecated in ESLint/u; - const deprecatedInfoRegExp = /This rule was .+deprecated.+in ESLint/u; - - return deprecatedTagRegExp.test(ruleCode) && deprecatedInfoRegExp.test(docText); - } - - /** - * Check if the rule code has the jsdoc comment with the rule type annotation. - * @returns {boolean} true if present - * @private - */ - function hasRuleTypeJSDocComment() { - const comment = "/** @type {import('../shared/types').Rule} */"; - - return ruleCode.includes(comment); - } - - // check for docs - if (!test("-f", docFilename)) { - console.error("Missing documentation for rule %s", basename); - errors++; - } else { - - // check for proper doc h1 format - if (!hasIdInTitle(basename)) { - console.error("Missing id in the doc page's title of rule %s", basename); - errors++; - } - - // check for proper doc headers - if (!hasKnownHeaders()) { - console.error("Unknown or misplaced header in the doc page of rule %s, allowed headers (and their order) are: '%s'", basename, knownHeaders.join("', '")); - errors++; - } - } - - // check for recommended configuration - if (!isInRuleTypes()) { - console.error("Missing setting for %s in tools/rule-types.json", basename); - errors++; - } - - // check parity between rules index file and rules directory - const ruleIdsInIndex = require("./lib/rules/index"); - const ruleDef = ruleIdsInIndex.get(basename); - - if (!ruleDef) { - console.error(`Missing rule from index (./lib/rules/index.js): ${basename}. If you just added a new rule then add an entry for it in this file.`); - errors++; - } else { - - // check deprecated - if (ruleDef.meta.deprecated && !hasDeprecatedInfo()) { - console.error(`Missing deprecated information in ${basename} rule code or README.md. Please write @deprecated tag in code and「This rule was deprecated in ESLint ...」 in README.md.`); - errors++; - } - - // check eslint:recommended - const recommended = require("./packages/js").configs.recommended; - - if (ruleDef.meta.docs.recommended) { - if (recommended.rules[basename] !== "error") { - console.error(`Missing rule from eslint:recommended (./packages/js/src/configs/eslint-recommended.js): ${basename}. If you just made a rule recommended then add an entry for it in this file.`); - errors++; - } - } else { - if (basename in recommended.rules) { - console.error(`Extra rule in eslint:recommended (./packages/js/src/configs/eslint-recommended.js): ${basename}. If you just added a rule then don't add an entry for it in this file.`); - errors++; - } - } - - if (!hasRuleTypeJSDocComment()) { - console.error(`Missing rule type JSDoc comment from ${basename} rule code.`); - errors++; - } - } - - // check for tests - if (!test("-f", `tests/lib/rules/${basename}.js`)) { - console.error("Missing tests for rule %s", basename); - errors++; - } - - }); - - if (errors) { - exit(1); - } - +target.checkRuleFiles = function () { + echo("Validating rules"); + + let errors = 0; + + RULE_FILES.forEach(filename => { + const basename = path.basename(filename, ".js"); + const docFilename = `docs/src/rules/${basename}.md`; + const docText = cat(docFilename); + const docTextWithoutFrontmatter = matter(String(docText)).content; + const docMarkdown = marked.lexer(docTextWithoutFrontmatter, { + gfm: true, + silent: false, + }); + const ruleCode = cat(filename); + const knownHeaders = [ + "Rule Details", + "Options", + "Environments", + "Examples", + "Known Limitations", + "When Not To Use It", + "Compatibility", + ]; + + /** + * Check if id is present in title + * @param {string} id id to check for + * @returns {boolean} true if present + * @private + * @todo Will remove this check when the main heading is automatically generated from rule metadata. + */ + function hasIdInTitle(id) { + return new RegExp(`title: ${id}`, "u").test(docText); + } + + /** + * Check if all H2 headers are known and in the expected order + * Only H2 headers are checked as H1 and H3 are variable and/or rule specific. + * @returns {boolean} true if all headers are known and in the right order + */ + function hasKnownHeaders() { + const headers = docMarkdown + .filter(token => token.type === "heading" && token.depth === 2) + .map(header => header.text); + + for (const header of headers) { + if (!knownHeaders.includes(header)) { + return false; + } + } + + /* + * Check only the subset of used headers for the correct order + */ + const presentHeaders = knownHeaders.filter(header => + headers.includes(header), + ); + + for (let i = 0; i < presentHeaders.length; ++i) { + if (presentHeaders[i] !== headers[i]) { + return false; + } + } + + return true; + } + + /** + * Check if deprecated information is in rule code. + * @returns {boolean} true if present + * @private + */ + function hasDeprecatedInfo() { + const deprecatedTagRegExp = /@deprecated in ESLint/u; + + return deprecatedTagRegExp.test(ruleCode); + } + + /** + * Check if the rule code has the jsdoc comment with the rule type annotation. + * @returns {boolean} true if present + * @private + */ + function hasRuleTypeJSDocComment() { + const comment = "/** @type {import('../types').Rule.RuleModule} */"; + + return ruleCode.includes(comment); + } + + // check for docs + if (!test("-f", docFilename)) { + console.error("Missing documentation for rule %s", basename); + errors++; + } else { + // check for proper doc h1 format + if (!hasIdInTitle(basename)) { + console.error( + "Missing id in the doc page's title of rule %s", + basename, + ); + errors++; + } + + // check for proper doc headers + if (!hasKnownHeaders()) { + console.error( + "Unknown or misplaced header in the doc page of rule %s, allowed headers (and their order) are: '%s'", + basename, + knownHeaders.join("', '"), + ); + errors++; + } + } + + // check parity between rules index file and rules directory + const ruleIdsInIndex = require("./lib/rules/index"); + const ruleDef = ruleIdsInIndex.get(basename); + + if (!ruleDef) { + console.error( + `Missing rule from index (./lib/rules/index.js): ${basename}. If you just added a new rule then add an entry for it in this file.`, + ); + errors++; + } else { + // check deprecated + if (ruleDef.meta.deprecated && !hasDeprecatedInfo()) { + console.error( + `Missing deprecated information in ${basename} rule code. Please write @deprecated tag in code.`, + ); + errors++; + } + + // check eslint:recommended + const recommended = require("./packages/js").configs.recommended; + + if (ruleDef.meta.docs.recommended) { + if (recommended.rules[basename] !== "error") { + console.error( + `Missing rule from eslint:recommended (./packages/js/src/configs/eslint-recommended.js): ${basename}. If you just made a rule recommended then add an entry for it in this file.`, + ); + errors++; + } + } else { + if (basename in recommended.rules) { + console.error( + `Extra rule in eslint:recommended (./packages/js/src/configs/eslint-recommended.js): ${basename}. If you just added a rule then don't add an entry for it in this file.`, + ); + errors++; + } + } + + if (!hasRuleTypeJSDocComment()) { + console.error( + `Missing rule type JSDoc comment from ${basename} rule code.`, + ); + errors++; + } + } + + // check for tests + if (!test("-f", `tests/lib/rules/${basename}.js`)) { + console.error("Missing tests for rule %s", basename); + errors++; + } + }); + + if (errors) { + exit(1); + } }; -target.checkRuleExamples = function() { - const { execFileSync } = require("child_process"); - - // We don't need the stack trace of execFileSync if the command fails. - try { - execFileSync(process.execPath, ["tools/check-rule-examples.js", "docs/src/rules/*.md"], { stdio: "inherit" }); - } catch { - exit(1); - } +target.checkRuleExamples = function () { + const { execFileSync } = require("node:child_process"); + + // We don't need the stack trace of execFileSync if the command fails. + try { + execFileSync( + process.execPath, + ["tools/check-rule-examples.js", "docs/src/rules/*.md"], + { stdio: "inherit" }, + ); + } catch { + exit(1); + } }; -target.checkLicenses = function() { - - /** - * Check if a dependency is eligible to be used by us - * @param {Object} dependency dependency to check - * @returns {boolean} true if we have permission - * @private - */ - function isPermissible(dependency) { - const licenses = dependency.licenses; - - if (Array.isArray(licenses)) { - return licenses.some(license => isPermissible({ - name: dependency.name, - licenses: license - })); - } - - return OPEN_SOURCE_LICENSES.some(license => license.test(licenses)); - } - - echo("Validating licenses"); - - checker.init({ - start: __dirname - }, deps => { - const impermissible = Object.keys(deps).map(dependency => ({ - name: dependency, - licenses: deps[dependency].licenses - })).filter(dependency => !isPermissible(dependency)); - - if (impermissible.length) { - impermissible.forEach(dependency => { - console.error( - "%s license for %s is impermissible.", - dependency.licenses, - dependency.name - ); - }); - exit(1); - } - }); +target.checkLicenses = function () { + /** + * Check if a dependency is eligible to be used by us + * @param {Object} dependency dependency to check + * @returns {boolean} true if we have permission + * @private + */ + function isPermissible(dependency) { + const licenses = dependency.licenses; + + if (Array.isArray(licenses)) { + return licenses.some(license => + isPermissible({ + name: dependency.name, + licenses: license, + }), + ); + } + + return OPEN_SOURCE_LICENSES.some(license => license.test(licenses)); + } + + echo("Validating licenses"); + + checker.init( + { + start: __dirname, + }, + deps => { + const impermissible = Object.keys(deps) + .map(dependency => ({ + name: dependency, + licenses: deps[dependency].licenses, + })) + .filter(dependency => !isPermissible(dependency)); + + if (impermissible.length) { + impermissible.forEach(dependency => { + console.error( + "%s license for %s is impermissible.", + dependency.licenses, + dependency.name, + ); + }); + exit(1); + } + }, + ); }; /** @@ -929,13 +1016,19 @@ target.checkLicenses = function() { * @returns {void} */ function downloadMultifilesTestTarget(cb) { - if (test("-d", PERF_MULTIFILES_TARGET_DIR)) { - process.nextTick(cb); - } else { - mkdir("-p", PERF_MULTIFILES_TARGET_DIR); - echo("Downloading the repository of multi-files performance test target."); - exec(`git clone -b v1.10.3 --depth 1 https://github.com/eslint/eslint.git "${PERF_MULTIFILES_TARGET_DIR}"`, { silent: true }, cb); - } + if (test("-d", PERF_MULTIFILES_TARGET_DIR)) { + process.nextTick(cb); + } else { + mkdir("-p", PERF_MULTIFILES_TARGET_DIR); + echo( + "Downloading the repository of multi-files performance test target.", + ); + exec( + `git clone -b v1.10.3 --depth 1 https://github.com/eslint/eslint.git "${PERF_MULTIFILES_TARGET_DIR}"`, + { silent: true }, + cb, + ); + } } /** @@ -944,13 +1037,13 @@ function downloadMultifilesTestTarget(cb) { * @returns {void} */ function createConfigForPerformanceTest() { - let rules = ""; + let rules = ""; - for (const [ruleId] of builtinRules) { - rules += (` "${ruleId}": 1,\n`); - } + for (const [ruleId] of builtinRules) { + rules += ` "${ruleId}": 1,\n`; + } - const content = ` + const content = ` module.exports = [{ "languageOptions": {sourceType: "commonjs"}, "rules": { @@ -958,53 +1051,55 @@ module.exports = [{ } }];`; - content.to(PERF_ESLINTRC); + content.to(PERF_ESLINTRC); } /** * @callback TimeCallback - * @param {?int[]} results + * @param {number[] | null} results * @returns {void} */ /** * Calculates the time for each run for performance * @param {string} cmd cmd - * @param {int} runs Total number of runs to do - * @param {int} runNumber Current run number - * @param {int[]} results Collection results from each run + * @param {number} runs Total number of runs to do + * @param {number} runNumber Current run number + * @param {number[]} results Collection results from each run * @param {TimeCallback} cb Function to call when everything is done * @returns {void} calls the cb with all the results * @private */ function time(cmd, runs, runNumber, results, cb) { - const start = process.hrtime(); - - exec(cmd, { maxBuffer: 64 * 1024 * 1024, silent: true }, (code, stdout, stderr) => { - const diff = process.hrtime(start), - actual = (diff[0] * 1e3 + diff[1] / 1e6); // ms - - if (code) { - echo(` Performance Run #${runNumber} failed.`); - if (stdout) { - echo(`STDOUT:\n${stdout}\n\n`); - } - - if (stderr) { - echo(`STDERR:\n${stderr}\n\n`); - } - return cb(null); - } - - results.push(actual); - echo(` Performance Run #${runNumber}: %dms`, actual); - if (runs > 1) { - return time(cmd, runs - 1, runNumber + 1, results, cb); - } - return cb(results); - - }); - + const start = process.hrtime(); + + exec( + cmd, + { maxBuffer: 64 * 1024 * 1024, silent: true }, + (code, stdout, stderr) => { + const diff = process.hrtime(start), + actual = diff[0] * 1e3 + diff[1] / 1e6; // ms + + if (code) { + echo(` Performance Run #${runNumber} failed.`); + if (stdout) { + echo(`STDOUT:\n${stdout}\n\n`); + } + + if (stderr) { + echo(`STDERR:\n${stderr}\n\n`); + } + return cb(null); + } + + results.push(actual); + echo(` Performance Run #${runNumber}: %dms`, actual); + if (runs > 1) { + return time(cmd, runs - 1, runNumber + 1, results, cb); + } + return cb(results); + }, + ); } /** @@ -1016,32 +1111,37 @@ function time(cmd, runs, runNumber, results, cb) { * @returns {void} */ function runPerformanceTest(title, targets, multiplier, cb) { - const cpuSpeed = os.cpus()[0].speed, - max = multiplier / cpuSpeed, - cmd = `${ESLINT}--config "${PERF_ESLINTRC}" --no-config-lookup --no-ignore ${targets}`; - - echo(""); - echo(title); - echo(" CPU Speed is %d with multiplier %d", cpuSpeed, multiplier); - - time(cmd, 5, 1, [], results => { - if (!results || results.length === 0) { // No results? Something is wrong. - throw new Error("Performance test failed."); - } - - results.sort((a, b) => a - b); - - const median = results[~~(results.length / 2)]; - - echo(""); - if (median > max) { - echo(" Performance budget exceeded: %dms (limit: %dms)", median, max); - } else { - echo(" Performance budget ok: %dms (limit: %dms)", median, max); - } - echo(""); - cb(); - }); + const cpuSpeed = os.cpus()[0].speed, + max = multiplier / cpuSpeed, + cmd = `${ESLINT}--config "${PERF_ESLINTRC}" --no-config-lookup --no-ignore ${targets}`; + + echo(""); + echo(title); + echo(" CPU Speed is %d with multiplier %d", cpuSpeed, multiplier); + + time(cmd, 5, 1, [], results => { + if (!results || results.length === 0) { + // No results? Something is wrong. + throw new Error("Performance test failed."); + } + + results.sort((a, b) => a - b); + + const median = results[~~(results.length / 2)]; + + echo(""); + if (median > max) { + echo( + " Performance budget exceeded: %dms (limit: %dms)", + median, + max, + ); + } else { + echo(" Performance budget ok: %dms (limit: %dms)", median, max); + } + echo(""); + cb(); + }); } /** @@ -1050,61 +1150,62 @@ function runPerformanceTest(title, targets, multiplier, cb) { * @private */ function loadPerformance() { - echo(""); - echo("Loading:"); + echo(""); + echo("Loading:"); - const results = []; + const results = []; - for (let cnt = 0; cnt < 5; cnt++) { - const loadPerfData = loadPerf({ - checkDependencies: false - }); + for (let cnt = 0; cnt < 5; cnt++) { + const loadPerfData = loadPerf({ + checkDependencies: false, + }); - echo(` Load performance Run #${cnt + 1}: %dms`, loadPerfData.loadTime); - results.push(loadPerfData.loadTime); - } + echo( + ` Load performance Run #${cnt + 1}: %dms`, + loadPerfData.loadTime, + ); + results.push(loadPerfData.loadTime); + } - results.sort((a, b) => a - b); - const median = results[~~(results.length / 2)]; + results.sort((a, b) => a - b); + const median = results[~~(results.length / 2)]; - echo(""); - echo(" Load Performance median: %dms", median); - echo(""); + echo(""); + echo(" Load Performance median: %dms", median); + echo(""); } -target.perf = function() { - downloadMultifilesTestTarget(() => { - createConfigForPerformanceTest(); - - loadPerformance(); - - runPerformanceTest( - "Single File:", - "tests/performance/jshint.js", - PERF_MULTIPLIER, - () => { - - // Count test target files. - const count = glob.sync( - ( - process.platform === "win32" - ? PERF_MULTIFILES_TARGETS.replace(/\\/gu, "/") - : PERF_MULTIFILES_TARGETS - ) - .slice(1, -1) // strip quotes - ).length; - - runPerformanceTest( - `Multi Files (${count} files):`, - PERF_MULTIFILES_TARGETS, - 3 * PERF_MULTIPLIER, - () => {} - ); - } - ); - }); +target.perf = function () { + downloadMultifilesTestTarget(() => { + createConfigForPerformanceTest(); + + loadPerformance(); + + runPerformanceTest( + "Single File:", + "tests/performance/jshint.js", + PERF_MULTIPLIER, + () => { + // Count test target files. + const count = glob.sync( + (process.platform === "win32" + ? PERF_MULTIFILES_TARGETS.replace(/\\/gu, "/") + : PERF_MULTIFILES_TARGETS + ).slice(1, -1), // strip quotes + ).length; + + runPerformanceTest( + `Multi Files (${count} files):`, + PERF_MULTIFILES_TARGETS, + 3 * PERF_MULTIPLIER, + () => {}, + ); + }, + ); + }); }; -target.generateRelease = generateRelease; -target.generatePrerelease = ([prereleaseType]) => generatePrerelease(prereleaseType); +target.generateRelease = ([packageTag]) => generateRelease({ packageTag }); +target.generatePrerelease = ([prereleaseId]) => + generateRelease({ prereleaseId, packageTag: "next" }); target.publishRelease = publishRelease; diff --git a/README.md b/README.md index 227d40c7cc50..522cc9faf2da 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,9 @@ [![npm version](https://img.shields.io/npm/v/eslint.svg)](https://www.npmjs.com/package/eslint) [![Downloads](https://img.shields.io/npm/dm/eslint.svg)](https://www.npmjs.com/package/eslint) [![Build Status](https://github.com/eslint/eslint/workflows/CI/badge.svg)](https://github.com/eslint/eslint/actions) -[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint.svg?type=shield)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_shield) -
+
[![Open Collective Backers](https://img.shields.io/opencollective/backers/eslint)](https://opencollective.com/eslint) [![Open Collective Sponsors](https://img.shields.io/opencollective/sponsors/eslint)](https://opencollective.com/eslint) -[![Follow us on Twitter](https://img.shields.io/twitter/follow/geteslint?label=Follow&style=social)](https://twitter.com/intent/user?screen_name=geteslint) # ESLint @@ -17,107 +15,118 @@ [Code of Conduct](https://eslint.org/conduct) | [Twitter](https://twitter.com/geteslint) | [Discord](https://eslint.org/chat) | -[Mastodon](https://fosstodon.org/@eslint) +[Mastodon](https://fosstodon.org/@eslint) | +[Bluesky](https://bsky.app/profile/eslint.org) ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. In many ways, it is similar to JSLint and JSHint with a few exceptions: -* ESLint uses [Espree](https://github.com/eslint/espree) for JavaScript parsing. -* ESLint uses an AST to evaluate patterns in code. -* ESLint is completely pluggable, every single rule is a plugin and you can add more at runtime. +- ESLint uses [Espree](https://github.com/eslint/js/tree/main/packages/espree) for JavaScript parsing. +- ESLint uses an AST to evaluate patterns in code. +- ESLint is completely pluggable, every single rule is a plugin and you can add more at runtime. ## Table of Contents 1. [Installation and Usage](#installation-and-usage) -2. [Configuration](#configuration) -3. [Code of Conduct](#code-of-conduct) -4. [Filing Issues](#filing-issues) -5. [Frequently Asked Questions](#frequently-asked-questions) -6. [Releases](#releases) -7. [Security Policy](#security-policy) -8. [Semantic Versioning Policy](#semantic-versioning-policy) -9. [Stylistic Rule Updates](#stylistic-rule-updates) -10. [License](#license) -11. [Team](#team) -12. [Sponsors](#sponsors) -13. [Technology Sponsors](#technology-sponsors) +1. [Configuration](#configuration) +1. [Version Support](#version-support) +1. [Code of Conduct](#code-of-conduct) +1. [Filing Issues](#filing-issues) +1. [Frequently Asked Questions](#frequently-asked-questions) +1. [Releases](#releases) +1. [Security Policy](#security-policy) +1. [Semantic Versioning Policy](#semantic-versioning-policy) +1. [License](#license) +1. [Team](#team) +1. [Sponsors](#sponsors) +1. [Technology Sponsors](#technology-sponsors) ## Installation and Usage -Prerequisites: [Node.js](https://nodejs.org/) (`^12.22.0`, `^14.17.0`, or `>=16.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.) +Prerequisites: [Node.js](https://nodejs.org/) (`^18.18.0`, `^20.9.0`, or `>=21.1.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.) You can install and configure ESLint using this command: ```shell -npm init @eslint/config +npm init @eslint/config@latest ``` After that, you can run ESLint on any file or directory like this: ```shell -./node_modules/.bin/eslint yourfile.js +npx eslint yourfile.js ``` -## Configuration +### pnpm Installation -After running `npm init @eslint/config`, you'll have an `.eslintrc` file in your directory. In it, you'll see some rules configured like this: +To use ESLint with pnpm, we recommend setting up a `.npmrc` file with at least the following settings: -```json -{ - "rules": { - "semi": ["error", "always"], - "quotes": ["error", "double"] - } -} +```text +auto-install-peers=true +node-linker=hoisted ``` -The names `"semi"` and `"quotes"` are the names of [rules](https://eslint.org/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values: +This ensures that pnpm installs dependencies in a way that is more compatible with npm and is less likely to produce errors. -* `"off"` or `0` - turn the rule off -* `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code) -* `"error"` or `2` - turn the rule on as an error (exit code will be 1) +## Configuration -The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](https://eslint.org/docs/latest/use/configure)). +You can configure rules in your `eslint.config.js` files as in this example: -## Code of Conduct +```js +import { defineConfig } from "eslint/config"; -ESLint adheres to the [JS Foundation Code of Conduct](https://eslint.org/conduct). +export default defineConfig([ + { + files: ["**/*.js", "**/*.cjs", "**/*.mjs"], + rules: { + "prefer-const": "warn", + "no-constant-binary-expression": "error", + }, + }, +]); +``` -## Filing Issues +The names `"prefer-const"` and `"no-constant-binary-expression"` are the names of [rules](https://eslint.org/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values: -Before filing an issue, please be sure to read the guidelines for what you're reporting: +- `"off"` or `0` - turn the rule off +- `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code) +- `"error"` or `2` - turn the rule on as an error (exit code will be 1) -* [Bug Report](https://eslint.org/docs/latest/contribute/report-bugs) -* [Propose a New Rule](https://eslint.org/docs/latest/contribute/propose-new-rule) -* [Proposing a Rule Change](https://eslint.org/docs/latest/contribute/propose-rule-change) -* [Request a Change](https://eslint.org/docs/latest/contribute/request-change) +The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](https://eslint.org/docs/latest/use/configure)). -## Frequently Asked Questions +## Version Support -### I'm using JSCS, should I migrate to ESLint? +The ESLint team provides ongoing support for the current version and six months of limited support for the previous version. Limited support includes critical bug fixes, security issues, and compatibility issues only. -Yes. [JSCS has reached end of life](https://eslint.org/blog/2016/07/jscs-end-of-life) and is no longer supported. +ESLint offers commercial support for both current and previous versions through our partners, [Tidelift][tidelift] and [HeroDevs][herodevs]. -We have prepared a [migration guide](https://eslint.org/docs/latest/use/migrating-from-jscs) to help you convert your JSCS settings to an ESLint configuration. +See [Version Support](https://eslint.org/version-support) for more details. -We are now at or near 100% compatibility with JSCS. If you try ESLint and believe we are not yet compatible with a JSCS rule/configuration, please create an issue (mentioning that it is a JSCS compatibility issue) and we will evaluate it as per our normal process. +## Code of Conduct -### Does Prettier replace ESLint? +ESLint adheres to the [OpenJS Foundation Code of Conduct](https://eslint.org/conduct). + +## Filing Issues -No, ESLint and Prettier have diffent jobs: ESLint is a linter (looking for problematic patterns) and Prettier is a code formatter. Using both tools is common, refer to [Prettier's documentation](https://prettier.io/docs/en/install#eslint-and-other-linters) to learn how to configure them to work well with each other. +Before filing an issue, please be sure to read the guidelines for what you're reporting: -### Why can't ESLint find my plugins? +- [Bug Report](https://eslint.org/docs/latest/contribute/report-bugs) +- [Propose a New Rule](https://eslint.org/docs/latest/contribute/propose-new-rule) +- [Proposing a Rule Change](https://eslint.org/docs/latest/contribute/propose-rule-change) +- [Request a Change](https://eslint.org/docs/latest/contribute/request-change) -* Make sure your plugins (and ESLint) are both in your project's `package.json` as devDependencies (or dependencies, if your project uses ESLint at runtime). -* Make sure you have run `npm install` and all your dependencies are installed. -* Make sure your plugins' peerDependencies have been installed as well. You can use `npm view eslint-plugin-myplugin peerDependencies` to see what peer dependencies `eslint-plugin-myplugin` has. +## Frequently Asked Questions ### Does ESLint support JSX? -Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](https://eslint.org/docs/latest/use/configure)). Please note that supporting JSX syntax *is not* the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics. +Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](https://eslint.org/docs/latest/use/configure)). Please note that supporting JSX syntax _is not_ the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics. + +### Does Prettier replace ESLint? + +No, ESLint and Prettier have different jobs: ESLint is a linter (looking for problematic patterns) and Prettier is a code formatter. Using both tools is common, refer to [Prettier's documentation](https://prettier.io/docs/en/install#eslint-and-other-linters) to learn how to configure them to work well with each other. ### What ECMAScript versions does ESLint support? -ESLint has full support for ECMAScript 3, 5 (default), 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, and 2023. You can set your desired ECMAScript syntax (and other settings, like global variables or your target environments) through [configuration](https://eslint.org/docs/latest/use/configure). +ESLint has full support for ECMAScript 3, 5, and every year from 2015 up until the most recent stage 4 specification (the default). You can set your desired ECMAScript syntax and other settings (like global variables) through [configuration](https://eslint.org/docs/latest/use/configure). ### What about experimental features? @@ -127,6 +136,18 @@ In other cases (including if rules need to warn on more or fewer cases due to ne Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the [TC39 process](https://tc39.github.io/process-document/)), we will accept issues and pull requests related to the new feature, subject to our [contributing guidelines](https://eslint.org/docs/latest/contribute). Until then, please use the appropriate parser and plugin(s) for your experimental feature. +### Which Node.js versions does ESLint support? + +ESLint updates the supported Node.js versions with each major release of ESLint. At that time, ESLint's supported Node.js versions are updated to be: + +1. The most recent maintenance release of Node.js +1. The lowest minor version of the Node.js LTS release that includes the features the ESLint team wants to use. +1. The Node.js Current release + +ESLint is also expected to work with Node.js versions released after the Node.js Current release. + +Refer to the [Quick Start Guide](https://eslint.org/docs/latest/use/getting-started#prerequisites) for the officially supported Node.js versions for a given ESLint release. + ### Where to ask for help? Open a [discussion](https://github.com/eslint/eslint/discussions) or stop by our [Discord server](https://eslint.org/chat). @@ -153,47 +174,58 @@ ESLint takes security seriously. We work hard to ensure that ESLint is safe for ESLint follows [semantic versioning](https://semver.org). However, due to the nature of ESLint as a code quality tool, it's not always clear when a minor or major version bump occurs. To help clarify this for everyone, we've defined the following semantic versioning policy for ESLint: -* Patch release (intended to not break your lint build) - * A bug fix in a rule that results in ESLint reporting fewer linting errors. - * A bug fix to the CLI or core (including formatters). - * Improvements to documentation. - * Non-user-facing changes such as refactoring code, adding, deleting, or modifying tests, and increasing test coverage. - * Re-releasing after a failed release (i.e., publishing a release that doesn't work for anyone). -* Minor release (might break your lint build) - * A bug fix in a rule that results in ESLint reporting more linting errors. - * A new rule is created. - * A new option to an existing rule that does not result in ESLint reporting more linting errors by default. - * A new addition to an existing rule to support a newly-added language feature (within the last 12 months) that will result in ESLint reporting more linting errors by default. - * An existing rule is deprecated. - * A new CLI capability is created. - * New capabilities to the public API are added (new classes, new methods, new arguments to existing methods, etc.). - * A new formatter is created. - * `eslint:recommended` is updated and will result in strictly fewer linting errors (e.g., rule removals). -* Major release (likely to break your lint build) - * `eslint:recommended` is updated and may result in new linting errors (e.g., rule additions, most rule option updates). - * A new option to an existing rule that results in ESLint reporting more linting errors by default. - * An existing formatter is removed. - * Part of the public API is removed or changed in an incompatible way. The public API includes: - * Rule schemas - * Configuration schema - * Command-line options - * Node.js API - * Rule, formatter, parser, plugin APIs +- Patch release (intended to not break your lint build) + - A bug fix in a rule that results in ESLint reporting fewer linting errors. + - A bug fix to the CLI or core (including formatters). + - Improvements to documentation. + - Non-user-facing changes such as refactoring code, adding, deleting, or modifying tests, and increasing test coverage. + - Re-releasing after a failed release (i.e., publishing a release that doesn't work for anyone). +- Minor release (might break your lint build) + - A bug fix in a rule that results in ESLint reporting more linting errors. + - A new rule is created. + - A new option to an existing rule that does not result in ESLint reporting more linting errors by default. + - A new addition to an existing rule to support a newly-added language feature (within the last 12 months) that will result in ESLint reporting more linting errors by default. + - An existing rule is deprecated. + - A new CLI capability is created. + - New capabilities to the public API are added (new classes, new methods, new arguments to existing methods, etc.). + - A new formatter is created. + - `eslint:recommended` is updated and will result in strictly fewer linting errors (e.g., rule removals). +- Major release (likely to break your lint build) + - `eslint:recommended` is updated and may result in new linting errors (e.g., rule additions, most rule option updates). + - A new option to an existing rule that results in ESLint reporting more linting errors by default. + - An existing formatter is removed. + - Part of the public API is removed or changed in an incompatible way. The public API includes: + - Rule schemas + - Configuration schema + - Command-line options + - Node.js API + - Rule, formatter, parser, plugin APIs According to our policy, any minor update may report more linting errors than the previous release (ex: from a bug fix). As such, we recommend using the tilde (`~`) in `package.json` e.g. `"eslint": "~3.1.0"` to guarantee the results of your builds. -## Stylistic Rule Updates +## License -Stylistic rules are frozen according to [our policy](https://eslint.org/blog/2020/05/changes-to-rules-policies) on how we evaluate new rules and rule changes. -This means: +MIT License -* **Bug fixes**: We will still fix bugs in stylistic rules. -* **New ECMAScript features**: We will also make sure stylistic rules are compatible with new ECMAScript features. -* **New options**: We will **not** add any new options to stylistic rules unless an option is the only way to fix a bug or support a newly-added ECMAScript feature. +Copyright OpenJS Foundation and other contributors, -## License +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint.svg?type=large)](https://app.fossa.io/projects/git%2Bhttps%3A%2F%2Fgithub.com%2Feslint%2Feslint?ref=badge_large) +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. ## Team @@ -213,6 +245,11 @@ The people who manage releases, review feature requests, and meet regularly to e Nicholas C. Zakas + +Francesco Trotta's Avatar
+Francesco Trotta +
+ Milos Djermanovic's Avatar
Milos Djermanovic @@ -240,25 +277,20 @@ Nitin Kumar The people who review and fix bugs and help triage issues.
- -Bryan Mishkin's Avatar
-Bryan Mishkin -
-
- -Francesco Trotta's Avatar
-Francesco Trotta -
-
- -Yosuke Ota's Avatar
-Yosuke Ota +
+Josh Goldberg ✨'s Avatar
+Josh Goldberg ✨
Tanuj Kanti's Avatar
Tanuj Kanti
+
+ +ëŖ¨ë°€LuMir's Avatar
+ëŖ¨ë°€LuMir +
### Website Team @@ -272,8 +304,8 @@ Amaresh S M
-Strek's Avatar
-Strek +Harish's Avatar
+Harish
@@ -284,21 +316,25 @@ Percy Ma + + + ## Sponsors -The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://opencollective.com/eslint) to get your logo on our README and website. +The following companies, organizations, and individuals support ESLint's ongoing maintenance and development. [Become a Sponsor](https://eslint.org/donate) +to get your logo on our READMEs and [website](https://eslint.org/sponsors). - - -

Platinum Sponsors

-

Chrome Frameworks Fund Automattic

Gold Sponsors

-

Salesforce Airbnb

Silver Sponsors

-

Liftoff American Express Workleap

Bronze Sponsors

-

ThemeIsle Anagram Solver Icons8 Discord Transloadit Ignition Nx HeroCoders

- +

Diamond Sponsors

+

AG Grid

Platinum Sponsors

+

Automattic Airbnb

Gold Sponsors

+

Qlty Software trunk.io Shopify

Silver Sponsors

+

Vite Liftoff American Express StackBlitz

Bronze Sponsors

+

Cybozu Sentry Anagram Solver Icons8 Discord GitBook Nx Mercedes-Benz Group HeroCoders LambdaTest

+

Technology Sponsors

+Technology sponsors allow us to use their products and services for free as part of a contribution to the open source ecosystem and our work. +

Netlify Algolia 1Password

-## Technology Sponsors + -* Site search ([eslint.org](https://eslint.org)) is sponsored by [Algolia](https://www.algolia.com) -* Hosting for ([eslint.org](https://eslint.org)) is sponsored by [Netlify](https://www.netlify.com) -* Password management is sponsored by [1Password](https://www.1password.com) +[tidelift]: https://tidelift.com/funding/github/npm/eslint +[herodevs]: https://www.herodevs.com/support/eslint-nes?utm_source=ESLintWebsite&utm_medium=ESLintWebsite&utm_campaign=ESLintNES&utm_id=ESLintNES diff --git a/bin/eslint.js b/bin/eslint.js index eeb4647e70b1..9b202a982331 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -9,9 +9,14 @@ "use strict"; +const mod = require("node:module"); + +// to use V8's code cache to speed up instantiation time +mod.enableCompileCache?.(); + // must do this initialization *before* other requires in order to work if (process.argv.includes("--debug")) { - require("debug").enable("eslint:*,-eslint:code-path,eslintrc:*"); + require("debug").enable("eslint:*,-eslint:code-path,eslintrc:*"); } //------------------------------------------------------------------------------ @@ -40,20 +45,20 @@ if (process.argv.includes("--debug")) { * @returns {Promise} The read text. */ function readStdin() { - return new Promise((resolve, reject) => { - let content = ""; - let chunk = ""; - - process.stdin - .setEncoding("utf8") - .on("readable", () => { - while ((chunk = process.stdin.read()) !== null) { - content += chunk; - } - }) - .on("end", () => resolve(content)) - .on("error", reject); - }); + return new Promise((resolve, reject) => { + let content = ""; + let chunk = ""; + + process.stdin + .setEncoding("utf8") + .on("readable", () => { + while ((chunk = process.stdin.read()) !== null) { + content += chunk; + } + }) + .on("end", () => resolve(content)) + .on("error", reject); + }); } /** @@ -62,34 +67,32 @@ function readStdin() { * @returns {string} The error message. */ function getErrorMessage(error) { - - // Lazy loading because this is used only if an error happened. - const util = require("util"); - - // Foolproof -- third-party module might throw non-object. - if (typeof error !== "object" || error === null) { - return String(error); - } - - // Use templates if `error.messageTemplate` is present. - if (typeof error.messageTemplate === "string") { - try { - const template = require(`../messages/${error.messageTemplate}.js`); - - return template(error.messageData || {}); - } catch { - - // Ignore template error then fallback to use `error.stack`. - } - } - - // Use the stacktrace if it's an error object. - if (typeof error.stack === "string") { - return error.stack; - } - - // Otherwise, dump the object. - return util.format("%o", error); + // Lazy loading because this is used only if an error happened. + const util = require("node:util"); + + // Foolproof -- third-party module might throw non-object. + if (typeof error !== "object" || error === null) { + return String(error); + } + + // Use templates if `error.messageTemplate` is present. + if (typeof error.messageTemplate === "string") { + try { + const template = require(`../messages/${error.messageTemplate}.js`); + + return template(error.messageData || {}); + } catch { + // Ignore template error then fallback to use `error.stack`. + } + } + + // Use the stacktrace if it's an error object. + if (typeof error.stack === "string") { + return error.stack; + } + + // Otherwise, dump the object. + return util.format("%o", error); } /** @@ -111,21 +114,21 @@ let hadFatalError = false; * @returns {void} */ function onFatalError(error) { - process.exitCode = 2; - hadFatalError = true; + process.exitCode = 2; + hadFatalError = true; - const { version } = require("../package.json"); - const message = ` + const { version } = require("../package.json"); + const message = ` Oops! Something went wrong! :( ESLint: ${version} ${getErrorMessage(error)}`; - if (!displayedErrors.has(message)) { - console.error(message); - displayedErrors.add(message); - } + if (!displayedErrors.has(message)) { + console.error(message); + displayedErrors.add(message); + } } //------------------------------------------------------------------------------ @@ -133,41 +136,61 @@ ${getErrorMessage(error)}`; //------------------------------------------------------------------------------ (async function main() { - process.on("uncaughtException", onFatalError); - process.on("unhandledRejection", onFatalError); - - // Call the config initializer if `--init` is present. - if (process.argv.includes("--init")) { - - // `eslint --init` has been moved to `@eslint/create-config` - console.warn("You can also run this command directly using 'npm init @eslint/config'."); - - const spawn = require("cross-spawn"); - - spawn.sync("npm", ["init", "@eslint/config"], { encoding: "utf8", stdio: "inherit" }); - return; - } - - // Otherwise, call the CLI. - const exitCode = await require("../lib/cli").execute( - process.argv, - process.argv.includes("--stdin") ? await readStdin() : null, - true - ); - - /* - * If an uncaught exception or unhandled rejection was detected in the meantime, - * keep the fatal exit code 2 that is already assigned to `process.exitCode`. - * Without this condition, exit code 2 (unsuccessful execution) could be overwritten with - * 1 (successful execution, lint problems found) or even 0 (successful execution, no lint problems found). - * This ensures that unexpected errors that seemingly don't affect the success - * of the execution will still cause a non-zero exit code, as it's a common - * practice and the default behavior of Node.js to exit with non-zero - * in case of an uncaught exception or unhandled rejection. - * - * Otherwise, assign the exit code returned from CLI. - */ - if (!hadFatalError) { - process.exitCode = exitCode; - } -}()).catch(onFatalError); + process.on("uncaughtException", onFatalError); + process.on("unhandledRejection", onFatalError); + + // Call the config initializer if `--init` is present. + if (process.argv.includes("--init")) { + // `eslint --init` has been moved to `@eslint/create-config` + console.warn( + "You can also run this command directly using 'npm init @eslint/config@latest'.", + ); + + const spawn = require("cross-spawn"); + + spawn.sync("npm", ["init", "@eslint/config@latest"], { + encoding: "utf8", + stdio: "inherit", + }); + return; + } + + // start the MCP server if `--mcp` is present + if (process.argv.includes("--mcp")) { + console.warn( + "You can also run this command directly using 'npx @eslint/mcp@latest'.", + ); + + const spawn = require("cross-spawn"); + + spawn.sync("npx", ["@eslint/mcp@latest"], { + encoding: "utf8", + stdio: "inherit", + }); + return; + } + + // Otherwise, call the CLI. + const cli = require("../lib/cli"); + const exitCode = await cli.execute( + process.argv, + process.argv.includes("--stdin") ? await readStdin() : null, + true, + ); + + /* + * If an uncaught exception or unhandled rejection was detected in the meantime, + * keep the fatal exit code 2 that is already assigned to `process.exitCode`. + * Without this condition, exit code 2 (unsuccessful execution) could be overwritten with + * 1 (successful execution, lint problems found) or even 0 (successful execution, no lint problems found). + * This ensures that unexpected errors that seemingly don't affect the success + * of the execution will still cause a non-zero exit code, as it's a common + * practice and the default behavior of Node.js to exit with non-zero + * in case of an uncaught exception or unhandled rejection. + * + * Otherwise, assign the exit code returned from CLI. + */ + if (!hadFatalError) { + process.exitCode = exitCode; + } +})().catch(onFatalError); diff --git a/conf/config-schema.js b/conf/config-schema.js deleted file mode 100644 index b83f6578832d..000000000000 --- a/conf/config-schema.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - * STOP!!! DO NOT MODIFY. - * - * This file is part of the ongoing work to move the eslintrc-style config - * system into the @eslint/eslintrc package. This file needs to remain - * unchanged in order for this work to proceed. - * - * If you think you need to change this file, please contact @nzakas first. - * - * Thanks in advance for your cooperation. - */ - -/** - * @fileoverview Defines a schema for configs. - * @author Sylvan Mably - */ - -"use strict"; - -const baseConfigProperties = { - $schema: { type: "string" }, - env: { type: "object" }, - extends: { $ref: "#/definitions/stringOrStrings" }, - globals: { type: "object" }, - overrides: { - type: "array", - items: { $ref: "#/definitions/overrideConfig" }, - additionalItems: false - }, - parser: { type: ["string", "null"] }, - parserOptions: { type: "object" }, - plugins: { type: "array" }, - processor: { type: "string" }, - rules: { type: "object" }, - settings: { type: "object" }, - noInlineConfig: { type: "boolean" }, - reportUnusedDisableDirectives: { type: "boolean" }, - - ecmaFeatures: { type: "object" } // deprecated; logs a warning when used -}; - -const configSchema = { - definitions: { - stringOrStrings: { - oneOf: [ - { type: "string" }, - { - type: "array", - items: { type: "string" }, - additionalItems: false - } - ] - }, - stringOrStringsRequired: { - oneOf: [ - { type: "string" }, - { - type: "array", - items: { type: "string" }, - additionalItems: false, - minItems: 1 - } - ] - }, - - // Config at top-level. - objectConfig: { - type: "object", - properties: { - root: { type: "boolean" }, - ignorePatterns: { $ref: "#/definitions/stringOrStrings" }, - ...baseConfigProperties - }, - additionalProperties: false - }, - - // Config in `overrides`. - overrideConfig: { - type: "object", - properties: { - excludedFiles: { $ref: "#/definitions/stringOrStrings" }, - files: { $ref: "#/definitions/stringOrStringsRequired" }, - ...baseConfigProperties - }, - required: ["files"], - additionalProperties: false - } - }, - - $ref: "#/definitions/objectConfig" -}; - -module.exports = configSchema; diff --git a/conf/default-cli-options.js b/conf/default-cli-options.js index dad03d89e93d..dda88d655904 100644 --- a/conf/default-cli-options.js +++ b/conf/default-cli-options.js @@ -6,27 +6,27 @@ "use strict"; module.exports = { - configFile: null, - baseConfig: false, - rulePaths: [], - useEslintrc: true, - envs: [], - globals: [], - extensions: null, - ignore: true, - ignorePath: void 0, - cache: false, + configFile: null, + baseConfig: false, + rulePaths: [], + useEslintrc: true, + envs: [], + globals: [], + extensions: null, + ignore: true, + ignorePath: void 0, + cache: false, - /* - * in order to honor the cacheFile option if specified - * this option should not have a default value otherwise - * it will always be used - */ - cacheLocation: "", - cacheFile: ".eslintcache", - cacheStrategy: "metadata", - fix: false, - allowInlineConfig: true, - reportUnusedDisableDirectives: void 0, - globInputPaths: true + /* + * in order to honor the cacheFile option if specified + * this option should not have a default value otherwise + * it will always be used + */ + cacheLocation: "", + cacheFile: ".eslintcache", + cacheStrategy: "metadata", + fix: false, + allowInlineConfig: true, + reportUnusedDisableDirectives: void 0, + globInputPaths: true, }; diff --git a/conf/ecma-version.js b/conf/ecma-version.js new file mode 100644 index 000000000000..e68d30206271 --- /dev/null +++ b/conf/ecma-version.js @@ -0,0 +1,16 @@ +/** + * @fileoverview Configuration related to ECMAScript versions + * @author Milos Djermanovic + */ + +"use strict"; + +/** + * The latest ECMAScript version supported by ESLint. + * @type {number} year-based ECMAScript version + */ +const LATEST_ECMA_VERSION = 2026; + +module.exports = { + LATEST_ECMA_VERSION, +}; diff --git a/conf/globals.js b/conf/globals.js index 58710e05bc6d..b630b272a155 100644 --- a/conf/globals.js +++ b/conf/globals.js @@ -10,145 +10,160 @@ //----------------------------------------------------------------------------- const commonjs = { - exports: true, - global: false, - module: false, - require: false + exports: true, + global: false, + module: false, + require: false, }; const es3 = { - Array: false, - Boolean: false, - constructor: false, - Date: false, - decodeURI: false, - decodeURIComponent: false, - encodeURI: false, - encodeURIComponent: false, - Error: false, - escape: false, - eval: false, - EvalError: false, - Function: false, - hasOwnProperty: false, - Infinity: false, - isFinite: false, - isNaN: false, - isPrototypeOf: false, - Math: false, - NaN: false, - Number: false, - Object: false, - parseFloat: false, - parseInt: false, - propertyIsEnumerable: false, - RangeError: false, - ReferenceError: false, - RegExp: false, - String: false, - SyntaxError: false, - toLocaleString: false, - toString: false, - TypeError: false, - undefined: false, - unescape: false, - URIError: false, - valueOf: false + Array: false, + Boolean: false, + constructor: false, + Date: false, + decodeURI: false, + decodeURIComponent: false, + encodeURI: false, + encodeURIComponent: false, + Error: false, + escape: false, + eval: false, + EvalError: false, + Function: false, + hasOwnProperty: false, + Infinity: false, + isFinite: false, + isNaN: false, + isPrototypeOf: false, + Math: false, + NaN: false, + Number: false, + Object: false, + parseFloat: false, + parseInt: false, + propertyIsEnumerable: false, + RangeError: false, + ReferenceError: false, + RegExp: false, + String: false, + SyntaxError: false, + toLocaleString: false, + toString: false, + TypeError: false, + undefined: false, + unescape: false, + URIError: false, + valueOf: false, }; const es5 = { - ...es3, - JSON: false + ...es3, + JSON: false, }; const es2015 = { - ...es5, - ArrayBuffer: false, - DataView: false, - Float32Array: false, - Float64Array: false, - Int16Array: false, - Int32Array: false, - Int8Array: false, - Map: false, - Promise: false, - Proxy: false, - Reflect: false, - Set: false, - Symbol: false, - Uint16Array: false, - Uint32Array: false, - Uint8Array: false, - Uint8ClampedArray: false, - WeakMap: false, - WeakSet: false + ...es5, + ArrayBuffer: false, + DataView: false, + Float32Array: false, + Float64Array: false, + Int16Array: false, + Int32Array: false, + Int8Array: false, + Intl: false, + Map: false, + Promise: false, + Proxy: false, + Reflect: false, + Set: false, + Symbol: false, + Uint16Array: false, + Uint32Array: false, + Uint8Array: false, + Uint8ClampedArray: false, + WeakMap: false, + WeakSet: false, }; // no new globals in ES2016 const es2016 = { - ...es2015 + ...es2015, }; const es2017 = { - ...es2016, - Atomics: false, - SharedArrayBuffer: false + ...es2016, + Atomics: false, + SharedArrayBuffer: false, }; // no new globals in ES2018 const es2018 = { - ...es2017 + ...es2017, }; // no new globals in ES2019 const es2019 = { - ...es2018 + ...es2018, }; const es2020 = { - ...es2019, - BigInt: false, - BigInt64Array: false, - BigUint64Array: false, - globalThis: false + ...es2019, + BigInt: false, + BigInt64Array: false, + BigUint64Array: false, + globalThis: false, }; const es2021 = { - ...es2020, - AggregateError: false, - FinalizationRegistry: false, - WeakRef: false + ...es2020, + AggregateError: false, + FinalizationRegistry: false, + WeakRef: false, }; const es2022 = { - ...es2021 + ...es2021, }; const es2023 = { - ...es2022 + ...es2022, }; const es2024 = { - ...es2023 + ...es2023, }; +const es2025 = { + ...es2024, + Float16Array: false, + Iterator: false, +}; + +const es2026 = { + ...es2025, + AsyncDisposableStack: false, + DisposableStack: false, + SuppressedError: false, +}; //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- module.exports = { - commonjs, - es3, - es5, - es2015, - es2016, - es2017, - es2018, - es2019, - es2020, - es2021, - es2022, - es2023, - es2024 + commonjs, + es3, + es5, + es2015, + es2016, + es2017, + es2018, + es2019, + es2020, + es2021, + es2022, + es2023, + es2024, + es2025, + es2026, }; diff --git a/conf/replacements.json b/conf/replacements.json index c047811e602d..27329c4c9c62 100644 --- a/conf/replacements.json +++ b/conf/replacements.json @@ -1,22 +1,26 @@ { - "rules": { - "generator-star": ["generator-star-spacing"], - "global-strict": ["strict"], - "no-arrow-condition": ["no-confusing-arrow", "no-constant-condition"], - "no-comma-dangle": ["comma-dangle"], - "no-empty-class": ["no-empty-character-class"], - "no-empty-label": ["no-labels"], - "no-extra-strict": ["strict"], - "no-reserved-keys": ["quote-props"], - "no-space-before-semi": ["semi-spacing"], - "no-wrap-func": ["no-extra-parens"], - "space-after-function-name": ["space-before-function-paren"], - "space-after-keywords": ["keyword-spacing"], - "space-before-function-parentheses": ["space-before-function-paren"], - "space-before-keywords": ["keyword-spacing"], - "space-in-brackets": ["object-curly-spacing", "array-bracket-spacing", "computed-property-spacing"], - "space-return-throw-case": ["keyword-spacing"], - "space-unary-word-ops": ["space-unary-ops"], - "spaced-line-comment": ["spaced-comment"] - } + "rules": { + "generator-star": ["generator-star-spacing"], + "global-strict": ["strict"], + "no-arrow-condition": ["no-confusing-arrow", "no-constant-condition"], + "no-comma-dangle": ["comma-dangle"], + "no-empty-class": ["no-empty-character-class"], + "no-empty-label": ["no-labels"], + "no-extra-strict": ["strict"], + "no-reserved-keys": ["quote-props"], + "no-space-before-semi": ["semi-spacing"], + "no-wrap-func": ["no-extra-parens"], + "space-after-function-name": ["space-before-function-paren"], + "space-after-keywords": ["keyword-spacing"], + "space-before-function-parentheses": ["space-before-function-paren"], + "space-before-keywords": ["keyword-spacing"], + "space-in-brackets": [ + "object-curly-spacing", + "array-bracket-spacing", + "computed-property-spacing" + ], + "space-return-throw-case": ["keyword-spacing"], + "space-unary-word-ops": ["space-unary-ops"], + "spaced-line-comment": ["spaced-comment"] + } } diff --git a/conf/rule-type-list.json b/conf/rule-type-list.json index 6ca730f34f02..05ddd3cb02af 100644 --- a/conf/rule-type-list.json +++ b/conf/rule-type-list.json @@ -1,28 +1,91 @@ { - "types": { - "problem": [], - "suggestion": [], - "layout": [] - }, - "deprecated": [], - "removed": [ - { "removed": "generator-star", "replacedBy": ["generator-star-spacing"] }, - { "removed": "global-strict", "replacedBy": ["strict"] }, - { "removed": "no-arrow-condition", "replacedBy": ["no-confusing-arrow", "no-constant-condition"] }, - { "removed": "no-comma-dangle", "replacedBy": ["comma-dangle"] }, - { "removed": "no-empty-class", "replacedBy": ["no-empty-character-class"] }, - { "removed": "no-empty-label", "replacedBy": ["no-labels"] }, - { "removed": "no-extra-strict", "replacedBy": ["strict"] }, - { "removed": "no-reserved-keys", "replacedBy": ["quote-props"] }, - { "removed": "no-space-before-semi", "replacedBy": ["semi-spacing"] }, - { "removed": "no-wrap-func", "replacedBy": ["no-extra-parens"] }, - { "removed": "space-after-function-name", "replacedBy": ["space-before-function-paren"] }, - { "removed": "space-after-keywords", "replacedBy": ["keyword-spacing"] }, - { "removed": "space-before-function-parentheses", "replacedBy": ["space-before-function-paren"] }, - { "removed": "space-before-keywords", "replacedBy": ["keyword-spacing"] }, - { "removed": "space-in-brackets", "replacedBy": ["object-curly-spacing", "array-bracket-spacing"] }, - { "removed": "space-return-throw-case", "replacedBy": ["keyword-spacing"] }, - { "removed": "space-unary-word-ops", "replacedBy": ["space-unary-ops"] }, - { "removed": "spaced-line-comment", "replacedBy": ["spaced-comment"] } - ] + "types": { + "problem": [], + "suggestion": [], + "layout": [] + }, + "deprecated": [], + "removed": [ + { + "removed": "generator-star", + "replacedBy": [{ "rule": { "name": "generator-star-spacing" } }] + }, + { + "removed": "global-strict", + "replacedBy": [{ "rule": { "name": "strict" } }] + }, + { + "removed": "no-arrow-condition", + "replacedBy": [ + { "rule": { "name": "no-confusing-arrow" } }, + { "rule": { "name": "no-constant-condition" } } + ] + }, + { + "removed": "no-comma-dangle", + "replacedBy": [{ "rule": { "name": "comma-dangle" } }] + }, + { + "removed": "no-empty-class", + "replacedBy": [{ "rule": { "name": "no-empty-character-class" } }] + }, + { + "removed": "no-empty-label", + "replacedBy": [{ "rule": { "name": "no-labels" } }] + }, + { + "removed": "no-extra-strict", + "replacedBy": [{ "rule": { "name": "strict" } }] + }, + { + "removed": "no-reserved-keys", + "replacedBy": [{ "rule": { "name": "quote-props" } }] + }, + { + "removed": "no-space-before-semi", + "replacedBy": [{ "rule": { "name": "semi-spacing" } }] + }, + { + "removed": "no-wrap-func", + "replacedBy": [{ "rule": { "name": "no-extra-parens" } }] + }, + { + "removed": "space-after-function-name", + "replacedBy": [{ "rule": { "name": "space-before-function-paren" } }] + }, + { + "removed": "space-after-keywords", + "replacedBy": [{ "rule": { "name": "keyword-spacing" } }] + }, + { + "removed": "space-before-function-parentheses", + "replacedBy": [{ "rule": { "name": "space-before-function-paren" } }] + }, + { + "removed": "space-before-keywords", + "replacedBy": [{ "rule": { "name": "keyword-spacing" } }] + }, + { + "removed": "space-in-brackets", + "replacedBy": [ + { "rule": { "name": "object-curly-spacing" } }, + { "rule": { "name": "array-bracket-spacing" } }, + { "rule": { "name": "computed-property-spacing" } } + ] + }, + { + "removed": "space-return-throw-case", + "replacedBy": [{ "rule": { "name": "keyword-spacing" } }] + }, + { + "removed": "space-unary-word-ops", + "replacedBy": [{ "rule": { "name": "space-unary-ops" } }] + }, + { + "removed": "spaced-line-comment", + "replacedBy": [{ "rule": { "name": "spaced-comment" } }] + }, + { "removed": "valid-jsdoc", "replacedBy": [] }, + { "removed": "require-jsdoc", "replacedBy": [] } + ] } diff --git a/cypress.config.js b/cypress.config.js new file mode 100644 index 000000000000..834c84a112e8 --- /dev/null +++ b/cypress.config.js @@ -0,0 +1,50 @@ +"use strict"; + +const { defineConfig } = require("cypress"); +const path = require("node:path"); +const webpack = require("webpack"); +const webpackPreprocessor = require("@cypress/webpack-preprocessor"); +const NodePolyfillPlugin = require("node-polyfill-webpack-plugin"); + +module.exports = defineConfig({ + e2e: { + setupNodeEvents(on) { + on( + "file:preprocessor", + webpackPreprocessor({ + webpackOptions: { + mode: "none", + resolve: { + alias: { + "../../../lib/linter$": "../../../build/eslint", + }, + }, + plugins: [ + new webpack.NormalModuleReplacementPlugin( + /^node:/u, + resource => { + resource.request = resource.request.replace( + /^node:/u, + "", + ); + }, + ), + new NodePolyfillPlugin(), + ], + stats: "errors-only", + }, + }), + ); + }, + specPattern: path.join( + __dirname, + "tests", + "lib", + "linter", + "linter.js", + ), + supportFile: false, + reporter: "progress", + screenshotOnRunFailure: false, + }, +}); diff --git a/docs/.eleventy.js b/docs/.eleventy.js index 01b9c96aaba4..355365ace440 100644 --- a/docs/.eleventy.js +++ b/docs/.eleventy.js @@ -7,164 +7,176 @@ const pluginTOC = require("eleventy-plugin-nesting-toc"); const markdownItAnchor = require("markdown-it-anchor"); const markdownItContainer = require("markdown-it-container"); const Image = require("@11ty/eleventy-img"); -const path = require("path"); +const path = require("node:path"); const { slug } = require("github-slugger"); const yaml = require("js-yaml"); -const { highlighter, lineNumberPlugin } = require("./src/_plugins/md-syntax-highlighter"); const { - DateTime -} = require("luxon"); + highlighter, + lineNumberPlugin, +} = require("./src/_plugins/md-syntax-highlighter"); +const { DateTime } = require("luxon"); const markdownIt = require("markdown-it"); const markdownItRuleExample = require("./tools/markdown-it-rule-example"); - -module.exports = function(eleventyConfig) { - - /* - * The docs stored in the eslint repo are loaded through eslint.org at - * at /docs/head to show the most recent version of the documentation - * based on the HEAD commit. This gives users a preview of what's coming - * in the next release. This is the way that the site works locally so - * it's easier to see if URLs are broken. - * - * When a release is published, HEAD is pushed to the "latest" branch. - * When a pre-release is published, HEAD is pushed to the "next" branch. - * Netlify deploys those branches as well, and in that case, we want the - * docs to be loaded from /docs/latest or /docs/next on eslint.org. - * - * The path prefix is turned off for deploy previews so we can properly - * see changes before deployed. - */ - - let pathPrefix = "/docs/head/"; - - if (process.env.CONTEXT === "deploy-preview") { - pathPrefix = "/"; - } else if (process.env.BRANCH === "latest") { - pathPrefix = "/docs/latest/"; - } else if (process.env.BRANCH === "next") { - pathPrefix = "/docs/next/"; - } - - //------------------------------------------------------------------------------ - // Data - //------------------------------------------------------------------------------ - - // Load site-specific data - const siteName = process.env.ESLINT_SITE_NAME || "en"; - - eleventyConfig.addGlobalData("site_name", siteName); - eleventyConfig.addGlobalData("GIT_BRANCH", process.env.BRANCH); - eleventyConfig.addGlobalData("HEAD", process.env.BRANCH === "main"); - eleventyConfig.addGlobalData("NOINDEX", process.env.BRANCH !== "latest"); - eleventyConfig.addGlobalData("PATH_PREFIX", pathPrefix); - eleventyConfig.addDataExtension("yml", contents => yaml.load(contents)); - - //------------------------------------------------------------------------------ - // Filters - //------------------------------------------------------------------------------ - - eleventyConfig.addFilter("limitTo", (arr, limit) => arr.slice(0, limit)); - - eleventyConfig.addFilter("jsonify", variable => JSON.stringify(variable)); - - eleventyConfig.addFilter("slugify", str => { - if (!str) { - return ""; - } - - return slug(str); - }); - - eleventyConfig.addFilter("URIencode", str => { - if (!str) { - return ""; - } - return encodeURI(str); - }); - - /* order collection by the order specified in the front matter */ - eleventyConfig.addFilter("sortByPageOrder", values => values.slice().sort((a, b) => a.data.order - b.data.order)); - - eleventyConfig.addFilter("readableDate", dateObj => { - - // turn it into a JS Date string - const date = new Date(dateObj); - - // pass it to luxon for formatting - return DateTime.fromJSDate(date).toFormat("dd MMM, yyyy"); - }); - - eleventyConfig.addFilter("blogPermalinkDate", dateObj => { - - // turn it into a JS Date string - const date = new Date(dateObj); - - // pass it to luxon for formatting - return DateTime.fromJSDate(date).toFormat("yyyy/MM"); - }); - - eleventyConfig.addFilter("readableDateFromISO", ISODate => DateTime.fromISO(ISODate).toUTC().toLocaleString(DateTime.DATE_FULL)); - - eleventyConfig.addFilter("dollars", value => new Intl.NumberFormat("en-US", { - style: "currency", - currency: "USD" - }).format(value)); - - /* - * parse markdown from includes, used for author bios - * Source: https://github.com/11ty/eleventy/issues/658 - */ - eleventyConfig.addFilter("markdown", value => { - const markdown = markdownIt({ - html: true - }); - - return markdown.render(value); - }); - - /* - * Removes `.html` suffix from the given url. - * `page.url` will include the `.html` suffix for all documents - * except for those written as `index.html` (their `page.url` ends with a `/`). - */ - eleventyConfig.addFilter("prettyURL", url => { - if (url.endsWith(".html")) { - return url.slice(0, -".html".length); - } - - return url; - }); - - //------------------------------------------------------------------------------ - // Plugins - //------------------------------------------------------------------------------ - - eleventyConfig.addPlugin(eleventyNavigationPlugin); - eleventyConfig.addPlugin(syntaxHighlight, { - alwaysWrapLineHighlights: true, - templateFormats: ["liquid", "njk"] - }); - eleventyConfig.addPlugin(pluginRss); - eleventyConfig.addPlugin(pluginTOC, { - tags: ["h2", "h3", "h4"], - wrapper: "nav", // Element to put around the root `ol` - wrapperClass: "c-toc", // Class for the element around the root `ol` - headingText: "", // Optional text to show in heading above the wrapper element - headingTag: "h2" // Heading tag when showing heading above the wrapper element - }); - - /** @typedef {import("markdown-it/lib/token")} MarkdownItToken A MarkdownIt token. */ - - /** - * Generates HTML markup for an inline alert. - * @param {"warning"|"tip"|"important"} type The type of alert to create. - * @param {Array} tokens Array of MarkdownIt tokens to use. - * @param {number} index The index of the current token in the tokens array. - * @returns {string} The markup for the alert. - */ - function generateAlertMarkup(type, tokens, index) { - if (tokens[index].nesting === 1) { - return ` +const prismESLintHook = require("./tools/prism-eslint-hook"); +const preWrapperPlugin = require("./src/_plugins/pre-wrapper.js"); +const typescriptESLintParser = require("@typescript-eslint/parser"); + +module.exports = function (eleventyConfig) { + /* + * The docs stored in the eslint repo are loaded through eslint.org at + * at /docs/head to show the most recent version of the documentation + * based on the HEAD commit. This gives users a preview of what's coming + * in the next release. This is the way that the site works locally so + * it's easier to see if URLs are broken. + * + * When a release is published, HEAD is pushed to the "latest" branch. + * When a pre-release is published, HEAD is pushed to the "next" branch. + * Netlify deploys those branches as well, and in that case, we want the + * docs to be loaded from /docs/latest or /docs/next on eslint.org. + * + * The path prefix is turned off for deploy previews so we can properly + * see changes before deployed. + */ + + let pathPrefix = "/docs/head/"; + const isNumberVersion = + process.env.BRANCH && /^v\d+\.x$/u.test(process.env.BRANCH); + + if (process.env.CONTEXT === "deploy-preview") { + pathPrefix = "/"; + } else if (process.env.BRANCH === "latest") { + pathPrefix = "/docs/latest/"; + } else if (process.env.BRANCH === "next") { + pathPrefix = "/docs/next/"; + } else if (isNumberVersion) { + pathPrefix = `/docs/${process.env.BRANCH}/`; // `/docs/v8.x/`, `/docs/v9.x/`, `/docs/v10.x/` ... + } + + //------------------------------------------------------------------------------ + // Data + //------------------------------------------------------------------------------ + + // Load site-specific data + const siteName = process.env.ESLINT_SITE_NAME || "en"; + + eleventyConfig.addGlobalData("site_name", siteName); + eleventyConfig.addGlobalData("GIT_BRANCH", process.env.BRANCH); + eleventyConfig.addGlobalData("HEAD", process.env.BRANCH === "main"); + eleventyConfig.addGlobalData("NOINDEX", process.env.BRANCH !== "latest"); + eleventyConfig.addGlobalData("PATH_PREFIX", pathPrefix); + eleventyConfig.addGlobalData("is_number_version", isNumberVersion); + eleventyConfig.addDataExtension("yml", contents => yaml.load(contents)); + + //------------------------------------------------------------------------------ + // Filters + //------------------------------------------------------------------------------ + + eleventyConfig.addFilter("limitTo", (arr, limit) => arr.slice(0, limit)); + + eleventyConfig.addFilter("jsonify", variable => JSON.stringify(variable)); + + eleventyConfig.addFilter("slugify", str => { + if (!str) { + return ""; + } + + return slug(str); + }); + + eleventyConfig.addFilter("URIencode", str => { + if (!str) { + return ""; + } + return encodeURI(str); + }); + + /* order collection by the order specified in the front matter */ + eleventyConfig.addFilter("sortByPageOrder", values => + values.slice().sort((a, b) => a.data.order - b.data.order), + ); + + eleventyConfig.addFilter("readableDate", dateObj => { + // turn it into a JS Date string + const date = new Date(dateObj); + + // pass it to luxon for formatting + return DateTime.fromJSDate(date).toFormat("dd MMM, yyyy"); + }); + + eleventyConfig.addFilter("blogPermalinkDate", dateObj => { + // turn it into a JS Date string + const date = new Date(dateObj); + + // pass it to luxon for formatting + return DateTime.fromJSDate(date).toFormat("yyyy/MM"); + }); + + eleventyConfig.addFilter("readableDateFromISO", ISODate => + DateTime.fromISO(ISODate).toUTC().toLocaleString(DateTime.DATE_FULL), + ); + + eleventyConfig.addFilter("dollars", value => + new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + }).format(value), + ); + + /* + * parse markdown from includes, used for author bios + * Source: https://github.com/11ty/eleventy/issues/658 + */ + eleventyConfig.addFilter("markdown", value => { + const markdown = markdownIt({ + html: true, + }); + + return markdown.render(value); + }); + + /* + * Removes `.html` suffix from the given url. + * `page.url` will include the `.html` suffix for all documents + * except for those written as `index.html` (their `page.url` ends with a `/`). + */ + eleventyConfig.addFilter("prettyURL", url => { + if (url.endsWith(".html")) { + return url.slice(0, -".html".length); + } + + return url; + }); + + //------------------------------------------------------------------------------ + // Plugins + //------------------------------------------------------------------------------ + + eleventyConfig.addPlugin(eleventyNavigationPlugin); + eleventyConfig.addPlugin(syntaxHighlight, { + alwaysWrapLineHighlights: true, + templateFormats: ["liquid", "njk"], + }); + eleventyConfig.addPlugin(pluginRss); + eleventyConfig.addPlugin(pluginTOC, { + tags: ["h2", "h3", "h4"], + wrapper: "nav", // Element to put around the root `ol` + wrapperClass: "c-toc", // Class for the element around the root `ol` + headingText: "", // Optional text to show in heading above the wrapper element + headingTag: "h2", // Heading tag when showing heading above the wrapper element + }); + + /** @typedef {import("markdown-it/lib/token")} MarkdownItToken A MarkdownIt token. */ + + /** + * Generates HTML markup for an inline alert. + * @param {"warning"|"tip"|"important"} type The type of alert to create. + * @param {Array} tokens Array of MarkdownIt tokens to use. + * @param {number} index The index of the current token in the tokens array. + * @returns {string} The markup for the alert. + */ + function generateAlertMarkup(type, tokens, index) { + if (tokens[index].nesting === 1) { + return ` `.trim(); - } - - /** - * Encodes text in the base 64 format used in playground URL params. - * @param {string} text Text to be encoded to base 64. - * @see https://github.com/eslint/eslint.org/blob/1b2f2aabeac2955a076d61788da8a0008bca6fb6/src/playground/utils/unicode.js - * @returns {string} The base 64 encoded equivalent of the text. - */ - function encodeToBase64(text) { - /* global btoa -- It does exist, and is what the playground uses. */ - return btoa(unescape(encodeURIComponent(text))); - } - - // markdown-it plugin options for playground-linked code blocks in rule examples. - const ruleExampleOptions = markdownItRuleExample({ - open({ type, code, parserOptions, env }) { - const isRuleRemoved = !Object.prototype.hasOwnProperty.call(env.rules_meta, env.title); - - if (isRuleRemoved) { - return `
`; - } - - // See https://github.com/eslint/eslint.org/blob/ac38ab41f99b89a8798d374f74e2cce01171be8b/src/playground/App.js#L44 - const state = encodeToBase64( - JSON.stringify({ - options: { parserOptions }, - text: code - }) - ); - const prefix = process.env.CONTEXT && process.env.CONTEXT !== "deploy-preview" - ? "" - : "https://eslint.org"; - - return ` + } + + /** + * Encodes text in the base 64 format used in playground URL params. + * @param {string} text Text to be encoded to base 64. + * @see https://github.com/eslint/eslint.org/blob/1b2f2aabeac2955a076d61788da8a0008bca6fb6/src/playground/utils/unicode.js + * @returns {string} The base 64 encoded equivalent of the text. + */ + function encodeToBase64(text) { + return btoa(unescape(encodeURIComponent(text))); + } + + // markdown-it plugin options for playground-linked code blocks in rule examples. + const ruleExampleOptions = markdownItRuleExample({ + open({ type, code, languageOptions, env, codeBlockToken }) { + const isTypeScriptCode = + codeBlockToken.info === "ts" || codeBlockToken.info === "tsx"; + + prismESLintHook.addContentMustBeMarked( + codeBlockToken.content, + isTypeScriptCode + ? { ...languageOptions, parser: typescriptESLintParser } + : languageOptions, + ); + + const isRuleRemoved = !Object.hasOwn(env.rules_meta, env.title); + + /* + * TypeScript isn't yet supported on the playground: + * https://github.com/eslint/eslint.org/issues/709 + */ + if (isRuleRemoved || isTypeScriptCode) { + return `
`; + } + + // See https://github.com/eslint/eslint.org/blob/29e1d8a000592245e4a30c1996e794643e9b263a/src/playground/App.js#L91-L105 + const state = encodeToBase64( + JSON.stringify({ + options: languageOptions ? { languageOptions } : void 0, + text: code, + }), + ); + const prefix = + process.env.CONTEXT && process.env.CONTEXT !== "deploy-preview" + ? "" + : "https://eslint.org"; + + return `
Open in Playground `.trim(); - }, - close() { - return "
"; - } - }); - - const md = markdownIt({ html: true, linkify: true, typographer: true, highlight: (str, lang) => highlighter(md, str, lang) }) - .use(markdownItAnchor, { - slugify: s => slug(s) - }) - .use(markdownItContainer, "img-container", {}) - .use(markdownItContainer, "rule-example", ruleExampleOptions) - .use(markdownItContainer, "warning", { - render(tokens, idx) { - return generateAlertMarkup("warning", tokens, idx); - } - }) - .use(markdownItContainer, "tip", { - render(tokens, idx) { - return generateAlertMarkup("tip", tokens, idx); - } - }) - .use(markdownItContainer, "important", { - render(tokens, idx) { - return generateAlertMarkup("important", tokens, idx); - } - }) - .use(lineNumberPlugin) - .disable("code"); - - eleventyConfig.setLibrary("md", md); - - //------------------------------------------------------------------------------ - // Shortcodes - //------------------------------------------------------------------------------ - - eleventyConfig.addNunjucksShortcode("link", function(url) { - - // eslint-disable-next-line no-invalid-this -- Eleventy API - const urlData = this.ctx.further_reading_links[url]; - - if (!urlData) { - throw new Error(`Data missing for ${url}`); - } - - const { - domain, - title, - logo - } = urlData; - - return ` + }, + close() { + return "
"; + }, + }); + + const md = markdownIt({ + html: true, + linkify: true, + typographer: true, + highlight: (str, lang) => highlighter(md, str, lang), + }) + .use(markdownItAnchor, { + slugify: s => slug(s), + }) + .use(markdownItContainer, "img-container", {}) + .use(markdownItContainer, "rule-example", ruleExampleOptions) + .use(markdownItContainer, "warning", { + render(tokens, idx) { + return generateAlertMarkup("warning", tokens, idx); + }, + }) + .use(markdownItContainer, "tip", { + render(tokens, idx) { + return generateAlertMarkup("tip", tokens, idx); + }, + }) + .use(markdownItContainer, "important", { + render(tokens, idx) { + return generateAlertMarkup("important", tokens, idx); + }, + }) + .use(lineNumberPlugin) + .use(preWrapperPlugin) + .disable("code"); + + eleventyConfig.setLibrary("md", md); + + //------------------------------------------------------------------------------ + // Shortcodes + //------------------------------------------------------------------------------ + + eleventyConfig.addNunjucksShortcode("link", function (url) { + // eslint-disable-next-line no-invalid-this -- Eleventy API + const urlData = this.ctx.further_reading_links[url]; + + if (!urlData) { + throw new Error(`Data missing for ${url}`); + } + + const { domain, title, logo } = urlData; + + return `
Avatar image for ${domain} @@ -283,38 +310,47 @@ module.exports = function(eleventyConfig) {
`; - }); + }); - eleventyConfig.addShortcode("fixable", () => ` + eleventyConfig.addShortcode( + "fixable", + () => `
🔧 Fixable

if some problems reported by the rule are automatically fixable by the --fix command line option

-
`); +
`, + ); - eleventyConfig.addShortcode("recommended", () => ` + eleventyConfig.addShortcode( + "recommended", + () => `
✅ Recommended

if the "extends": "eslint:recommended" property in a configuration file enables the rule.

-
`); + `, + ); - eleventyConfig.addShortcode("hasSuggestions", () => ` + eleventyConfig.addShortcode( + "hasSuggestions", + () => `
💡 hasSuggestions

if some problems reported by the rule are manually fixable by editor suggestions

-
`); + `, + ); - eleventyConfig.addShortcode("related_rules", arr => { - const rules = arr; - let items = ""; + eleventyConfig.addShortcode("related_rules", arr => { + const rules = arr; + let items = ""; - rules.forEach(rule => { - const listItem = ``; - items += listItem; - }); + items += listItem; + }); - return ` + return ` `; - }); + }); - eleventyConfig.addShortcode("important", (text, url) => ` + eleventyConfig.addShortcode( + "important", + (text, url) => `
Important
${text}
- Learn more + ${ + url + ? `Learn more` + : "" + }
-
`); + `, + ); - eleventyConfig.addShortcode("warning", (text, url) => ` + eleventyConfig.addShortcode( + "warning", + (text, url) => `
${text}
Learn more
- `); + `, + ); - eleventyConfig.addShortcode("tip", (text, url) => ` + eleventyConfig.addShortcode( + "tip", + (text, url) => `
${text}
Learn more
- `); - - - eleventyConfig.addWatchTarget("./src/assets/"); + `, + ); - //------------------------------------------------------------------------------ - // File PassThroughs - //------------------------------------------------------------------------------ + eleventyConfig.addWatchTarget("./src/assets/"); - eleventyConfig.addPassthroughCopy({ - "./src/static": "/" - }); + //------------------------------------------------------------------------------ + // File PassThroughs + //------------------------------------------------------------------------------ - eleventyConfig.addPassthroughCopy("./src/assets/"); + eleventyConfig.addPassthroughCopy({ + "./src/static": "/", + }); - eleventyConfig.addPassthroughCopy({ - "./src/content/**/*.png": "/assets/images" - }); + eleventyConfig.addPassthroughCopy("./src/assets/"); - eleventyConfig.addPassthroughCopy({ - "./src/content/**/*.jpg": "/assets/images" - }); + eleventyConfig.addPassthroughCopy({ + "./src/content/**/*.png": "/assets/images", + }); - eleventyConfig.addPassthroughCopy({ - "./src/content/**/*.jpeg": "/assets/images" - }); + eleventyConfig.addPassthroughCopy({ + "./src/content/**/*.jpg": "/assets/images", + }); - eleventyConfig.addPassthroughCopy({ - "./src/content/**/*.svg": "/assets/images" - }); + eleventyConfig.addPassthroughCopy({ + "./src/content/**/*.jpeg": "/assets/images", + }); - eleventyConfig.addPassthroughCopy({ - "./src/content/**/*.mp4": "/assets/videos" - }); + eleventyConfig.addPassthroughCopy({ + "./src/content/**/*.svg": "/assets/images", + }); - eleventyConfig.addPassthroughCopy({ - "./src/content/**/*.pdf": "/assets/documents" - }); + eleventyConfig.addPassthroughCopy({ + "./src/content/**/*.mp4": "/assets/videos", + }); - eleventyConfig.addPassthroughCopy({ - "./node_modules/algoliasearch/dist/algoliasearch-lite.esm.browser.js": "/assets/js/algoliasearch.js" - }); + eleventyConfig.addPassthroughCopy({ + "./src/content/**/*.pdf": "/assets/documents", + }); - //------------------------------------------------------------------------------ - // Collections - //------------------------------------------------------------------------------ + eleventyConfig.addPassthroughCopy({ + "./node_modules/algoliasearch/dist/algoliasearch-lite.esm.browser.js": + "/assets/js/algoliasearch.js", + }); - eleventyConfig.addCollection("docs", collection => collection.getFilteredByGlob("./src/**/**/*.md")); + //------------------------------------------------------------------------------ + // Collections + //------------------------------------------------------------------------------ - eleventyConfig.addCollection("library", collection => collection.getFilteredByGlob("./src/library/**/*.md")); + eleventyConfig.addCollection("docs", collection => + collection.getFilteredByGlob("./src/**/**/*.md"), + ); + eleventyConfig.addCollection("library", collection => + collection.getFilteredByGlob("./src/library/**/*.md"), + ); - // START, eleventy-img (https://www.11ty.dev/docs/plugins/image/) - /* eslint-disable-next-line jsdoc/require-jsdoc + // START, eleventy-img (https://www.11ty.dev/docs/plugins/image/) + /* eslint-disable-next-line jsdoc/require-jsdoc -- This shortcode is currently unused. If we are going to use it, add JSDoc and describe what exactly is this doing. */ - function imageShortcode(source, alt, cls, sizes = "(max-width: 768px) 100vw, 50vw") { - const options = { - widths: [600, 900, 1500], - formats: ["webp", "jpeg"], - urlPath: "/assets/images/", - outputDir: "./_site/assets/images/", - filenameFormat(id, src, width, format) { - const extension = path.extname(src); - const name = path.basename(src, extension); - - return `${name}-${width}w.${format}`; - } - }; - - /** - * Resolves source - * @returns {string} URL or a local file path - */ - function getSRC() { - if (source.startsWith("http://") || source.startsWith("https://")) { - return source; - } - - /* - * for convenience, you only need to use the image's name in the shortcode, - * and this will handle appending the full path to it - */ - return path.join("./src/assets/images/", source); - } - - const fullSrc = getSRC(); - - - // generate images - Image(fullSrc, options); // eslint-disable-line new-cap -- `Image` is a function - - const imageAttributes = { - alt, - class: cls, - sizes, - loading: "lazy", - decoding: "async" - }; - - // get metadata - const metadata = Image.statsSync(fullSrc, options); - - return Image.generateHTML(metadata, imageAttributes); - } - eleventyConfig.addShortcode("image", imageShortcode); - - // END, eleventy-img - - //------------------------------------------------------------------------------ - // Settings - //------------------------------------------------------------------------------ - - /* - * Generate the sitemap only in certain contexts to prevent unwanted discovery of sitemaps that - * contain URLs we'd prefer not to appear in search results (URLs in sitemaps are considered important). - * In particular, we don't want to deploy https://eslint.org/docs/head/sitemap.xml - * We want to generate the sitemap for: - * - Local previews - * - Netlify deploy previews - * - Netlify production deploy of the `latest` branch (https://eslint.org/docs/latest/sitemap.xml) - * - * Netlify always sets `CONTEXT` environment variable. If it isn't set, we assume this is a local build. - */ - if ( - process.env.CONTEXT && // if this is a build on Netlify ... - process.env.CONTEXT !== "deploy-preview" && // ... and not for a deploy preview ... - process.env.BRANCH !== "latest" // .. and not of the `latest` branch ... - ) { - eleventyConfig.ignores.add("src/static/sitemap.njk"); // ... then don't generate the sitemap. - } - - return { - passthroughFileCopy: true, - - pathPrefix, - - markdownTemplateEngine: "njk", - htmlTemplateEngine: "njk", - - dir: { - input: "src", - includes: "_includes", - layouts: "_includes/layouts", - data: "_data", - output: "_site" - } - }; + function imageShortcode( + source, + alt, + cls, + sizes = "(max-width: 768px) 100vw, 50vw", + ) { + const options = { + widths: [600, 900, 1500], + formats: ["webp", "jpeg"], + urlPath: "/assets/images/", + outputDir: "./_site/assets/images/", + filenameFormat(id, src, width, format) { + const extension = path.extname(src); + const name = path.basename(src, extension); + + return `${name}-${width}w.${format}`; + }, + }; + + /** + * Resolves source + * @returns {string} URL or a local file path + */ + function getSRC() { + if (source.startsWith("http://") || source.startsWith("https://")) { + return source; + } + + /* + * for convenience, you only need to use the image's name in the shortcode, + * and this will handle appending the full path to it + */ + return path.join("./src/assets/images/", source); + } + + const fullSrc = getSRC(); + + // generate images + Image(fullSrc, options); // eslint-disable-line new-cap -- `Image` is a function + + const imageAttributes = { + alt, + class: cls, + sizes, + loading: "lazy", + decoding: "async", + }; + + // get metadata + const metadata = Image.statsSync(fullSrc, options); + + return Image.generateHTML(metadata, imageAttributes); + } + eleventyConfig.addShortcode("image", imageShortcode); + + // END, eleventy-img + + //------------------------------------------------------------------------------ + // Settings + //------------------------------------------------------------------------------ + + /* + * Generate the sitemap only in certain contexts to prevent unwanted discovery of sitemaps that + * contain URLs we'd prefer not to appear in search results (URLs in sitemaps are considered important). + * In particular, we don't want to deploy https://eslint.org/docs/head/sitemap.xml + * We want to generate the sitemap for: + * - Local previews + * - Netlify deploy previews + * - Netlify production deploy of the `latest` branch (https://eslint.org/docs/latest/sitemap.xml) + * + * Netlify always sets `CONTEXT` environment variable. If it isn't set, we assume this is a local build. + */ + if ( + process.env.CONTEXT && // if this is a build on Netlify ... + process.env.CONTEXT !== "deploy-preview" && // ... and not for a deploy preview ... + process.env.BRANCH !== "latest" // .. and not of the `latest` branch ... + ) { + eleventyConfig.ignores.add("src/static/sitemap.njk"); // ... then don't generate the sitemap. + } + + return { + passthroughFileCopy: true, + + pathPrefix, + + markdownTemplateEngine: "njk", + htmlTemplateEngine: "njk", + + dir: { + input: "src", + includes: "_includes", + layouts: "_includes/layouts", + data: "_data", + output: "_site", + }, + }; }; diff --git a/docs/.stylelintrc.json b/docs/.stylelintrc.json index ab3b3fd039d8..5c0cc6677b2a 100644 --- a/docs/.stylelintrc.json +++ b/docs/.stylelintrc.json @@ -1,33 +1,40 @@ { - "extends": ["stylelint-config-standard-scss"], - "rules": { - "alpha-value-notation": "number", - "at-rule-empty-line-before": null, - "color-function-notation": "legacy", - "custom-property-empty-line-before": null, - "custom-property-pattern": null, - "declaration-block-no-duplicate-properties": [true, { - "ignore": ["consecutive-duplicates-with-different-values"] - }], - "declaration-block-no-redundant-longhand-properties": null, - "hue-degree-notation": "number", - "indentation": 4, - "max-line-length": null, - "no-descending-specificity": null, - "number-leading-zero": null, - "number-no-trailing-zeros": null, - "selector-class-pattern": null, - "value-keyword-case": null - }, - "overrides": [ - { - "files": [ - "**/*.html" - ], - "extends": ["stylelint-config-html/html", "stylelint-config-standard"] - } + "extends": ["stylelint-config-standard-scss", "stylelint-config-prettier"], + "rules": { + "alpha-value-notation": "number", + "at-rule-empty-line-before": null, + "color-function-notation": "legacy", + "custom-property-empty-line-before": null, + "custom-property-pattern": null, + "declaration-block-no-duplicate-properties": [ + true, + { + "ignore": ["consecutive-duplicates-with-different-values"] + } ], - "ignoreFiles": [ - "_site/**" - ] - } + "declaration-block-no-redundant-longhand-properties": null, + "hue-degree-notation": "number", + "no-descending-specificity": null, + "number-leading-zero": null, + "number-no-trailing-zeros": null, + "selector-class-pattern": null, + "value-keyword-case": null + }, + "overrides": [ + { + "files": ["**/*.html"], + "extends": [ + "stylelint-config-html/html", + "stylelint-config-standard", + "stylelint-config-prettier" + ] + }, + { + "files": ["**/*.scss"], + "rules": { + "scss/operator-no-newline-after": null + } + } + ], + "ignoreFiles": ["_site/**"] +} diff --git a/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.js b/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.js index 5f3e677f638f..c0dd5360b64e 100644 --- a/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.js +++ b/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.js @@ -7,51 +7,57 @@ // The enforce-foo-bar rule definition module.exports = { - meta: { - type: "problem", - docs: { - description: "Enforce that a variable named `foo` can only be assigned a value of 'bar'." - }, - fixable: "code", - schema: [] - }, - create(context) { - return { - - // Performs action in the function on every variable declarator - VariableDeclarator(node) { - - // Check if a `const` variable declaration - if (node.parent.kind === "const") { - - // Check if variable name is `foo` - if (node.id.type === "Identifier" && node.id.name === "foo") { - - // Check if value of variable is "bar" - if (node.init && node.init.type === "Literal" && node.init.value !== "bar") { - - /* - * Report error to ESLint. Error message uses - * a message placeholder to include the incorrect value - * in the error message. - * Also includes a `fix(fixer)` function that replaces - * any values assigned to `const foo` with "bar". - */ - context.report({ - node, - message: 'Value other than "bar" assigned to `const foo`. Unexpected value: {{ notBar }}.', - data: { - notBar: node.init.value - }, - fix(fixer) { - return fixer.replaceText(node.init, '"bar"'); - } - }); - } - } - } - } - }; - } + meta: { + type: "problem", + docs: { + description: + "Enforce that a variable named `foo` can only be assigned a value of 'bar'.", + }, + fixable: "code", + schema: [], + }, + create(context) { + return { + // Performs action in the function on every variable declarator + VariableDeclarator(node) { + // Check if a `const` variable declaration + if (node.parent.kind === "const") { + // Check if variable name is `foo` + if ( + node.id.type === "Identifier" && + node.id.name === "foo" + ) { + // Check if value of variable is "bar" + if ( + node.init && + node.init.type === "Literal" && + node.init.value !== "bar" + ) { + /* + * Report error to ESLint. Error message uses + * a message placeholder to include the incorrect value + * in the error message. + * Also includes a `fix(fixer)` function that replaces + * any values assigned to `const foo` with "bar". + */ + context.report({ + node, + message: + 'Value other than "bar" assigned to `const foo`. Unexpected value: {{ notBar }}.', + data: { + notBar: node.init.value, + }, + fix(fixer) { + return fixer.replaceText( + node.init, + '"bar"', + ); + }, + }); + } + } + } + }, + }; + }, }; - diff --git a/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.test.js b/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.test.js index d5f9c40334db..19882546b817 100644 --- a/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.test.js +++ b/docs/_examples/custom-rule-tutorial-code/enforce-foo-bar.test.js @@ -1,34 +1,39 @@ -/** +/** * @fileoverview Tests for enforce-foo-bar.js rule. * @author Ben Perlmutter -*/ + */ "use strict"; -const {RuleTester} = require("eslint"); +const { RuleTester } = require("eslint"); const fooBarRule = require("./enforce-foo-bar"); const ruleTester = new RuleTester({ - // Must use at least ecmaVersion 2015 because - // that's when `const` variable were introduced. - parserOptions: { ecmaVersion: 2015 } + // Must use at least ecmaVersion 2015 because + // that's when `const` variable were introduced. + languageOptions: { ecmaVersion: 2015 }, }); // Throws error if the tests in ruleTester.run() do not pass ruleTester.run( - "enforce-foo-bar", // rule name - fooBarRule, // rule code - { // checks - // 'valid' checks cases that should pass - valid: [{ - code: "const foo = 'bar';", - }], - // 'invalid' checks cases that should not pass - invalid: [{ - code: "const foo = 'baz';", - output: 'const foo = "bar";', - errors: 1, - }], - } + "enforce-foo-bar", // rule name + fooBarRule, // rule code + { + // checks + // 'valid' checks cases that should pass + valid: [ + { + code: "const foo = 'bar';", + }, + ], + // 'invalid' checks cases that should not pass + invalid: [ + { + code: "const foo = 'baz';", + output: 'const foo = "bar";', + errors: 1, + }, + ], + }, ); -console.log("All tests passed!"); \ No newline at end of file +console.log("All tests passed!"); diff --git a/docs/_examples/custom-rule-tutorial-code/eslint-plugin-example.js b/docs/_examples/custom-rule-tutorial-code/eslint-plugin-example.js index 1a32ca4db0a2..e4fecd276924 100644 --- a/docs/_examples/custom-rule-tutorial-code/eslint-plugin-example.js +++ b/docs/_examples/custom-rule-tutorial-code/eslint-plugin-example.js @@ -1,7 +1,7 @@ -/** +/** * @fileoverview Example an ESLint plugin with a custom rule. * @author Ben Perlmutter -*/ + */ "use strict"; const fooBarRule = require("./enforce-foo-bar"); diff --git a/docs/_examples/custom-rule-tutorial-code/eslint.config.js b/docs/_examples/custom-rule-tutorial-code/eslint.config.js index cf08f1ee57cd..a55e4f26fd71 100644 --- a/docs/_examples/custom-rule-tutorial-code/eslint.config.js +++ b/docs/_examples/custom-rule-tutorial-code/eslint.config.js @@ -1,23 +1,23 @@ -/** +/** * @fileoverview Example ESLint config file that uses the custom rule from this tutorial. * @author Ben Perlmutter -*/ + */ "use strict"; // Import the ESLint plugin const eslintPluginExample = require("./eslint-plugin-example"); module.exports = [ - { - files: ["**/*.js"], - languageOptions: { - sourceType: "commonjs", - ecmaVersion: "latest", - }, - // Using the eslint-plugin-example plugin defined locally - plugins: {"example": eslintPluginExample}, - rules: { - "example/enforce-foo-bar": "error", - }, - } -] + { + files: ["**/*.js"], + languageOptions: { + sourceType: "commonjs", + ecmaVersion: "latest", + }, + // Using the eslint-plugin-example plugin defined locally + plugins: { example: eslintPluginExample }, + rules: { + "example/enforce-foo-bar": "error", + }, + }, +]; diff --git a/docs/_examples/custom-rule-tutorial-code/example.js b/docs/_examples/custom-rule-tutorial-code/example.js index 0d6da91d49ea..1f81f9880227 100644 --- a/docs/_examples/custom-rule-tutorial-code/example.js +++ b/docs/_examples/custom-rule-tutorial-code/example.js @@ -1,7 +1,7 @@ -/** +/** * @fileoverview Example of a file that will fail the custom rule in this tutorial. * @author Ben Perlmutter -*/ + */ "use strict"; /* eslint-disable no-unused-vars -- Disable other rule causing problem for this file */ @@ -13,10 +13,9 @@ // npx eslint example.js --fix function correctFooBar() { - const foo = "bar"; + const foo = "bar"; } -function incorrectFoo(){ - const foo = "baz"; // Problem! +function incorrectFoo() { + const foo = "baz"; // Problem! } - diff --git a/docs/_examples/custom-rule-tutorial-code/package.json b/docs/_examples/custom-rule-tutorial-code/package.json index 0578c79496c9..12c159677a23 100644 --- a/docs/_examples/custom-rule-tutorial-code/package.json +++ b/docs/_examples/custom-rule-tutorial-code/package.json @@ -1,5 +1,6 @@ { "name": "eslint-plugin-example", + "private": true, "version": "1.0.0", "description": "ESLint plugin for enforce-foo-bar rule.", "main": "eslint-plugin-example.js", @@ -9,7 +10,7 @@ "eslint-plugin" ], "peerDependencies": { - "eslint": ">=8.0.0" + "eslint": ">=9.0.0" }, "scripts": { "test": "node enforce-foo-bar.test.js" @@ -17,6 +18,6 @@ "author": "", "license": "ISC", "devDependencies": { - "eslint": "^8.36.0" + "eslint": "^9.1.1" } -} \ No newline at end of file +} diff --git a/docs/_examples/integration-tutorial-code/example-eslint-integration.js b/docs/_examples/integration-tutorial-code/example-eslint-integration.js index f36b4e46e760..8840fe7fc624 100644 --- a/docs/_examples/integration-tutorial-code/example-eslint-integration.js +++ b/docs/_examples/integration-tutorial-code/example-eslint-integration.js @@ -1,4 +1,4 @@ -/** +/** * @fileoverview An example of how to integrate ESLint into your own tool * @author Ben Perlmutter */ @@ -6,57 +6,60 @@ const { ESLint } = require("eslint"); // Create an instance of ESLint with the configuration passed to the function -function createESLintInstance(overrideConfig){ - return new ESLint({ useEslintrc: false, overrideConfig: overrideConfig, fix: true }); +function createESLintInstance(overrideConfig) { + return new ESLint({ + overrideConfigFile: true, + overrideConfig, + fix: true, + }); } // Lint the specified files and return the error results async function lintAndFix(eslint, filePaths) { - const results = await eslint.lintFiles(filePaths); + const results = await eslint.lintFiles(filePaths); - // Apply automatic fixes and output fixed code - await ESLint.outputFixes(results); + // Apply automatic fixes and output fixed code + await ESLint.outputFixes(results); - return results; + return results; } // Log results to console if there are any problems function outputLintingResults(results) { - // Identify the number of problems found - const problems = results.reduce((acc, result) => acc + result.errorCount + result.warningCount, 0); - - if (problems > 0) { - console.log("Linting errors found!"); - console.log(results); - } else { - console.log("No linting errors found."); - } - return results; + // Identify the number of problems found + const problems = results.reduce( + (acc, result) => acc + result.errorCount + result.warningCount, + 0, + ); + + if (problems > 0) { + console.log("Linting errors found!"); + console.log(results); + } else { + console.log("No linting errors found."); + } + return results; } // Put previous functions all together async function lintFiles(filePaths) { + // The ESLint configuration. Alternatively, you could load the configuration + // from an eslint.config.js file or just use the default config. + const overrideConfig = { + languageOptions: { + ecmaVersion: 2018, + sourceType: "commonjs", + }, + rules: { + "no-console": "error", + "no-unused-vars": "warn", + }, + }; - // The ESLint configuration. Alternatively, you could load the configuration - // from a .eslintrc file or just use the default config. - const overrideConfig = { - env: { - es6: true, - node: true, - }, - parserOptions: { - ecmaVersion: 2018, - }, - rules: { - "no-console": "error", - "no-unused-vars": "warn", - }, - }; - - const eslint = createESLintInstance(overrideConfig); - const results = await lintAndFix(eslint, filePaths); - return outputLintingResults(results); + const eslint = createESLintInstance(overrideConfig); + const results = await lintAndFix(eslint, filePaths); + return outputLintingResults(results); } // Export integration -module.exports = { lintFiles } \ No newline at end of file +module.exports = { lintFiles }; diff --git a/docs/_examples/integration-tutorial-code/example-eslint-integration.test.js b/docs/_examples/integration-tutorial-code/example-eslint-integration.test.js index 5db9aead60ac..a3760238e2a7 100644 --- a/docs/_examples/integration-tutorial-code/example-eslint-integration.test.js +++ b/docs/_examples/integration-tutorial-code/example-eslint-integration.test.js @@ -5,23 +5,28 @@ const { lintFiles } = require("./example-eslint-integration"); -async function testExampleEslintIntegration(){ - const filePaths = ["sample-data/test-file.js"]; - const lintResults = await lintFiles(filePaths); +async function testExampleEslintIntegration() { + const filePaths = ["sample-data/test-file.js"]; + const lintResults = await lintFiles(filePaths); - // Test cases - if(lintResults[0].messages.length !== 6){ - throw new Error("Expected 6 linting problems, got " + lintResults[0].messages.length); - } - const messageRuleIds = new Set() - lintResults[0].messages.forEach(msg => messageRuleIds.add(msg.ruleId)); - if(messageRuleIds.size !== 2){ - throw new Error("Expected 2 linting rule, got " + messageRuleIds.size); - } - if(!messageRuleIds.has("no-console")){ - throw new Error("Expected linting rule 'no-console', got " + messageRuleIds); - } - console.log("All tests passed!"); + // Test cases + if (lintResults[0].messages.length !== 6) { + throw new Error( + "Expected 6 linting problems, got " + + lintResults[0].messages.length, + ); + } + const messageRuleIds = new Set(); + lintResults[0].messages.forEach(msg => messageRuleIds.add(msg.ruleId)); + if (messageRuleIds.size !== 2) { + throw new Error("Expected 2 linting rule, got " + messageRuleIds.size); + } + if (!messageRuleIds.has("no-console")) { + throw new Error( + "Expected linting rule 'no-console', got " + messageRuleIds, + ); + } + console.log("All tests passed!"); } -testExampleEslintIntegration() \ No newline at end of file +testExampleEslintIntegration(); diff --git a/docs/_examples/integration-tutorial-code/package.json b/docs/_examples/integration-tutorial-code/package.json index df00d7382f19..c31fecbaf2fe 100644 --- a/docs/_examples/integration-tutorial-code/package.json +++ b/docs/_examples/integration-tutorial-code/package.json @@ -1,5 +1,6 @@ { - "name": "_integration-tutorial-code", + "name": "integration-tutorial-code", + "private": true, "version": "1.0.0", "description": "", "main": "index.js", @@ -10,6 +11,6 @@ "author": "", "license": "ISC", "dependencies": { - "eslint": "^8.39.0" + "eslint": "^9.1.1" } } diff --git a/docs/_examples/integration-tutorial-code/sample-data/test-file.js b/docs/_examples/integration-tutorial-code/sample-data/test-file.js index 425375f8f8ad..06791a8cd133 100644 --- a/docs/_examples/integration-tutorial-code/sample-data/test-file.js +++ b/docs/_examples/integration-tutorial-code/sample-data/test-file.js @@ -1,4 +1,4 @@ -/** +/** * @fileoverview Example data to lint using ESLint. This file contains a variety of errors. * @author Ben Perlmutter */ @@ -7,23 +7,23 @@ const y = 20; function add(a, b) { - // Unexpected console statement (no-console from configured rules) - console.log('Adding two numbers'); - return a + b; + // Unexpected console statement (no-console from configured rules) + console.log("Adding two numbers"); + return a + b; } // 'result' is assigned a value but never used (no-unused-vars from configured rules) const result = add(x, 5); if (x > 5) { - // Unexpected console statement (no-console from configured rules) - console.log('x is greater than 5'); + // Unexpected console statement (no-console from configured rules) + console.log("x is greater than 5"); } else { - // Unexpected console statement (no-console from configured rules) - console.log('x is not greater than 5'); + // Unexpected console statement (no-console from configured rules) + console.log("x is not greater than 5"); } // 'subtract' is defined but never used (no-unused-vars from configured rules) function subtract(a, b) { - return a - b; + return a - b; } diff --git a/docs/netlify.toml b/docs/netlify.toml new file mode 100644 index 000000000000..541ade36818c --- /dev/null +++ b/docs/netlify.toml @@ -0,0 +1,2 @@ +[build] +command = "cd .. && npm install && cd ./docs && npm run build" diff --git a/docs/package.json b/docs/package.json index 163db5f943b1..3194255ff85c 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,7 +1,7 @@ { "name": "docs-eslint", "private": true, - "version": "8.56.0", + "version": "9.31.0", "description": "", "main": "index.js", "keywords": [], @@ -23,45 +23,42 @@ "start": "npm-run-all build:sass build:postcss --parallel *:*:watch" }, "devDependencies": { - "@11ty/eleventy": "^2.0.1", + "@11ty/eleventy": "^3.0.0", + "@11ty/eleventy-fetch": "^4.0.0", "@11ty/eleventy-img": "^3.1.1", "@11ty/eleventy-navigation": "^0.3.5", "@11ty/eleventy-plugin-rss": "^1.1.1", "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.0", "@munter/tap-render": "^0.2.0", "@types/markdown-it": "^12.2.3", + "@typescript-eslint/parser": "^8.27.0", "algoliasearch": "^4.12.1", "autoprefixer": "^10.4.13", "cross-env": "^7.0.3", "cssnano": "^5.1.14", - "dom-parser": "^0.1.6", "eleventy-plugin-nesting-toc": "^1.3.0", - "eleventy-plugin-page-assets": "^0.3.0", - "eleventy-plugin-reading-time": "^0.0.1", "github-slugger": "^1.5.0", "hyperlink": "^5.0.4", - "imagemin": "^8.0.1", "imagemin-cli": "^7.0.0", "js-yaml": "^3.14.1", "luxon": "^2.4.0", "markdown-it": "^12.2.0", "markdown-it-anchor": "^8.1.2", "markdown-it-container": "^3.0.0", - "netlify-cli": "^10.3.1", - "npm-run-all": "^4.1.5", + "npm-run-all2": "^5.0.0", "postcss-cli": "^10.0.0", "postcss-html": "^1.5.0", "prismjs": "^1.29.0", - "rimraf": "^3.0.2", - "sass": "^1.52.1", + "sass": "^1.85.1", "stylelint": "^14.13.0", "stylelint-config-html": "^1.1.0", + "stylelint-config-prettier": "^9.0.5", "stylelint-config-standard": "^29.0.0", "stylelint-config-standard-scss": "^5.0.0", "tap-spot": "^1.1.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=20.0.0" }, "browserslist": [ "defaults", diff --git a/docs/postcss.config.js b/docs/postcss.config.js index 128e741f0277..1862be85c1d5 100644 --- a/docs/postcss.config.js +++ b/docs/postcss.config.js @@ -1,9 +1,6 @@ "use strict"; module.exports = { - plugins: [ - require("autoprefixer"), - require("cssnano") - ], - map: false + plugins: [require("autoprefixer"), require("cssnano")], + map: false, }; diff --git a/docs/src/_data/config.json b/docs/src/_data/config.json index 9bd751d18098..e7918441959b 100644 --- a/docs/src/_data/config.json +++ b/docs/src/_data/config.json @@ -1,5 +1,3 @@ { - "lang": "en", - "version": "7.26.0", - "showNextVersion": false + "lang": "en" } diff --git a/docs/src/_data/conversions.json b/docs/src/_data/conversions.json new file mode 100644 index 000000000000..dd9023606682 --- /dev/null +++ b/docs/src/_data/conversions.json @@ -0,0 +1,42 @@ +{ + "toNpmCommands": { + "install": "install", + "init": "init", + "init-create": "init" + }, + "toNpmArgs": { + "--global": "--global", + "--save-dev": "--save-dev", + "-y": "-y" + }, + "toYarnCommands": { + "install": "add", + "init": "init", + "init-create": "create" + }, + "toYarnArgs": { + "--global": "global", + "--save-dev": "--dev", + "-y": "-y" + }, + "toPnpmCommands": { + "install": "add", + "init": "init", + "init-create": "create" + }, + "toPnpmArgs": { + "--global": "--global", + "--save-dev": "--save-dev", + "-y": "-y" + }, + "toBunCommands": { + "install": "add", + "init": "init", + "init-create": "create" + }, + "toBunArgs": { + "--global": "--global", + "--save-dev": "--dev", + "-y": "-y" + } +} diff --git a/docs/src/_data/eslintVersion.js b/docs/src/_data/eslintVersion.js deleted file mode 100644 index 24964276c0d3..000000000000 --- a/docs/src/_data/eslintVersion.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @fileoverview Data file for package information - * @author Nicholas C. Zakas - */ - -//----------------------------------------------------------------------------- -// Requirements -//----------------------------------------------------------------------------- - -const fs = require("fs"); -const path = require("path"); - -//----------------------------------------------------------------------------- -// Initialization -//----------------------------------------------------------------------------- - -const pkgPath = path.resolve(__dirname, "../../package.json"); -const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); -const { ESLINT_VERSION } = process.env; - -//----------------------------------------------------------------------------- -// Exports -//----------------------------------------------------------------------------- - -/* - * Because we want to differentiate between the development branch and the - * most recent release, we need a way to override the version. The - * ESLINT_VERSION environment variable allows us to set this to override - * the value displayed on the website. The most common case is we will set - * this equal to "HEAD" for the version that is currently in development on - * GitHub. Otherwise, we will use the version from package.json. - */ - -module.exports = ESLINT_VERSION ?? pkg.version; diff --git a/docs/src/_data/eslintVersions.js b/docs/src/_data/eslintVersions.js new file mode 100644 index 000000000000..a9d3b3735591 --- /dev/null +++ b/docs/src/_data/eslintVersions.js @@ -0,0 +1,77 @@ +/** + * @fileoverview Data for version selectors + * @author Milos Djermanovic + */ + +"use strict"; + +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + +const eleventyFetch = require("@11ty/eleventy-fetch"); + +//----------------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------------- + +module.exports = async function () { + const thisBranch = process.env.BRANCH; + const thisVersion = require("../../package.json").version; + + // Fetch the current list of ESLint versions from the `main` branch on GitHub + const url = + "https://raw.githubusercontent.com/eslint/eslint/main/docs/src/_data/versions.json"; + + const data = await eleventyFetch(url, { + duration: "1d", // Cache for local development. Netlify does not keep this cache and will therefore always fetch from GitHub. + type: "json", + }); + + const { items } = data; + + let foundItemForThisBranch = false; + let isPrereleasePhase = false; + + for (const item of items) { + const isItemForThisBranch = item.branch === thisBranch; + + foundItemForThisBranch ||= isItemForThisBranch; + + const isNumberVersion = /^\d/u.test(item.version); // `false` for HEAD + + if (isNumberVersion) { + // Make sure the version is correct + if (isItemForThisBranch) { + item.version = thisVersion; + } + + item.display = `v${item.version}`; + } else { + item.display = item.version; + } + + if (isItemForThisBranch) { + item.selected = true; + } + + if (item.branch === "next") { + isPrereleasePhase = true; + } + } + + // Add an empty item if this is not a production branch + if (!foundItemForThisBranch) { + items.unshift({ + version: "", + branch: "", + display: "", + path: "", + selected: true, + }); + } + + data.isPrereleasePhase = isPrereleasePhase; + + return data; +}; diff --git a/docs/src/_data/flags.js b/docs/src/_data/flags.js new file mode 100644 index 000000000000..6e11aaf9887e --- /dev/null +++ b/docs/src/_data/flags.js @@ -0,0 +1,49 @@ +/** + * @fileoverview Convenience helper for feature flags. + * @author Nicholas C. Zakas + */ + +"use strict"; + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +/** + * Determines whether the flag is used for test purposes only. + * @param {string} name The flag name to check. + * @returns {boolean} `true` if the flag is used for test purposes only. + */ +function isTestOnlyFlag(name) { + return name.startsWith("test_only"); +} + +//----------------------------------------------------------------------------- +// Exports +//----------------------------------------------------------------------------- + +module.exports = function () { + const { + activeFlags, + inactiveFlags, + getInactivityReasonMessage, + } = require("../../../lib/shared/flags"); + + return { + active: Object.fromEntries( + [...activeFlags].filter(([name]) => !isTestOnlyFlag(name)), + ), + inactive: Object.fromEntries( + [...inactiveFlags] + .filter(([name]) => !isTestOnlyFlag(name)) + .map(([name, inactiveFlagData]) => [ + name, + { + ...inactiveFlagData, + inactivityReason: + getInactivityReasonMessage(inactiveFlagData), + }, + ]), + ), + }; +}; diff --git a/docs/src/_data/further_reading_links.json b/docs/src/_data/further_reading_links.json index a33ce6c0b3bb..fdc47abc82f7 100644 --- a/docs/src/_data/further_reading_links.json +++ b/docs/src/_data/further_reading_links.json @@ -1,751 +1,800 @@ { - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "setter - JavaScript | MDN", - "description": "The set syntax binds an object property to a function to be called when there is an attempt to set that property." - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "getter - JavaScript | MDN", - "description": "The get syntax binds an object property to a function that will be called when that property is looked up." - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Working with objects - JavaScript | MDN", - "description": "JavaScript is designed on a simple object-based paradigm. An object is a collection of properties, and a property is an association between a name (or key) and a value. A property’s value can be a function, in which case the property is known as a method. In addition to objects that are predefined iâ€Ļ" - }, - "https://github.com/airbnb/javascript#arrows--one-arg-parens": { - "domain": "github.com", - "url": "https://github.com/airbnb/javascript#arrows--one-arg-parens", - "logo": "https://github.com/fluidicon.png", - "title": "GitHub - airbnb/javascript: JavaScript Style Guide", - "description": "JavaScript Style Guide. Contribute to airbnb/javascript development by creating an account on GitHub." - }, - "https://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html": { - "domain": "www.adequatelygood.com", - "url": "https://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html", - "logo": "https://www.adequatelygood.com/favicon.ico", - "title": "JavaScript Scoping and Hoisting", - "description": null - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#var_hoisting": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#var_hoisting", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "var - JavaScript | MDN", - "description": "The var statement declares a function-scoped or globally-scoped variable, optionally initializing it to a value." - }, - "https://en.wikipedia.org/wiki/Indent_style": { - "domain": "en.wikipedia.org", - "url": "https://en.wikipedia.org/wiki/Indent_style", - "logo": "https://en.wikipedia.org/static/apple-touch/wikipedia.png", - "title": "Indentation style - Wikipedia", - "description": null - }, - "https://github.com/maxogden/art-of-node#callbacks": { - "domain": "github.com", - "url": "https://github.com/maxogden/art-of-node#callbacks", - "logo": "https://github.com/fluidicon.png", - "title": "GitHub - maxogden/art-of-node: a short introduction to node.js", - "description": ":snowflake: a short introduction to node.js. Contribute to maxogden/art-of-node development by creating an account on GitHub." - }, - "https://web.archive.org/web/20171224042620/https://docs.nodejitsu.com/articles/errors/what-are-the-error-conventions/": { - "domain": "web.archive.org", - "url": "https://web.archive.org/web/20171224042620/https://docs.nodejitsu.com/articles/errors/what-are-the-error-conventions/", - "logo": "https://archive.org/favicon.ico", - "title": "What are the error conventions? - docs.nodejitsu.com", - "description": "docs.nodejitsu.com is a growing collection of how-to articles for node.js, written by the community and curated by Nodejitsu and friends. These articles range from basic to advanced, and provide relevant code samples and insights into the design and philosophy of node itself." - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Classes - JavaScript | MDN", - "description": "Classes are a template for creating objects. They encapsulate data with code to work on that data. Classes in JS are built on prototypes but also have some syntax and semantics that are not shared with ES5 class-like semantics." - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "static - JavaScript | MDN", - "description": "The static keyword defines a static method or property for a class, or a class static initialization block (see the link for more information about this usage). Neither static methods nor static properties can be called on instances of the class. Instead, they’re called on the class itself." - }, - "https://www.crockford.com/code.html": { - "domain": "www.crockford.com", - "url": "https://www.crockford.com/code.html", - "logo": "https://www.crockford.com/favicon.png", - "title": "Code Conventions for the JavaScript Programming Language", - "description": null - }, - "https://dojotoolkit.org/reference-guide/1.9/developer/styleguide.html": { - "domain": "dojotoolkit.org", - "url": "https://dojotoolkit.org/reference-guide/1.9/developer/styleguide.html", - "logo": "https://dojotoolkit.org/images/favicons/apple-touch-icon-152x152.png", - "title": "Dojo Style Guide — The Dojo Toolkit - Reference Guide", - "description": null - }, - "https://gist.github.com/isaacs/357981": { - "domain": "gist.github.com", - "url": "https://gist.github.com/isaacs/357981", - "logo": "https://gist.github.com/fluidicon.png", - "title": "A better coding convention for lists and object literals in JavaScript", - "description": "A better coding convention for lists and object literals in JavaScript - comma-first-var.js" - }, - "https://en.wikipedia.org/wiki/Cyclomatic_complexity": { - "domain": "en.wikipedia.org", - "url": "https://en.wikipedia.org/wiki/Cyclomatic_complexity", - "logo": "https://en.wikipedia.org/static/apple-touch/wikipedia.png", - "title": "Cyclomatic complexity - Wikipedia", - "description": null - }, - "https://ariya.io/2012/12/complexity-analysis-of-javascript-code": { - "domain": "ariya.io", - "url": "https://ariya.io/2012/12/complexity-analysis-of-javascript-code", - "logo": "https://ariya.io/favicon.ico", - "title": "Complexity Analysis of JavaScript Code", - "description": "Nobody likes to read complex code, especially if it’s someone’s else code. A preventive approach to block any complex code entering the application is by watching its complexity carefully." - }, - "https://craftsmanshipforsoftware.com/2015/05/25/complexity-for-javascript/": { - "domain": "craftsmanshipforsoftware.com", - "url": "https://craftsmanshipforsoftware.com/2015/05/25/complexity-for-javascript/", - "logo": "https://s0.wp.com/i/webclip.png", - "title": "Complexity for JavaScript", - "description": "The control of complexity control presents the core problem of software development. The huge variety of decisions a developer faces on a day-to-day basis cry for methods of controlling and containâ€Ļ" - }, - "https://web.archive.org/web/20160808115119/http://jscomplexity.org/complexity": { - "domain": "web.archive.org", - "url": "https://web.archive.org/web/20160808115119/http://jscomplexity.org/complexity", - "logo": "https://archive.org/favicon.ico", - "title": "About complexity | JSComplexity.org", - "description": "A discussion of software complexity metrics and how they are calculated." - }, - "https://github.com/eslint/eslint/issues/4808#issuecomment-167795140": { - "domain": "github.com", - "url": "https://github.com/eslint/eslint/issues/4808#issuecomment-167795140", - "logo": "https://github.com/fluidicon.png", - "title": "Complexity has no default ¡ Issue #4808 ¡ eslint/eslint", - "description": "Enabling the complexity rule with only a severity has no effect. We have tried to give sane defaults to all rules, and I think this should be no exception. I don't know what a good number would..." - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "switch - JavaScript | MDN", - "description": "The switch statement evaluates an expression, matching the expression’s value to a case clause, and executes statements associated with that case, as well as statements in cases that follow the matching case." - }, - "https://web.archive.org/web/20201112040809/http://markdaggett.com/blog/2013/02/15/functions-explained/": { - "domain": "web.archive.org", - "url": "https://web.archive.org/web/20201112040809/http://markdaggett.com/blog/2013/02/15/functions-explained/", - "logo": "https://web.archive.org/web/20201112040809im_/http://markdaggett.com/favicon.ico", - "title": "Functions Explained - Mark Daggett’s Blog", - "description": "A Deep Dive into JavaScript Functions\nBased on my readership I have to assume most of you are familiar with JavaScript already. Therefore, it may â€Ļ" - }, - "https://2ality.com/2015/09/function-names-es6.html": { - "domain": "2ality.com", - "url": "https://2ality.com/2015/09/function-names-es6.html", - "logo": "https://2ality.com/img/favicon.png", - "title": "The names of functions in ES6", - "description": null - }, - "https://leanpub.com/understandinges6/read/#leanpub-auto-generators": { - "domain": "leanpub.com", - "url": "https://leanpub.com/understandinges6/read/#leanpub-auto-generators", - "logo": "https://leanpub.com/understandinges6/read/favicons/mstile-310x310.png", - "title": "Read Understanding ECMAScript 6 | Leanpub", - "description": null - }, - "https://leanpub.com/understandinges6/read/#leanpub-auto-accessor-properties": { - "domain": "leanpub.com", - "url": "https://leanpub.com/understandinges6/read/#leanpub-auto-accessor-properties", - "logo": "https://leanpub.com/understandinges6/read/favicons/mstile-310x310.png", - "title": "Read Understanding ECMAScript 6 | Leanpub", - "description": null - }, - "https://javascriptweblog.wordpress.com/2011/01/04/exploring-javascript-for-in-loops/": { - "domain": "javascriptweblog.wordpress.com", - "url": "https://javascriptweblog.wordpress.com/2011/01/04/exploring-javascript-for-in-loops/", - "logo": "https://s1.wp.com/i/favicon.ico", - "title": "Exploring JavaScript for-in loops", - "description": "The for-in loop is the only cross-browser technique for iterating the properties of generic objects. There’s a bunch of literature about the dangers of using for-in to iterate arrays and whenâ€Ļ" - }, - "https://2ality.com/2012/01/objects-as-maps.html": { - "domain": "2ality.com", - "url": "https://2ality.com/2012/01/objects-as-maps.html", - "logo": "https://2ality.com/img/favicon.png", - "title": "The pitfalls of using objects as maps in JavaScript", - "description": null - }, - "https://web.archive.org/web/20160725154648/http://www.mind2b.com/component/content/article/24-software-module-size-and-file-size": { - "domain": "web.archive.org", - "url": "https://web.archive.org/web/20160725154648/http://www.mind2b.com/component/content/article/24-software-module-size-and-file-size", - "logo": "https://archive.org/favicon.ico", - "title": "Software Module size and file size", - "description": null - }, - "http://book.mixu.net/node/ch7.html": { - "domain": "book.mixu.net", - "url": "http://book.mixu.net/node/ch7.html", - "logo": null, - "title": "7. Control flow - Mixu’s Node book", - "description": null - }, - "https://web.archive.org/web/20220104141150/https://howtonode.org/control-flow": { - "domain": "web.archive.org", - "url": "https://web.archive.org/web/20220104141150/https://howtonode.org/control-flow", - "logo": "https://web.archive.org/web/20220104141150im_/https://howtonode.org/favicon.ico", - "title": "Control Flow in Node - How To Node - NodeJS", - "description": "Learn the zen of coding in NodeJS." - }, - "https://web.archive.org/web/20220127215850/https://howtonode.org/control-flow-part-ii": { - "domain": "web.archive.org", - "url": "https://web.archive.org/web/20220127215850/https://howtonode.org/control-flow-part-ii", - "logo": "https://web.archive.org/web/20220127215850im_/https://howtonode.org/favicon.ico", - "title": "Control Flow in Node Part II - How To Node - NodeJS", - "description": "Learn the zen of coding in NodeJS." - }, - "https://nodejs.org/api/buffer.html": { - "domain": "nodejs.org", - "url": "https://nodejs.org/api/buffer.html", - "logo": "https://nodejs.org/favicon.ico", - "title": "Buffer | Node.js v18.2.0 Documentation", - "description": null - }, - "https://github.com/ChALkeR/notes/blob/master/Lets-fix-Buffer-API.md": { - "domain": "github.com", - "url": "https://github.com/ChALkeR/notes/blob/master/Lets-fix-Buffer-API.md", - "logo": "https://github.com/fluidicon.png", - "title": "notes/Lets-fix-Buffer-API.md at master ¡ ChALkeR/notes", - "description": "Some public notes. Contribute to ChALkeR/notes development by creating an account on GitHub." - }, - "https://github.com/nodejs/node/issues/4660": { - "domain": "github.com", - "url": "https://github.com/nodejs/node/issues/4660", - "logo": "https://github.com/fluidicon.png", - "title": "Buffer(number) is unsafe ¡ Issue #4660 ¡ nodejs/node", - "description": "tl;dr This issue proposes: Change new Buffer(number) to return safe, zeroed-out memory Create a new API for creating uninitialized Buffers, Buffer.alloc(number) Update: Jan 15, 2016 Upon further co..." - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "debugger - JavaScript | MDN", - "description": "The debugger statement invokes any available debugging functionality, such as setting a breakpoint. If no debugging functionality is available, this statement has no effect." - }, - "https://ericlippert.com/2003/11/01/eval-is-evil-part-one/": { - "domain": "ericlippert.com", - "url": "https://ericlippert.com/2003/11/01/eval-is-evil-part-one/", - "logo": "https://s1.wp.com/i/favicon.ico", - "title": "Eval is evil, part one", - "description": "The eval method — which takes a string containing JScript code, compiles it and runs it — is probably the most powerful and most misused method in JScript. There are a few scenarios in â€Ļ" - }, - "https://javascriptweblog.wordpress.com/2010/04/19/how-evil-is-eval/": { - "domain": "javascriptweblog.wordpress.com", - "url": "https://javascriptweblog.wordpress.com/2010/04/19/how-evil-is-eval/", - "logo": "https://s1.wp.com/i/favicon.ico", - "title": "How evil is eval?", - "description": "“eval is Evil: The eval function is the most misused feature of JavaScript. Avoid it” Douglas Crockford in JavaScript: The Good Parts I like The Good Parts. It’s essential readingâ€Ļ" - }, - "https://bocoup.com/blog/the-catch-with-try-catch": { - "domain": "bocoup.com", - "url": "https://bocoup.com/blog/the-catch-with-try-catch", - "logo": "https://static3.bocoup.com/assets/2015/10/06163533/favicon.png", - "title": "The", - "description": "I’ve recently been working on an update to JavaScript Debug, which has me doing a lot of cross-browser testing, and I noticed a few “interesting quirks” with tryâ€Ļcatch in Internet Explorer 6-8 that I couldn’t find documented anywhere." - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Function.prototype.bind() - JavaScript | MDN", - "description": "The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called." - }, - "https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/": { - "domain": "www.smashingmagazine.com", - "url": "https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/", - "logo": "https://www.smashingmagazine.com/images/favicon/apple-touch-icon.png", - "title": "Understanding JavaScript Bind () — Smashing Magazine", - "description": "Function binding is probably your least concern when beginning with JavaScript, but when you realize that you need a solution to the problem of how to keep the context of “this” within another function, then you might not realize that what you actually need is Function.prototype.bind()." - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Operator precedence - JavaScript | MDN", - "description": "Operator precedence determines how operators are parsed concerning each other. Operators with higher precedence become the operands of operators with lower precedence." - }, - "https://es5.github.io/#C": { - "domain": "es5.github.io", - "url": "https://es5.github.io/#C", - "logo": "https://es5.github.io/favicon.ico", - "title": "Annotated ES5", - "description": null - }, - "https://benalman.com/news/2010/11/immediately-invoked-function-expression/": { - "domain": "benalman.com", - "url": "https://benalman.com/news/2010/11/immediately-invoked-function-expression/", - "logo": "https://benalman.com/favicon.ico", - "title": "Ben Alman Âģ Immediately-Invoked Function Expression (IIFE)", - "description": null - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Undeclared_var": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Undeclared_var", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "ReferenceError: assignment to undeclared variable “x” - JavaScript | MDN", - "description": "The JavaScript strict mode-only exception “Assignment to undeclared variable” occurs when the value has been assigned to an undeclared variable." - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_dead_zone": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_dead_zone", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "let - JavaScript | MDN", - "description": "The let statement declares a block-scoped local variable, optionally initializing it to a value." - }, - "https://es5.github.io/#x7.8.5": { - "domain": "es5.github.io", - "url": "https://es5.github.io/#x7.8.5", - "logo": "https://es5.github.io/favicon.ico", - "title": "Annotated ES5", - "description": null - }, - "https://es5.github.io/#x7.2": { - "domain": "es5.github.io", - "url": "https://es5.github.io/#x7.2", - "logo": "https://es5.github.io/favicon.ico", - "title": "Annotated ES5", - "description": null - }, - "https://web.archive.org/web/20200414142829/http://timelessrepo.com/json-isnt-a-javascript-subset": { - "domain": "web.archive.org", - "url": "https://web.archive.org/web/20200414142829/http://timelessrepo.com/json-isnt-a-javascript-subset", - "logo": "https://archive.org/favicon.ico", - "title": "JSON: The JavaScript subset that isn’t - Timeless", - "description": null - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Iterators and generators - JavaScript | MDN", - "description": "Iterators and Generators bring the concept of iteration directly into the core language and provide a mechanism for customizing the behavior of for...of loops." - }, - "https://kangax.github.io/es5-compat-table/es6/#Iterators": { - "domain": "kangax.github.io", - "url": "https://kangax.github.io/es5-compat-table/es6/#Iterators", - "logo": "https://github.io/favicon.ico", - "title": null, - "description": null - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Deprecated_and_obsolete_features#Object_methods": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Deprecated_and_obsolete_features#Object_methods", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Deprecated and obsolete features - JavaScript | MDN", - "description": "This page lists features of JavaScript that are deprecated (that is, still available but planned for removal) and obsolete (that is, no longer usable)." - }, - "https://www.emacswiki.org/emacs/SmartTabs": { - "domain": "www.emacswiki.org", - "url": "https://www.emacswiki.org/emacs/SmartTabs", - "logo": "https://www.emacswiki.org/favicon.ico", - "title": "EmacsWiki: Smart Tabs", - "description": null - }, - "https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-objects": { - "domain": "www.ecma-international.org", - "url": "https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-objects", - "logo": "https://www.ecma-international.org/ecma-262/6.0/favicon.ico", - "title": "ECMAScript 2015 Language Specification – ECMA-262 6th Edition", - "description": null - }, - "https://www.inkling.com/read/javascript-definitive-guide-david-flanagan-6th/chapter-3/wrapper-objects": { - "domain": "www.inkling.com", - "url": "https://www.inkling.com/read/javascript-definitive-guide-david-flanagan-6th/chapter-3/wrapper-objects", - "logo": "https://inklingstatic.a.ssl.fastly.net/static_assets/20220214.223700z.8c5796a9.docker/images/favicon.ico", - "title": "Unsupported Browser", - "description": null - }, - "https://tc39.es/ecma262/#prod-annexB-NonOctalDecimalEscapeSequence": { - "domain": "tc39.es", - "url": "https://tc39.es/ecma262/#prod-annexB-NonOctalDecimalEscapeSequence", - "logo": "https://tc39.es/ecma262/img/favicon.ico", - "title": "ECMAScriptÂŽ 2023 Language Specification", - "description": null - }, - "https://es5.github.io/#x15.8": { - "domain": "es5.github.io", - "url": "https://es5.github.io/#x15.8", - "logo": "https://es5.github.io/favicon.ico", - "title": "Annotated ES5", - "description": null - }, - "https://spin.atomicobject.com/2011/04/10/javascript-don-t-reassign-your-function-arguments/": { - "domain": "spin.atomicobject.com", - "url": "https://spin.atomicobject.com/2011/04/10/javascript-don-t-reassign-your-function-arguments/", - "logo": "https://spin.atomicobject.com/wp-content/themes/spin/images/favicon.ico", - "title": "JavaScript: Don’t Reassign Your Function Arguments", - "description": "The point of this post is to raise awareness that reassigning the value of an argument variable mutates the arguments object." - }, - "https://stackoverflow.com/questions/5869216/how-to-store-node-js-deployment-settings-configuration-files": { - "domain": "stackoverflow.com", - "url": "https://stackoverflow.com/questions/5869216/how-to-store-node-js-deployment-settings-configuration-files", - "logo": "https://cdn.sstatic.net/Sites/stackoverflow/Img/apple-touch-icon.png?v=c78bd457575a", - "title": "How to store Node.js deployment settings/configuration files?", - "description": "I have been working on a few Node apps, and I’ve been looking for a good pattern of storing deployment-related settings. In the Django world (where I come from), the common practise would be to hav..." - }, - "https://blog.benhall.me.uk/2012/02/storing-application-config-data-in/": { - "domain": "blog.benhall.me.uk", - "url": "https://blog.benhall.me.uk/2012/02/storing-application-config-data-in/", - "logo": null, - "title": "Storing Node.js application config data – Ben Hall’s Blog", - "description": null - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Promise - JavaScript | MDN", - "description": "The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value." - }, - "https://johnresig.com/blog/objectgetprototypeof/": { - "domain": "johnresig.com", - "url": "https://johnresig.com/blog/objectgetprototypeof/", - "logo": "https://johnresig.com/wp-content/uploads/2017/04/cropped-jeresig-2016.1024-270x270.jpg", - "title": "John Resig - Object.getPrototypeOf", - "description": null - }, - "https://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names": { - "domain": "kangax.github.io", - "url": "https://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names", - "logo": "https://kangax.github.io/compat-table/favicon.ico", - "title": "ECMAScript 5 compatibility table", - "description": null - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "async function - JavaScript | MDN", - "description": "An async function is a function declared with the async keyword, and the await keyword is permitted within it. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains." - }, - "https://jakearchibald.com/2017/await-vs-return-vs-return-await/": { - "domain": "jakearchibald.com", - "url": "https://jakearchibald.com/2017/await-vs-return-vs-return-await/", - "logo": "https://jakearchibald.com/c/favicon-67801369.png", - "title": "await vs return vs return await", - "description": null - }, - "https://stackoverflow.com/questions/13497971/what-is-the-matter-with-script-targeted-urls": { - "domain": "stackoverflow.com", - "url": "https://stackoverflow.com/questions/13497971/what-is-the-matter-with-script-targeted-urls", - "logo": "https://cdn.sstatic.net/Sites/stackoverflow/Img/apple-touch-icon.png?v=c78bd457575a", - "title": "What is the matter with script-targeted URLs?", - "description": "I’m using JSHint, and it got the following error: Script URL. Which I noticed that happened because on this particular line there is a string containing a javascript:... URL. I know that JSHint" - }, - "https://es5.github.io/#x15.1.1": { - "domain": "es5.github.io", - "url": "https://es5.github.io/#x15.1.1", - "logo": "https://es5.github.io/favicon.ico", - "title": "Annotated ES5", - "description": null - }, - "https://en.wikipedia.org/wiki/Variable_shadowing": { - "domain": "en.wikipedia.org", - "url": "https://en.wikipedia.org/wiki/Variable_shadowing", - "logo": "https://en.wikipedia.org/static/apple-touch/wikipedia.png", - "title": "Variable shadowing - Wikipedia", - "description": null - }, - "https://www.nczonline.net/blog/2007/09/09/inconsistent-array-literals/": { - "domain": "www.nczonline.net", - "url": "https://www.nczonline.net/blog/2007/09/09/inconsistent-array-literals/", - "logo": "https://www.nczonline.net/images/favicon.png", - "title": "Inconsistent array literals", - "description": "Back at the Rich Web Experience, I helped lead a “birds of a feather” group discussion on JavaScript. In that discussion, someone called me a JavaScript expert. I quickly explained that I don’t..." - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "undefined - JavaScript | MDN", - "description": "The global undefined property represents the primitive value undefined. It is one of JavaScript’s primitive types." - }, - "https://javascriptweblog.wordpress.com/2010/08/16/understanding-undefined-and-preventing-referenceerrors/": { - "domain": "javascriptweblog.wordpress.com", - "url": "https://javascriptweblog.wordpress.com/2010/08/16/understanding-undefined-and-preventing-referenceerrors/", - "logo": "https://s1.wp.com/i/favicon.ico", - "title": "Understanding JavaScript’s ‘undefined’", - "description": "Compared to other languages, JavaScript’s concept of undefined is a little confusing. In particular, trying to understand ReferenceErrors (“x is not defined”) and how best to codeâ€Ļ" - }, - "https://es5.github.io/#x15.1.1.3": { - "domain": "es5.github.io", - "url": "https://es5.github.io/#x15.1.1.3", - "logo": "https://es5.github.io/favicon.ico", - "title": "Annotated ES5", - "description": null - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Regular expressions - JavaScript | MDN", - "description": "Regular expressions are patterns used to match character combinations in strings. In JavaScript, regular expressions are also objects. These patterns are used with the exec() and test() methods of RegExp, and with the match(), matchAll(), replace(), replaceAll(), search(), and split() methods of Sâ€Ļ" - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "void operator - JavaScript | MDN", - "description": "The void operator evaluates the given expression and then returns undefined." - }, - "https://oreilly.com/javascript/excerpts/javascript-good-parts/bad-parts.html": { - "domain": "oreilly.com", - "url": "https://oreilly.com/javascript/excerpts/javascript-good-parts/bad-parts.html", - "logo": "https://www.oreilly.com/favicon.ico", - "title": "O’Reilly Media - Technology and Business Training", - "description": "Gain technology and business knowledge and hone your skills with learning resources created and curated by O’Reilly’s experts: live online training, video, books, our platform has content from 200+ of the world’s best publishers." - }, - "https://web.archive.org/web/20200717110117/https://yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/": { - "domain": "web.archive.org", - "url": "https://web.archive.org/web/20200717110117/https://yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/", - "logo": "https://web.archive.org/web/20200717110117im_/https://yuiblog.com/favicon.ico", - "title": "with Statement Considered Harmful", - "description": null - }, - "https://jscs-dev.github.io/rule/requireNewlineBeforeSingleStatementsInIf": { - "domain": "jscs-dev.github.io", - "url": "https://jscs-dev.github.io/rule/requireNewlineBeforeSingleStatementsInIf", - "logo": "https://jscs-dev.github.io/favicon.ico", - "title": "JSCS", - "description": null - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Object initializer - JavaScript | MDN", - "description": "Objects can be initialized using new Object(), Object.create(), or using the literal notation (initializer notation). An object initializer is a comma-delimited list of zero or more pairs of property names and associated values of an object, enclosed in curly braces ({})." - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Arrow function expressions - JavaScript | MDN", - "description": "An arrow function expression is a compact alternative to a traditional function expression, but is limited and can’t be used in all situations." - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Destructuring assignment - JavaScript | MDN", - "description": "The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables." - }, - "https://2ality.com/2015/01/es6-destructuring.html": { - "domain": "2ality.com", - "url": "https://2ality.com/2015/01/es6-destructuring.html", - "logo": "https://2ality.com/img/favicon.png", - "title": "Destructuring and parameter handling in ECMAScript 6", - "description": null - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Exponentiation": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Exponentiation", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Expressions and operators - JavaScript | MDN", - "description": "This chapter documents all the JavaScript language operators, expressions and keywords." - }, - "https://bugs.chromium.org/p/v8/issues/detail?id=5848": { - "domain": "bugs.chromium.org", - "url": "https://bugs.chromium.org/p/v8/issues/detail?id=5848", - "logo": "https://bugs.chromium.org/static/images/monorail.ico", - "title": "5848 - v8 - V8 JavaScript Engine - Monorail", - "description": null - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Object.hasOwn() - JavaScript | MDN", - "description": "The Object.hasOwn() static method returns true if the specified object has the indicated property as its own property. If the property is inherited, or does not exist, the method returns false." - }, - "http://bluebirdjs.com/docs/warning-explanations.html#warning-a-promise-was-rejected-with-a-non-error": { - "domain": "bluebirdjs.com", - "url": "http://bluebirdjs.com/docs/warning-explanations.html#warning-a-promise-was-rejected-with-a-non-error", - "logo": "//bluebirdjs.com/img/favicon.png", - "title": "Warning Explanations | bluebird", - "description": "Bluebird is a fully featured JavaScript promises library with unmatched performance." - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "RegExp - JavaScript | MDN", - "description": "The RegExp object is used for matching text with a pattern." - }, - "https://mathiasbynens.be/notes/javascript-properties": { - "domain": "mathiasbynens.be", - "url": "https://mathiasbynens.be/notes/javascript-properties", - "logo": "https://mathiasbynens.be/favicon.ico", - "title": "Unquoted property names / object keys in JavaScript ¡ Mathias Bynens", - "description": null - }, - "https://davidwalsh.name/parseint-radix": { - "domain": "davidwalsh.name", - "url": "https://davidwalsh.name/parseint-radix", - "logo": "https://davidwalsh.name/wp-content/themes/punky/images/favicon-144.png", - "title": "parseInt Radix", - "description": "The radix is important if you’re need to guarantee accuracy with variable input (basic number, binary, etc.). For best results, always use a radix of 10!" - }, - "https://github.com/tc39/proposal-object-rest-spread": { - "domain": "github.com", - "url": "https://github.com/tc39/proposal-object-rest-spread", - "logo": "https://github.com/fluidicon.png", - "title": "GitHub - tc39/proposal-object-rest-spread: Rest/Spread Properties for ECMAScript", - "description": "Rest/Spread Properties for ECMAScript. Contribute to tc39/proposal-object-rest-spread development by creating an account on GitHub." - }, - "https://blog.izs.me/2010/12/an-open-letter-to-javascript-leaders-regarding/": { - "domain": "blog.izs.me", - "url": "https://blog.izs.me/2010/12/an-open-letter-to-javascript-leaders-regarding/", - "logo": "https://blog.izs.me/favicon.ico", - "title": "An Open Letter to JavaScript Leaders Regarding Semicolons", - "description": "Writing and Stuff from Isaac Z. Schlueter" - }, - "https://web.archive.org/web/20200420230322/http://inimino.org/~inimino/blog/javascript_semicolons": { - "domain": "web.archive.org", - "url": "https://web.archive.org/web/20200420230322/http://inimino.org/~inimino/blog/javascript_semicolons", - "logo": "https://archive.org/favicon.ico", - "title": "JavaScript Semicolon Insertion", - "description": null - }, - "https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-description": { - "domain": "www.ecma-international.org", - "url": "https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-description", - "logo": "https://www.ecma-international.org/ecma-262/6.0/favicon.ico", - "title": "ECMAScript 2015 Language Specification – ECMA-262 6th Edition", - "description": null - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Template literals (Template strings) - JavaScript | MDN", - "description": "Template literals are literals delimited with backtick (`) characters, allowing for multi-line strings, for string interpolation with embedded expressions, and for special constructs called tagged templates." - }, - "https://exploringjs.com/es6/ch_template-literals.html#_examples-of-using-tagged-template-literals": { - "domain": "exploringjs.com", - "url": "https://exploringjs.com/es6/ch_template-literals.html#_examples-of-using-tagged-template-literals", - "logo": "https://exploringjs.com/es6/images/favicon-128.png", - "title": "8. Template literals", - "description": null - }, - "https://jsdoc.app": { - "domain": "jsdoc.app", - "url": "https://jsdoc.app", - "logo": null, - "title": "Use JSDoc: Index", - "description": "Official documentation for JSDoc 3." - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "typeof - JavaScript | MDN", - "description": "The typeof operator returns a string indicating the type of the unevaluated operand." - }, - "https://danhough.com/blog/single-var-pattern-rant/": { - "domain": "danhough.com", - "url": "https://danhough.com/blog/single-var-pattern-rant/", - "logo": "https://danhough.com/img/meta/apple-touch-icon-152x152.png", - "title": "A criticism of the Single Var Pattern in JavaScript, and a simple alternative — Dan Hough", - "description": "Dan Hough is a software developer & consultant, a writer and public speaker." - }, - "https://benalman.com/news/2012/05/multiple-var-statements-javascript/": { - "domain": "benalman.com", - "url": "https://benalman.com/news/2012/05/multiple-var-statements-javascript/", - "logo": "https://benalman.com/favicon.ico", - "title": "Ben Alman Âģ Multiple var statements in JavaScript, not superfluous", - "description": null - }, - "https://en.wikipedia.org/wiki/Yoda_conditions": { - "domain": "en.wikipedia.org", - "url": "https://en.wikipedia.org/wiki/Yoda_conditions", - "logo": "https://en.wikipedia.org/static/apple-touch/wikipedia.png", - "title": "Yoda conditions - Wikipedia", - "description": null - }, - "http://thomas.tuerke.net/on/design/?with=1249091668#msg1146181680": { - "domain": "thomas.tuerke.net", - "url": "http://thomas.tuerke.net/on/design/?with=1249091668#msg1146181680", - "logo": "//thomas.tuerke.net/images/tmtlogo.ico", - "title": "Coding in Style", - "description": "Thomas M. Tuerke topical weblog" - }, - "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation": { - "domain": "developer.mozilla.org", - "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation", - "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", - "title": "Exponentiation (**) - JavaScript | MDN", - "description": "The exponentiation operator (**) returns the result of raising the first operand to the power of the second operand. It is equivalent to Math.pow, except it also accepts BigInts as operands." - }, - "https://eslint.org/blog/2022/07/interesting-bugs-caught-by-no-constant-binary-expression/": { - "domain": "eslint.org", - "url": "https://eslint.org/blog/2022/07/interesting-bugs-caught-by-no-constant-binary-expression/", - "logo": "https://eslint.org/apple-touch-icon.png", - "title": "Interesting bugs caught by no-constant-binary-expression - ESLint - Pluggable JavaScript Linter", - "description": "A pluggable and configurable linter tool for identifying and reporting on patterns in JavaScript. Maintain your code quality with ease." - }, - "https://github.com/tc39/proposal-class-static-block": { - "domain": "github.com", - "url": "https://github.com/tc39/proposal-class-static-block", - "logo": "https://github.com/fluidicon.png", - "title": "GitHub - tc39/proposal-class-static-block: ECMAScript class static initialization blocks", - "description": "ECMAScript class static initialization blocks. Contribute to tc39/proposal-class-static-block development by creating an account on GitHub." - }, - "https://tc39.es/ecma262/#sec-symbol-constructor": { - "domain": "tc39.es", - "url": "https://tc39.es/ecma262/#sec-symbol-constructor", - "logo": "https://tc39.es/ecma262/img/favicon.ico", - "title": "ECMAScriptÂŽ 2023 Language Specification", - "description": null - }, - "https://tc39.es/ecma262/#sec-bigint-constructor": { - "domain": "tc39.es", - "url": "https://tc39.es/ecma262/#sec-bigint-constructor", - "logo": "https://tc39.es/ecma262/img/favicon.ico", - "title": "ECMAScriptÂŽ 2023 Language Specification", - "description": null - }, - "https://v8.dev/blog/fast-async": { - "domain": "v8.dev", - "url": "https://v8.dev/blog/fast-async", - "logo": "https://v8.dev/favicon.ico", - "title": "Faster async functions and promises ¡ V8", - "description": "Faster and easier-to-debug async functions and promises are coming to V8 v7.2 / Chrome 72." - }, - "https://github.com/tc39/proposal-regexp-v-flag": { - "domain": "github.com", - "url": "https://github.com/tc39/proposal-regexp-v-flag", - "logo": "https://github.com/fluidicon.png", - "title": "GitHub - tc39/proposal-regexp-v-flag: UTS18 set notation in regular expressions", - "description": "UTS18 set notation in regular expressions. Contribute to tc39/proposal-regexp-v-flag development by creating an account on GitHub." - }, - "https://v8.dev/features/regexp-v-flag": { - "domain": "v8.dev", - "url": "https://v8.dev/features/regexp-v-flag", - "logo": "https://v8.dev/favicon.ico", - "title": "RegExp v flag with set notation and properties of strings ¡ V8", - "description": "The new RegExp `v` flag enables `unicodeSets` mode, unlocking support for extended character classes, including Unicode properties of strings, set notation, and improved case-insensitive matching." - }, - "https://codepoints.net/U+1680": { - "domain": "codepoints.net", - "url": "https://codepoints.net/U+1680", - "logo": "https://codepoints.net/favicon.ico", - "title": "U+1680 OGHAM SPACE MARK:   – Unicode – Codepoints", - "description": " , codepoint U+1680 OGHAM SPACE MARK in Unicode, is located in the block “Ogham”. It belongs to the Ogham script and is a Space Separator." - } -} \ No newline at end of file + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/set", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "setter - JavaScript | MDN", + "description": "The set syntax binds an object property to a function to be called when there is an attempt to set that property." + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "getter - JavaScript | MDN", + "description": "The get syntax binds an object property to a function that will be called when that property is looked up." + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Working_with_Objects", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Working with objects - JavaScript | MDN", + "description": "JavaScript is designed on a simple object-based paradigm. An object is a collection of properties, and a property is an association between a name (or key) and a value. A property’s value can be a function, in which case the property is known as a method. In addition to objects that are predefined iâ€Ļ" + }, + "https://github.com/airbnb/javascript#arrows--one-arg-parens": { + "domain": "github.com", + "url": "https://github.com/airbnb/javascript#arrows--one-arg-parens", + "logo": "https://github.com/fluidicon.png", + "title": "GitHub - airbnb/javascript: JavaScript Style Guide", + "description": "JavaScript Style Guide. Contribute to airbnb/javascript development by creating an account on GitHub." + }, + "https://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html": { + "domain": "www.adequatelygood.com", + "url": "https://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html", + "logo": "https://www.adequatelygood.com/favicon.ico", + "title": "JavaScript Scoping and Hoisting", + "description": null + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#var_hoisting": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#var_hoisting", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "var - JavaScript | MDN", + "description": "The var statement declares a function-scoped or globally-scoped variable, optionally initializing it to a value." + }, + "https://en.wikipedia.org/wiki/Indent_style": { + "domain": "en.wikipedia.org", + "url": "https://en.wikipedia.org/wiki/Indent_style", + "logo": "https://en.wikipedia.org/static/apple-touch/wikipedia.png", + "title": "Indentation style - Wikipedia", + "description": null + }, + "https://github.com/maxogden/art-of-node#callbacks": { + "domain": "github.com", + "url": "https://github.com/maxogden/art-of-node#callbacks", + "logo": "https://github.com/fluidicon.png", + "title": "GitHub - maxogden/art-of-node: a short introduction to node.js", + "description": ":snowflake: a short introduction to node.js. Contribute to maxogden/art-of-node development by creating an account on GitHub." + }, + "https://web.archive.org/web/20171224042620/https://docs.nodejitsu.com/articles/errors/what-are-the-error-conventions/": { + "domain": "web.archive.org", + "url": "https://web.archive.org/web/20171224042620/https://docs.nodejitsu.com/articles/errors/what-are-the-error-conventions/", + "logo": "https://archive.org/favicon.ico", + "title": "What are the error conventions? - docs.nodejitsu.com", + "description": "docs.nodejitsu.com is a growing collection of how-to articles for node.js, written by the community and curated by Nodejitsu and friends. These articles range from basic to advanced, and provide relevant code samples and insights into the design and philosophy of node itself." + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Classes - JavaScript | MDN", + "description": "Classes are a template for creating objects. They encapsulate data with code to work on that data. Classes in JS are built on prototypes but also have some syntax and semantics that are not shared with ES5 class-like semantics." + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "static - JavaScript | MDN", + "description": "The static keyword defines a static method or property for a class, or a class static initialization block (see the link for more information about this usage). Neither static methods nor static properties can be called on instances of the class. Instead, they’re called on the class itself." + }, + "https://www.crockford.com/code.html": { + "domain": "www.crockford.com", + "url": "https://www.crockford.com/code.html", + "logo": "https://www.crockford.com/favicon.png", + "title": "Code Conventions for the JavaScript Programming Language", + "description": null + }, + "https://dojotoolkit.org/reference-guide/1.9/developer/styleguide.html": { + "domain": "dojotoolkit.org", + "url": "https://dojotoolkit.org/reference-guide/1.9/developer/styleguide.html", + "logo": "https://dojotoolkit.org/images/favicons/apple-touch-icon-152x152.png", + "title": "Dojo Style Guide — The Dojo Toolkit - Reference Guide", + "description": null + }, + "https://gist.github.com/isaacs/357981": { + "domain": "gist.github.com", + "url": "https://gist.github.com/isaacs/357981", + "logo": "https://gist.github.com/fluidicon.png", + "title": "A better coding convention for lists and object literals in JavaScript", + "description": "A better coding convention for lists and object literals in JavaScript - comma-first-var.js" + }, + "https://en.wikipedia.org/wiki/Cyclomatic_complexity": { + "domain": "en.wikipedia.org", + "url": "https://en.wikipedia.org/wiki/Cyclomatic_complexity", + "logo": "https://en.wikipedia.org/static/apple-touch/wikipedia.png", + "title": "Cyclomatic complexity - Wikipedia", + "description": null + }, + "https://ariya.io/2012/12/complexity-analysis-of-javascript-code": { + "domain": "ariya.io", + "url": "https://ariya.io/2012/12/complexity-analysis-of-javascript-code", + "logo": "https://ariya.io/favicon.ico", + "title": "Complexity Analysis of JavaScript Code", + "description": "Nobody likes to read complex code, especially if it’s someone’s else code. A preventive approach to block any complex code entering the application is by watching its complexity carefully." + }, + "https://craftsmanshipforsoftware.com/2015/05/25/complexity-for-javascript/": { + "domain": "craftsmanshipforsoftware.com", + "url": "https://craftsmanshipforsoftware.com/2015/05/25/complexity-for-javascript/", + "logo": "https://s0.wp.com/i/webclip.png", + "title": "Complexity for JavaScript", + "description": "The control of complexity control presents the core problem of software development. The huge variety of decisions a developer faces on a day-to-day basis cry for methods of controlling and containâ€Ļ" + }, + "https://web.archive.org/web/20160808115119/http://jscomplexity.org/complexity": { + "domain": "web.archive.org", + "url": "https://web.archive.org/web/20160808115119/http://jscomplexity.org/complexity", + "logo": "https://archive.org/favicon.ico", + "title": "About complexity | JSComplexity.org", + "description": "A discussion of software complexity metrics and how they are calculated." + }, + "https://github.com/eslint/eslint/issues/4808#issuecomment-167795140": { + "domain": "github.com", + "url": "https://github.com/eslint/eslint/issues/4808#issuecomment-167795140", + "logo": "https://github.com/fluidicon.png", + "title": "Complexity has no default ¡ Issue #4808 ¡ eslint/eslint", + "description": "Enabling the complexity rule with only a severity has no effect. We have tried to give sane defaults to all rules, and I think this should be no exception. I don't know what a good number would..." + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "switch - JavaScript | MDN", + "description": "The switch statement evaluates an expression, matching the expression’s value to a case clause, and executes statements associated with that case, as well as statements in cases that follow the matching case." + }, + "https://web.archive.org/web/20201112040809/http://markdaggett.com/blog/2013/02/15/functions-explained/": { + "domain": "web.archive.org", + "url": "https://web.archive.org/web/20201112040809/http://markdaggett.com/blog/2013/02/15/functions-explained/", + "logo": "https://web.archive.org/web/20201112040809im_/http://markdaggett.com/favicon.ico", + "title": "Functions Explained - Mark Daggett’s Blog", + "description": "A Deep Dive into JavaScript Functions\nBased on my readership I have to assume most of you are familiar with JavaScript already. Therefore, it may â€Ļ" + }, + "https://2ality.com/2015/09/function-names-es6.html": { + "domain": "2ality.com", + "url": "https://2ality.com/2015/09/function-names-es6.html", + "logo": "https://2ality.com/img/favicon.png", + "title": "The names of functions in ES6", + "description": null + }, + "https://leanpub.com/understandinges6/read/#leanpub-auto-generators": { + "domain": "leanpub.com", + "url": "https://leanpub.com/understandinges6/read/#leanpub-auto-generators", + "logo": "https://leanpub.com/understandinges6/read/favicons/mstile-310x310.png", + "title": "Read Understanding ECMAScript 6 | Leanpub", + "description": null + }, + "https://leanpub.com/understandinges6/read/#leanpub-auto-accessor-properties": { + "domain": "leanpub.com", + "url": "https://leanpub.com/understandinges6/read/#leanpub-auto-accessor-properties", + "logo": "https://leanpub.com/understandinges6/read/favicons/mstile-310x310.png", + "title": "Read Understanding ECMAScript 6 | Leanpub", + "description": null + }, + "https://javascriptweblog.wordpress.com/2011/01/04/exploring-javascript-for-in-loops/": { + "domain": "javascriptweblog.wordpress.com", + "url": "https://javascriptweblog.wordpress.com/2011/01/04/exploring-javascript-for-in-loops/", + "logo": "https://s1.wp.com/i/favicon.ico", + "title": "Exploring JavaScript for-in loops", + "description": "The for-in loop is the only cross-browser technique for iterating the properties of generic objects. There’s a bunch of literature about the dangers of using for-in to iterate arrays and whenâ€Ļ" + }, + "https://2ality.com/2012/01/objects-as-maps.html": { + "domain": "2ality.com", + "url": "https://2ality.com/2012/01/objects-as-maps.html", + "logo": "https://2ality.com/img/favicon.png", + "title": "The pitfalls of using objects as maps in JavaScript", + "description": null + }, + "https://web.archive.org/web/20160725154648/http://www.mind2b.com/component/content/article/24-software-module-size-and-file-size": { + "domain": "web.archive.org", + "url": "https://web.archive.org/web/20160725154648/http://www.mind2b.com/component/content/article/24-software-module-size-and-file-size", + "logo": "https://archive.org/favicon.ico", + "title": "Software Module size and file size", + "description": null + }, + "http://book.mixu.net/node/ch7.html": { + "domain": "book.mixu.net", + "url": "http://book.mixu.net/node/ch7.html", + "logo": null, + "title": "7. Control flow - Mixu’s Node book", + "description": null + }, + "https://web.archive.org/web/20220104141150/https://howtonode.org/control-flow": { + "domain": "web.archive.org", + "url": "https://web.archive.org/web/20220104141150/https://howtonode.org/control-flow", + "logo": "https://web.archive.org/web/20220104141150im_/https://howtonode.org/favicon.ico", + "title": "Control Flow in Node - How To Node - NodeJS", + "description": "Learn the zen of coding in NodeJS." + }, + "https://web.archive.org/web/20220127215850/https://howtonode.org/control-flow-part-ii": { + "domain": "web.archive.org", + "url": "https://web.archive.org/web/20220127215850/https://howtonode.org/control-flow-part-ii", + "logo": "https://web.archive.org/web/20220127215850im_/https://howtonode.org/favicon.ico", + "title": "Control Flow in Node Part II - How To Node - NodeJS", + "description": "Learn the zen of coding in NodeJS." + }, + "https://nodejs.org/api/buffer.html": { + "domain": "nodejs.org", + "url": "https://nodejs.org/api/buffer.html", + "logo": "https://nodejs.org/favicon.ico", + "title": "Buffer | Node.js v18.2.0 Documentation", + "description": null + }, + "https://github.com/ChALkeR/notes/blob/master/Lets-fix-Buffer-API.md": { + "domain": "github.com", + "url": "https://github.com/ChALkeR/notes/blob/master/Lets-fix-Buffer-API.md", + "logo": "https://github.com/fluidicon.png", + "title": "notes/Lets-fix-Buffer-API.md at master ¡ ChALkeR/notes", + "description": "Some public notes. Contribute to ChALkeR/notes development by creating an account on GitHub." + }, + "https://github.com/nodejs/node/issues/4660": { + "domain": "github.com", + "url": "https://github.com/nodejs/node/issues/4660", + "logo": "https://github.com/fluidicon.png", + "title": "Buffer(number) is unsafe ¡ Issue #4660 ¡ nodejs/node", + "description": "tl;dr This issue proposes: Change new Buffer(number) to return safe, zeroed-out memory Create a new API for creating uninitialized Buffers, Buffer.alloc(number) Update: Jan 15, 2016 Upon further co..." + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/debugger", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "debugger - JavaScript | MDN", + "description": "The debugger statement invokes any available debugging functionality, such as setting a breakpoint. If no debugging functionality is available, this statement has no effect." + }, + "https://ericlippert.com/2003/11/01/eval-is-evil-part-one/": { + "domain": "ericlippert.com", + "url": "https://ericlippert.com/2003/11/01/eval-is-evil-part-one/", + "logo": "https://s1.wp.com/i/favicon.ico", + "title": "Eval is evil, part one", + "description": "The eval method — which takes a string containing JScript code, compiles it and runs it — is probably the most powerful and most misused method in JScript. There are a few scenarios in â€Ļ" + }, + "https://javascriptweblog.wordpress.com/2010/04/19/how-evil-is-eval/": { + "domain": "javascriptweblog.wordpress.com", + "url": "https://javascriptweblog.wordpress.com/2010/04/19/how-evil-is-eval/", + "logo": "https://s1.wp.com/i/favicon.ico", + "title": "How evil is eval?", + "description": "“eval is Evil: The eval function is the most misused feature of JavaScript. Avoid it” Douglas Crockford in JavaScript: The Good Parts I like The Good Parts. It’s essential readingâ€Ļ" + }, + "https://bocoup.com/blog/the-catch-with-try-catch": { + "domain": "bocoup.com", + "url": "https://bocoup.com/blog/the-catch-with-try-catch", + "logo": "https://static3.bocoup.com/assets/2015/10/06163533/favicon.png", + "title": "The", + "description": "I’ve recently been working on an update to JavaScript Debug, which has me doing a lot of cross-browser testing, and I noticed a few “interesting quirks” with tryâ€Ļcatch in Internet Explorer 6-8 that I couldn’t find documented anywhere." + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Function.prototype.bind() - JavaScript | MDN", + "description": "The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called." + }, + "https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/": { + "domain": "www.smashingmagazine.com", + "url": "https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/", + "logo": "https://www.smashingmagazine.com/images/favicon/apple-touch-icon.png", + "title": "Understanding JavaScript Bind () — Smashing Magazine", + "description": "Function binding is probably your least concern when beginning with JavaScript, but when you realize that you need a solution to the problem of how to keep the context of “this” within another function, then you might not realize that what you actually need is Function.prototype.bind()." + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Operator precedence - JavaScript | MDN", + "description": "Operator precedence determines how operators are parsed concerning each other. Operators with higher precedence become the operands of operators with lower precedence." + }, + "https://es5.github.io/#C": { + "domain": "es5.github.io", + "url": "https://es5.github.io/#C", + "logo": "https://es5.github.io/favicon.ico", + "title": "Annotated ES5", + "description": null + }, + "https://benalman.com/news/2010/11/immediately-invoked-function-expression/": { + "domain": "benalman.com", + "url": "https://benalman.com/news/2010/11/immediately-invoked-function-expression/", + "logo": "https://benalman.com/favicon.ico", + "title": "Ben Alman Âģ Immediately-Invoked Function Expression (IIFE)", + "description": null + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Undeclared_var": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Undeclared_var", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "ReferenceError: assignment to undeclared variable “x” - JavaScript | MDN", + "description": "The JavaScript strict mode-only exception “Assignment to undeclared variable” occurs when the value has been assigned to an undeclared variable." + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_dead_zone": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_dead_zone", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "let - JavaScript | MDN", + "description": "The let statement declares a block-scoped local variable, optionally initializing it to a value." + }, + "https://es5.github.io/#x7.8.5": { + "domain": "es5.github.io", + "url": "https://es5.github.io/#x7.8.5", + "logo": "https://es5.github.io/favicon.ico", + "title": "Annotated ES5", + "description": null + }, + "https://es5.github.io/#x7.2": { + "domain": "es5.github.io", + "url": "https://es5.github.io/#x7.2", + "logo": "https://es5.github.io/favicon.ico", + "title": "Annotated ES5", + "description": null + }, + "https://web.archive.org/web/20200414142829/http://timelessrepo.com/json-isnt-a-javascript-subset": { + "domain": "web.archive.org", + "url": "https://web.archive.org/web/20200414142829/http://timelessrepo.com/json-isnt-a-javascript-subset", + "logo": "https://archive.org/favicon.ico", + "title": "JSON: The JavaScript subset that isn’t - Timeless", + "description": null + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Iterators and generators - JavaScript | MDN", + "description": "Iterators and Generators bring the concept of iteration directly into the core language and provide a mechanism for customizing the behavior of for...of loops." + }, + "https://kangax.github.io/es5-compat-table/es6/#Iterators": { + "domain": "kangax.github.io", + "url": "https://kangax.github.io/es5-compat-table/es6/#Iterators", + "logo": "https://github.io/favicon.ico", + "title": null, + "description": null + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Deprecated_and_obsolete_features#Object_methods": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Deprecated_and_obsolete_features#Object_methods", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Deprecated and obsolete features - JavaScript | MDN", + "description": "This page lists features of JavaScript that are deprecated (that is, still available but planned for removal) and obsolete (that is, no longer usable)." + }, + "https://www.emacswiki.org/emacs/SmartTabs": { + "domain": "www.emacswiki.org", + "url": "https://www.emacswiki.org/emacs/SmartTabs", + "logo": "https://www.emacswiki.org/favicon.ico", + "title": "EmacsWiki: Smart Tabs", + "description": null + }, + "https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-objects": { + "domain": "www.ecma-international.org", + "url": "https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-objects", + "logo": "https://www.ecma-international.org/ecma-262/6.0/favicon.ico", + "title": "ECMAScript 2015 Language Specification – ECMA-262 6th Edition", + "description": null + }, + "https://www.inkling.com/read/javascript-definitive-guide-david-flanagan-6th/chapter-3/wrapper-objects": { + "domain": "www.inkling.com", + "url": "https://www.inkling.com/read/javascript-definitive-guide-david-flanagan-6th/chapter-3/wrapper-objects", + "logo": "https://inklingstatic.a.ssl.fastly.net/static_assets/20220214.223700z.8c5796a9.docker/images/favicon.ico", + "title": "Unsupported Browser", + "description": null + }, + "https://tc39.es/ecma262/#prod-annexB-NonOctalDecimalEscapeSequence": { + "domain": "tc39.es", + "url": "https://tc39.es/ecma262/#prod-annexB-NonOctalDecimalEscapeSequence", + "logo": "https://tc39.es/ecma262/img/favicon.ico", + "title": "ECMAScriptÂŽ 2023 Language Specification", + "description": null + }, + "https://es5.github.io/#x15.8": { + "domain": "es5.github.io", + "url": "https://es5.github.io/#x15.8", + "logo": "https://es5.github.io/favicon.ico", + "title": "Annotated ES5", + "description": null + }, + "https://spin.atomicobject.com/2011/04/10/javascript-don-t-reassign-your-function-arguments/": { + "domain": "spin.atomicobject.com", + "url": "https://spin.atomicobject.com/2011/04/10/javascript-don-t-reassign-your-function-arguments/", + "logo": "https://spin.atomicobject.com/wp-content/themes/spin/images/favicon.ico", + "title": "JavaScript: Don’t Reassign Your Function Arguments", + "description": "The point of this post is to raise awareness that reassigning the value of an argument variable mutates the arguments object." + }, + "https://stackoverflow.com/questions/5869216/how-to-store-node-js-deployment-settings-configuration-files": { + "domain": "stackoverflow.com", + "url": "https://stackoverflow.com/questions/5869216/how-to-store-node-js-deployment-settings-configuration-files", + "logo": "https://cdn.sstatic.net/Sites/stackoverflow/Img/apple-touch-icon.png?v=c78bd457575a", + "title": "How to store Node.js deployment settings/configuration files?", + "description": "I have been working on a few Node apps, and I’ve been looking for a good pattern of storing deployment-related settings. In the Django world (where I come from), the common practise would be to hav..." + }, + "https://blog.benhall.me.uk/2012/02/storing-application-config-data-in/": { + "domain": "blog.benhall.me.uk", + "url": "https://blog.benhall.me.uk/2012/02/storing-application-config-data-in/", + "logo": null, + "title": "Storing Node.js application config data – Ben Hall’s Blog", + "description": null + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Promise - JavaScript | MDN", + "description": "The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value." + }, + "https://johnresig.com/blog/objectgetprototypeof/": { + "domain": "johnresig.com", + "url": "https://johnresig.com/blog/objectgetprototypeof/", + "logo": "https://johnresig.com/wp-content/uploads/2017/04/cropped-jeresig-2016.1024-270x270.jpg", + "title": "John Resig - Object.getPrototypeOf", + "description": null + }, + "https://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names": { + "domain": "kangax.github.io", + "url": "https://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names", + "logo": "https://kangax.github.io/compat-table/favicon.ico", + "title": "ECMAScript 5 compatibility table", + "description": null + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "async function - JavaScript | MDN", + "description": "An async function is a function declared with the async keyword, and the await keyword is permitted within it. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains." + }, + "https://jakearchibald.com/2017/await-vs-return-vs-return-await/": { + "domain": "jakearchibald.com", + "url": "https://jakearchibald.com/2017/await-vs-return-vs-return-await/", + "logo": "https://jakearchibald.com/c/favicon-67801369.png", + "title": "await vs return vs return await", + "description": null + }, + "https://stackoverflow.com/questions/13497971/what-is-the-matter-with-script-targeted-urls": { + "domain": "stackoverflow.com", + "url": "https://stackoverflow.com/questions/13497971/what-is-the-matter-with-script-targeted-urls", + "logo": "https://cdn.sstatic.net/Sites/stackoverflow/Img/apple-touch-icon.png?v=c78bd457575a", + "title": "What is the matter with script-targeted URLs?", + "description": "I’m using JSHint, and it got the following error: Script URL. Which I noticed that happened because on this particular line there is a string containing a javascript:... URL. I know that JSHint" + }, + "https://es5.github.io/#x15.1.1": { + "domain": "es5.github.io", + "url": "https://es5.github.io/#x15.1.1", + "logo": "https://es5.github.io/favicon.ico", + "title": "Annotated ES5", + "description": null + }, + "https://en.wikipedia.org/wiki/Variable_shadowing": { + "domain": "en.wikipedia.org", + "url": "https://en.wikipedia.org/wiki/Variable_shadowing", + "logo": "https://en.wikipedia.org/static/apple-touch/wikipedia.png", + "title": "Variable shadowing - Wikipedia", + "description": null + }, + "https://www.nczonline.net/blog/2007/09/09/inconsistent-array-literals/": { + "domain": "www.nczonline.net", + "url": "https://www.nczonline.net/blog/2007/09/09/inconsistent-array-literals/", + "logo": "https://www.nczonline.net/images/favicon.png", + "title": "Inconsistent array literals", + "description": "Back at the Rich Web Experience, I helped lead a “birds of a feather” group discussion on JavaScript. In that discussion, someone called me a JavaScript expert. I quickly explained that I don’t..." + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "undefined - JavaScript | MDN", + "description": "The global undefined property represents the primitive value undefined. It is one of JavaScript’s primitive types." + }, + "https://javascriptweblog.wordpress.com/2010/08/16/understanding-undefined-and-preventing-referenceerrors/": { + "domain": "javascriptweblog.wordpress.com", + "url": "https://javascriptweblog.wordpress.com/2010/08/16/understanding-undefined-and-preventing-referenceerrors/", + "logo": "https://s1.wp.com/i/favicon.ico", + "title": "Understanding JavaScript’s ‘undefined’", + "description": "Compared to other languages, JavaScript’s concept of undefined is a little confusing. In particular, trying to understand ReferenceErrors (“x is not defined”) and how best to codeâ€Ļ" + }, + "https://es5.github.io/#x15.1.1.3": { + "domain": "es5.github.io", + "url": "https://es5.github.io/#x15.1.1.3", + "logo": "https://es5.github.io/favicon.ico", + "title": "Annotated ES5", + "description": null + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Regular expressions - JavaScript | MDN", + "description": "Regular expressions are patterns used to match character combinations in strings. In JavaScript, regular expressions are also objects. These patterns are used with the exec() and test() methods of RegExp, and with the match(), matchAll(), replace(), replaceAll(), search(), and split() methods of Sâ€Ļ" + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "void operator - JavaScript | MDN", + "description": "The void operator evaluates the given expression and then returns undefined." + }, + "https://oreilly.com/javascript/excerpts/javascript-good-parts/bad-parts.html": { + "domain": "oreilly.com", + "url": "https://oreilly.com/javascript/excerpts/javascript-good-parts/bad-parts.html", + "logo": "https://www.oreilly.com/favicon.ico", + "title": "O’Reilly Media - Technology and Business Training", + "description": "Gain technology and business knowledge and hone your skills with learning resources created and curated by O’Reilly’s experts: live online training, video, books, our platform has content from 200+ of the world’s best publishers." + }, + "https://web.archive.org/web/20200717110117/https://yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/": { + "domain": "web.archive.org", + "url": "https://web.archive.org/web/20200717110117/https://yuiblog.com/blog/2006/04/11/with-statement-considered-harmful/", + "logo": "https://web.archive.org/web/20200717110117im_/https://yuiblog.com/favicon.ico", + "title": "with Statement Considered Harmful", + "description": null + }, + "https://jscs-dev.github.io/rule/requireNewlineBeforeSingleStatementsInIf": { + "domain": "jscs-dev.github.io", + "url": "https://jscs-dev.github.io/rule/requireNewlineBeforeSingleStatementsInIf", + "logo": "https://jscs-dev.github.io/favicon.ico", + "title": "JSCS", + "description": null + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Object initializer - JavaScript | MDN", + "description": "Objects can be initialized using new Object(), Object.create(), or using the literal notation (initializer notation). An object initializer is a comma-delimited list of zero or more pairs of property names and associated values of an object, enclosed in curly braces ({})." + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Arrow function expressions - JavaScript | MDN", + "description": "An arrow function expression is a compact alternative to a traditional function expression, but is limited and can’t be used in all situations." + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Destructuring assignment - JavaScript | MDN", + "description": "The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables." + }, + "https://2ality.com/2015/01/es6-destructuring.html": { + "domain": "2ality.com", + "url": "https://2ality.com/2015/01/es6-destructuring.html", + "logo": "https://2ality.com/img/favicon.png", + "title": "Destructuring and parameter handling in ECMAScript 6", + "description": null + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Exponentiation": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Arithmetic_Operators#Exponentiation", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Expressions and operators - JavaScript | MDN", + "description": "This chapter documents all the JavaScript language operators, expressions and keywords." + }, + "https://bugs.chromium.org/p/v8/issues/detail?id=5848": { + "domain": "bugs.chromium.org", + "url": "https://bugs.chromium.org/p/v8/issues/detail?id=5848", + "logo": "https://bugs.chromium.org/static/images/monorail.ico", + "title": "5848 - v8 - V8 JavaScript Engine - Monorail", + "description": null + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwn", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Object.hasOwn() - JavaScript | MDN", + "description": "The Object.hasOwn() static method returns true if the specified object has the indicated property as its own property. If the property is inherited, or does not exist, the method returns false." + }, + "http://bluebirdjs.com/docs/warning-explanations.html#warning-a-promise-was-rejected-with-a-non-error": { + "domain": "bluebirdjs.com", + "url": "http://bluebirdjs.com/docs/warning-explanations.html#warning-a-promise-was-rejected-with-a-non-error", + "logo": "//bluebirdjs.com/img/favicon.png", + "title": "Warning Explanations | bluebird", + "description": "Bluebird is a fully featured JavaScript promises library with unmatched performance." + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "RegExp - JavaScript | MDN", + "description": "The RegExp object is used for matching text with a pattern." + }, + "https://mathiasbynens.be/notes/javascript-properties": { + "domain": "mathiasbynens.be", + "url": "https://mathiasbynens.be/notes/javascript-properties", + "logo": "https://mathiasbynens.be/favicon.ico", + "title": "Unquoted property names / object keys in JavaScript ¡ Mathias Bynens", + "description": null + }, + "https://davidwalsh.name/parseint-radix": { + "domain": "davidwalsh.name", + "url": "https://davidwalsh.name/parseint-radix", + "logo": "https://davidwalsh.name/wp-content/themes/punky/images/favicon-144.png", + "title": "parseInt Radix", + "description": "The radix is important if you’re need to guarantee accuracy with variable input (basic number, binary, etc.). For best results, always use a radix of 10!" + }, + "https://github.com/tc39/proposal-object-rest-spread": { + "domain": "github.com", + "url": "https://github.com/tc39/proposal-object-rest-spread", + "logo": "https://github.com/fluidicon.png", + "title": "GitHub - tc39/proposal-object-rest-spread: Rest/Spread Properties for ECMAScript", + "description": "Rest/Spread Properties for ECMAScript. Contribute to tc39/proposal-object-rest-spread development by creating an account on GitHub." + }, + "https://blog.izs.me/2010/12/an-open-letter-to-javascript-leaders-regarding/": { + "domain": "blog.izs.me", + "url": "https://blog.izs.me/2010/12/an-open-letter-to-javascript-leaders-regarding/", + "logo": "https://blog.izs.me/favicon.ico", + "title": "An Open Letter to JavaScript Leaders Regarding Semicolons", + "description": "Writing and Stuff from Isaac Z. Schlueter" + }, + "https://web.archive.org/web/20200420230322/http://inimino.org/~inimino/blog/javascript_semicolons": { + "domain": "web.archive.org", + "url": "https://web.archive.org/web/20200420230322/http://inimino.org/~inimino/blog/javascript_semicolons", + "logo": "https://archive.org/favicon.ico", + "title": "JavaScript Semicolon Insertion", + "description": null + }, + "https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-description": { + "domain": "www.ecma-international.org", + "url": "https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-description", + "logo": "https://www.ecma-international.org/ecma-262/6.0/favicon.ico", + "title": "ECMAScript 2015 Language Specification – ECMA-262 6th Edition", + "description": null + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#Tagged_template_literals", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Template literals (Template strings) - JavaScript | MDN", + "description": "Template literals are literals delimited with backtick (`) characters, allowing for multi-line strings, for string interpolation with embedded expressions, and for special constructs called tagged templates." + }, + "https://exploringjs.com/es6/ch_template-literals.html#_examples-of-using-tagged-template-literals": { + "domain": "exploringjs.com", + "url": "https://exploringjs.com/es6/ch_template-literals.html#_examples-of-using-tagged-template-literals", + "logo": "https://exploringjs.com/es6/images/favicon-128.png", + "title": "8. Template literals", + "description": null + }, + "https://jsdoc.app": { + "domain": "jsdoc.app", + "url": "https://jsdoc.app", + "logo": null, + "title": "Use JSDoc: Index", + "description": "Official documentation for JSDoc 3." + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/typeof", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "typeof - JavaScript | MDN", + "description": "The typeof operator returns a string indicating the type of the unevaluated operand." + }, + "https://danhough.com/blog/single-var-pattern-rant/": { + "domain": "danhough.com", + "url": "https://danhough.com/blog/single-var-pattern-rant/", + "logo": "https://danhough.com/img/meta/apple-touch-icon-152x152.png", + "title": "A criticism of the Single Var Pattern in JavaScript, and a simple alternative — Dan Hough", + "description": "Dan Hough is a software developer & consultant, a writer and public speaker." + }, + "https://benalman.com/news/2012/05/multiple-var-statements-javascript/": { + "domain": "benalman.com", + "url": "https://benalman.com/news/2012/05/multiple-var-statements-javascript/", + "logo": "https://benalman.com/favicon.ico", + "title": "Ben Alman Âģ Multiple var statements in JavaScript, not superfluous", + "description": null + }, + "https://en.wikipedia.org/wiki/Yoda_conditions": { + "domain": "en.wikipedia.org", + "url": "https://en.wikipedia.org/wiki/Yoda_conditions", + "logo": "https://en.wikipedia.org/static/apple-touch/wikipedia.png", + "title": "Yoda conditions - Wikipedia", + "description": null + }, + "http://thomas.tuerke.net/on/design/?with=1249091668#msg1146181680": { + "domain": "thomas.tuerke.net", + "url": "http://thomas.tuerke.net/on/design/?with=1249091668#msg1146181680", + "logo": "//thomas.tuerke.net/images/tmtlogo.ico", + "title": "Coding in Style", + "description": "Thomas M. Tuerke topical weblog" + }, + "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation": { + "domain": "developer.mozilla.org", + "url": "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation", + "logo": "https://developer.mozilla.org/favicon-48x48.cbbd161b.png", + "title": "Exponentiation (**) - JavaScript | MDN", + "description": "The exponentiation operator (**) returns the result of raising the first operand to the power of the second operand. It is equivalent to Math.pow, except it also accepts BigInts as operands." + }, + "https://eslint.org/blog/2022/07/interesting-bugs-caught-by-no-constant-binary-expression/": { + "domain": "eslint.org", + "url": "https://eslint.org/blog/2022/07/interesting-bugs-caught-by-no-constant-binary-expression/", + "logo": "https://eslint.org/apple-touch-icon.png", + "title": "Interesting bugs caught by no-constant-binary-expression - ESLint - Pluggable JavaScript Linter", + "description": "A pluggable and configurable linter tool for identifying and reporting on patterns in JavaScript. Maintain your code quality with ease." + }, + "https://github.com/tc39/proposal-class-static-block": { + "domain": "github.com", + "url": "https://github.com/tc39/proposal-class-static-block", + "logo": "https://github.com/fluidicon.png", + "title": "GitHub - tc39/proposal-class-static-block: ECMAScript class static initialization blocks", + "description": "ECMAScript class static initialization blocks. Contribute to tc39/proposal-class-static-block development by creating an account on GitHub." + }, + "https://tc39.es/ecma262/#sec-symbol-constructor": { + "domain": "tc39.es", + "url": "https://tc39.es/ecma262/#sec-symbol-constructor", + "logo": "https://tc39.es/ecma262/img/favicon.ico", + "title": "ECMAScriptÂŽ 2023 Language Specification", + "description": null + }, + "https://tc39.es/ecma262/#sec-bigint-constructor": { + "domain": "tc39.es", + "url": "https://tc39.es/ecma262/#sec-bigint-constructor", + "logo": "https://tc39.es/ecma262/img/favicon.ico", + "title": "ECMAScriptÂŽ 2023 Language Specification", + "description": null + }, + "https://v8.dev/blog/fast-async": { + "domain": "v8.dev", + "url": "https://v8.dev/blog/fast-async", + "logo": "https://v8.dev/favicon.ico", + "title": "Faster async functions and promises ¡ V8", + "description": "Faster and easier-to-debug async functions and promises are coming to V8 v7.2 / Chrome 72." + }, + "https://github.com/tc39/proposal-regexp-v-flag": { + "domain": "github.com", + "url": "https://github.com/tc39/proposal-regexp-v-flag", + "logo": "https://github.com/fluidicon.png", + "title": "GitHub - tc39/proposal-regexp-v-flag: UTS18 set notation in regular expressions", + "description": "UTS18 set notation in regular expressions. Contribute to tc39/proposal-regexp-v-flag development by creating an account on GitHub." + }, + "https://v8.dev/features/regexp-v-flag": { + "domain": "v8.dev", + "url": "https://v8.dev/features/regexp-v-flag", + "logo": "https://v8.dev/favicon.ico", + "title": "RegExp v flag with set notation and properties of strings ¡ V8", + "description": "The new RegExp `v` flag enables `unicodeSets` mode, unlocking support for extended character classes, including Unicode properties of strings, set notation, and improved case-insensitive matching." + }, + "https://codepoints.net/U+1680": { + "domain": "codepoints.net", + "url": "https://codepoints.net/U+1680", + "logo": "https://codepoints.net/favicon.ico", + "title": "U+1680 OGHAM SPACE MARK:   – Unicode – Codepoints", + "description": " , codepoint U+1680 OGHAM SPACE MARK in Unicode, is located in the block “Ogham”. It belongs to the Ogham script and is a Space Separator." + }, + "https://en.wikipedia.org/wiki/Dead_store": { + "domain": "en.wikipedia.org", + "url": "https://en.wikipedia.org/wiki/Dead_store", + "logo": "https://en.wikipedia.org/static/apple-touch/wikipedia.png", + "title": "Dead store - Wikipedia", + "description": null + }, + "https://rules.sonarsource.com/javascript/RSPEC-1854/": { + "domain": "rules.sonarsource.com", + "url": "https://rules.sonarsource.com/javascript/RSPEC-1854/", + "logo": "https://rules.sonarsource.com/favicon.ico", + "title": "JavaScript static code analysis: Unused assignments should be removed", + "description": "Dead stores refer to assignments made to local variables that are subsequently never used or immediately overwritten. Such assignments are\nunnecessary and don’t contribute to the functionality or clarity of the code. They may even negatively impact performance. Removing them enhances code\ncleanlinesâ€Ļ" + }, + "https://cwe.mitre.org/data/definitions/563.html": { + "domain": "cwe.mitre.org", + "url": "https://cwe.mitre.org/data/definitions/563.html", + "logo": "https://cwe.mitre.org/favicon.ico", + "title": "CWE - CWE-563: Assignment to Variable without Use (4.13)", + "description": "Common Weakness Enumeration (CWE) is a list of software weaknesses." + }, + "https://wiki.sei.cmu.edu/confluence/display/c/MSC13-C.+Detect+and+remove+unused+values": { + "domain": "wiki.sei.cmu.edu", + "url": "https://wiki.sei.cmu.edu/confluence/display/c/MSC13-C.+Detect+and+remove+unused+values", + "logo": "https://wiki.sei.cmu.edu/confluence/s/-ctumb3/9012/tu5x00/7/_/favicon.ico", + "title": "MSC13-C. Detect and remove unused values - SEI CERT C Coding Standard - Confluence", + "description": null + }, + "https://wiki.sei.cmu.edu/confluence/display/java/MSC56-J.+Detect+and+remove+superfluous+code+and+values": { + "domain": "wiki.sei.cmu.edu", + "url": "https://wiki.sei.cmu.edu/confluence/display/java/MSC56-J.+Detect+and+remove+superfluous+code+and+values", + "logo": "https://wiki.sei.cmu.edu/confluence/s/-ctumb3/9012/tu5x00/7/_/favicon.ico", + "title": "MSC56-J. Detect and remove superfluous code and values - SEI CERT Oracle Coding Standard for Java - Confluence", + "description": null + }, + "https://262.ecma-international.org/11.0/#sec-value-properties-of-the-global-object": { + "domain": "262.ecma-international.org", + "url": "https://262.ecma-international.org/11.0/#sec-value-properties-of-the-global-object", + "logo": "https://tc39.es/ecma262/2020/img/favicon.ico", + "title": "ECMAScriptÂŽ 2020 Language Specification", + "description": null + }, + "https://262.ecma-international.org/11.0/#sec-strict-mode-of-ecmascript": { + "domain": "262.ecma-international.org", + "url": "https://262.ecma-international.org/11.0/#sec-strict-mode-of-ecmascript", + "logo": "https://tc39.es/ecma262/2020/img/favicon.ico", + "title": "ECMAScriptÂŽ 2020 Language Specification", + "description": null + } +} diff --git a/docs/src/_data/helpers.js b/docs/src/_data/helpers.js index a7c4ef6fab7d..0fd09876dd3a 100644 --- a/docs/src/_data/helpers.js +++ b/docs/src/_data/helpers.js @@ -1,31 +1,34 @@ +"use strict"; + module.exports = { - /** - * Returns some attributes based on whether the link is active or - * a parent of an active item - * - * @param {String} itemUrl is the link in question - * @param {String} pageUrl is the page context - * @returns {String} is the attributes or empty - */ - getLinkActiveState: function(itemUrl, pageUrl) { - let response = ''; + /** + * Returns some attributes based on whether the link is active or + * a parent of an active item + * @param {string} itemUrl is the link in question + * @param {string} pageUrl is the page context + * @returns {string} is the attributes or empty + */ + getLinkActiveState(itemUrl, pageUrl) { + let response = ""; - if (itemUrl === pageUrl) { - response = ' aria-current="page" '; - } + if (itemUrl === pageUrl) { + response = ' aria-current="page" '; + } - if (itemUrl.length > 1 && pageUrl.indexOf(itemUrl) === 0) { - response += ' data-current="true" '; - } + if (itemUrl.length > 1 && pageUrl.indexOf(itemUrl) === 0) { + response += ' data-current="true" '; + } - return response; - }, - excludeThis: function(arr, pageUrl) { - var newArray = []; - arr.forEach(item => { - if(item.url !== pageUrl) newArray.push(item); - }); - return newArray; - } + return response; + }, + excludeThis(arr, pageUrl) { + const newArray = []; + arr.forEach(item => { + if (item.url !== pageUrl) { + newArray.push(item); + } + }); + return newArray; + }, }; diff --git a/docs/src/_data/languages.json b/docs/src/_data/languages.json index 7defcb63eb1a..5f041cb60245 100644 --- a/docs/src/_data/languages.json +++ b/docs/src/_data/languages.json @@ -1,27 +1,28 @@ { - "items": [{ - "flag": "đŸ‡ē🇸", - "code": "en", - "name": "English (US)", - "url": "https://eslint.org" - }, - { - "flag": "đŸ‡¯đŸ‡ĩ", - "code": "ja", - "name": "Japanese - æ—ĨæœŦčĒž", - "url": "https://ja.eslint.org" - }, - { - "flag": "đŸ‡Ģ🇷", - "code": "fr", - "name": "Français", - "url": "https://fr.eslint.org" - }, - { - "flag": "đŸ‡¨đŸ‡ŗ", - "code": "cn", - "name": "Chinese - 中文", - "url": "https://cn.eslint.org" - } - ] + "items": [ + { + "flag": "đŸ‡ē🇸", + "code": "en", + "name": "English (US)", + "url": "https://eslint.org" + }, + { + "flag": "đŸ‡¯đŸ‡ĩ", + "code": "ja", + "name": "Japanese - æ—ĨæœŦčĒž", + "url": "https://ja.eslint.org" + }, + { + "flag": "đŸ‡Ģ🇷", + "code": "fr", + "name": "Français", + "url": "https://fr.eslint.org" + }, + { + "flag": "đŸ‡¨đŸ‡ŗ", + "code": "cn", + "name": "Chinese - 中文", + "url": "https://cn.eslint.org" + } + ] } diff --git a/docs/src/_data/layout.js b/docs/src/_data/layout.js index 2665a708914e..9114836a9361 100644 --- a/docs/src/_data/layout.js +++ b/docs/src/_data/layout.js @@ -1 +1,3 @@ +"use strict"; + module.exports = "doc.html"; diff --git a/docs/src/_data/links.json b/docs/src/_data/links.json index 9bf8222a4f58..b01eeb2747e6 100644 --- a/docs/src/_data/links.json +++ b/docs/src/_data/links.json @@ -1,19 +1,21 @@ { - "github": "https://github.com/eslint/eslint", - "twitter": "https://twitter.com/geteslint", - "chat": "https://eslint.org/chat", - "mastodon": "https://fosstodon.org/@eslint", - "blog": "/blog", - "docs": "/docs/latest/", - "playground": "/play", - "getStarted": "/docs/latest/use/getting-started", - "sponsors": "/sponsors", - "branding": "/branding", - "store": "https://eslint.threadless.com", - "team": "/team", - "configuring": "https://eslint.org/docs/latest/use/configure/", - "fixProblems": "https://eslint.org/docs/latest/use/command-line-interface#fix-problems", - "donate": "/donate", - "openCollective": "https://opencollective.com/eslint", - "githubSponsors": "https://github.com/sponsors/eslint" -} \ No newline at end of file + "github": "https://github.com/eslint/eslint", + "twitter": "https://twitter.com/geteslint", + "chat": "https://eslint.org/chat", + "mastodon": "https://fosstodon.org/@eslint", + "bluesky": "https://bsky.app/profile/eslint.org", + "blog": "/blog", + "docs": "/docs/latest/", + "playground": "/play", + "codeExplorer": "https://explorer.eslint.org/", + "getStarted": "/docs/latest/use/getting-started", + "sponsors": "/sponsors", + "branding": "/branding", + "store": "https://eslint.threadless.com", + "team": "/team", + "configuring": "https://eslint.org/docs/latest/use/configure/", + "fixProblems": "https://eslint.org/docs/latest/use/command-line-interface#fix-problems", + "donate": "/donate", + "openCollective": "https://opencollective.com/eslint", + "githubSponsors": "https://github.com/sponsors/eslint" +} diff --git a/docs/src/_data/navigation.json b/docs/src/_data/navigation.json index bc831667e91a..14973172906e 100644 --- a/docs/src/_data/navigation.json +++ b/docs/src/_data/navigation.json @@ -19,6 +19,10 @@ { "text": "Playground", "url": "https://eslint.org/play" + }, + { + "text": "Code Explorer", + "url": "https://explorer.eslint.org/" } ] } diff --git a/docs/src/_data/rule_versions.json b/docs/src/_data/rule_versions.json index 14e6d858ac02..c497cace7193 100644 --- a/docs/src/_data/rule_versions.json +++ b/docs/src/_data/rule_versions.json @@ -308,7 +308,9 @@ "logical-assignment-operators": "8.24.0", "no-empty-static-block": "8.27.0", "no-new-native-nonconstructor": "8.27.0", - "no-object-constructor": "8.50.0" + "no-object-constructor": "8.50.0", + "no-useless-assignment": "9.0.0-alpha.1", + "no-unassigned-vars": "9.27.0" }, "removed": { "generator-star": "1.0.0-rc-1", @@ -328,6 +330,8 @@ "space-in-brackets": "1.0.0-rc-1", "space-return-throw-case": "2.0.0-beta.3", "space-unary-word-ops": "0.10.0", - "spaced-line-comment": "1.0.0-rc-1" + "spaced-line-comment": "1.0.0-rc-1", + "require-jsdoc": "9.0.0-alpha.0", + "valid-jsdoc": "9.0.0-alpha.0" } } \ No newline at end of file diff --git a/docs/src/_data/rules.json b/docs/src/_data/rules.json index 3fe9ddcc3d52..b88c8da9ca7f 100644 --- a/docs/src/_data/rules.json +++ b/docs/src/_data/rules.json @@ -6,6 +6,7 @@ "description": "Enforce `return` statements in callbacks of array methods", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": true }, { @@ -13,13 +14,15 @@ "description": "Require `super()` calls in constructors", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { "name": "for-direction", - "description": "Enforce \"for\" loop update clause moving the counter in the right direction", + "description": "Enforce `for` loop update clause moving the counter in the right direction", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -27,6 +30,7 @@ "description": "Enforce `return` statements in getters", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -34,6 +38,7 @@ "description": "Disallow using an async function as a Promise executor", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -41,6 +46,7 @@ "description": "Disallow `await` inside of loops", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -48,13 +54,15 @@ "description": "Disallow reassigning class members", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { "name": "no-compare-neg-zero", - "description": "Disallow comparing against -0", + "description": "Disallow comparing against `-0`", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -62,20 +70,23 @@ "description": "Disallow assignment operators in conditional expressions", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { "name": "no-const-assign", - "description": "Disallow reassigning `const` variables", + "description": "Disallow reassigning `const`, `using`, and `await using` variables", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { "name": "no-constant-binary-expression", "description": "Disallow expressions where the operation doesn't affect the value", - "recommended": false, + "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -83,6 +94,7 @@ "description": "Disallow constant expressions in conditions", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -90,6 +102,7 @@ "description": "Disallow returning value from constructor", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -97,6 +110,7 @@ "description": "Disallow control characters in regular expressions", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -104,6 +118,7 @@ "description": "Disallow the use of `debugger`", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -111,6 +126,7 @@ "description": "Disallow duplicate arguments in `function` definitions", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -118,6 +134,7 @@ "description": "Disallow duplicate class members", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -125,6 +142,7 @@ "description": "Disallow duplicate conditions in if-else-if chains", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -132,6 +150,7 @@ "description": "Disallow duplicate keys in object literals", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -139,6 +158,7 @@ "description": "Disallow duplicate case labels", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -146,6 +166,7 @@ "description": "Disallow duplicate module imports", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -153,6 +174,7 @@ "description": "Disallow empty character classes in regular expressions", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -160,6 +182,7 @@ "description": "Disallow empty destructuring patterns", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -167,6 +190,7 @@ "description": "Disallow reassigning exceptions in `catch` clauses", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -174,6 +198,7 @@ "description": "Disallow fallthrough of `case` statements", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -181,6 +206,7 @@ "description": "Disallow reassigning `function` declarations", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -188,13 +214,15 @@ "description": "Disallow assigning to imported bindings", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { "name": "no-inner-declarations", "description": "Disallow variable or `function` declarations in nested blocks", - "recommended": true, + "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -202,6 +230,7 @@ "description": "Disallow invalid regular expression strings in `RegExp` constructors", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -209,6 +238,7 @@ "description": "Disallow irregular whitespace", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -216,6 +246,7 @@ "description": "Disallow literal numbers that lose precision", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -223,20 +254,15 @@ "description": "Disallow characters which are made with multiple code points in character class syntax", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": true }, { "name": "no-new-native-nonconstructor", "description": "Disallow `new` operators with global non-constructor functions", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, - { - "name": "no-new-symbol", - "description": "Disallow `new` operators with the `Symbol` object", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -244,6 +270,7 @@ "description": "Disallow calling global object properties as functions", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -251,6 +278,7 @@ "description": "Disallow returning values from Promise executor functions", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": true }, { @@ -258,6 +286,7 @@ "description": "Disallow calling some `Object.prototype` methods directly on objects", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": true }, { @@ -265,6 +294,7 @@ "description": "Disallow assignments where both sides are exactly the same", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -272,6 +302,7 @@ "description": "Disallow comparisons where both sides are exactly the same", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -279,6 +310,7 @@ "description": "Disallow returning values from setters", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -286,6 +318,7 @@ "description": "Disallow sparse arrays", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -293,6 +326,7 @@ "description": "Disallow template literal placeholder syntax in regular strings", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -300,6 +334,15 @@ "description": "Disallow `this`/`super` before calling `super()` in constructors", "recommended": true, "fixable": false, + "frozen": false, + "hasSuggestions": false + }, + { + "name": "no-unassigned-vars", + "description": "Disallow `let` or `var` variables that are read but never assigned", + "recommended": false, + "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -307,6 +350,7 @@ "description": "Disallow the use of undeclared variables unless mentioned in `/*global */` comments", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -314,6 +358,7 @@ "description": "Disallow confusing multiline expressions", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -321,6 +366,7 @@ "description": "Disallow unmodified loop conditions", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -328,6 +374,7 @@ "description": "Disallow unreachable code after `return`, `throw`, `continue`, and `break` statements", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -335,6 +382,7 @@ "description": "Disallow loops with a body that allows only one iteration", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -342,6 +390,7 @@ "description": "Disallow control flow statements in `finally` blocks", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -349,6 +398,7 @@ "description": "Disallow negating the left operand of relational operators", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": true }, { @@ -356,13 +406,15 @@ "description": "Disallow use of optional chaining in contexts where the `undefined` value is not allowed", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { "name": "no-unused-private-class-members", "description": "Disallow unused private class members", - "recommended": false, + "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -370,13 +422,23 @@ "description": "Disallow unused variables", "recommended": true, "fixable": false, - "hasSuggestions": false + "frozen": false, + "hasSuggestions": true }, { "name": "no-use-before-define", "description": "Disallow the use of variables before they are defined", "recommended": false, "fixable": false, + "frozen": false, + "hasSuggestions": false + }, + { + "name": "no-useless-assignment", + "description": "Disallow variable assignments when the value is not used", + "recommended": false, + "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -384,6 +446,7 @@ "description": "Disallow useless backreferences in regular expressions", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -391,6 +454,7 @@ "description": "Disallow assignments that can lead to race conditions due to usage of `await` or `yield`", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -398,13 +462,15 @@ "description": "Require calls to `isNaN()` when checking for `NaN`", "recommended": true, "fixable": false, - "hasSuggestions": false + "frozen": false, + "hasSuggestions": true }, { "name": "valid-typeof", "description": "Enforce comparing `typeof` expressions against valid strings", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": true } ], @@ -414,6 +480,7 @@ "description": "Enforce getter and setter pairs in objects and classes", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -421,6 +488,7 @@ "description": "Require braces around arrow function bodies", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -428,6 +496,7 @@ "description": "Enforce the use of variables within the scope they are defined", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -435,6 +504,7 @@ "description": "Enforce camelcase naming convention", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -442,6 +512,7 @@ "description": "Enforce or disallow capitalization of the first letter of a comment", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -449,6 +520,7 @@ "description": "Enforce that class methods utilize `this`", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -456,6 +528,7 @@ "description": "Enforce a maximum cyclomatic complexity allowed in a program", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -463,6 +536,7 @@ "description": "Require `return` statements to either always or never specify values", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -470,6 +544,7 @@ "description": "Enforce consistent naming when capturing the current execution context", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -477,6 +552,7 @@ "description": "Enforce consistent brace style for all control statements", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -484,13 +560,15 @@ "description": "Require `default` cases in `switch` statements", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { "name": "default-case-last", - "description": "Enforce default clauses in switch statements to be last", + "description": "Enforce `default` clauses in `switch` statements to be last", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -498,6 +576,7 @@ "description": "Enforce default parameters to be last", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -505,6 +584,7 @@ "description": "Enforce dot notation whenever possible", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -512,13 +592,15 @@ "description": "Require the use of `===` and `!==`", "recommended": false, "fixable": true, - "hasSuggestions": false + "frozen": false, + "hasSuggestions": true }, { "name": "func-name-matching", "description": "Require function names to match the name of the variable or property to which they are assigned", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -526,13 +608,15 @@ "description": "Require or disallow named `function` expressions", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { "name": "func-style", - "description": "Enforce the consistent use of either `function` declarations or expressions", + "description": "Enforce the consistent use of either `function` declarations or expressions assigned to variables", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -540,6 +624,7 @@ "description": "Require grouped accessor pairs in object literals and classes", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -547,6 +632,7 @@ "description": "Require `for-in` loops to include an `if` statement", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -554,6 +640,7 @@ "description": "Disallow specified identifiers", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -561,6 +648,7 @@ "description": "Enforce minimum and maximum identifier lengths", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -568,6 +656,7 @@ "description": "Require identifiers to match a specified regular expression", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -575,6 +664,7 @@ "description": "Require or disallow initialization in variable declarations", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -582,6 +672,7 @@ "description": "Require or disallow logical assignment operator shorthand", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": true }, { @@ -589,6 +680,7 @@ "description": "Enforce a maximum number of classes per file", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -596,6 +688,7 @@ "description": "Enforce a maximum depth that blocks can be nested", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -603,6 +696,7 @@ "description": "Enforce a maximum number of lines per file", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -610,6 +704,7 @@ "description": "Enforce a maximum number of lines of code in a function", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -617,6 +712,7 @@ "description": "Enforce a maximum depth that callbacks can be nested", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -624,6 +720,7 @@ "description": "Enforce a maximum number of parameters in function definitions", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -631,13 +728,7 @@ "description": "Enforce a maximum number of statements allowed in function blocks", "recommended": false, "fixable": false, - "hasSuggestions": false - }, - { - "name": "multiline-comment-style", - "description": "Enforce a particular style for multiline comments", - "recommended": false, - "fixable": true, + "frozen": false, "hasSuggestions": false }, { @@ -645,6 +736,7 @@ "description": "Require constructor names to begin with a capital letter", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -652,13 +744,15 @@ "description": "Disallow the use of `alert`, `confirm`, and `prompt`", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { "name": "no-array-constructor", "description": "Disallow `Array` constructors", "recommended": false, - "fixable": false, + "fixable": true, + "frozen": false, "hasSuggestions": true }, { @@ -666,6 +760,7 @@ "description": "Disallow bitwise operators", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -673,6 +768,7 @@ "description": "Disallow the use of `arguments.caller` or `arguments.callee`", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -680,13 +776,15 @@ "description": "Disallow lexical declarations in case clauses", "recommended": true, "fixable": false, - "hasSuggestions": false + "frozen": false, + "hasSuggestions": true }, { "name": "no-console", "description": "Disallow the use of `console`", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": true }, { @@ -694,6 +792,7 @@ "description": "Disallow `continue` statements", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -701,6 +800,7 @@ "description": "Disallow deleting variables", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -708,6 +808,7 @@ "description": "Disallow equal signs explicitly at the beginning of regular expressions", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -715,6 +816,7 @@ "description": "Disallow `else` blocks after `return` statements in `if` statements", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -722,6 +824,7 @@ "description": "Disallow empty block statements", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": true }, { @@ -729,13 +832,15 @@ "description": "Disallow empty functions", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { "name": "no-empty-static-block", "description": "Disallow empty static blocks", - "recommended": false, + "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -743,6 +848,7 @@ "description": "Disallow `null` comparisons without type-checking operators", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -750,6 +856,7 @@ "description": "Disallow the use of `eval()`", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -757,6 +864,7 @@ "description": "Disallow extending native types", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -764,6 +872,7 @@ "description": "Disallow unnecessary calls to `.bind()`", "recommended": false, "fixable": true, + "frozen": false, "hasSuggestions": false }, { @@ -771,6 +880,7 @@ "description": "Disallow unnecessary boolean casts", "recommended": true, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -778,6 +888,7 @@ "description": "Disallow unnecessary labels", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -785,6 +896,7 @@ "description": "Disallow assignments to native objects or read-only global variables", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -792,13 +904,15 @@ "description": "Disallow shorthand type conversions", "recommended": false, "fixable": true, - "hasSuggestions": false + "frozen": true, + "hasSuggestions": true }, { "name": "no-implicit-globals", "description": "Disallow declarations in the global scope", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -806,6 +920,7 @@ "description": "Disallow the use of `eval()`-like methods", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -813,6 +928,7 @@ "description": "Disallow inline comments after code", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -820,6 +936,7 @@ "description": "Disallow use of `this` in contexts where the value of `this` is `undefined`", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -827,6 +944,7 @@ "description": "Disallow the use of the `__iterator__` property", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -834,6 +952,7 @@ "description": "Disallow labels that share a name with a variable", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -841,6 +960,7 @@ "description": "Disallow labeled statements", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -848,6 +968,7 @@ "description": "Disallow unnecessary nested blocks", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -855,6 +976,7 @@ "description": "Disallow `if` statements as the only statement in `else` blocks", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -862,6 +984,7 @@ "description": "Disallow function declarations that contain unsafe references inside loop statements", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -869,6 +992,7 @@ "description": "Disallow magic numbers", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -876,6 +1000,7 @@ "description": "Disallow use of chained assignment expressions", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -883,6 +1008,7 @@ "description": "Disallow multiline strings", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -890,6 +1016,7 @@ "description": "Disallow negated conditions", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -897,6 +1024,7 @@ "description": "Disallow nested ternary expressions", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -904,6 +1032,7 @@ "description": "Disallow `new` operators outside of assignments or comparisons", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -911,6 +1040,7 @@ "description": "Disallow `new` operators with the `Function` object", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -918,6 +1048,7 @@ "description": "Disallow `new` operators with the `String`, `Number`, and `Boolean` objects", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -925,6 +1056,7 @@ "description": "Disallow `\\8` and `\\9` escape sequences in string literals", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": true }, { @@ -932,6 +1064,7 @@ "description": "Disallow calls to the `Object` constructor without an argument", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": true }, { @@ -939,6 +1072,7 @@ "description": "Disallow octal literals", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -946,13 +1080,15 @@ "description": "Disallow octal escape sequences in string literals", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { "name": "no-param-reassign", - "description": "Disallow reassigning `function` parameters", + "description": "Disallow reassigning function parameters", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -960,6 +1096,7 @@ "description": "Disallow the unary operators `++` and `--`", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -967,6 +1104,7 @@ "description": "Disallow the use of the `__proto__` property", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -974,6 +1112,7 @@ "description": "Disallow variable redeclaration", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -981,6 +1120,7 @@ "description": "Disallow multiple spaces in regular expressions", "recommended": true, "fixable": true, + "frozen": false, "hasSuggestions": false }, { @@ -988,6 +1128,7 @@ "description": "Disallow specified names in exports", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -995,6 +1136,7 @@ "description": "Disallow specified global variables", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1002,6 +1144,7 @@ "description": "Disallow specified modules when loaded by `import`", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1009,6 +1152,7 @@ "description": "Disallow certain properties on certain objects", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1016,6 +1160,7 @@ "description": "Disallow specified syntax", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1023,13 +1168,15 @@ "description": "Disallow assignment operators in `return` statements", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { "name": "no-script-url", - "description": "Disallow `javascript:` urls", + "description": "Disallow `javascript:` URLs", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1037,6 +1184,7 @@ "description": "Disallow comma operators", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1044,6 +1192,7 @@ "description": "Disallow variable declarations from shadowing variables declared in the outer scope", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1051,6 +1200,7 @@ "description": "Disallow identifiers from shadowing restricted names", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1058,6 +1208,7 @@ "description": "Disallow ternary operators", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -1065,6 +1216,7 @@ "description": "Disallow throwing literals as exceptions", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1072,6 +1224,7 @@ "description": "Disallow initializing variables to `undefined`", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -1079,6 +1232,7 @@ "description": "Disallow the use of `undefined` as an identifier", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -1086,6 +1240,7 @@ "description": "Disallow dangling underscores in identifiers", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -1093,6 +1248,7 @@ "description": "Disallow ternary operators when simpler alternatives exist", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -1100,6 +1256,7 @@ "description": "Disallow unused expressions", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1107,6 +1264,7 @@ "description": "Disallow unused labels", "recommended": true, "fixable": true, + "frozen": false, "hasSuggestions": false }, { @@ -1114,6 +1272,7 @@ "description": "Disallow unnecessary calls to `.call()` and `.apply()`", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1121,6 +1280,7 @@ "description": "Disallow unnecessary `catch` clauses", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1128,6 +1288,7 @@ "description": "Disallow unnecessary computed property keys in objects and classes", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -1135,6 +1296,7 @@ "description": "Disallow unnecessary concatenation of literals or template literals", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -1142,13 +1304,15 @@ "description": "Disallow unnecessary constructors", "recommended": false, "fixable": false, - "hasSuggestions": false + "frozen": false, + "hasSuggestions": true }, { "name": "no-useless-escape", "description": "Disallow unnecessary escape characters", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": true }, { @@ -1156,6 +1320,7 @@ "description": "Disallow renaming import, export, and destructured assignments to the same name", "recommended": false, "fixable": true, + "frozen": false, "hasSuggestions": false }, { @@ -1163,6 +1328,7 @@ "description": "Disallow redundant return statements", "recommended": false, "fixable": true, + "frozen": false, "hasSuggestions": false }, { @@ -1170,6 +1336,7 @@ "description": "Require `let` or `const` instead of `var`", "recommended": false, "fixable": true, + "frozen": false, "hasSuggestions": false }, { @@ -1177,6 +1344,7 @@ "description": "Disallow `void` operators", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -1184,6 +1352,7 @@ "description": "Disallow specified warning terms in comments", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -1191,6 +1360,7 @@ "description": "Disallow `with` statements", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1198,6 +1368,7 @@ "description": "Require or disallow method and property shorthand syntax for object literals", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -1205,6 +1376,7 @@ "description": "Enforce variables to be declared either together or separately in functions", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -1212,6 +1384,7 @@ "description": "Require or disallow assignment operator shorthand where possible", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -1219,6 +1392,7 @@ "description": "Require using arrow functions for callbacks", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -1226,6 +1400,7 @@ "description": "Require `const` declarations for variables that are never reassigned after declared", "recommended": false, "fixable": true, + "frozen": false, "hasSuggestions": false }, { @@ -1233,6 +1408,7 @@ "description": "Require destructuring from arrays and/or objects", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -1240,6 +1416,7 @@ "description": "Disallow the use of `Math.pow` in favor of the `**` operator", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -1247,6 +1424,7 @@ "description": "Enforce using named capture group in regular expression", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": true }, { @@ -1254,6 +1432,7 @@ "description": "Disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -1261,13 +1440,15 @@ "description": "Disallow use of `Object.prototype.hasOwnProperty.call()` and prefer use of `Object.hasOwn()`", "recommended": false, "fixable": true, + "frozen": false, "hasSuggestions": false }, { "name": "prefer-object-spread", - "description": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead", + "description": "Disallow using `Object.assign` with an object literal as the first argument and prefer the use of object spread instead", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -1275,6 +1456,7 @@ "description": "Require using Error objects as Promise rejection reasons", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1282,6 +1464,7 @@ "description": "Disallow use of the `RegExp` constructor in favor of regular expression literals", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": true }, { @@ -1289,6 +1472,7 @@ "description": "Require rest parameters instead of `arguments`", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1296,6 +1480,7 @@ "description": "Require spread operators instead of `.apply()`", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -1303,6 +1488,7 @@ "description": "Require template literals instead of string concatenation", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -1310,6 +1496,7 @@ "description": "Enforce the consistent use of the radix argument when using `parseInt()`", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": true }, { @@ -1317,13 +1504,15 @@ "description": "Disallow async functions which have no `await` expression", "recommended": false, "fixable": false, - "hasSuggestions": false + "frozen": false, + "hasSuggestions": true }, { "name": "require-unicode-regexp", - "description": "Enforce the use of `u` or `v` flag on RegExp", + "description": "Enforce the use of `u` or `v` flag on regular expressions", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": true }, { @@ -1331,13 +1520,15 @@ "description": "Require generator functions to contain `yield`", "recommended": true, "fixable": false, + "frozen": false, "hasSuggestions": false }, { "name": "sort-imports", - "description": "Enforce sorted import declarations within modules", + "description": "Enforce sorted `import` declarations within modules", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -1345,6 +1536,7 @@ "description": "Require object keys to be sorted", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -1352,6 +1544,7 @@ "description": "Require variables within the same declaration block to be sorted", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false }, { @@ -1359,6 +1552,7 @@ "description": "Require or disallow strict mode directives", "recommended": false, "fixable": true, + "frozen": false, "hasSuggestions": false }, { @@ -1366,6 +1560,7 @@ "description": "Require symbol descriptions", "recommended": false, "fixable": false, + "frozen": false, "hasSuggestions": false }, { @@ -1373,6 +1568,7 @@ "description": "Require `var` declarations be placed at the top of their containing scope", "recommended": false, "fixable": false, + "frozen": true, "hasSuggestions": false }, { @@ -1380,22 +1576,17 @@ "description": "Require or disallow \"Yoda\" conditions", "recommended": false, "fixable": true, + "frozen": true, "hasSuggestions": false } ], "layout": [ - { - "name": "line-comment-position", - "description": "Enforce position of line comments", - "recommended": false, - "fixable": false, - "hasSuggestions": false - }, { "name": "unicode-bom", "description": "Require or disallow Unicode byte order mark (BOM)", "recommended": false, "fixable": true, + "frozen": false, "hasSuggestions": false } ] @@ -1403,224 +1594,713 @@ "deprecated": [ { "name": "array-bracket-newline", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "array-bracket-newline", + "url": "https://eslint.style/rules/array-bracket-newline" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "array-bracket-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "array-bracket-spacing", + "url": "https://eslint.style/rules/array-bracket-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "array-element-newline", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "array-element-newline", + "url": "https://eslint.style/rules/array-element-newline" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "arrow-parens", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "arrow-parens", + "url": "https://eslint.style/rules/arrow-parens" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "arrow-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "arrow-spacing", + "url": "https://eslint.style/rules/arrow-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "block-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "block-spacing", + "url": "https://eslint.style/rules/block-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "brace-style", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "brace-style", + "url": "https://eslint.style/rules/brace-style" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "callback-return", - "replacedBy": [], + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "callback-return", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/callback-return.md" + } + } + ], "fixable": false, "hasSuggestions": false }, { "name": "comma-dangle", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "comma-dangle", + "url": "https://eslint.style/rules/comma-dangle" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "comma-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "comma-spacing", + "url": "https://eslint.style/rules/comma-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "comma-style", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "comma-style", + "url": "https://eslint.style/rules/comma-style" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "computed-property-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "computed-property-spacing", + "url": "https://eslint.style/rules/computed-property-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "dot-location", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "dot-location", + "url": "https://eslint.style/rules/dot-location" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "eol-last", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "eol-last", + "url": "https://eslint.style/rules/eol-last" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "func-call-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "function-call-spacing", + "url": "https://eslint.style/rules/function-call-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "function-call-argument-newline", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "function-call-argument-newline", + "url": "https://eslint.style/rules/function-call-argument-newline" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "function-paren-newline", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "function-paren-newline", + "url": "https://eslint.style/rules/function-paren-newline" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "generator-star-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "generator-star-spacing", + "url": "https://eslint.style/rules/generator-star-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "global-require", - "replacedBy": [], + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "global-require", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/global-require.md" + } + } + ], "fixable": false, "hasSuggestions": false }, { "name": "handle-callback-err", - "replacedBy": [], + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "handle-callback-err", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/handle-callback-err.md" + } + } + ], "fixable": false, "hasSuggestions": false }, { "name": "id-blacklist", "replacedBy": [ - "id-denylist" + { + "rule": { + "name": "id-denylist", + "url": "https://eslint.org/docs/rules/id-denylist" + } + } ], "fixable": false, "hasSuggestions": false }, { "name": "implicit-arrow-linebreak", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "implicit-arrow-linebreak", + "url": "https://eslint.style/rules/implicit-arrow-linebreak" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "indent", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "indent", + "url": "https://eslint.style/rules/indent" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "indent-legacy", "replacedBy": [ - "indent" + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "indent", + "url": "https://eslint.style/rules/indent" + } + } ], "fixable": true, "hasSuggestions": false }, { "name": "jsx-quotes", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "jsx-quotes", + "url": "https://eslint.style/rules/jsx-quotes" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "key-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "key-spacing", + "url": "https://eslint.style/rules/key-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "keyword-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "keyword-spacing", + "url": "https://eslint.style/rules/keyword-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, + { + "name": "line-comment-position", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "line-comment-position", + "url": "https://eslint.style/rules/line-comment-position" + } + } + ], + "fixable": false, + "hasSuggestions": false + }, { "name": "linebreak-style", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "linebreak-style", + "url": "https://eslint.style/rules/linebreak-style" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "lines-around-comment", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "lines-around-comment", + "url": "https://eslint.style/rules/lines-around-comment" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "lines-around-directive", "replacedBy": [ - "padding-line-between-statements" + { + "message": "The new rule moved to a plugin.", + "url": "https://eslint.org/docs/latest/rules/padding-line-between-statements#examples", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "padding-line-between-statements", + "url": "https://eslint.style/rules/padding-line-between-statements" + } + } ], "fixable": true, "hasSuggestions": false }, { "name": "lines-between-class-members", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "lines-between-class-members", + "url": "https://eslint.style/rules/lines-between-class-members" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "max-len", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "max-len", + "url": "https://eslint.style/rules/max-len" + } + } + ], "fixable": false, "hasSuggestions": false }, { "name": "max-statements-per-line", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "max-statements-per-line", + "url": "https://eslint.style/rules/max-statements-per-line" + } + } + ], "fixable": false, "hasSuggestions": false }, + { + "name": "multiline-comment-style", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "multiline-comment-style", + "url": "https://eslint.style/rules/multiline-comment-style" + } + } + ], + "fixable": true, + "hasSuggestions": false + }, { "name": "multiline-ternary", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "multiline-ternary", + "url": "https://eslint.style/rules/multiline-ternary" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "new-parens", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "new-parens", + "url": "https://eslint.style/rules/new-parens" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "newline-after-var", "replacedBy": [ - "padding-line-between-statements" + { + "message": "The new rule moved to a plugin.", + "url": "https://eslint.org/docs/latest/rules/padding-line-between-statements#examples", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "padding-line-between-statements", + "url": "https://eslint.style/rules/padding-line-between-statements" + } + } ], "fixable": true, "hasSuggestions": false @@ -1628,89 +2308,251 @@ { "name": "newline-before-return", "replacedBy": [ - "padding-line-between-statements" + { + "message": "The new rule moved to a plugin.", + "url": "https://eslint.org/docs/latest/rules/padding-line-between-statements#examples", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "padding-line-between-statements", + "url": "https://eslint.style/rules/padding-line-between-statements" + } + } ], "fixable": true, "hasSuggestions": false }, { "name": "newline-per-chained-call", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "newline-per-chained-call", + "url": "https://eslint.style/rules/newline-per-chained-call" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "no-buffer-constructor", - "replacedBy": [], + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-deprecated-api", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-deprecated-api.md" + } + } + ], "fixable": false, "hasSuggestions": false }, { "name": "no-catch-shadow", "replacedBy": [ - "no-shadow" + { + "rule": { + "name": "no-shadow", + "url": "https://eslint.org/docs/rules/no-shadow" + } + } ], "fixable": false, "hasSuggestions": false }, { "name": "no-confusing-arrow", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-confusing-arrow", + "url": "https://eslint.style/rules/no-confusing-arrow" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "no-extra-parens", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-extra-parens", + "url": "https://eslint.style/rules/no-extra-parens" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "no-extra-semi", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-extra-semi", + "url": "https://eslint.style/rules/no-extra-semi" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "no-floating-decimal", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-floating-decimal", + "url": "https://eslint.style/rules/no-floating-decimal" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "no-mixed-operators", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-mixed-operators", + "url": "https://eslint.style/rules/no-mixed-operators" + } + } + ], "fixable": false, "hasSuggestions": false }, { "name": "no-mixed-requires", - "replacedBy": [], + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-mixed-requires", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-mixed-requires.md" + } + } + ], "fixable": false, "hasSuggestions": false }, { "name": "no-mixed-spaces-and-tabs", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-mixed-spaces-and-tabs", + "url": "https://eslint.style/rules/no-mixed-spaces-and-tabs" + } + } + ], "fixable": false, "hasSuggestions": false }, { "name": "no-multi-spaces", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-multi-spaces", + "url": "https://eslint.style/rules/no-multi-spaces" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "no-multiple-empty-lines", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-multiple-empty-lines", + "url": "https://eslint.style/rules/no-multiple-empty-lines" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "no-native-reassign", "replacedBy": [ - "no-global-assign" + { + "rule": { + "name": "no-global-assign", + "url": "https://eslint.org/docs/rules/no-global-assign" + } + } ], "fixable": false, "hasSuggestions": false @@ -1718,7 +2560,12 @@ { "name": "no-negated-in-lhs", "replacedBy": [ - "no-unsafe-negation" + { + "rule": { + "name": "no-unsafe-negation", + "url": "https://eslint.org/docs/rules/no-unsafe-negation" + } + } ], "fixable": false, "hasSuggestions": false @@ -1726,38 +2573,116 @@ { "name": "no-new-object", "replacedBy": [ - "no-object-constructor" + { + "rule": { + "name": "no-object-constructor", + "url": "https://eslint.org/docs/rules/no-object-constructor" + } + } ], "fixable": false, "hasSuggestions": false }, { "name": "no-new-require", - "replacedBy": [], + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-new-require", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-new-require.md" + } + } + ], + "fixable": false, + "hasSuggestions": false + }, + { + "name": "no-new-symbol", + "replacedBy": [ + { + "rule": { + "name": "no-new-native-nonconstructor", + "url": "https://eslint.org/docs/latest/rules/no-new-native-nonconstructor" + } + } + ], "fixable": false, "hasSuggestions": false }, { "name": "no-path-concat", - "replacedBy": [], + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-path-concat", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-path-concat.md" + } + } + ], "fixable": false, "hasSuggestions": false }, { "name": "no-process-env", - "replacedBy": [], + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-process-env", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-process-env.md" + } + } + ], "fixable": false, "hasSuggestions": false }, { "name": "no-process-exit", - "replacedBy": [], + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-process-exit", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-process-exit.md" + } + } + ], "fixable": false, "hasSuggestions": false }, { "name": "no-restricted-modules", - "replacedBy": [], + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-restricted-require", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-restricted-require.md" + } + } + ], "fixable": false, "hasSuggestions": false }, @@ -1770,80 +2695,246 @@ { "name": "no-spaced-func", "replacedBy": [ - "func-call-spacing" + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "function-call-spacing", + "url": "https://eslint.style/rules/function-call-spacing" + } + } ], "fixable": true, "hasSuggestions": false }, { "name": "no-sync", - "replacedBy": [], + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-sync", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-sync.md" + } + } + ], "fixable": false, "hasSuggestions": false }, { "name": "no-tabs", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-tabs", + "url": "https://eslint.style/rules/no-tabs" + } + } + ], "fixable": false, "hasSuggestions": false }, { "name": "no-trailing-spaces", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-trailing-spaces", + "url": "https://eslint.style/rules/no-trailing-spaces" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "no-whitespace-before-property", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-whitespace-before-property", + "url": "https://eslint.style/rules/no-whitespace-before-property" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "nonblock-statement-body-position", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "nonblock-statement-body-position", + "url": "https://eslint.style/rules/nonblock-statement-body-position" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "object-curly-newline", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "object-curly-newline", + "url": "https://eslint.style/rules/object-curly-newline" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "object-curly-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "object-curly-spacing", + "url": "https://eslint.style/rules/object-curly-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "object-property-newline", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "object-property-newline", + "url": "https://eslint.style/rules/object-property-newline" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "one-var-declaration-per-line", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "one-var-declaration-per-line", + "url": "https://eslint.style/rules/one-var-declaration-per-line" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "operator-linebreak", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "operator-linebreak", + "url": "https://eslint.style/rules/operator-linebreak" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "padded-blocks", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "padded-blocks", + "url": "https://eslint.style/rules/padded-blocks" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "padding-line-between-statements", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "padding-line-between-statements", + "url": "https://eslint.style/rules/padding-line-between-statements" + } + } + ], "fixable": true, "hasSuggestions": false }, @@ -1855,121 +2946,343 @@ }, { "name": "quote-props", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "quote-props", + "url": "https://eslint.style/rules/quote-props" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "quotes", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "quotes", + "url": "https://eslint.style/rules/quotes" + } + } + ], "fixable": true, "hasSuggestions": false }, - { - "name": "require-jsdoc", - "replacedBy": [], - "fixable": false, - "hasSuggestions": false - }, { "name": "rest-spread-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "rest-spread-spacing", + "url": "https://eslint.style/rules/rest-spread-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "semi", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "semi", + "url": "https://eslint.style/rules/semi" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "semi-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "semi-spacing", + "url": "https://eslint.style/rules/semi-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "semi-style", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "semi-style", + "url": "https://eslint.style/rules/semi-style" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "space-before-blocks", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "space-before-blocks", + "url": "https://eslint.style/rules/space-before-blocks" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "space-before-function-paren", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "space-before-function-paren", + "url": "https://eslint.style/rules/space-before-function-paren" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "space-in-parens", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "space-in-parens", + "url": "https://eslint.style/rules/space-in-parens" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "space-infix-ops", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "space-infix-ops", + "url": "https://eslint.style/rules/space-infix-ops" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "space-unary-ops", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "space-unary-ops", + "url": "https://eslint.style/rules/space-unary-ops" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "spaced-comment", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "spaced-comment", + "url": "https://eslint.style/rules/spaced-comment" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "switch-colon-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "switch-colon-spacing", + "url": "https://eslint.style/rules/switch-colon-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "template-curly-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "template-curly-spacing", + "url": "https://eslint.style/rules/template-curly-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "template-tag-spacing", - "replacedBy": [], - "fixable": true, - "hasSuggestions": false - }, - { - "name": "valid-jsdoc", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "template-tag-spacing", + "url": "https://eslint.style/rules/template-tag-spacing" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "wrap-iife", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "wrap-iife", + "url": "https://eslint.style/rules/wrap-iife" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "wrap-regex", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "wrap-regex", + "url": "https://eslint.style/rules/wrap-regex" + } + } + ], "fixable": true, "hasSuggestions": false }, { "name": "yield-star-spacing", - "replacedBy": [], + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "yield-star-spacing", + "url": "https://eslint.style/rules/yield-star-spacing" + } + } + ], "fixable": true, "hasSuggestions": false } @@ -1978,112 +3291,205 @@ { "removed": "generator-star", "replacedBy": [ - "generator-star-spacing" + { + "rule": { + "name": "generator-star-spacing" + } + } ] }, { "removed": "global-strict", "replacedBy": [ - "strict" + { + "rule": { + "name": "strict" + } + } ] }, { "removed": "no-arrow-condition", "replacedBy": [ - "no-confusing-arrow", - "no-constant-condition" + { + "rule": { + "name": "no-confusing-arrow" + } + }, + { + "rule": { + "name": "no-constant-condition" + } + } ] }, { "removed": "no-comma-dangle", "replacedBy": [ - "comma-dangle" + { + "rule": { + "name": "comma-dangle" + } + } ] }, { "removed": "no-empty-class", "replacedBy": [ - "no-empty-character-class" + { + "rule": { + "name": "no-empty-character-class" + } + } ] }, { "removed": "no-empty-label", "replacedBy": [ - "no-labels" + { + "rule": { + "name": "no-labels" + } + } ] }, { "removed": "no-extra-strict", "replacedBy": [ - "strict" + { + "rule": { + "name": "strict" + } + } ] }, { "removed": "no-reserved-keys", "replacedBy": [ - "quote-props" + { + "rule": { + "name": "quote-props" + } + } ] }, { "removed": "no-space-before-semi", "replacedBy": [ - "semi-spacing" + { + "rule": { + "name": "semi-spacing" + } + } ] }, { "removed": "no-wrap-func", "replacedBy": [ - "no-extra-parens" + { + "rule": { + "name": "no-extra-parens" + } + } ] }, { "removed": "space-after-function-name", "replacedBy": [ - "space-before-function-paren" + { + "rule": { + "name": "space-before-function-paren" + } + } ] }, { "removed": "space-after-keywords", "replacedBy": [ - "keyword-spacing" + { + "rule": { + "name": "keyword-spacing" + } + } ] }, { "removed": "space-before-function-parentheses", "replacedBy": [ - "space-before-function-paren" + { + "rule": { + "name": "space-before-function-paren" + } + } ] }, { "removed": "space-before-keywords", "replacedBy": [ - "keyword-spacing" + { + "rule": { + "name": "keyword-spacing" + } + } ] }, { "removed": "space-in-brackets", "replacedBy": [ - "object-curly-spacing", - "array-bracket-spacing" + { + "rule": { + "name": "object-curly-spacing" + } + }, + { + "rule": { + "name": "array-bracket-spacing" + } + }, + { + "rule": { + "name": "computed-property-spacing" + } + } ] }, { "removed": "space-return-throw-case", "replacedBy": [ - "keyword-spacing" + { + "rule": { + "name": "keyword-spacing" + } + } ] }, { "removed": "space-unary-word-ops", "replacedBy": [ - "space-unary-ops" + { + "rule": { + "name": "space-unary-ops" + } + } ] }, { "removed": "spaced-line-comment", "replacedBy": [ - "spaced-comment" + { + "rule": { + "name": "spaced-comment" + } + } ] + }, + { + "removed": "valid-jsdoc", + "replacedBy": [] + }, + { + "removed": "require-jsdoc", + "replacedBy": [] } ] } \ No newline at end of file diff --git a/docs/src/_data/rules_categories.js b/docs/src/_data/rules_categories.js index 46856958f228..88d04a79aa92 100644 --- a/docs/src/_data/rules_categories.js +++ b/docs/src/_data/rules_categories.js @@ -1,26 +1,29 @@ +"use strict"; + module.exports = eleventy => { - const PATH_PREFIX = eleventy.PATH_PREFIX; + const PATH_PREFIX = eleventy.PATH_PREFIX; - return { - problem: { - displayName: "Possible Problems", - description: "These rules relate to possible logic errors in code:" - }, - suggestion: { - displayName: "Suggestions", - description: "These rules suggest alternate ways of doing things:" - }, - layout: { - displayName: "Layout & Formatting", - description: "These rules care about how the code looks rather than how it executes:" - }, - deprecated: { - displayName: "Deprecated", - description: `These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:` - }, - removed: { - displayName: "Removed", - description: `These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:` - } - } + return { + problem: { + displayName: "Possible Problems", + description: "These rules relate to possible logic errors in code:", + }, + suggestion: { + displayName: "Suggestions", + description: "These rules suggest alternate ways of doing things:", + }, + layout: { + displayName: "Layout & Formatting", + description: + "These rules care about how the code looks rather than how it executes:", + }, + deprecated: { + displayName: "Deprecated", + description: `These rules have been deprecated in accordance with the deprecation policy, and replaced by newer rules:`, + }, + removed: { + displayName: "Removed", + description: `These rules from older versions of ESLint (before the deprecation policy existed) have been replaced by newer rules:`, + }, + }; }; diff --git a/docs/src/_data/rules_meta.json b/docs/src/_data/rules_meta.json index 06db19be5099..5e240ae89314 100644 --- a/docs/src/_data/rules_meta.json +++ b/docs/src/_data/rules_meta.json @@ -1,6 +1,13 @@ { "accessor-pairs": { "type": "suggestion", + "defaultOptions": [ + { + "enforceForClassMembers": true, + "getWithoutSet": false, + "setWithoutGet": true + } + ], "docs": { "description": "Enforce getter and setter pairs in objects and classes", "recommended": false, @@ -8,8 +15,26 @@ } }, "array-bracket-newline": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "array-bracket-newline", + "url": "https://eslint.style/rules/array-bracket-newline" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce linebreaks after opening and before closing array brackets", @@ -19,8 +44,26 @@ "fixable": "whitespace" }, "array-bracket-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "array-bracket-spacing", + "url": "https://eslint.style/rules/array-bracket-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent spacing inside array brackets", @@ -31,6 +74,13 @@ }, "array-callback-return": { "type": "problem", + "defaultOptions": [ + { + "allowImplicit": false, + "checkForEach": false, + "allowVoid": false + } + ], "docs": { "description": "Enforce `return` statements in callbacks of array methods", "recommended": false, @@ -39,8 +89,26 @@ "hasSuggestions": true }, "array-element-newline": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "array-element-newline", + "url": "https://eslint.style/rules/array-element-newline" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce line breaks after each array element", @@ -51,16 +119,38 @@ }, "arrow-body-style": { "type": "suggestion", + "defaultOptions": [ + "as-needed" + ], "docs": { "description": "Require braces around arrow function bodies", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/arrow-body-style" }, "fixable": "code" }, "arrow-parens": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "arrow-parens", + "url": "https://eslint.style/rules/arrow-parens" + } + } + ] + }, "type": "layout", "docs": { "description": "Require parentheses around arrow function arguments", @@ -70,8 +160,26 @@ "fixable": "code" }, "arrow-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "arrow-spacing", + "url": "https://eslint.style/rules/arrow-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent spacing before and after the arrow in arrow functions", @@ -89,8 +197,26 @@ } }, "block-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "block-spacing", + "url": "https://eslint.style/rules/block-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Disallow or enforce spaces inside of blocks after opening block and before closing block", @@ -100,8 +226,26 @@ "fixable": "whitespace" }, "brace-style": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "brace-style", + "url": "https://eslint.style/rules/brace-style" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent brace style for blocks", @@ -111,8 +255,25 @@ "fixable": "whitespace" }, "callback-return": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Node.js rules were moved out of ESLint core.", + "url": "https://eslint.org/docs/latest/use/migrating-to-7.0.0#deprecate-node-rules", + "deprecatedSince": "7.0.0", + "availableUntil": null, + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "callback-return", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/callback-return.md" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Require `return` statements after callbacks", @@ -122,9 +283,19 @@ }, "camelcase": { "type": "suggestion", + "defaultOptions": [ + { + "allow": [], + "ignoreDestructuring": false, + "ignoreGlobals": false, + "ignoreImports": false, + "properties": "always" + } + ], "docs": { "description": "Enforce camelcase naming convention", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/camelcase" } }, @@ -133,12 +304,25 @@ "docs": { "description": "Enforce or disallow capitalization of the first letter of a comment", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/capitalized-comments" }, "fixable": "code" }, "class-methods-use-this": { - "type": "suggestion", + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", + "type": "suggestion", + "defaultOptions": [ + { + "enforceForClassFields": true, + "exceptMethods": [], + "ignoreOverrideMethods": false + } + ], "docs": { "description": "Enforce that class methods utilize `this`", "recommended": false, @@ -146,8 +330,26 @@ } }, "comma-dangle": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "comma-dangle", + "url": "https://eslint.style/rules/comma-dangle" + } + } + ] + }, "type": "layout", "docs": { "description": "Require or disallow trailing commas", @@ -157,8 +359,26 @@ "fixable": "code" }, "comma-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "comma-spacing", + "url": "https://eslint.style/rules/comma-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent spacing before and after commas", @@ -168,8 +388,26 @@ "fixable": "whitespace" }, "comma-style": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "comma-style", + "url": "https://eslint.style/rules/comma-style" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent comma style", @@ -180,6 +418,9 @@ }, "complexity": { "type": "suggestion", + "defaultOptions": [ + 20 + ], "docs": { "description": "Enforce a maximum cyclomatic complexity allowed in a program", "recommended": false, @@ -187,8 +428,26 @@ } }, "computed-property-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "computed-property-spacing", + "url": "https://eslint.style/rules/computed-property-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent spacing inside computed property brackets", @@ -203,15 +462,24 @@ "description": "Require `return` statements to either always or never specify values", "recommended": false, "url": "https://eslint.org/docs/latest/rules/consistent-return" - } + }, + "defaultOptions": [ + { + "treatUndefinedAsUnspecified": false + } + ] }, "consistent-this": { "type": "suggestion", "docs": { "description": "Enforce consistent naming when capturing the current execution context", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/consistent-this" - } + }, + "defaultOptions": [ + "that" + ] }, "constructor-super": { "type": "problem", @@ -226,12 +494,19 @@ "docs": { "description": "Enforce consistent brace style for all control statements", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/curly" }, + "defaultOptions": [ + "all" + ], "fixable": "code" }, "default-case": { "type": "suggestion", + "defaultOptions": [ + {} + ], "docs": { "description": "Require `default` cases in `switch` statements", "recommended": false, @@ -241,22 +516,46 @@ "default-case-last": { "type": "suggestion", "docs": { - "description": "Enforce default clauses in switch statements to be last", + "description": "Enforce `default` clauses in `switch` statements to be last", "recommended": false, "url": "https://eslint.org/docs/latest/rules/default-case-last" } }, "default-param-last": { + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", "type": "suggestion", "docs": { "description": "Enforce default parameters to be last", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/default-param-last" } }, "dot-location": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "dot-location", + "url": "https://eslint.style/rules/dot-location" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent newlines before and after dots", @@ -267,16 +566,41 @@ }, "dot-notation": { "type": "suggestion", + "defaultOptions": [ + { + "allowKeywords": true, + "allowPattern": "" + } + ], "docs": { "description": "Enforce dot notation whenever possible", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/dot-notation" }, "fixable": "code" }, "eol-last": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "eol-last", + "url": "https://eslint.style/rules/eol-last" + } + } + ] + }, "type": "layout", "docs": { "description": "Require or disallow newline at the end of files", @@ -287,6 +611,7 @@ }, "eqeqeq": { "type": "suggestion", + "hasSuggestions": true, "docs": { "description": "Require the use of `===` and `!==`", "recommended": false, @@ -297,15 +622,33 @@ "for-direction": { "type": "problem", "docs": { - "description": "Enforce \"for\" loop update clause moving the counter in the right direction", + "description": "Enforce `for` loop update clause moving the counter in the right direction", "recommended": true, "url": "https://eslint.org/docs/latest/rules/for-direction" }, "fixable": null }, "func-call-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "function-call-spacing", + "url": "https://eslint.style/rules/function-call-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Require or disallow spacing between function identifiers and their invocations", @@ -319,11 +662,16 @@ "docs": { "description": "Require function names to match the name of the variable or property to which they are assigned", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/func-name-matching" } }, "func-names": { "type": "suggestion", + "defaultOptions": [ + "always", + {} + ], "docs": { "description": "Require or disallow named `function` expressions", "recommended": false, @@ -331,16 +679,48 @@ } }, "func-style": { - "type": "suggestion", + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", + "type": "suggestion", + "defaultOptions": [ + "expression", + { + "allowArrowFunctions": false, + "allowTypeAnnotation": false, + "overrides": {} + } + ], "docs": { - "description": "Enforce the consistent use of either `function` declarations or expressions", + "description": "Enforce the consistent use of either `function` declarations or expressions assigned to variables", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/func-style" } }, "function-call-argument-newline": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "function-call-argument-newline", + "url": "https://eslint.style/rules/function-call-argument-newline" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce line breaks between arguments of a function call", @@ -350,8 +730,26 @@ "fixable": "whitespace" }, "function-paren-newline": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "function-paren-newline", + "url": "https://eslint.style/rules/function-paren-newline" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent line breaks inside function parentheses", @@ -361,8 +759,26 @@ "fixable": "whitespace" }, "generator-star-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "generator-star-spacing", + "url": "https://eslint.style/rules/generator-star-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent spacing around `*` operators in generator functions", @@ -373,6 +789,11 @@ }, "getter-return": { "type": "problem", + "defaultOptions": [ + { + "allowImplicit": false + } + ], "docs": { "description": "Enforce `return` statements in getters", "recommended": true, @@ -381,8 +802,25 @@ "fixable": null }, "global-require": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Node.js rules were moved out of ESLint core.", + "url": "https://eslint.org/docs/latest/use/migrating-to-7.0.0#deprecate-node-rules", + "deprecatedSince": "7.0.0", + "availableUntil": null, + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "global-require", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/global-require.md" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Require `require()` calls to be placed at top-level module scope", @@ -392,6 +830,9 @@ }, "grouped-accessor-pairs": { "type": "suggestion", + "defaultOptions": [ + "anyOrder" + ], "docs": { "description": "Require grouped accessor pairs in object literals and classes", "recommended": false, @@ -407,8 +848,25 @@ } }, "handle-callback-err": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Node.js rules were moved out of ESLint core.", + "url": "https://eslint.org/docs/latest/use/migrating-to-7.0.0#deprecate-node-rules", + "deprecatedSince": "7.0.0", + "availableUntil": null, + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "handle-callback-err", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/handle-callback-err.md" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Require error handling in callbacks", @@ -417,10 +875,20 @@ } }, "id-blacklist": { - "deprecated": true, - "replacedBy": [ - "id-denylist" - ], + "deprecated": { + "message": "The rule was renamed.", + "url": "https://eslint.org/blog/2020/07/eslint-v7.5.0-released/#deprecating-id-blacklist", + "deprecatedSince": "7.5.0", + "availableUntil": null, + "replacedBy": [ + { + "rule": { + "name": "id-denylist", + "url": "https://eslint.org/docs/rules/id-denylist" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Disallow specified identifiers", @@ -430,31 +898,70 @@ }, "id-denylist": { "type": "suggestion", + "defaultOptions": [], "docs": { "description": "Disallow specified identifiers", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/id-denylist" } }, "id-length": { "type": "suggestion", + "defaultOptions": [ + { + "exceptionPatterns": [], + "exceptions": [], + "min": 2, + "properties": "always" + } + ], "docs": { "description": "Enforce minimum and maximum identifier lengths", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/id-length" } }, "id-match": { "type": "suggestion", + "defaultOptions": [ + "^.+$", + { + "classFields": false, + "ignoreDestructuring": false, + "onlyDeclarations": false, + "properties": false + } + ], "docs": { "description": "Require identifiers to match a specified regular expression", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/id-match" } }, "implicit-arrow-linebreak": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "implicit-arrow-linebreak", + "url": "https://eslint.style/rules/implicit-arrow-linebreak" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce the location of arrow function bodies", @@ -464,8 +971,26 @@ "fixable": "whitespace" }, "indent": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "indent", + "url": "https://eslint.style/rules/indent" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent indentation", @@ -481,23 +1006,62 @@ "recommended": false, "url": "https://eslint.org/docs/latest/rules/indent-legacy" }, - "deprecated": true, - "replacedBy": [ - "indent" - ], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "4.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "indent", + "url": "https://eslint.style/rules/indent" + } + } + ] + }, "fixable": "whitespace" }, "init-declarations": { "type": "suggestion", + "dialects": [ + "typescript", + "javascript" + ], + "language": "javascript", "docs": { "description": "Require or disallow initialization in variable declarations", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/init-declarations" } }, "jsx-quotes": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "jsx-quotes", + "url": "https://eslint.style/rules/jsx-quotes" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce the consistent use of either double or single quotes in JSX attributes", @@ -507,8 +1071,26 @@ "fixable": "whitespace" }, "key-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "key-spacing", + "url": "https://eslint.style/rules/key-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent spacing between keys and values in object literal properties", @@ -518,8 +1100,26 @@ "fixable": "whitespace" }, "keyword-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "keyword-spacing", + "url": "https://eslint.style/rules/keyword-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent spacing before and after keywords", @@ -529,6 +1129,26 @@ "fixable": "whitespace" }, "line-comment-position": { + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "9.3.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "line-comment-position", + "url": "https://eslint.style/rules/line-comment-position" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce position of line comments", @@ -537,8 +1157,26 @@ } }, "linebreak-style": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "linebreak-style", + "url": "https://eslint.style/rules/linebreak-style" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent linebreak style", @@ -548,8 +1186,26 @@ "fixable": "whitespace" }, "lines-around-comment": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "lines-around-comment", + "url": "https://eslint.style/rules/lines-around-comment" + } + } + ] + }, "type": "layout", "docs": { "description": "Require empty lines around comments", @@ -566,14 +1222,48 @@ "url": "https://eslint.org/docs/latest/rules/lines-around-directive" }, "fixable": "whitespace", - "deprecated": true, - "replacedBy": [ - "padding-line-between-statements" - ] + "deprecated": { + "message": "The rule was replaced with a more general rule.", + "url": "https://eslint.org/blog/2017/06/eslint-v4.0.0-released/", + "deprecatedSince": "4.0.0", + "availableUntil": null, + "replacedBy": [ + { + "message": "The new rule moved to a plugin.", + "url": "https://eslint.org/docs/latest/rules/padding-line-between-statements#examples", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "padding-line-between-statements", + "url": "https://eslint.style/rules/padding-line-between-statements" + } + } + ] + } }, "lines-between-class-members": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "lines-between-class-members", + "url": "https://eslint.style/rules/lines-between-class-members" + } + } + ] + }, "type": "layout", "docs": { "description": "Require or disallow an empty line between class members", @@ -587,6 +1277,7 @@ "docs": { "description": "Require or disallow logical assignment operator shorthand", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/logical-assignment-operators" }, "fixable": "code", @@ -609,8 +1300,26 @@ } }, "max-len": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "max-len", + "url": "https://eslint.style/rules/max-len" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce a maximum line length", @@ -644,6 +1353,11 @@ }, "max-params": { "type": "suggestion", + "dialects": [ + "typescript", + "javascript" + ], + "language": "javascript", "docs": { "description": "Enforce a maximum number of parameters in function definitions", "recommended": false, @@ -659,8 +1373,26 @@ } }, "max-statements-per-line": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "max-statements-per-line", + "url": "https://eslint.style/rules/max-statements-per-line" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce a maximum number of statements allowed per line", @@ -669,6 +1401,26 @@ } }, "multiline-comment-style": { + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "9.3.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "multiline-comment-style", + "url": "https://eslint.style/rules/multiline-comment-style" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Enforce a particular style for multiline comments", @@ -678,8 +1430,26 @@ "fixable": "whitespace" }, "multiline-ternary": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "multiline-ternary", + "url": "https://eslint.style/rules/multiline-ternary" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce newlines between operands of ternary expressions", @@ -694,11 +1464,50 @@ "description": "Require constructor names to begin with a capital letter", "recommended": false, "url": "https://eslint.org/docs/latest/rules/new-cap" - } + }, + "defaultOptions": [ + { + "capIsNew": true, + "capIsNewExceptions": [ + "Array", + "Boolean", + "Date", + "Error", + "Function", + "Number", + "Object", + "RegExp", + "String", + "Symbol", + "BigInt" + ], + "newIsCap": true, + "newIsCapExceptions": [], + "properties": true + } + ] }, "new-parens": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "new-parens", + "url": "https://eslint.style/rules/new-parens" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce or disallow parentheses when invoking a constructor with no arguments", @@ -715,10 +1524,26 @@ "url": "https://eslint.org/docs/latest/rules/newline-after-var" }, "fixable": "whitespace", - "deprecated": true, - "replacedBy": [ - "padding-line-between-statements" - ] + "deprecated": { + "message": "The rule was replaced with a more general rule.", + "url": "https://eslint.org/blog/2017/06/eslint-v4.0.0-released/", + "deprecatedSince": "4.0.0", + "availableUntil": null, + "replacedBy": [ + { + "message": "The new rule moved to a plugin.", + "url": "https://eslint.org/docs/latest/rules/padding-line-between-statements#examples", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "padding-line-between-statements", + "url": "https://eslint.style/rules/padding-line-between-statements" + } + } + ] + } }, "newline-before-return": { "type": "layout", @@ -728,14 +1553,48 @@ "url": "https://eslint.org/docs/latest/rules/newline-before-return" }, "fixable": "whitespace", - "deprecated": true, - "replacedBy": [ - "padding-line-between-statements" - ] + "deprecated": { + "message": "The rule was replaced with a more general rule.", + "url": "https://eslint.org/blog/2017/06/eslint-v4.0.0-released/", + "deprecatedSince": "4.0.0", + "availableUntil": null, + "replacedBy": [ + { + "message": "The new rule moved to a plugin.", + "url": "https://eslint.org/docs/latest/rules/padding-line-between-statements#examples", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "padding-line-between-statements", + "url": "https://eslint.style/rules/padding-line-between-statements" + } + } + ] + } }, "newline-per-chained-call": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "newline-per-chained-call", + "url": "https://eslint.style/rules/newline-per-chained-call" + } + } + ] + }, "type": "layout", "docs": { "description": "Require a newline after each call in a method chain", @@ -753,12 +1612,18 @@ } }, "no-array-constructor": { + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", "type": "suggestion", "docs": { "description": "Disallow `Array` constructors", "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-array-constructor" }, + "fixable": "code", "hasSuggestions": true }, "no-async-promise-executor": { @@ -780,6 +1645,12 @@ }, "no-bitwise": { "type": "suggestion", + "defaultOptions": [ + { + "allow": [], + "int32Hint": false + } + ], "docs": { "description": "Disallow bitwise operators", "recommended": false, @@ -787,8 +1658,25 @@ } }, "no-buffer-constructor": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Node.js rules were moved out of ESLint core.", + "url": "https://eslint.org/docs/latest/use/migrating-to-7.0.0#deprecate-node-rules", + "deprecatedSince": "7.0.0", + "availableUntil": null, + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-deprecated-api", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-deprecated-api.md" + } + } + ] + }, "type": "problem", "docs": { "description": "Disallow use of the `Buffer()` constructor", @@ -810,7 +1698,8 @@ "description": "Disallow lexical declarations in case clauses", "recommended": true, "url": "https://eslint.org/docs/latest/rules/no-case-declarations" - } + }, + "hasSuggestions": true }, "no-catch-shadow": { "type": "suggestion", @@ -819,10 +1708,20 @@ "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-catch-shadow" }, - "replacedBy": [ - "no-shadow" - ], - "deprecated": true + "deprecated": { + "message": "This rule was renamed.", + "url": "https://eslint.org/blog/2018/07/eslint-v5.1.0-released/", + "deprecatedSince": "5.1.0", + "availableUntil": null, + "replacedBy": [ + { + "rule": { + "name": "no-shadow", + "url": "https://eslint.org/docs/rules/no-shadow" + } + } + ] + } }, "no-class-assign": { "type": "problem", @@ -835,7 +1734,7 @@ "no-compare-neg-zero": { "type": "problem", "docs": { - "description": "Disallow comparing against -0", + "description": "Disallow comparing against `-0`", "recommended": true, "url": "https://eslint.org/docs/latest/rules/no-compare-neg-zero" }, @@ -843,6 +1742,9 @@ }, "no-cond-assign": { "type": "problem", + "defaultOptions": [ + "except-parens" + ], "docs": { "description": "Disallow assignment operators in conditional expressions", "recommended": true, @@ -850,8 +1752,26 @@ } }, "no-confusing-arrow": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-confusing-arrow", + "url": "https://eslint.style/rules/no-confusing-arrow" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Disallow arrow functions where they could be confused with comparisons", @@ -862,6 +1782,9 @@ }, "no-console": { "type": "suggestion", + "defaultOptions": [ + {} + ], "docs": { "description": "Disallow the use of `console`", "recommended": false, @@ -872,7 +1795,7 @@ "no-const-assign": { "type": "problem", "docs": { - "description": "Disallow reassigning `const` variables", + "description": "Disallow reassigning `const`, `using`, and `await using` variables", "recommended": true, "url": "https://eslint.org/docs/latest/rules/no-const-assign" } @@ -881,12 +1804,17 @@ "type": "problem", "docs": { "description": "Disallow expressions where the operation doesn't affect the value", - "recommended": false, + "recommended": true, "url": "https://eslint.org/docs/latest/rules/no-constant-binary-expression" } }, "no-constant-condition": { "type": "problem", + "defaultOptions": [ + { + "checkLoops": "allExceptWhileTrue" + } + ], "docs": { "description": "Disallow constant expressions in conditions", "recommended": true, @@ -907,6 +1835,7 @@ "docs": { "description": "Disallow `continue` statements", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-continue" } }, @@ -940,6 +1869,7 @@ "docs": { "description": "Disallow equal signs explicitly at the beginning of regular expressions", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-div-regex" }, "fixable": "code" @@ -954,6 +1884,11 @@ }, "no-dupe-class-members": { "type": "problem", + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", "docs": { "description": "Disallow duplicate class members", "recommended": true, @@ -985,7 +1920,18 @@ } }, "no-duplicate-imports": { + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", "type": "problem", + "defaultOptions": [ + { + "includeExports": false, + "allowSeparateTypeImports": false + } + ], "docs": { "description": "Disallow duplicate module imports", "recommended": false, @@ -994,9 +1940,15 @@ }, "no-else-return": { "type": "suggestion", + "defaultOptions": [ + { + "allowElseIf": true + } + ], "docs": { "description": "Disallow `else` blocks after `return` statements in `if` statements", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-else-return" }, "fixable": "code" @@ -1004,6 +1956,11 @@ "no-empty": { "hasSuggestions": true, "type": "suggestion", + "defaultOptions": [ + { + "allowEmptyCatch": false + } + ], "docs": { "description": "Disallow empty block statements", "recommended": true, @@ -1019,7 +1976,17 @@ } }, "no-empty-function": { + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", "type": "suggestion", + "defaultOptions": [ + { + "allow": [] + } + ], "docs": { "description": "Disallow empty functions", "recommended": false, @@ -1028,6 +1995,11 @@ }, "no-empty-pattern": { "type": "problem", + "defaultOptions": [ + { + "allowObjectPatternsAsParameters": false + } + ], "docs": { "description": "Disallow empty destructuring patterns", "recommended": true, @@ -1038,7 +2010,7 @@ "type": "suggestion", "docs": { "description": "Disallow empty static blocks", - "recommended": false, + "recommended": true, "url": "https://eslint.org/docs/latest/rules/no-empty-static-block" } }, @@ -1052,6 +2024,11 @@ }, "no-eval": { "type": "suggestion", + "defaultOptions": [ + { + "allowIndirect": false + } + ], "docs": { "description": "Disallow the use of `eval()`", "recommended": false, @@ -1068,6 +2045,11 @@ }, "no-extend-native": { "type": "suggestion", + "defaultOptions": [ + { + "exceptions": [] + } + ], "docs": { "description": "Disallow extending native types", "recommended": false, @@ -1085,9 +2067,13 @@ }, "no-extra-boolean-cast": { "type": "suggestion", + "defaultOptions": [ + {} + ], "docs": { "description": "Disallow unnecessary boolean casts", "recommended": true, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-extra-boolean-cast" }, "fixable": "code" @@ -1097,13 +2083,32 @@ "docs": { "description": "Disallow unnecessary labels", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-extra-label" }, "fixable": "code" }, "no-extra-parens": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-extra-parens", + "url": "https://eslint.style/rules/no-extra-parens" + } + } + ] + }, "type": "layout", "docs": { "description": "Disallow unnecessary parentheses", @@ -1113,18 +2118,42 @@ "fixable": "code" }, "no-extra-semi": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-extra-semi", + "url": "https://eslint.style/rules/no-extra-semi" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Disallow unnecessary semicolons", - "recommended": true, + "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-extra-semi" }, "fixable": "code" }, "no-fallthrough": { "type": "problem", + "defaultOptions": [ + { + "allowEmptyCase": false, + "reportUnusedFallthroughComment": false + } + ], "docs": { "description": "Disallow fallthrough of `case` statements", "recommended": true, @@ -1132,8 +2161,26 @@ } }, "no-floating-decimal": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-floating-decimal", + "url": "https://eslint.style/rules/no-floating-decimal" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Disallow leading or trailing decimal points in numeric literals", @@ -1152,6 +2199,11 @@ }, "no-global-assign": { "type": "suggestion", + "defaultOptions": [ + { + "exceptions": [] + } + ], "docs": { "description": "Disallow assignments to native objects or read-only global variables", "recommended": true, @@ -1159,16 +2211,32 @@ } }, "no-implicit-coercion": { + "hasSuggestions": true, "type": "suggestion", "docs": { "description": "Disallow shorthand type conversions", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-implicit-coercion" }, - "fixable": "code" + "fixable": "code", + "defaultOptions": [ + { + "allow": [], + "boolean": true, + "disallowTemplateShorthand": false, + "number": true, + "string": true + } + ] }, "no-implicit-globals": { "type": "suggestion", + "defaultOptions": [ + { + "lexicalBindings": false + } + ], "docs": { "description": "Disallow declarations in the global scope", "recommended": false, @@ -1193,22 +2261,35 @@ }, "no-inline-comments": { "type": "suggestion", + "defaultOptions": [ + {} + ], "docs": { "description": "Disallow inline comments after code", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-inline-comments" } }, "no-inner-declarations": { "type": "problem", + "defaultOptions": [ + "functions", + { + "blockScopedFunctions": "allow" + } + ], "docs": { "description": "Disallow variable or `function` declarations in nested blocks", - "recommended": true, + "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-inner-declarations" } }, "no-invalid-regexp": { "type": "problem", + "defaultOptions": [ + {} + ], "docs": { "description": "Disallow invalid regular expression strings in `RegExp` constructors", "recommended": true, @@ -1216,7 +2297,17 @@ } }, "no-invalid-this": { + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", "type": "suggestion", + "defaultOptions": [ + { + "capIsConstructor": true + } + ], "docs": { "description": "Disallow use of `this` in contexts where the value of `this` is `undefined`", "recommended": false, @@ -1225,6 +2316,15 @@ }, "no-irregular-whitespace": { "type": "problem", + "defaultOptions": [ + { + "skipComments": false, + "skipJSXText": false, + "skipRegExps": false, + "skipStrings": true, + "skipTemplates": false + } + ], "docs": { "description": "Disallow irregular whitespace", "recommended": true, @@ -1244,14 +2344,22 @@ "docs": { "description": "Disallow labels that share a name with a variable", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-label-var" } }, "no-labels": { "type": "suggestion", + "defaultOptions": [ + { + "allowLoop": false, + "allowSwitch": false + } + ], "docs": { "description": "Disallow labeled statements", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-labels" } }, @@ -1268,12 +2376,18 @@ "docs": { "description": "Disallow `if` statements as the only statement in `else` blocks", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-lonely-if" }, "fixable": "code" }, "no-loop-func": { "type": "suggestion", + "dialects": [ + "typescript", + "javascript" + ], + "language": "javascript", "docs": { "description": "Disallow function declarations that contain unsafe references inside loop statements", "recommended": false, @@ -1282,6 +2396,11 @@ }, "no-loss-of-precision": { "type": "problem", + "dialects": [ + "typescript", + "javascript" + ], + "language": "javascript", "docs": { "description": "Disallow literal numbers that lose precision", "recommended": true, @@ -1290,9 +2409,15 @@ }, "no-magic-numbers": { "type": "suggestion", + "dialects": [ + "typescript", + "javascript" + ], + "language": "javascript", "docs": { "description": "Disallow magic numbers", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-magic-numbers" } }, @@ -1306,8 +2431,26 @@ "hasSuggestions": true }, "no-mixed-operators": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-mixed-operators", + "url": "https://eslint.style/rules/no-mixed-operators" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Disallow mixed binary operators", @@ -1316,8 +2459,25 @@ } }, "no-mixed-requires": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Node.js rules were moved out of ESLint core.", + "url": "https://eslint.org/docs/latest/use/migrating-to-7.0.0#deprecate-node-rules", + "deprecatedSince": "7.0.0", + "availableUntil": null, + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-mixed-requires", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-mixed-requires.md" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Disallow `require` calls to be mixed with regular variable declarations", @@ -1326,17 +2486,40 @@ } }, "no-mixed-spaces-and-tabs": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-mixed-spaces-and-tabs", + "url": "https://eslint.style/rules/no-mixed-spaces-and-tabs" + } + } + ] + }, "type": "layout", "docs": { "description": "Disallow mixed spaces and tabs for indentation", - "recommended": true, + "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-mixed-spaces-and-tabs" } }, "no-multi-assign": { "type": "suggestion", + "defaultOptions": [ + { + "ignoreNonDeclaration": false + } + ], "docs": { "description": "Disallow use of chained assignment expressions", "recommended": false, @@ -1344,8 +2527,26 @@ } }, "no-multi-spaces": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-multi-spaces", + "url": "https://eslint.style/rules/no-multi-spaces" + } + } + ] + }, "type": "layout", "docs": { "description": "Disallow multiple spaces", @@ -1359,12 +2560,31 @@ "docs": { "description": "Disallow multiline strings", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-multi-str" } }, "no-multiple-empty-lines": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-multiple-empty-lines", + "url": "https://eslint.style/rules/no-multiple-empty-lines" + } + } + ] + }, "type": "layout", "docs": { "description": "Disallow multiple empty lines", @@ -1380,16 +2600,27 @@ "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-native-reassign" }, - "deprecated": true, - "replacedBy": [ - "no-global-assign" - ] + "deprecated": { + "message": "Renamed rule.", + "url": "https://eslint.org/blog/2016/08/eslint-v3.3.0-released/#deprecated-rules", + "deprecatedSince": "3.3.0", + "availableUntil": null, + "replacedBy": [ + { + "rule": { + "name": "no-global-assign", + "url": "https://eslint.org/docs/rules/no-global-assign" + } + } + ] + } }, "no-negated-condition": { "type": "suggestion", "docs": { "description": "Disallow negated conditions", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-negated-condition" } }, @@ -1400,16 +2631,27 @@ "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-negated-in-lhs" }, - "replacedBy": [ - "no-unsafe-negation" - ], - "deprecated": true + "deprecated": { + "message": "Renamed rule.", + "url": "https://eslint.org/blog/2016/08/eslint-v3.3.0-released/#deprecated-rules", + "deprecatedSince": "3.3.0", + "availableUntil": null, + "replacedBy": [ + { + "rule": { + "name": "no-unsafe-negation", + "url": "https://eslint.org/docs/rules/no-unsafe-negation" + } + } + ] + } }, "no-nested-ternary": { "type": "suggestion", "docs": { "description": "Disallow nested ternary expressions", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-nested-ternary" } }, @@ -1433,7 +2675,7 @@ "type": "problem", "docs": { "description": "Disallow `new` operators with global non-constructor functions", - "recommended": false, + "recommended": true, "url": "https://eslint.org/docs/latest/rules/no-new-native-nonconstructor" } }, @@ -1444,14 +2686,41 @@ "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-new-object" }, - "deprecated": true, - "replacedBy": [ - "no-object-constructor" - ] + "deprecated": { + "message": "The new rule flags more situations where object literal syntax can be used, and it does not report a problem when the `Object` constructor is invoked with an argument.", + "url": "https://eslint.org/blog/2023/09/eslint-v8.50.0-released/", + "deprecatedSince": "8.50.0", + "availableUntil": null, + "replacedBy": [ + { + "rule": { + "name": "no-object-constructor", + "url": "https://eslint.org/docs/rules/no-object-constructor" + } + } + ] + } }, "no-new-require": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Node.js rules were moved out of ESLint core.", + "url": "https://eslint.org/docs/latest/use/migrating-to-7.0.0#deprecate-node-rules", + "deprecatedSince": "7.0.0", + "availableUntil": null, + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-new-require", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-new-require.md" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Disallow `new` operators with calls to `require`", @@ -1463,8 +2732,22 @@ "type": "problem", "docs": { "description": "Disallow `new` operators with the `Symbol` object", - "recommended": true, + "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-new-symbol" + }, + "deprecated": { + "message": "The rule was replaced with a more general rule.", + "url": "https://eslint.org/docs/latest/use/migrate-to-9.0.0#eslint-recommended", + "deprecatedSince": "9.0.0", + "availableUntil": null, + "replacedBy": [ + { + "rule": { + "name": "no-new-native-nonconstructor", + "url": "https://eslint.org/docs/latest/rules/no-new-native-nonconstructor" + } + } + ] } }, "no-new-wrappers": { @@ -1520,14 +2803,31 @@ "no-param-reassign": { "type": "suggestion", "docs": { - "description": "Disallow reassigning `function` parameters", + "description": "Disallow reassigning function parameters", "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-param-reassign" } }, "no-path-concat": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Node.js rules were moved out of ESLint core.", + "url": "https://eslint.org/docs/latest/use/migrating-to-7.0.0#deprecate-node-rules", + "deprecatedSince": "7.0.0", + "availableUntil": null, + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-path-concat", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-path-concat.md" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Disallow string concatenation with `__dirname` and `__filename`", @@ -1537,15 +2837,38 @@ }, "no-plusplus": { "type": "suggestion", + "defaultOptions": [ + { + "allowForLoopAfterthoughts": false + } + ], "docs": { "description": "Disallow the unary operators `++` and `--`", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-plusplus" } }, "no-process-env": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Node.js rules were moved out of ESLint core.", + "url": "https://eslint.org/docs/latest/use/migrating-to-7.0.0#deprecate-node-rules", + "deprecatedSince": "7.0.0", + "availableUntil": null, + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-process-env", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-process-env.md" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Disallow the use of `process.env`", @@ -1554,8 +2877,25 @@ } }, "no-process-exit": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Node.js rules were moved out of ESLint core.", + "url": "https://eslint.org/docs/latest/use/migrating-to-7.0.0#deprecate-node-rules", + "deprecatedSince": "7.0.0", + "availableUntil": null, + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-process-exit", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-process-exit.md" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Disallow the use of `process.exit()`", @@ -1565,6 +2905,11 @@ }, "no-promise-executor-return": { "type": "problem", + "defaultOptions": [ + { + "allowVoid": false + } + ], "docs": { "description": "Disallow returning values from Promise executor functions", "recommended": false, @@ -1591,6 +2936,11 @@ }, "no-redeclare": { "type": "suggestion", + "defaultOptions": [ + { + "builtinGlobals": true + } + ], "docs": { "description": "Disallow variable redeclaration", "recommended": true, @@ -1615,6 +2965,11 @@ } }, "no-restricted-globals": { + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", "type": "suggestion", "docs": { "description": "Disallow specified global variables", @@ -1631,8 +2986,25 @@ } }, "no-restricted-modules": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Node.js rules were moved out of ESLint core.", + "url": "https://eslint.org/docs/latest/use/migrating-to-7.0.0#deprecate-node-rules", + "deprecatedSince": "7.0.0", + "availableUntil": null, + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-restricted-require", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-restricted-require.md" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Disallow specified modules when loaded by `require`", @@ -1658,6 +3030,9 @@ }, "no-return-assign": { "type": "suggestion", + "defaultOptions": [ + "except-parens" + ], "docs": { "description": "Disallow assignment operators in `return` statements", "recommended": false, @@ -1673,19 +3048,28 @@ "url": "https://eslint.org/docs/latest/rules/no-return-await" }, "fixable": null, - "deprecated": true, - "replacedBy": [] + "deprecated": { + "message": "The original assumption of the rule no longer holds true because of engine optimization.", + "deprecatedSince": "8.46.0", + "availableUntil": null, + "replacedBy": [] + } }, "no-script-url": { "type": "suggestion", "docs": { - "description": "Disallow `javascript:` urls", + "description": "Disallow `javascript:` URLs", "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-script-url" } }, "no-self-assign": { "type": "problem", + "defaultOptions": [ + { + "props": true + } + ], "docs": { "description": "Disallow assignments where both sides are exactly the same", "recommended": true, @@ -1706,7 +3090,12 @@ "description": "Disallow comma operators", "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-sequences" - } + }, + "defaultOptions": [ + { + "allowInParentheses": true + } + ] }, "no-setter-return": { "type": "problem", @@ -1718,6 +3107,21 @@ }, "no-shadow": { "type": "suggestion", + "dialects": [ + "typescript", + "javascript" + ], + "language": "javascript", + "defaultOptions": [ + { + "allow": [], + "builtinGlobals": false, + "hoist": "functions", + "ignoreOnInitialization": false, + "ignoreTypeValueShadow": true, + "ignoreFunctionTypeParameterNameValueShadow": true + } + ], "docs": { "description": "Disallow variable declarations from shadowing variables declared in the outer scope", "recommended": false, @@ -1726,6 +3130,11 @@ }, "no-shadow-restricted-names": { "type": "suggestion", + "defaultOptions": [ + { + "reportGlobalThis": false + } + ], "docs": { "description": "Disallow identifiers from shadowing restricted names", "recommended": true, @@ -1739,10 +3148,26 @@ "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-spaced-func" }, - "deprecated": true, - "replacedBy": [ - "func-call-spacing" - ], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2016/08/eslint-v3.3.0-released/#deprecated-rules", + "deprecatedSince": "3.3.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "function-call-spacing", + "url": "https://eslint.style/rules/function-call-spacing" + } + } + ] + }, "fixable": "whitespace" }, "no-sparse-arrays": { @@ -1754,8 +3179,25 @@ } }, "no-sync": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Node.js rules were moved out of ESLint core.", + "url": "https://eslint.org/docs/latest/use/migrating-to-7.0.0#deprecate-node-rules", + "deprecatedSince": "7.0.0", + "availableUntil": null, + "replacedBy": [ + { + "message": "eslint-plugin-n now maintains deprecated Node.js-related rules.", + "plugin": { + "name": "eslint-plugin-n", + "url": "https://github.com/eslint-community/eslint-plugin-n" + }, + "rule": { + "name": "no-sync", + "url": "https://github.com/eslint-community/eslint-plugin-n/tree/master/docs/rules/no-sync.md" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Disallow synchronous methods", @@ -1764,8 +3206,26 @@ } }, "no-tabs": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-tabs", + "url": "https://eslint.style/rules/no-tabs" + } + } + ] + }, "type": "layout", "docs": { "description": "Disallow all tabs", @@ -1786,6 +3246,7 @@ "docs": { "description": "Disallow ternary operators", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-ternary" } }, @@ -1806,8 +3267,26 @@ } }, "no-trailing-spaces": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-trailing-spaces", + "url": "https://eslint.style/rules/no-trailing-spaces" + } + } + ] + }, "type": "layout", "docs": { "description": "Disallow trailing whitespace at the end of lines", @@ -1816,8 +3295,26 @@ }, "fixable": "whitespace" }, + "no-unassigned-vars": { + "type": "problem", + "dialects": [ + "typescript", + "javascript" + ], + "language": "javascript", + "docs": { + "description": "Disallow `let` or `var` variables that are read but never assigned", + "recommended": false, + "url": "https://eslint.org/docs/latest/rules/no-unassigned-vars" + } + }, "no-undef": { "type": "problem", + "defaultOptions": [ + { + "typeof": false + } + ], "docs": { "description": "Disallow the use of undeclared variables unless mentioned in `/*global */` comments", "recommended": true, @@ -1829,6 +3326,7 @@ "docs": { "description": "Disallow initializing variables to `undefined`", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-undef-init" }, "fixable": "code" @@ -1838,14 +3336,29 @@ "docs": { "description": "Disallow the use of `undefined` as an identifier", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-undefined" } }, "no-underscore-dangle": { "type": "suggestion", + "defaultOptions": [ + { + "allow": [], + "allowAfterSuper": false, + "allowAfterThis": false, + "allowAfterThisConstructor": false, + "allowFunctionParams": true, + "allowInArrayDestructuring": true, + "allowInObjectDestructuring": true, + "enforceInClassFields": false, + "enforceInMethodNames": false + } + ], "docs": { "description": "Disallow dangling underscores in identifiers", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-underscore-dangle" } }, @@ -1867,9 +3380,15 @@ }, "no-unneeded-ternary": { "type": "suggestion", + "defaultOptions": [ + { + "defaultAssignment": true + } + ], "docs": { "description": "Disallow ternary operators when simpler alternatives exist", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-unneeded-ternary" }, "fixable": "code" @@ -1884,6 +3403,11 @@ }, "no-unreachable-loop": { "type": "problem", + "defaultOptions": [ + { + "ignore": [] + } + ], "docs": { "description": "Disallow loops with a body that allows only one iteration", "recommended": false, @@ -1900,6 +3424,11 @@ }, "no-unsafe-negation": { "type": "problem", + "defaultOptions": [ + { + "enforceForOrderingRelations": false + } + ], "docs": { "description": "Disallow negating the left operand of relational operators", "recommended": true, @@ -1910,6 +3439,11 @@ }, "no-unsafe-optional-chaining": { "type": "problem", + "defaultOptions": [ + { + "disallowArithmeticOperators": false + } + ], "docs": { "description": "Disallow use of optional chaining in contexts where the `undefined` value is not allowed", "recommended": true, @@ -1918,12 +3452,26 @@ "fixable": null }, "no-unused-expressions": { + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", "type": "suggestion", "docs": { "description": "Disallow unused expressions", "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-unused-expressions" - } + }, + "defaultOptions": [ + { + "allowShortCircuit": false, + "allowTernary": false, + "allowTaggedTemplates": false, + "enforceForJSX": false, + "ignoreDirectives": false + } + ] }, "no-unused-labels": { "type": "suggestion", @@ -1938,7 +3486,7 @@ "type": "problem", "docs": { "description": "Disallow unused private class members", - "recommended": false, + "recommended": true, "url": "https://eslint.org/docs/latest/rules/no-unused-private-class-members" } }, @@ -1948,14 +3496,39 @@ "description": "Disallow unused variables", "recommended": true, "url": "https://eslint.org/docs/latest/rules/no-unused-vars" - } + }, + "hasSuggestions": true }, "no-use-before-define": { + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", "type": "problem", "docs": { "description": "Disallow the use of variables before they are defined", "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-use-before-define" + }, + "defaultOptions": [ + { + "classes": true, + "functions": true, + "variables": true, + "allowNamedExports": false, + "enums": true, + "typedefs": true, + "ignoreTypeReferences": true + } + ] + }, + "no-useless-assignment": { + "type": "problem", + "docs": { + "description": "Disallow variable assignments when the value is not used", + "recommended": false, + "url": "https://eslint.org/docs/latest/rules/no-useless-assignment" } }, "no-useless-backreference": { @@ -1984,9 +3557,15 @@ }, "no-useless-computed-key": { "type": "suggestion", + "defaultOptions": [ + { + "enforceForClassMembers": true + } + ], "docs": { "description": "Disallow unnecessary computed property keys in objects and classes", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-useless-computed-key" }, "fixable": "code" @@ -1996,19 +3575,31 @@ "docs": { "description": "Disallow unnecessary concatenation of literals or template literals", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-useless-concat" } }, "no-useless-constructor": { + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", "type": "suggestion", "docs": { "description": "Disallow unnecessary constructors", "recommended": false, "url": "https://eslint.org/docs/latest/rules/no-useless-constructor" - } + }, + "hasSuggestions": true }, "no-useless-escape": { "type": "suggestion", + "defaultOptions": [ + { + "allowRegexCharacters": [] + } + ], "docs": { "description": "Disallow unnecessary escape characters", "recommended": true, @@ -2018,6 +3609,13 @@ }, "no-useless-rename": { "type": "suggestion", + "defaultOptions": [ + { + "ignoreDestructuring": false, + "ignoreImport": false, + "ignoreExport": false + } + ], "docs": { "description": "Disallow renaming import, export, and destructured assignments to the same name", "recommended": false, @@ -2036,6 +3634,11 @@ }, "no-var": { "type": "suggestion", + "dialects": [ + "typescript", + "javascript" + ], + "language": "javascript", "docs": { "description": "Require `let` or `const` instead of `var`", "recommended": false, @@ -2045,23 +3648,58 @@ }, "no-void": { "type": "suggestion", + "defaultOptions": [ + { + "allowAsStatement": false + } + ], "docs": { "description": "Disallow `void` operators", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-void" } }, "no-warning-comments": { "type": "suggestion", + "defaultOptions": [ + { + "location": "start", + "terms": [ + "todo", + "fixme", + "xxx" + ] + } + ], "docs": { "description": "Disallow specified warning terms in comments", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/no-warning-comments" } }, "no-whitespace-before-property": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "no-whitespace-before-property", + "url": "https://eslint.style/rules/no-whitespace-before-property" + } + } + ] + }, "type": "layout", "docs": { "description": "Disallow whitespace before properties", @@ -2079,8 +3717,26 @@ } }, "nonblock-statement-body-position": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "nonblock-statement-body-position", + "url": "https://eslint.style/rules/nonblock-statement-body-position" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce the location of single-line statements", @@ -2090,8 +3746,26 @@ "fixable": "whitespace" }, "object-curly-newline": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "object-curly-newline", + "url": "https://eslint.style/rules/object-curly-newline" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent line breaks after opening and before closing braces", @@ -2101,8 +3775,26 @@ "fixable": "whitespace" }, "object-curly-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "object-curly-spacing", + "url": "https://eslint.style/rules/object-curly-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent spacing inside braces", @@ -2112,8 +3804,26 @@ "fixable": "whitespace" }, "object-property-newline": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "object-property-newline", + "url": "https://eslint.style/rules/object-property-newline" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce placing object properties on separate lines", @@ -2127,6 +3837,7 @@ "docs": { "description": "Require or disallow method and property shorthand syntax for object literals", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/object-shorthand" }, "fixable": "code" @@ -2136,13 +3847,32 @@ "docs": { "description": "Enforce variables to be declared either together or separately in functions", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/one-var" }, "fixable": "code" }, "one-var-declaration-per-line": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "one-var-declaration-per-line", + "url": "https://eslint.style/rules/one-var-declaration-per-line" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Require or disallow newlines around variable declarations", @@ -2153,16 +3883,38 @@ }, "operator-assignment": { "type": "suggestion", + "defaultOptions": [ + "always" + ], "docs": { "description": "Require or disallow assignment operator shorthand where possible", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/operator-assignment" }, "fixable": "code" }, "operator-linebreak": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "operator-linebreak", + "url": "https://eslint.style/rules/operator-linebreak" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent linebreak style for operators", @@ -2172,8 +3924,26 @@ "fixable": "code" }, "padded-blocks": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "padded-blocks", + "url": "https://eslint.style/rules/padded-blocks" + } + } + ] + }, "type": "layout", "docs": { "description": "Require or disallow padding within blocks", @@ -2183,8 +3953,26 @@ "fixable": "whitespace" }, "padding-line-between-statements": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "padding-line-between-statements", + "url": "https://eslint.style/rules/padding-line-between-statements" + } + } + ] + }, "type": "layout", "docs": { "description": "Require or disallow padding lines between statements", @@ -2195,15 +3983,33 @@ }, "prefer-arrow-callback": { "type": "suggestion", + "dialects": [ + "javascript", + "typescript" + ], + "language": "javascript", + "defaultOptions": [ + { + "allowNamedFunctions": false, + "allowUnboundThis": true + } + ], "docs": { "description": "Require using arrow functions for callbacks", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/prefer-arrow-callback" }, "fixable": "code" }, "prefer-const": { "type": "suggestion", + "defaultOptions": [ + { + "destructuring": "any", + "ignoreReadBeforeAssign": false + } + ], "docs": { "description": "Require `const` declarations for variables that are never reassigned after declared", "recommended": false, @@ -2216,6 +4022,7 @@ "docs": { "description": "Require destructuring from arrays and/or objects", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/prefer-destructuring" }, "fixable": "code" @@ -2225,6 +4032,7 @@ "docs": { "description": "Disallow the use of `Math.pow` in favor of the `**` operator", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/prefer-exponentiation-operator" }, "fixable": "code" @@ -2243,6 +4051,7 @@ "docs": { "description": "Disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/prefer-numeric-literals" }, "fixable": "code" @@ -2259,14 +4068,20 @@ "prefer-object-spread": { "type": "suggestion", "docs": { - "description": "Disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead", + "description": "Disallow using `Object.assign` with an object literal as the first argument and prefer the use of object spread instead", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/prefer-object-spread" }, "fixable": "code" }, "prefer-promise-reject-errors": { "type": "suggestion", + "defaultOptions": [ + { + "allowEmptyReject": false + } + ], "docs": { "description": "Require using Error objects as Promise rejection reasons", "recommended": false, @@ -2281,11 +4096,20 @@ "recommended": false, "url": "https://eslint.org/docs/latest/rules/prefer-reflect" }, - "deprecated": true, - "replacedBy": [] + "deprecated": { + "message": "The original intention of this rule was misguided.", + "deprecatedSince": "3.9.0", + "availableUntil": null, + "replacedBy": [] + } }, "prefer-regex-literals": { "type": "suggestion", + "defaultOptions": [ + { + "disallowRedundantWrapping": false + } + ], "docs": { "description": "Disallow use of the `RegExp` constructor in favor of regular expression literals", "recommended": false, @@ -2306,6 +4130,7 @@ "docs": { "description": "Require spread operators instead of `.apply()`", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/prefer-spread" }, "fixable": null @@ -2315,13 +4140,32 @@ "docs": { "description": "Require template literals instead of string concatenation", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/prefer-template" }, "fixable": "code" }, "quote-props": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "quote-props", + "url": "https://eslint.style/rules/quote-props" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Require quotes around object literal property names", @@ -2331,8 +4175,26 @@ "fixable": "code" }, "quotes": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "quotes", + "url": "https://eslint.style/rules/quotes" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce the consistent use of either backticks, double, or single quotes", @@ -2343,6 +4205,9 @@ }, "radix": { "type": "suggestion", + "defaultOptions": [ + "always" + ], "docs": { "description": "Enforce the consistent use of the radix argument when using `parseInt()`", "recommended": false, @@ -2352,6 +4217,11 @@ }, "require-atomic-updates": { "type": "problem", + "defaultOptions": [ + { + "allowProperties": false + } + ], "docs": { "description": "Disallow assignments that can lead to race conditions due to usage of `await` or `yield`", "recommended": false, @@ -2365,22 +4235,13 @@ "description": "Disallow async functions which have no `await` expression", "recommended": false, "url": "https://eslint.org/docs/latest/rules/require-await" - } - }, - "require-jsdoc": { - "type": "suggestion", - "docs": { - "description": "Require JSDoc comments", - "recommended": false, - "url": "https://eslint.org/docs/latest/rules/require-jsdoc" }, - "deprecated": true, - "replacedBy": [] + "hasSuggestions": true }, "require-unicode-regexp": { "type": "suggestion", "docs": { - "description": "Enforce the use of `u` or `v` flag on RegExp", + "description": "Enforce the use of `u` or `v` flag on regular expressions", "recommended": false, "url": "https://eslint.org/docs/latest/rules/require-unicode-regexp" }, @@ -2395,8 +4256,26 @@ } }, "rest-spread-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "rest-spread-spacing", + "url": "https://eslint.style/rules/rest-spread-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce spacing between rest and spread operators and their expressions", @@ -2406,8 +4285,26 @@ "fixable": "whitespace" }, "semi": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "semi", + "url": "https://eslint.style/rules/semi" + } + } + ] + }, "type": "layout", "docs": { "description": "Require or disallow semicolons instead of ASI", @@ -2417,8 +4314,26 @@ "fixable": "code" }, "semi-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "semi-spacing", + "url": "https://eslint.style/rules/semi-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent spacing before and after semicolons", @@ -2428,8 +4343,26 @@ "fixable": "whitespace" }, "semi-style": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "semi-style", + "url": "https://eslint.style/rules/semi-style" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce location of semicolons", @@ -2440,33 +4373,83 @@ }, "sort-imports": { "type": "suggestion", + "defaultOptions": [ + { + "allowSeparatedGroups": false, + "ignoreCase": false, + "ignoreDeclarationSort": false, + "ignoreMemberSort": false, + "memberSyntaxSortOrder": [ + "none", + "all", + "multiple", + "single" + ] + } + ], "docs": { - "description": "Enforce sorted import declarations within modules", + "description": "Enforce sorted `import` declarations within modules", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/sort-imports" }, "fixable": "code" }, "sort-keys": { "type": "suggestion", + "defaultOptions": [ + "asc", + { + "allowLineSeparatedGroups": false, + "caseSensitive": true, + "ignoreComputedKeys": false, + "minKeys": 2, + "natural": false + } + ], "docs": { "description": "Require object keys to be sorted", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/sort-keys" } }, "sort-vars": { "type": "suggestion", + "defaultOptions": [ + { + "ignoreCase": false + } + ], "docs": { "description": "Require variables within the same declaration block to be sorted", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/sort-vars" }, "fixable": "code" }, "space-before-blocks": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "space-before-blocks", + "url": "https://eslint.style/rules/space-before-blocks" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent spacing before blocks", @@ -2476,8 +4459,26 @@ "fixable": "whitespace" }, "space-before-function-paren": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "space-before-function-paren", + "url": "https://eslint.style/rules/space-before-function-paren" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent spacing before `function` definition opening parenthesis", @@ -2487,8 +4488,26 @@ "fixable": "whitespace" }, "space-in-parens": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "space-in-parens", + "url": "https://eslint.style/rules/space-in-parens" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent spacing inside parentheses", @@ -2498,8 +4517,26 @@ "fixable": "whitespace" }, "space-infix-ops": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "space-infix-ops", + "url": "https://eslint.style/rules/space-infix-ops" + } + } + ] + }, "type": "layout", "docs": { "description": "Require spacing around infix operators", @@ -2509,8 +4546,26 @@ "fixable": "whitespace" }, "space-unary-ops": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "space-unary-ops", + "url": "https://eslint.style/rules/space-unary-ops" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce consistent spacing before or after unary operators", @@ -2520,8 +4575,26 @@ "fixable": "whitespace" }, "spaced-comment": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "spaced-comment", + "url": "https://eslint.style/rules/spaced-comment" + } + } + ] + }, "type": "suggestion", "docs": { "description": "Enforce consistent spacing after the `//` or `/*` in a comment", @@ -2532,6 +4605,9 @@ }, "strict": { "type": "suggestion", + "defaultOptions": [ + "safe" + ], "docs": { "description": "Require or disallow strict mode directives", "recommended": false, @@ -2540,8 +4616,26 @@ "fixable": "code" }, "switch-colon-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "switch-colon-spacing", + "url": "https://eslint.style/rules/switch-colon-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Enforce spacing around colons of switch statements", @@ -2560,8 +4654,26 @@ "fixable": null }, "template-curly-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "template-curly-spacing", + "url": "https://eslint.style/rules/template-curly-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Require or disallow spacing around embedded expressions of template strings", @@ -2571,8 +4683,26 @@ "fixable": "whitespace" }, "template-tag-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "template-tag-spacing", + "url": "https://eslint.style/rules/template-tag-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Require or disallow spacing between template tags and their literals", @@ -2583,6 +4713,9 @@ }, "unicode-bom": { "type": "layout", + "defaultOptions": [ + "never" + ], "docs": { "description": "Require or disallow Unicode byte order mark (BOM)", "recommended": false, @@ -2591,26 +4724,27 @@ "fixable": "whitespace" }, "use-isnan": { + "hasSuggestions": true, "type": "problem", "docs": { "description": "Require calls to `isNaN()` when checking for `NaN`", "recommended": true, "url": "https://eslint.org/docs/latest/rules/use-isnan" - } - }, - "valid-jsdoc": { - "type": "suggestion", - "docs": { - "description": "Enforce valid JSDoc comments", - "recommended": false, - "url": "https://eslint.org/docs/latest/rules/valid-jsdoc" }, - "fixable": "code", - "deprecated": true, - "replacedBy": [] + "defaultOptions": [ + { + "enforceForIndexOf": false, + "enforceForSwitchCase": true + } + ] }, "valid-typeof": { "type": "problem", + "defaultOptions": [ + { + "requireStringLiterals": false + } + ], "docs": { "description": "Enforce comparing `typeof` expressions against valid strings", "recommended": true, @@ -2623,12 +4757,31 @@ "docs": { "description": "Require `var` declarations be placed at the top of their containing scope", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/vars-on-top" } }, "wrap-iife": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "wrap-iife", + "url": "https://eslint.style/rules/wrap-iife" + } + } + ] + }, "type": "layout", "docs": { "description": "Require parentheses around immediate `function` invocations", @@ -2638,8 +4791,26 @@ "fixable": "code" }, "wrap-regex": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "wrap-regex", + "url": "https://eslint.style/rules/wrap-regex" + } + } + ] + }, "type": "layout", "docs": { "description": "Require parenthesis around regex literals", @@ -2649,8 +4820,26 @@ "fixable": "code" }, "yield-star-spacing": { - "deprecated": true, - "replacedBy": [], + "deprecated": { + "message": "Formatting rules are being moved out of ESLint core.", + "url": "https://eslint.org/blog/2023/10/deprecating-formatting-rules/", + "deprecatedSince": "8.53.0", + "availableUntil": "10.0.0", + "replacedBy": [ + { + "message": "ESLint Stylistic now maintains deprecated stylistic core rules.", + "url": "https://eslint.style/guide/migration", + "plugin": { + "name": "@stylistic/eslint-plugin", + "url": "https://eslint.style" + }, + "rule": { + "name": "yield-star-spacing", + "url": "https://eslint.style/rules/yield-star-spacing" + } + } + ] + }, "type": "layout", "docs": { "description": "Require or disallow spacing around the `*` in `yield*` expressions", @@ -2661,9 +4850,17 @@ }, "yoda": { "type": "suggestion", + "defaultOptions": [ + "never", + { + "exceptRange": false, + "onlyEquality": false + } + ], "docs": { "description": "Require or disallow \"Yoda\" conditions", "recommended": false, + "frozen": true, "url": "https://eslint.org/docs/latest/rules/yoda" }, "fixable": "code" diff --git a/docs/src/_data/site.js b/docs/src/_data/site.js index d0792694f48a..7f9802822716 100644 --- a/docs/src/_data/site.js +++ b/docs/src/_data/site.js @@ -3,24 +3,25 @@ * @author Nicholas C. Zakas */ +"use strict"; + //----------------------------------------------------------------------------- // Requirements //----------------------------------------------------------------------------- -const path = require("path"); -const fs = require("fs"); +const path = require("node:path"); +const fs = require("node:fs"); const yaml = require("js-yaml"); //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- -module.exports = function(eleventy) { - - const siteName = eleventy.site_name; - const siteDataFile = path.resolve(__dirname, `sites/${siteName}.yml`); +module.exports = function (eleventy) { + const siteName = eleventy.site_name; + const siteDataFile = path.resolve(__dirname, `sites/${siteName}.yml`); - fs.statSync(siteDataFile); + fs.statSync(siteDataFile); - return yaml.load(fs.readFileSync(siteDataFile)); -} + return yaml.load(fs.readFileSync(siteDataFile)); +}; diff --git a/docs/src/_data/sites/en.yml b/docs/src/_data/sites/en.yml index 532630be810a..6cc7e1de4c97 100644 --- a/docs/src/_data/sites/en.yml +++ b/docs/src/_data/sites/en.yml @@ -1,3 +1,4 @@ +--- #------------------------------------------------------------------------------ # English Site Details # The documentation site that is hosted at eslint.org/docs @@ -9,9 +10,9 @@ #------------------------------------------------------------------------------ language: - code: en - flag: đŸ‡ē🇸 - name: English (US) + code: en + flag: đŸ‡ē🇸 + name: English (US) locale: en-US hostname: eslint.org @@ -20,99 +21,106 @@ hostname: eslint.org #------------------------------------------------------------------------------ google_analytics: - code: "G-7DGPHY308T" + code: "G-7DGPHY308T" #------------------------------------------------------------------------------ # Ads #------------------------------------------------------------------------------ carbon_ads: - serve: CESDV2QM - placement: eslintorg + serve: "" + placement: "" +ethical_ads: true #------------------------------------------------------------------------------ # Shared #------------------------------------------------------------------------------ shared: - get_started: Get Started - become_a_sponsor: Become a Sponsor - eslint_logo_alt: ESLint logo - description: > - A pluggable and configurable linter tool for identifying and reporting on - patterns in JavaScript. Maintain your code quality with ease. - title_format: PAGE_TITLE - ESLint - Pluggable JavaScript Linter - skip_to_content: Skip to main content - donate: Donate + get_started: Get Started + become_a_sponsor: Become a Sponsor + eslint_logo_alt: ESLint logo + description: > + A pluggable and configurable linter tool for identifying and reporting on + patterns in JavaScript. Maintain your code quality with ease. + title_format: PAGE_TITLE - ESLint - Pluggable JavaScript Linter + skip_to_content: Skip to main content + donate: Donate #------------------------------------------------------------------------------ # Navigation #------------------------------------------------------------------------------ navigation: -- text: Team - link: team -- text: Blog - link: blog -- text: Docs - link: docs -- text: Store - link: store - target: _blank -- text: Playground - link: playground + - text: Team + link: team + - text: Blog + link: blog + - text: Docs + link: docs + - text: Store + link: store + target: _blank + - text: Playground + link: playground + - text: Code Explorer + link: codeExplorer + target: _blank #------------------------------------------------------------------------------ # Footer #------------------------------------------------------------------------------ footer: - title: Ready to fix your JavaScript code? - description: Install from npm or start donating today. - secondary: Secondary - social_icons: - title: Social Media - twitter: Twitter - chat: Discord - github: GitHub - mastodon: Mastodon - theme_switcher: - title: Theme Switcher - light: Light - dark: Dark - language_switcher: - title: Language Switcher - description: Selecting a language will take you to the ESLint website in that language. - change_language: Change Language - language: Language - latest: Latest - copyright: > - © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under MIT License. - links: - open_jsf: The OpenJS Foundation - terms: Terms of Use - privacy: Privacy Policy - bylaws: OpenJS Foundation Bylaws - trademark: Trademark Policy - trademark_list: Trademark List - cookies: Cookie Policy + title: Ready to fix your JavaScript code? + description: Install from npm or start donating today. + secondary: Secondary + social_icons: + title: Social Media + chat: Discord + github: GitHub + bluesky: Bluesky + mastodon: Mastodon + twitter: Twitter + theme_switcher: + title: Theme Switcher + light: Light + dark: Dark + system: System + language_switcher: + title: Language Switcher + description: Selecting a language will take you to the ESLint website in that language. + change_language: Change Language + language: Language + latest: Latest + copyright: > + © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under MIT License. + links: + open_jsf: The OpenJS Foundation + terms: Terms of Use + privacy: Privacy Policy + bylaws: OpenJS Foundation Bylaws + trademark: Trademark Policy + trademark_list: Trademark List + cookies: Cookie Policy #------------------------------------------------------------------------------ # 404 Page #------------------------------------------------------------------------------ 404_page: - title: 404 error - subtitle: Page not found - description: Sorry, the page you are looking for doesn't exist or has been moved. - actions: - back_to_home: Back to homepage - browse_docs: Browse the docs + title: 404 error + subtitle: Page not found + description: Sorry, the page you are looking for doesn't exist or has been moved. + actions: + back_to_home: Back to homepage + browse_docs: Browse the docs #------------------------------------------------------------------------------ # Edit link #------------------------------------------------------------------------------ edit_link: - start_with: https://github.com/eslint/eslint/edit/main/docs/ - text: Edit this page + start_with: https://github.com/eslint/eslint/edit/main/docs/ + start_with_latest: https://github.com/eslint/eslint/edit/latest/docs/ + text: Edit this page diff --git a/docs/src/_data/sites/zh-hans.yml b/docs/src/_data/sites/zh-hans.yml index 421401535d05..b6dc588f16d2 100644 --- a/docs/src/_data/sites/zh-hans.yml +++ b/docs/src/_data/sites/zh-hans.yml @@ -1,3 +1,4 @@ +--- #------------------------------------------------------------------------------ # Simplified Chinese Site Details # The documentation site that is hosted at zh-hans.eslint.org/docs @@ -9,9 +10,9 @@ #------------------------------------------------------------------------------ language: - code: zh-hans - flag: đŸ‡¨đŸ‡ŗ - name: įŽ€äŊ“中文 + code: zh-hans + flag: đŸ‡¨đŸ‡ŗ + name: įŽ€äŊ“中文 locale: zh-hans hostname: zh-hans.eslint.org @@ -20,97 +21,104 @@ hostname: zh-hans.eslint.org #------------------------------------------------------------------------------ google_analytics: - code: "G-6ELXTK7GZR" + code: "G-6ELXTK7GZR" #------------------------------------------------------------------------------ # Ads #------------------------------------------------------------------------------ carbon_ads: - serve: "" - placement: "" + serve: "" + placement: "" #------------------------------------------------------------------------------ # Shared #------------------------------------------------------------------------------ shared: - get_started: åŧ€å§‹ - become_a_sponsor: 捐čĩ  - eslint_logo_alt: ESLint 回标 - description: > - 插äģļ化、可配įŊŽįš„ JavaScript äģŖį æŖ€æŸĨåˇĨå…ˇīŧŒčŽŠäŊ čŊģæžåœ°æé̘äģŖį č´¨é‡ã€‚ - title_format: PAGE_TITLE - ESLint - 插äģļåŒ–įš„ JavaScript äģŖį æŖ€æŸĨåˇĨå…ˇ - skip_to_content: 莺čŊŦåˆ°æ­Ŗæ–‡ - donate: 捐čĩ  + get_started: åŧ€å§‹ + become_a_sponsor: 捐čĩ  + eslint_logo_alt: ESLint 回标 + description: > + 插äģļ化、可配įŊŽįš„ JavaScript äģŖį æŖ€æŸĨåˇĨå…ˇīŧŒčŽŠäŊ čŊģæžåœ°æé̘äģŖį č´¨é‡ã€‚ + title_format: PAGE_TITLE - ESLint - 插äģļåŒ–įš„ JavaScript äģŖį æŖ€æŸĨåˇĨå…ˇ + skip_to_content: 莺čŊŦåˆ°æ­Ŗæ–‡ + donate: 捐čĩ  #------------------------------------------------------------------------------ # Navigation #------------------------------------------------------------------------------ navigation: -- text: å›ĸ队 - link: team -- text: 博åŽĸ - link: blog -- text: æ–‡æĄŖ - link: docs -- text: 商åē— - link: store - target: _blank -- text: æŧ”įģƒåœē - link: playground + - text: å›ĸ队 + link: team + - text: 博åŽĸ + link: blog + - text: æ–‡æĄŖ + link: docs + - text: 商åē— + link: store + target: _blank + - text: æŧ”įģƒåœē + link: playground + - text: Code Explorer + link: codeExplorer + target: _blank #------------------------------------------------------------------------------ # Footer #------------------------------------------------------------------------------ footer: - title: 准备äŋŽå¤äŊ įš„ JavaScript äģŖį äē†å—īŧŸ - description: äģŽ npm åŽ‰čŖ…æˆ–æčĩ åŧ€å§‹ã€‚ - secondary: æŦĄčρ - social_icons: - title: į¤žäē¤åĒ’äŊ“ - twitter: Twitter - chat: Discord - github: GitHub - theme_switcher: - title: ä¸ģéĸ˜åˆ‡æĸ - light: æĩ…色 - dark: æˇąč‰˛ - language_switcher: - title: č¯­č¨€åˆ‡æĸ - description: 切æĸ到äŊ æ‰€é€‰æ‹Šč¯­č¨€į‰ˆæœŦ寚åē”įš„ ESLint įŊ‘įĢ™ã€‚ - change_language: æ›´æ”šč¯­č¨€ - language: 蝭荀 - latest: 最新 - copyright: > - © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under MIT License. - links: - open_jsf: OpenJS åŸē金äŧš - terms: äŊŋį”¨æĄæŦž - privacy: éšį§į­–į•Ĩ - bylaws: OpenJS åŸē金äŧšį̠ፋ - trademark: å•†æ ‡į­–į•Ĩ - trademark_list: å•†æ ‡åˆ—čĄ¨ - cookies: Cookie į­–į•Ĩ + title: 准备äŋŽå¤äŊ įš„ JavaScript äģŖį äē†å—īŧŸ + description: äģŽ npm åŽ‰čŖ…æˆ–æčĩ åŧ€å§‹ã€‚ + secondary: æŦĄčρ + social_icons: + title: į¤žäē¤åĒ’äŊ“ + chat: Discord + github: GitHub + bluesky: Bluesky + mastodon: Mastodon + twitter: Twitter + theme_switcher: + title: ä¸ģéĸ˜åˆ‡æĸ + light: æĩ…色 + dark: æˇąč‰˛ + system: įŗģįģŸ + language_switcher: + title: č¯­č¨€åˆ‡æĸ + description: 切æĸ到äŊ æ‰€é€‰æ‹Šč¯­č¨€į‰ˆæœŦ寚åē”įš„ ESLint įŊ‘įĢ™ã€‚ + change_language: æ›´æ”šč¯­č¨€ + language: 蝭荀 + latest: 最新 + copyright: > + © OpenJS Foundation and ESLint contributors, www.openjsf.org. Content licensed under MIT License. + links: + open_jsf: OpenJS åŸē金äŧš + terms: äŊŋį”¨æĄæŦž + privacy: éšį§į­–į•Ĩ + bylaws: OpenJS åŸē金äŧšį̠ፋ + trademark: å•†æ ‡į­–į•Ĩ + trademark_list: å•†æ ‡åˆ—čĄ¨ + cookies: Cookie į­–į•Ĩ #------------------------------------------------------------------------------ # 404 Page #------------------------------------------------------------------------------ 404_page: - title: 404 错蝝 - subtitle: éĄĩéĸæœĒ扞到 - description: 寚不čĩˇīŧŒäŊ æ­Ŗåœ¨å¯ģæ‰žįš„éĄĩéĸä¸å­˜åœ¨æˆ–åˇ˛čĸĢį§ģ动。 - actions: - back_to_home: 回到ä¸ģéĄĩ - browse_docs: æĩč§ˆæ–‡æĄŖ + title: 404 错蝝 + subtitle: éĄĩéĸæœĒ扞到 + description: 寚不čĩˇīŧŒäŊ æ­Ŗåœ¨å¯ģæ‰žįš„éĄĩéĸä¸å­˜åœ¨æˆ–åˇ˛čĸĢį§ģ动。 + actions: + back_to_home: 回到ä¸ģéĄĩ + browse_docs: æĩč§ˆæ–‡æĄŖ #------------------------------------------------------------------------------ # Edit link #------------------------------------------------------------------------------ edit_link: - start_with: https://github.com/eslint/eslint/edit/main/docs/ - text: įŧ–čž‘æ­¤éĄĩ + start_with: https://github.com/eslint/eslint/edit/main/docs/ + start_with_latest: https://github.com/eslint/eslint/edit/latest/docs/ + text: įŧ–čž‘æ­¤éĄĩ diff --git a/docs/src/_data/versions.json b/docs/src/_data/versions.json index 6e585d6ad5a4..fa517d3ed27a 100644 --- a/docs/src/_data/versions.json +++ b/docs/src/_data/versions.json @@ -1,4 +1,19 @@ { "items": [ + { + "version": "HEAD", + "branch": "main", + "path": "/docs/head/" + }, + { + "version": "9.31.0", + "branch": "latest", + "path": "/docs/latest/" + }, + { + "version": "8.57.1", + "branch": "v8.x", + "path": "/docs/v8.x/" + } ] } diff --git a/docs/src/_includes/components/hero.macro.html b/docs/src/_includes/components/hero.macro.html index 3ff0c9c6f80f..5b6ccb38bf4c 100644 --- a/docs/src/_includes/components/hero.macro.html +++ b/docs/src/_includes/components/hero.macro.html @@ -22,7 +22,7 @@

{{ params.title }}

{% endif %}
- {% include "partials/carbon-ad.html" %} + {% include "partials/ad.html" %}
diff --git a/docs/src/_includes/components/logo.html b/docs/src/_includes/components/logo.html index 5e422b6fa3ce..bd4c2ba4be91 100644 --- a/docs/src/_includes/components/logo.html +++ b/docs/src/_includes/components/logo.html @@ -1,21 +1,12 @@ diff --git a/docs/src/_includes/components/nav-version-switcher.html b/docs/src/_includes/components/nav-version-switcher.html index a01e5ddf30cf..d7629077998f 100644 --- a/docs/src/_includes/components/nav-version-switcher.html +++ b/docs/src/_includes/components/nav-version-switcher.html @@ -12,17 +12,10 @@ Version diff --git a/docs/src/_includes/components/npm_tabs.macro.html b/docs/src/_includes/components/npm_tabs.macro.html new file mode 100644 index 000000000000..1dfc5dc08885 --- /dev/null +++ b/docs/src/_includes/components/npm_tabs.macro.html @@ -0,0 +1,46 @@ +{%- macro npm_tabs(params) -%} +
+ +
+

npm

+ +```shell +{% if params.comment %}# {{ params.comment | safe }}{{ "\n" }}{% endif -%} +npm {% if conversions.toNpmCommands[params.command] %}{{ conversions.toNpmCommands[params.command] | safe }}{% else %}{{ params.command | safe }}{% endif -%}{% for arg in params.args %} {{ conversions.toNpmArgs[arg] | safe }}{% endfor %}{% for package in params.packages %} {{ package | safe }}{% endfor %} +``` + +
+
+

yarn

+ +```shell +{% if params.comment %}# {{ params.comment | safe }}{{ "\n" }}{% endif -%} +yarn {% if params.args.includes("--global") %}{{ conversions.toYarnArgs["--global"] | safe }} {% endif -%} {% if conversions.toYarnCommands[params.command] %}{{ conversions.toYarnCommands[params.command] | safe }}{% else %}{{ params.command | safe }}{% endif -%}{% for arg in params.args %}{% if arg !== "--global" %} {{ conversions.toYarnArgs[arg] | safe }}{% endif -%}{% endfor %}{% for package in params.packages %} {{ package.replace("@eslint/config@latest", "@eslint/config") | safe }}{% endfor %} +``` + +
+
+

pnpm

+ +```shell +{% if params.comment %}# {{ params.comment | safe }}{{ "\n" }}{% endif -%} +pnpm {% if conversions.toPnpmCommands[params.command] %}{{ conversions.toPnpmCommands[params.command] | safe }}{% else %}{{ params.command | safe }}{% endif -%}{% for arg in params.args %} {{ conversions.toPnpmArgs[arg] | safe}}{% endfor %}{% for package in params.packages %} {{ package | safe }}{% endfor %} +``` + +
+
+

bun

+ +```shell +{% if params.comment %}# {{params.comment | safe}}{{"\n"}}{% endif -%} +bun {% if conversions.toBunCommands[params.command] %}{{ conversions.toBunCommands[params.command] | safe }}{% else %}{{ params.command | safe }}{% endif -%}{% for arg in params.args %} {{ conversions.toBunArgs[arg] | safe }}{% endfor %}{% for package in params.packages %} {{ package | safe }}{% endfor %} +``` + +
+
+{%- endmacro -%} \ No newline at end of file diff --git a/docs/src/_includes/components/npx_tabs.macro.html b/docs/src/_includes/components/npx_tabs.macro.html new file mode 100644 index 000000000000..e45de8b81df8 --- /dev/null +++ b/docs/src/_includes/components/npx_tabs.macro.html @@ -0,0 +1,46 @@ +{%- macro npx_tabs(params) -%} +
+ +
+

npm

+ +```shell +{% if params.comment %}# {{ params.comment | safe }}{{ "\n" }}{% endif -%} +{% if params.previousCommands %}{% for item in params.previousCommands %}{{ item | safe }} {% endfor %}| {% endif -%}npx {{ params.package | safe }} {% for item in params.args %}{{ item | safe }} {% endfor %} +``` + +
+
+

yarn

+ +```shell +{% if params.comment %}# {{ params.comment | safe }}{{ "\n" }}{% endif -%} +{% if params.previousCommands %}{% for item in params.previousCommands %}{{ item | safe }} {% endfor %}| {% endif -%}yarn dlx {{ params.package | safe }} {% for item in params.args %}{{ item | safe }} {% endfor %} +``` + +
+
+

pnpm

+ +```shell +{% if params.comment %}# {{ params.comment | safe }}{{ "\n" }}{% endif -%} +{% if params.previousCommands %}{% for item in params.previousCommands %}{{ item | safe }} {% endfor %}| {% endif -%}pnpm dlx {{ params.package | safe }} {% for item in params.args %}{{ item | safe }} {% endfor %} +``` + +
+
+

bun

+ +```shell +{% if params.comment %}# {{ params.comment | safe }}{{ "\n" }}{% endif -%} +{% if params.previousCommands %}{% for item in params.previousCommands %}{{ item | safe }} {% endfor %}| {% endif -%}bunx {{ params.package | safe }} {% for item in params.args %}{{ item | safe }} {% endfor %} +``` + +
+
+{%- endmacro -%} \ No newline at end of file diff --git a/docs/src/_includes/components/rule-categories.macro.html b/docs/src/_includes/components/rule-categories.macro.html index f38d371049eb..a7246337ca34 100644 --- a/docs/src/_includes/components/rule-categories.macro.html +++ b/docs/src/_includes/components/rule-categories.macro.html @@ -1,11 +1,12 @@ - {%- macro ruleCategories(params) -%}
{%- if params.recommended == true -%}
✅ Recommended

- The "extends": "eslint:recommended" property in a configuration file enables this rule + Using the recommended config from @eslint/js in a configuration file + enables this rule

{%- endif -%} @@ -13,7 +14,8 @@
🔧 Fixable

- Some problems reported by this rule are automatically fixable by the --fix command line option + Some problems reported by this rule are automatically fixable by the --fix command line option

{%- endif -%} @@ -21,7 +23,16 @@
💡 hasSuggestions

- Some problems reported by this rule are manually fixable by editor suggestions + Some problems reported by this rule are manually fixable by editor suggestions +

+
+ {%- endif -%} + {%- if params.frozen == true -%} +
+ â„ī¸ Frozen +

+ This rule is currently frozen and is not accepting feature requests.

{%- endif -%} @@ -32,7 +43,7 @@
✅ Recommended

- if the "extends": "eslint:recommended" property in a configuration file enables the rule. + if the recommended config from @eslint/js in a configuration file enables the rule.

{%- endmacro -%} @@ -54,3 +65,12 @@

{%- endmacro -%} + +{%- macro frozen() -%} +
+ â„ī¸ Frozen +

+ if the rule is currently frozen and not accepting feature requests. +

+
+{%- endmacro -%} diff --git a/docs/src/_includes/components/rule-list.macro.html b/docs/src/_includes/components/rule-list.macro.html index 670a0a70be89..a81bb459dc87 100644 --- a/docs/src/_includes/components/rule-list.macro.html +++ b/docs/src/_includes/components/rule-list.macro.html @@ -1,3 +1,7 @@ -{%- macro ruleList(params) -%} - {% for rule in params.rules %}{{ rule }}{% endfor %} +{%- macro replacementRuleList(params) -%} + {% for specifier in params.specifiers %} + {{ specifier.rule.name }} + {% if specifier.plugin %} in {{ specifier.plugin.name }} {% endif %} + {%- if loop.length > 1 and not loop.last -%} or
{%- endif -%} + {% endfor %} {%- endmacro -%} diff --git a/docs/src/_includes/components/rule.macro.html b/docs/src/_includes/components/rule.macro.html index cd5d61b0386f..6ee0e9b201ac 100644 --- a/docs/src/_includes/components/rule.macro.html +++ b/docs/src/_includes/components/rule.macro.html @@ -1,4 +1,4 @@ -{% from 'components/rule-list.macro.html' import ruleList %} +{% from 'components/rule-list.macro.html' import replacementRuleList %} {%- macro rule(params) -%}
@@ -9,7 +9,7 @@ deprecated

{%- if params.replacedBy|length -%} -

Replaced by {{ ruleList({ rules: params.replacedBy }) }}

+

Replaced by {{ replacementRuleList({ specifiers: params.replacedBy }) }}

{%- else -%}

{{ params.description }}

{%- endif -%} {%- elseif params.removed == true -%} @@ -17,12 +17,17 @@ {{ params.name }} removed

- {%- if params.replacedBy -%} -

Replaced by {{ ruleList({ rules: params.replacedBy }) }}

+ {%- if params.replacedBy|length -%} +

Replaced by {{ replacementRuleList({ specifiers: params.replacedBy }) }}

{%- else -%}

{{ params.description }}

{%- endif -%} {%- else -%} +
{{ params.name }} + {%- if params.categories and params.categories.frozen %} +

â„ī¸ Frozen

+ {%- endif -%} +

{{ params.description }}

{%- endif -%} diff --git a/docs/src/_includes/components/search.html b/docs/src/_includes/components/search.html index 336385869b82..9635900f7529 100644 --- a/docs/src/_includes/components/search.html +++ b/docs/src/_includes/components/search.html @@ -9,14 +9,51 @@

Results will be shown and updated as you type.

-
+ diff --git a/docs/src/_includes/components/social-icons.html b/docs/src/_includes/components/social-icons.html index 6f2b887e9490..d346aec18eec 100644 --- a/docs/src/_includes/components/social-icons.html +++ b/docs/src/_includes/components/social-icons.html @@ -1,13 +1,6 @@ diff --git a/docs/src/_includes/components/theme-switcher.html b/docs/src/_includes/components/theme-switcher.html index 69924d9d7c33..a6aa9890f9c2 100644 --- a/docs/src/_includes/components/theme-switcher.html +++ b/docs/src/_includes/components/theme-switcher.html @@ -4,7 +4,7 @@

{{ si
+
diff --git a/docs/src/_includes/layouts/base.html b/docs/src/_includes/layouts/base.html index 86ea680c3cb0..0649e4e06679 100644 --- a/docs/src/_includes/layouts/base.html +++ b/docs/src/_includes/layouts/base.html @@ -73,9 +73,9 @@ diff --git a/docs/src/_includes/layouts/doc.html b/docs/src/_includes/layouts/doc.html index 58d8986a5dcc..2cb0e6d4751f 100644 --- a/docs/src/_includes/layouts/doc.html +++ b/docs/src/_includes/layouts/doc.html @@ -4,6 +4,7 @@ {% include "partials/docs-header.html" %} {% from 'components/rule-categories.macro.html' import ruleCategories %} +{% from 'components/alert.macro.html' import important %}
@@ -87,8 +88,25 @@

{{ title }}

index: id, recommended: rule_meta.docs.recommended, fixable: rule_meta.fixable, + frozen: rule_meta.docs.frozen, hasSuggestions: rule_meta.hasSuggestions }) }} + + {% if rule_meta.deprecated %} + {% set deprecated_description %} +

This rule was deprecated in ESLint v{{ rule_meta.deprecated.deprecatedSince }}. + + {% if rule_meta.deprecated.replacedBy.length === 0 %} + There is no replacement rule. + {% elif rule_meta.deprecated.replacedBy[0].plugin %} + Please use the corresponding rule in {{ rule_meta.deprecated.replacedBy[0].plugin.name }}. + {% else %} + Please replace the rule with {{ rule_meta.deprecated.replacedBy[0].rule.name }}. + {% endif %} + {% endset %} + + {% important deprecated_description, rule_meta.deprecated.url %} + {% endif %} {% endif %} {% include 'components/docs-toc.html' %} @@ -101,7 +119,13 @@

{{ title }}

{% if edit_link %} {{ edit_link }} {% else %} - {{ site.edit_link.start_with }}{{ page.inputPath }} + {% if eslintVersions.isPrereleasePhase and GIT_BRANCH === 'latest' %} + {{ site.edit_link.start_with_latest }}{{ page.inputPath }} + {% elseif is_number_version %} + https://github.com/eslint/eslint/edit/{{ GIT_BRANCH }}/docs/{{ page.inputPath }} + {% else %} + {{ site.edit_link.start_with }}{{ page.inputPath }} + {% endif %} {% endif %} " class="c-btn c-btn--secondary">{{ site.edit_link.text }} @@ -111,7 +135,7 @@

{{ title }}

{% include 'components/docs-toc.html' %}
@@ -119,9 +143,39 @@

{{ title }}

- +
+
+ + diff --git a/docs/src/_includes/partials/carbon-ad.html b/docs/src/_includes/partials/ad.html similarity index 51% rename from docs/src/_includes/partials/carbon-ad.html rename to docs/src/_includes/partials/ad.html index c79eba5a6794..e71b35855443 100644 --- a/docs/src/_includes/partials/carbon-ad.html +++ b/docs/src/_includes/partials/ad.html @@ -11,3 +11,15 @@ } {% endif %} +{% if site.ethical_ads %} + +
+{% endif %} \ No newline at end of file diff --git a/docs/src/_includes/partials/versions-list.html b/docs/src/_includes/partials/versions-list.html index 7f07e4fa4950..814040c2ccd8 100644 --- a/docs/src/_includes/partials/versions-list.html +++ b/docs/src/_includes/partials/versions-list.html @@ -1,10 +1,6 @@ diff --git a/docs/src/_plugins/md-syntax-highlighter.js b/docs/src/_plugins/md-syntax-highlighter.js index 9ae0dcd26ccd..24d08b4f0afb 100644 --- a/docs/src/_plugins/md-syntax-highlighter.js +++ b/docs/src/_plugins/md-syntax-highlighter.js @@ -26,6 +26,9 @@ SOFTWARE. const Prism = require("prismjs"); const loadLanguages = require("prismjs/components/"); +const prismESLintHook = require("../../tools/prism-eslint-hook"); + +prismESLintHook.installPrismESLintMarkerHook(); /** * @@ -35,21 +38,21 @@ const loadLanguages = require("prismjs/components/"); * @returns {string} highlighted result wrapped in pre */ const highlighter = function (md, str, lang) { - let result = ""; - if (lang) { - try { - loadLanguages([lang]); - result = Prism.highlight(str, Prism.languages[lang], lang); - } catch (err) { - console.log(lang, err); - // we still want to wrap the result later - result = md.utils.escapeHtml(str); - } - } else { - result = md.utils.escapeHtml(str); - } + let result = ""; + if (lang) { + try { + loadLanguages([lang]); + result = Prism.highlight(str, Prism.languages[lang], lang); + } catch (err) { + console.log(lang, err); + // we still want to wrap the result later + result = md.utils.escapeHtml(str); + } + } else { + result = md.utils.escapeHtml(str); + } - return `
${result}
`; + return `
${result}
`; }; /** @@ -58,33 +61,33 @@ const highlighter = function (md, str, lang) { * @param {MarkdownIt} md * @license MIT License. See file header. */ -const lineNumberPlugin = (md) => { - const fence = md.renderer.rules.fence; - md.renderer.rules.fence = (...args) => { - const [tokens, idx] = args; - const lang = tokens[idx].info.trim(); - const rawCode = fence(...args); - const code = rawCode.slice( - rawCode.indexOf(""), - rawCode.indexOf("") - ); - const lines = code.split("\n"); - const lineNumbersCode = [...Array(lines.length - 1)] - .map( - (line, index) => - `${index + 1}
` - ) - .join(""); +const lineNumberPlugin = md => { + const fence = md.renderer.rules.fence; + md.renderer.rules.fence = (...args) => { + const [tokens, idx] = args; + const lang = tokens[idx].info.trim(); + const rawCode = fence(...args); + const code = rawCode.slice( + rawCode.indexOf(""), + rawCode.indexOf(""), + ); + const lines = code.split("\n"); + const lineNumbersCode = [...Array(lines.length - 1)] + .map( + (line, index) => + `${index + 1}
`, + ) + .join(""); - const lineNumbersWrapperCode = ``; + const lineNumbersWrapperCode = ``; - const finalCode = rawCode - .replace(/<\/pre>\n/, `${lineNumbersWrapperCode}`) - .replace(/"(language-\S*?)"/, '"$1 line-numbers-mode"') - .replace(//, ``) + const finalCode = rawCode + .replace(/<\/pre>\n/, `${lineNumbersWrapperCode}`) + .replace(/"(language-\S*?)"/, '"$1 line-numbers-mode"') + .replace(//, ``); - return finalCode; - }; + return finalCode; + }; }; module.exports.highlighter = highlighter; diff --git a/docs/src/_plugins/pre-wrapper.js b/docs/src/_plugins/pre-wrapper.js new file mode 100644 index 000000000000..beb227486366 --- /dev/null +++ b/docs/src/_plugins/pre-wrapper.js @@ -0,0 +1,23 @@ +module.exports = md => { + const defaultFenceRenderer = md.renderer.rules.fence; + + md.renderer.rules.fence = (...args) => { + const [tokens, index] = args; + + if (/^\s*(?:in)?correct(?!\S)/u.test(tokens[index - 1].info)) { + return defaultFenceRenderer(...args); + } + + return ` +
+ ${defaultFenceRenderer(...args)} + +
+`; + }; +}; diff --git a/docs/src/about/index.md b/docs/src/about/index.md index 0f59d9cf39c0..53bf47e185eb 100644 --- a/docs/src/about/index.md +++ b/docs/src/about/index.md @@ -1,6 +1,5 @@ --- title: About - --- ESLint is an open source JavaScript linting utility originally created by Nicholas C. Zakas in June 2013. Code [linting][] is a type of static analysis that is frequently used to find problematic patterns or code that doesn't adhere to certain style guidelines. There are code linters for most programming languages, and compilers sometimes incorporate linting into the compilation process. @@ -18,24 +17,24 @@ ESLint is written using Node.js to provide a fast runtime environment and easy i Everything is pluggable: -* Rule API is used both by bundled and custom rules -* Formatter API is used both by bundled and custom formatters -* Additional rules and formatters can be specified at runtime -* Rules and formatters don't have to be bundled to be used +- Rule API is used both by bundled and custom rules. +- Formatter API is used both by bundled and custom formatters. +- Additional rules and formatters can be specified at runtime. +- Rules and formatters don't have to be bundled to be used. Every rule: -* Is standalone -* Can be turned off or on (nothing can be deemed "too important to turn off") -* Can be set to a warning or error individually +- Is standalone. +- Can be turned off or on (nothing can be deemed "too important to turn off"). +- Can be set to a warning or error individually. Additionally: -* Rules are "agenda free" - ESLint does not promote any particular coding style -* Any bundled rules are generalizable +- Rules are "agenda free" - ESLint does not promote any particular coding style. +- Any bundled rules are generalizable. The project: -* Values documentation and clear communication -* Is as transparent as possible -* Believes in the importance of testing +- Values documentation and clear communication. +- Is as transparent as possible. +- Believes in the importance of testing. diff --git a/docs/src/assets/images/404.png b/docs/src/assets/images/404.png index 347d16086ef8..2663176abc42 100644 Binary files a/docs/src/assets/images/404.png and b/docs/src/assets/images/404.png differ diff --git a/docs/src/assets/images/architecture/dependency.svg b/docs/src/assets/images/architecture/dependency.svg index 1609b53e1d99..b4809cc5137e 100644 --- a/docs/src/assets/images/architecture/dependency.svg +++ b/docs/src/assets/images/architecture/dependency.svg @@ -1 +1 @@ -binlibeslint.jscli.jsapi.jsinitcli-enginelintersource-coderule-testerrules \ No newline at end of file +binlibeslint.jscli.jsapi.jsinitcli-enginelintersource-coderule-testerrules \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-dowhilestatement.svg b/docs/src/assets/images/code-path-analysis/example-dowhilestatement.svg index f81d36123c4a..c8c189697f83 100644 --- a/docs/src/assets/images/code-path-analysis/example-dowhilestatement.svg +++ b/docs/src/assets/images/code-path-analysis/example-dowhilestatement.svg @@ -1 +1 @@ -ProgramDoWhileStatementBlockStatementExpressionStatementCallExpressionIdentifier (foo)ExpressionStatementCallExpressionIdentifier (bar)Identifier (a)DoWhileStatement:exitProgram:exit \ No newline at end of file +ProgramDoWhileStatementBlockStatementExpressionStatementCallExpressionIdentifier (foo)ExpressionStatementCallExpressionIdentifier (bar)Identifier (a)DoWhileStatement:exitProgram:exit \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-forinstatement.svg b/docs/src/assets/images/code-path-analysis/example-forinstatement.svg index a6bc754b1be8..e9c6e60d0c92 100644 --- a/docs/src/assets/images/code-path-analysis/example-forinstatement.svg +++ b/docs/src/assets/images/code-path-analysis/example-forinstatement.svg @@ -1 +1 @@ -ProgramForInStatementIdentifier (obj)VariableDeclarationVariableDeclaratorIdentifier (key)ForInStatement:exitProgram:exitBlockStatementExpressionStatementCallExpressionIdentifier (foo)Identifier (key) \ No newline at end of file +ProgramForInStatementIdentifier (obj)VariableDeclarationVariableDeclaratorIdentifier (key)ForInStatement:exitProgram:exitBlockStatementExpressionStatementCallExpressionIdentifier (foo)Identifier (key) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-forstatement-for-ever.svg b/docs/src/assets/images/code-path-analysis/example-forstatement-for-ever.svg index 4d334ca62d9e..603fe468c172 100644 --- a/docs/src/assets/images/code-path-analysis/example-forstatement-for-ever.svg +++ b/docs/src/assets/images/code-path-analysis/example-forstatement-for-ever.svg @@ -1 +1 @@ -ProgramForStatementBlockStatementExpressionStatementCallExpressionIdentifier (foo) \ No newline at end of file +ProgramForStatementBlockStatementExpressionStatementCallExpressionIdentifier (foo) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-forstatement.svg b/docs/src/assets/images/code-path-analysis/example-forstatement.svg index aa0ccf0d82f1..7b06b6ab4a48 100644 --- a/docs/src/assets/images/code-path-analysis/example-forstatement.svg +++ b/docs/src/assets/images/code-path-analysis/example-forstatement.svg @@ -1 +1 @@ -ProgramForStatementVariableDeclarationVariableDeclaratorIdentifier (i)Literal (0)BinaryExpressionIdentifier (i)Literal (10)BlockStatementExpressionStatementCallExpressionIdentifier (foo)IfStatementIdentifier (b)ForStatement:exitProgram:exitBlockStatementBreakStatementExpressionStatementCallExpressionIdentifier (bar)UpdateExpressionIdentifier (i) \ No newline at end of file +ProgramForStatementVariableDeclarationVariableDeclaratorIdentifier (i)Literal (0)BinaryExpressionIdentifier (i)Literal (10)BlockStatementExpressionStatementCallExpressionIdentifier (foo)IfStatementIdentifier (b)ForStatement:exitProgram:exitBlockStatementBreakStatementExpressionStatementCallExpressionIdentifier (bar)UpdateExpressionIdentifier (i) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-hello-world.svg b/docs/src/assets/images/code-path-analysis/example-hello-world.svg index fc28d1fdaf9c..7b0d775c6030 100644 --- a/docs/src/assets/images/code-path-analysis/example-hello-world.svg +++ b/docs/src/assets/images/code-path-analysis/example-hello-world.svg @@ -1 +1 @@ -ProgramExpressionStatementCallExpressionMemberExpressionIdentifier (console)Identifier (log)Literal (Hello world!) \ No newline at end of file +ProgramExpressionStatementCallExpressionMemberExpressionIdentifier (console)Identifier (log)Literal (Hello world!) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-ifstatement-chain.svg b/docs/src/assets/images/code-path-analysis/example-ifstatement-chain.svg index 0944c3bcf59c..e21fd583f251 100644 --- a/docs/src/assets/images/code-path-analysis/example-ifstatement-chain.svg +++ b/docs/src/assets/images/code-path-analysis/example-ifstatement-chain.svg @@ -1 +1 @@ -ProgramIfStatementIdentifier (a)BlockStatementExpressionStatementCallExpressionIdentifier (foo)IfStatementIdentifier (b)IfStatement:exitProgram:exitBlockStatementExpressionStatementCallExpressionIdentifier (bar)IfStatementIdentifier (c)BlockStatementExpressionStatementCallExpressionIdentifier (hoge) \ No newline at end of file +ProgramIfStatementIdentifier (a)BlockStatementExpressionStatementCallExpressionIdentifier (foo)IfStatementIdentifier (b)IfStatement:exitProgram:exitBlockStatementExpressionStatementCallExpressionIdentifier (bar)IfStatementIdentifier (c)BlockStatementExpressionStatementCallExpressionIdentifier (hoge) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-ifstatement.svg b/docs/src/assets/images/code-path-analysis/example-ifstatement.svg index b83c67b51061..25061c57b9f0 100644 --- a/docs/src/assets/images/code-path-analysis/example-ifstatement.svg +++ b/docs/src/assets/images/code-path-analysis/example-ifstatement.svg @@ -1 +1 @@ -ProgramIfStatementIdentifier (a)BlockStatementExpressionStatementCallExpressionIdentifier (foo)BlockStatementExpressionStatementCallExpressionIdentifier (bar)IfStatement:exitProgram:exit \ No newline at end of file +ProgramIfStatementIdentifier (a)BlockStatementExpressionStatementCallExpressionIdentifier (foo)BlockStatementExpressionStatementCallExpressionIdentifier (bar)IfStatement:exitProgram:exit \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-switchstatement-has-default.svg b/docs/src/assets/images/code-path-analysis/example-switchstatement-has-default.svg index 5d6d73998b37..2afa1c551838 100644 --- a/docs/src/assets/images/code-path-analysis/example-switchstatement-has-default.svg +++ b/docs/src/assets/images/code-path-analysis/example-switchstatement-has-default.svg @@ -1 +1 @@ -ProgramSwitchStatementIdentifier (a)SwitchCaseLiteral (0)ExpressionStatementCallExpressionIdentifier (foo)BreakStatementSwitchCaseLiteral (1)SwitchStatement:exitProgram:exitExpressionStatementCallExpressionIdentifier (bar)ExpressionStatementCallExpressionIdentifier (hoge)BreakStatementExpressionStatementCallExpressionIdentifier (fuga)BreakStatementSwitchCaseLiteral (2)SwitchCaseLiteral (3)SwitchCase \ No newline at end of file +ProgramSwitchStatementIdentifier (a)SwitchCaseLiteral (0)ExpressionStatementCallExpressionIdentifier (foo)BreakStatementSwitchCaseLiteral (1)SwitchStatement:exitProgram:exitExpressionStatementCallExpressionIdentifier (bar)ExpressionStatementCallExpressionIdentifier (hoge)BreakStatementExpressionStatementCallExpressionIdentifier (fuga)BreakStatementSwitchCaseLiteral (2)SwitchCaseLiteral (3)SwitchCase \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-switchstatement.svg b/docs/src/assets/images/code-path-analysis/example-switchstatement.svg index e43e5e11190b..4ec5982178c6 100644 --- a/docs/src/assets/images/code-path-analysis/example-switchstatement.svg +++ b/docs/src/assets/images/code-path-analysis/example-switchstatement.svg @@ -1 +1 @@ -ProgramSwitchStatementIdentifier (a)SwitchCaseLiteral (0)ExpressionStatementCallExpressionIdentifier (foo)BreakStatementSwitchCaseLiteral (1)SwitchStatement:exitProgram:exitExpressionStatementCallExpressionIdentifier (bar)ExpressionStatementCallExpressionIdentifier (hoge)BreakStatementSwitchCaseLiteral (2)SwitchCaseLiteral (3) \ No newline at end of file +ProgramSwitchStatementIdentifier (a)SwitchCaseLiteral (0)ExpressionStatementCallExpressionIdentifier (foo)BreakStatementSwitchCaseLiteral (1)SwitchStatement:exitProgram:exitExpressionStatementCallExpressionIdentifier (bar)ExpressionStatementCallExpressionIdentifier (hoge)BreakStatementSwitchCaseLiteral (2)SwitchCaseLiteral (3) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-trystatement-try-catch-finally.svg b/docs/src/assets/images/code-path-analysis/example-trystatement-try-catch-finally.svg index 60ec1cdf69b6..fcd016cdc160 100644 --- a/docs/src/assets/images/code-path-analysis/example-trystatement-try-catch-finally.svg +++ b/docs/src/assets/images/code-path-analysis/example-trystatement-try-catch-finally.svg @@ -1 +1 @@ -ProgramTryStatementBlockStatementExpressionStatementCallExpressionIdentifier (foo)ExpressionStatementCallExpressionIdentifier (bar)CatchClauseIdentifier (err)BlockStatementExpressionStatementCallExpressionIdentifier (hoge)Identifier (err)BlockStatementExpressionStatementCallExpressionIdentifier (fuga)ExpressionStatementCallExpressionIdentifier (last) \ No newline at end of file +ProgramTryStatementBlockStatementExpressionStatementCallExpressionIdentifier (foo)ExpressionStatementCallExpressionIdentifier (bar)CatchClauseIdentifier (err)BlockStatementExpressionStatementCallExpressionIdentifier (hoge)Identifier (err)BlockStatementExpressionStatementCallExpressionIdentifier (fuga)ExpressionStatementCallExpressionIdentifier (last) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-trystatement-try-catch.svg b/docs/src/assets/images/code-path-analysis/example-trystatement-try-catch.svg index a2a0c8af2507..611b290effad 100644 --- a/docs/src/assets/images/code-path-analysis/example-trystatement-try-catch.svg +++ b/docs/src/assets/images/code-path-analysis/example-trystatement-try-catch.svg @@ -1 +1 @@ -ProgramTryStatementBlockStatementExpressionStatementCallExpressionIdentifier (foo)IfStatementIdentifier (a)CatchClauseIdentifier (err)BlockStatementExpressionStatementCallExpressionIdentifier (hoge)Identifier (err)BlockStatementThrowStatementNewExpressionIdentifier (Error)ExpressionStatementCallExpressionIdentifier (bar)ExpressionStatementCallExpressionIdentifier (last) \ No newline at end of file +ProgramTryStatementBlockStatementExpressionStatementCallExpressionIdentifier (foo)IfStatementIdentifier (a)CatchClauseIdentifier (err)BlockStatementExpressionStatementCallExpressionIdentifier (hoge)Identifier (err)BlockStatementThrowStatementNewExpressionIdentifier (Error)ExpressionStatementCallExpressionIdentifier (bar)ExpressionStatementCallExpressionIdentifier (last) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-trystatement-try-finally.svg b/docs/src/assets/images/code-path-analysis/example-trystatement-try-finally.svg index 68c7801b7cd8..5125c96f111c 100644 --- a/docs/src/assets/images/code-path-analysis/example-trystatement-try-finally.svg +++ b/docs/src/assets/images/code-path-analysis/example-trystatement-try-finally.svg @@ -1 +1 @@ -ProgramTryStatementBlockStatementExpressionStatementCallExpressionIdentifier (foo)✘ExpressionStatementCallExpressionIdentifier (bar)BlockStatementExpressionStatementCallExpressionIdentifier (fuga)BlockStatementExpressionStatementCallExpressionIdentifier (fuga)ExpressionStatementCallExpressionIdentifier (last) \ No newline at end of file +ProgramTryStatementBlockStatementExpressionStatementCallExpressionIdentifier (foo)✘ExpressionStatementCallExpressionIdentifier (bar)BlockStatementExpressionStatementCallExpressionIdentifier (fuga)BlockStatementExpressionStatementCallExpressionIdentifier (fuga)ExpressionStatementCallExpressionIdentifier (last) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-when-there-is-a-function-f.svg b/docs/src/assets/images/code-path-analysis/example-when-there-is-a-function-f.svg index 53bb946cf162..bd527c0017ad 100644 --- a/docs/src/assets/images/code-path-analysis/example-when-there-is-a-function-f.svg +++ b/docs/src/assets/images/code-path-analysis/example-when-there-is-a-function-f.svg @@ -1 +1 @@ -FunctionDeclarationIdentifier (foo)Identifier (a)BlockStatementIfStatementIdentifier (a)BlockStatementReturnStatementExpressionStatementCallExpressionIdentifier (bar) \ No newline at end of file +FunctionDeclarationIdentifier (foo)Identifier (a)BlockStatementIfStatementIdentifier (a)BlockStatementReturnStatementExpressionStatementCallExpressionIdentifier (bar) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-when-there-is-a-function-g.svg b/docs/src/assets/images/code-path-analysis/example-when-there-is-a-function-g.svg index 4d3fe12b4a8f..ca22f81e2a09 100644 --- a/docs/src/assets/images/code-path-analysis/example-when-there-is-a-function-g.svg +++ b/docs/src/assets/images/code-path-analysis/example-when-there-is-a-function-g.svg @@ -1 +1 @@ -ProgramFunctionDeclarationExpressionStatementCallExpressionIdentifier (foo)Literal (false) \ No newline at end of file +ProgramFunctionDeclarationExpressionStatementCallExpressionIdentifier (foo)Literal (false) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/example-whilestatement.svg b/docs/src/assets/images/code-path-analysis/example-whilestatement.svg index f03944389cda..8f785b160af0 100644 --- a/docs/src/assets/images/code-path-analysis/example-whilestatement.svg +++ b/docs/src/assets/images/code-path-analysis/example-whilestatement.svg @@ -1 +1 @@ -ProgramWhileStatementIdentifier (a)BlockStatementExpressionStatementCallExpressionIdentifier (foo)IfStatementIdentifier (b)WhileStatement:exitProgram:exitBlockStatementContinueStatementExpressionStatementCallExpressionIdentifier (bar) \ No newline at end of file +ProgramWhileStatementIdentifier (a)BlockStatementExpressionStatementCallExpressionIdentifier (foo)IfStatementIdentifier (b)WhileStatement:exitProgram:exitBlockStatementContinueStatementExpressionStatementCallExpressionIdentifier (bar) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/helo.svg b/docs/src/assets/images/code-path-analysis/helo.svg index cd72a37d9aff..00c84fbea56a 100644 --- a/docs/src/assets/images/code-path-analysis/helo.svg +++ b/docs/src/assets/images/code-path-analysis/helo.svg @@ -1 +1 @@ -ProgramIfStatementLogicalExpressionIdentifier (a)Identifier (b)ExpressionStatementCallExpressionIdentifier (bar)BlockStatementExpressionStatementCallExpressionIdentifier (foo) \ No newline at end of file +ProgramIfStatementLogicalExpressionIdentifier (a)Identifier (b)ExpressionStatementCallExpressionIdentifier (bar)BlockStatementExpressionStatementCallExpressionIdentifier (foo) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/loop-event-example-for-1.svg b/docs/src/assets/images/code-path-analysis/loop-event-example-for-1.svg index 727ec12b132d..8a09c3e80b9a 100644 --- a/docs/src/assets/images/code-path-analysis/loop-event-example-for-1.svg +++ b/docs/src/assets/images/code-path-analysis/loop-event-example-for-1.svg @@ -1 +1 @@ -ProgramForStatementVariableDeclarationVariableDeclaratorIdentifier (i)Literal (0)BinaryExpressionIdentifier (i)Literal (10)UpdateExpressionIdentifier (i) \ No newline at end of file +ProgramForStatementVariableDeclarationVariableDeclaratorIdentifier (i)Literal (0)BinaryExpressionIdentifier (i)Literal (10)UpdateExpressionIdentifier (i) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/loop-event-example-for-2.svg b/docs/src/assets/images/code-path-analysis/loop-event-example-for-2.svg index 70d762f38bc1..06e4e7d15eba 100644 --- a/docs/src/assets/images/code-path-analysis/loop-event-example-for-2.svg +++ b/docs/src/assets/images/code-path-analysis/loop-event-example-for-2.svg @@ -1 +1 @@ -ProgramForStatementVariableDeclarationVariableDeclaratorIdentifier (i)Literal (0)BinaryExpressionIdentifier (i)Literal (10)BlockStatementExpressionStatementCallExpressionIdentifier (foo)Identifier (i)UpdateExpressionIdentifier (i) \ No newline at end of file +ProgramForStatementVariableDeclarationVariableDeclaratorIdentifier (i)Literal (0)BinaryExpressionIdentifier (i)Literal (10)BlockStatementExpressionStatementCallExpressionIdentifier (foo)Identifier (i)UpdateExpressionIdentifier (i) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/loop-event-example-for-3.svg b/docs/src/assets/images/code-path-analysis/loop-event-example-for-3.svg index 5adea136b40e..78bb754d93f4 100644 --- a/docs/src/assets/images/code-path-analysis/loop-event-example-for-3.svg +++ b/docs/src/assets/images/code-path-analysis/loop-event-example-for-3.svg @@ -1 +1 @@ -ProgramForStatementVariableDeclarationVariableDeclaratorIdentifier (i)Literal (0)BinaryExpressionIdentifier (i)Literal (10)BlockStatementExpressionStatementCallExpressionIdentifier (foo)Identifier (i)UpdateExpressionIdentifier (i) \ No newline at end of file +ProgramForStatementVariableDeclarationVariableDeclaratorIdentifier (i)Literal (0)BinaryExpressionIdentifier (i)Literal (10)BlockStatementExpressionStatementCallExpressionIdentifier (foo)Identifier (i)UpdateExpressionIdentifier (i) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/loop-event-example-for-4.svg b/docs/src/assets/images/code-path-analysis/loop-event-example-for-4.svg index 99389751f340..433dec08d24c 100644 --- a/docs/src/assets/images/code-path-analysis/loop-event-example-for-4.svg +++ b/docs/src/assets/images/code-path-analysis/loop-event-example-for-4.svg @@ -1 +1 @@ -ProgramForStatementVariableDeclarationVariableDeclaratorIdentifier (i)Literal (0)BinaryExpressionIdentifier (i)Literal (10)BlockStatementExpressionStatementCallExpressionIdentifier (foo)Identifier (i)UpdateExpressionIdentifier (i) \ No newline at end of file +ProgramForStatementVariableDeclarationVariableDeclaratorIdentifier (i)Literal (0)BinaryExpressionIdentifier (i)Literal (10)BlockStatementExpressionStatementCallExpressionIdentifier (foo)Identifier (i)UpdateExpressionIdentifier (i) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/loop-event-example-for-5.svg b/docs/src/assets/images/code-path-analysis/loop-event-example-for-5.svg index 070decb12924..e5f32155b70d 100644 --- a/docs/src/assets/images/code-path-analysis/loop-event-example-for-5.svg +++ b/docs/src/assets/images/code-path-analysis/loop-event-example-for-5.svg @@ -1 +1 @@ -ProgramForStatementVariableDeclarationVariableDeclaratorIdentifier (i)Literal (0)BinaryExpressionIdentifier (i)Literal (10)BlockStatementExpressionStatementCallExpressionIdentifier (foo)Identifier (i)ExpressionStatementCallExpressionIdentifier (bar)UpdateExpressionIdentifier (i) \ No newline at end of file +ProgramForStatementVariableDeclarationVariableDeclaratorIdentifier (i)Literal (0)BinaryExpressionIdentifier (i)Literal (10)BlockStatementExpressionStatementCallExpressionIdentifier (foo)Identifier (i)ExpressionStatementCallExpressionIdentifier (bar)UpdateExpressionIdentifier (i) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/loop-event-example-while-1.svg b/docs/src/assets/images/code-path-analysis/loop-event-example-while-1.svg index 7d0c1a02d686..2db2fa29193f 100644 --- a/docs/src/assets/images/code-path-analysis/loop-event-example-while-1.svg +++ b/docs/src/assets/images/code-path-analysis/loop-event-example-while-1.svg @@ -1 +1 @@ -ProgramWhileStatementIdentifier (a)BlockStatementExpressionStatementAssignmentExpressionIdentifier (a)CallExpressionIdentifier (foo) \ No newline at end of file +ProgramWhileStatementIdentifier (a)BlockStatementExpressionStatementAssignmentExpressionIdentifier (a)CallExpressionIdentifier (foo) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/loop-event-example-while-2.svg b/docs/src/assets/images/code-path-analysis/loop-event-example-while-2.svg index d5c31e276ca9..88c61fc4f51f 100644 --- a/docs/src/assets/images/code-path-analysis/loop-event-example-while-2.svg +++ b/docs/src/assets/images/code-path-analysis/loop-event-example-while-2.svg @@ -1 +1 @@ -ProgramWhileStatementIdentifier (a)BlockStatementExpressionStatementAssignmentExpressionIdentifier (a)CallExpressionIdentifier (foo) \ No newline at end of file +ProgramWhileStatementIdentifier (a)BlockStatementExpressionStatementAssignmentExpressionIdentifier (a)CallExpressionIdentifier (foo) \ No newline at end of file diff --git a/docs/src/assets/images/code-path-analysis/loop-event-example-while-3.svg b/docs/src/assets/images/code-path-analysis/loop-event-example-while-3.svg index 3f4e02c17db2..a372021d71a4 100644 --- a/docs/src/assets/images/code-path-analysis/loop-event-example-while-3.svg +++ b/docs/src/assets/images/code-path-analysis/loop-event-example-while-3.svg @@ -1 +1 @@ -ProgramWhileStatementIdentifier (a)BlockStatementExpressionStatementAssignmentExpressionIdentifier (a)CallExpressionIdentifier (foo)ExpressionStatementCallExpressionIdentifier (bar) \ No newline at end of file +ProgramWhileStatementIdentifier (a)BlockStatementExpressionStatementAssignmentExpressionIdentifier (a)CallExpressionIdentifier (foo)ExpressionStatementCallExpressionIdentifier (bar) \ No newline at end of file diff --git a/docs/src/assets/images/configure/config-inspector.png b/docs/src/assets/images/configure/config-inspector.png new file mode 100644 index 000000000000..b5faf7928190 Binary files /dev/null and b/docs/src/assets/images/configure/config-inspector.png differ diff --git a/docs/src/assets/images/icons/arrow-left.svg b/docs/src/assets/images/icons/arrow-left.svg index 83483a7f256a..c5e777a8bba4 100644 --- a/docs/src/assets/images/icons/arrow-left.svg +++ b/docs/src/assets/images/icons/arrow-left.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/arrow-right.svg b/docs/src/assets/images/icons/arrow-right.svg index 22bb24fc3d4d..c1117a5cd29c 100644 --- a/docs/src/assets/images/icons/arrow-right.svg +++ b/docs/src/assets/images/icons/arrow-right.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/arrow-top-right.svg b/docs/src/assets/images/icons/arrow-top-right.svg index 58bbed85264f..37705b13cbc5 100644 --- a/docs/src/assets/images/icons/arrow-top-right.svg +++ b/docs/src/assets/images/icons/arrow-top-right.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/chevron-down.svg b/docs/src/assets/images/icons/chevron-down.svg index b09f7f73216a..46788a0f8626 100644 --- a/docs/src/assets/images/icons/chevron-down.svg +++ b/docs/src/assets/images/icons/chevron-down.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/copy.svg b/docs/src/assets/images/icons/copy.svg index 24fc6afae9c7..29fd98808f9f 100644 --- a/docs/src/assets/images/icons/copy.svg +++ b/docs/src/assets/images/icons/copy.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/correct.svg b/docs/src/assets/images/icons/correct.svg index 4f589241cb28..a99d10ca0dac 100644 --- a/docs/src/assets/images/icons/correct.svg +++ b/docs/src/assets/images/icons/correct.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/discord.svg b/docs/src/assets/images/icons/discord.svg index 16bae7b3c46e..03032099b171 100644 --- a/docs/src/assets/images/icons/discord.svg +++ b/docs/src/assets/images/icons/discord.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/facebook.svg b/docs/src/assets/images/icons/facebook.svg index 194c83485029..1fe1fbeef8ae 100644 --- a/docs/src/assets/images/icons/facebook.svg +++ b/docs/src/assets/images/icons/facebook.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/features-list-icon.svg b/docs/src/assets/images/icons/features-list-icon.svg index 2e576cff9fd8..9a09f471be6f 100644 --- a/docs/src/assets/images/icons/features-list-icon.svg +++ b/docs/src/assets/images/icons/features-list-icon.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/github-icon-mono.svg b/docs/src/assets/images/icons/github-icon-mono.svg index f73b88b55b4f..2aca9b078599 100644 --- a/docs/src/assets/images/icons/github-icon-mono.svg +++ b/docs/src/assets/images/icons/github-icon-mono.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/github-img.svg b/docs/src/assets/images/icons/github-img.svg index 51ad25a46eb8..3144d364f9d8 100644 --- a/docs/src/assets/images/icons/github-img.svg +++ b/docs/src/assets/images/icons/github-img.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/github-large.svg b/docs/src/assets/images/icons/github-large.svg index c540e36168fc..14905d7e65d9 100644 --- a/docs/src/assets/images/icons/github-large.svg +++ b/docs/src/assets/images/icons/github-large.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/github-small.svg b/docs/src/assets/images/icons/github-small.svg index b410d3adcdff..31621c7815bf 100644 --- a/docs/src/assets/images/icons/github-small.svg +++ b/docs/src/assets/images/icons/github-small.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/github.svg b/docs/src/assets/images/icons/github.svg index 0f3149634b9e..fe7c60f0836f 100644 --- a/docs/src/assets/images/icons/github.svg +++ b/docs/src/assets/images/icons/github.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/google.svg b/docs/src/assets/images/icons/google.svg index 8b149df54a95..3675e9cd20f1 100644 --- a/docs/src/assets/images/icons/google.svg +++ b/docs/src/assets/images/icons/google.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/incorrect.svg b/docs/src/assets/images/icons/incorrect.svg index 666811ebe475..e58babe33c7e 100644 --- a/docs/src/assets/images/icons/incorrect.svg +++ b/docs/src/assets/images/icons/incorrect.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/languages.svg b/docs/src/assets/images/icons/languages.svg index 2653515fe681..7ab95865e607 100644 --- a/docs/src/assets/images/icons/languages.svg +++ b/docs/src/assets/images/icons/languages.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/learn-more-arrow.svg b/docs/src/assets/images/icons/learn-more-arrow.svg index 8aab0b95e408..de1617f34e09 100644 --- a/docs/src/assets/images/icons/learn-more-arrow.svg +++ b/docs/src/assets/images/icons/learn-more-arrow.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/link.svg b/docs/src/assets/images/icons/link.svg index 6dfe15866b02..7301ad7a097a 100644 --- a/docs/src/assets/images/icons/link.svg +++ b/docs/src/assets/images/icons/link.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/linkedin.svg b/docs/src/assets/images/icons/linkedin.svg index a7c36f64e258..84b8f078499f 100644 --- a/docs/src/assets/images/icons/linkedin.svg +++ b/docs/src/assets/images/icons/linkedin.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/menu.svg b/docs/src/assets/images/icons/menu.svg index d068dbd04db0..83648a811009 100644 --- a/docs/src/assets/images/icons/menu.svg +++ b/docs/src/assets/images/icons/menu.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/minus-circle.svg b/docs/src/assets/images/icons/minus-circle.svg index f8e8023389a0..6f4a04b93be1 100644 --- a/docs/src/assets/images/icons/minus-circle.svg +++ b/docs/src/assets/images/icons/minus-circle.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/npm.svg b/docs/src/assets/images/icons/npm.svg index c9baf323174d..1a40a8c8aab9 100644 --- a/docs/src/assets/images/icons/npm.svg +++ b/docs/src/assets/images/icons/npm.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/open-collectione-mono.svg b/docs/src/assets/images/icons/open-collectione-mono.svg index 660478343ac0..2d042792a152 100644 --- a/docs/src/assets/images/icons/open-collectione-mono.svg +++ b/docs/src/assets/images/icons/open-collectione-mono.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/opencollective-img.svg b/docs/src/assets/images/icons/opencollective-img.svg index a3b46dcd5d4b..c6f318c77ae0 100644 --- a/docs/src/assets/images/icons/opencollective-img.svg +++ b/docs/src/assets/images/icons/opencollective-img.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/plus-circle.svg b/docs/src/assets/images/icons/plus-circle.svg index 58533a0b7bdf..54b257e96c76 100644 --- a/docs/src/assets/images/icons/plus-circle.svg +++ b/docs/src/assets/images/icons/plus-circle.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/search.svg b/docs/src/assets/images/icons/search.svg index 6c70237669ba..76d7735d1f40 100644 --- a/docs/src/assets/images/icons/search.svg +++ b/docs/src/assets/images/icons/search.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/icons/twitter.svg b/docs/src/assets/images/icons/twitter.svg index ffee249edaed..4aa1168ccf2d 100644 --- a/docs/src/assets/images/icons/twitter.svg +++ b/docs/src/assets/images/icons/twitter.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/logo/brand-colors.svg b/docs/src/assets/images/logo/brand-colors.svg index 2c2048de281d..3b9f9a78c760 100644 --- a/docs/src/assets/images/logo/brand-colors.svg +++ b/docs/src/assets/images/logo/brand-colors.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/logo/eslint-logo-color.png b/docs/src/assets/images/logo/eslint-logo-color.png index efa54ec778d2..27c0be651419 100644 Binary files a/docs/src/assets/images/logo/eslint-logo-color.png and b/docs/src/assets/images/logo/eslint-logo-color.png differ diff --git a/docs/src/assets/images/logo/eslint-logo-color.svg b/docs/src/assets/images/logo/eslint-logo-color.svg index 5a8dbfc6818f..5f780a8f73c4 100644 --- a/docs/src/assets/images/logo/eslint-logo-color.svg +++ b/docs/src/assets/images/logo/eslint-logo-color.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/images/logo/eslint-logo-white.svg b/docs/src/assets/images/logo/eslint-logo-white.svg index 2493dc4cfdfc..b9608ce950e7 100644 --- a/docs/src/assets/images/logo/eslint-logo-white.svg +++ b/docs/src/assets/images/logo/eslint-logo-white.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/assets/js/components-index.js b/docs/src/assets/js/components-index.js index 9e3bc0f5cf9c..3e26526a3e8e 100644 --- a/docs/src/assets/js/components-index.js +++ b/docs/src/assets/js/components-index.js @@ -1,35 +1,35 @@ -(function() { - var index_trigger = document.getElementById("js-index-toggle"), - index = document.getElementById("js-index-list"), - body = document.getElementsByTagName("body")[0], - open = false; +(function () { + var index_trigger = document.getElementById("js-index-toggle"), + index = document.getElementById("js-index-list"), + body = document.getElementsByTagName("body")[0], + open = false; - if (matchMedia) { - const mq = window.matchMedia("(max-width: 1023px)"); - mq.addEventListener('change', WidthChange); - WidthChange(mq); - } + if (matchMedia) { + const mq = window.matchMedia("(max-width: 1023px)"); + mq.addEventListener("change", WidthChange); + WidthChange(mq); + } - function WidthChange(mq) { - initIndex(); - } + function WidthChange(mq) { + initIndex(); + } - function toggleindex(e) { - if (!open) { - this.setAttribute("aria-expanded", "true"); - index.setAttribute("data-open", "true"); - open = true; - } else { - this.setAttribute("aria-expanded", "false"); - index.setAttribute("data-open", "false"); - open = false; - } - } + function toggleindex(e) { + if (!open) { + this.setAttribute("aria-expanded", "true"); + index.setAttribute("data-open", "true"); + open = true; + } else { + this.setAttribute("aria-expanded", "false"); + index.setAttribute("data-open", "false"); + open = false; + } + } - function initIndex() { - index_trigger.removeAttribute("hidden"); - index_trigger.setAttribute("aria-expanded", "false"); - index.setAttribute("data-open", "false"); - index_trigger.addEventListener("click", toggleindex, false); - } + function initIndex() { + index_trigger.removeAttribute("hidden"); + index_trigger.setAttribute("aria-expanded", "false"); + index.setAttribute("data-open", "false"); + index_trigger.addEventListener("click", toggleindex, false); + } })(); diff --git a/docs/src/assets/js/css-vars-ponyfill@2.js b/docs/src/assets/js/css-vars-ponyfill@2.js index 3285a577a2a9..7a8d79b7a822 100644 --- a/docs/src/assets/js/css-vars-ponyfill@2.js +++ b/docs/src/assets/js/css-vars-ponyfill@2.js @@ -5,43 +5,1494 @@ * (c) 2018-2019 John Hildenbiddle * MIT license */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).cssVars=t()}(this,function(){"use strict";function e(){return(e=Object.assign||function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{},r={mimeType:t.mimeType||null,onBeforeSend:t.onBeforeSend||Function.prototype,onSuccess:t.onSuccess||Function.prototype,onError:t.onError||Function.prototype,onComplete:t.onComplete||Function.prototype},n=Array.isArray(e)?e:[e],o=Array.apply(null,Array(n.length)).map(function(e){return null});function s(){return!("<"===(arguments.length>0&&void 0!==arguments[0]?arguments[0]:"").trim().charAt(0))}function a(e,t){r.onError(e,n[t],t)}function c(e,t){var s=r.onSuccess(e,n[t],t);e=!1===s?"":s||e,o[t]=e,-1===o.indexOf(null)&&r.onComplete(o)}var i=document.createElement("a");n.forEach(function(e,t){if(i.setAttribute("href",e),i.href=String(i.href),Boolean(document.all&&!window.atob)&&i.host.split(":")[0]!==location.host.split(":")[0]){if(i.protocol===location.protocol){var n=new XDomainRequest;n.open("GET",e),n.timeout=0,n.onprogress=Function.prototype,n.ontimeout=Function.prototype,n.onload=function(){s(n.responseText)?c(n.responseText,t):a(n,t)},n.onerror=function(e){a(n,t)},setTimeout(function(){n.send()},0)}else console.warn("Internet Explorer 9 Cross-Origin (CORS) requests must use the same protocol (".concat(e,")")),a(null,t)}else{var o=new XMLHttpRequest;o.open("GET",e),r.mimeType&&o.overrideMimeType&&o.overrideMimeType(r.mimeType),r.onBeforeSend(o,e,t),o.onreadystatechange=function(){4===o.readyState&&(200===o.status&&s(o.responseText)?c(o.responseText,t):a(o,t))},o.send()}})}function n(e){var t={cssComments:/\/\*[\s\S]+?\*\//g,cssImports:/(?:@import\s*)(?:url\(\s*)?(?:['"])([^'"]*)(?:['"])(?:\s*\))?(?:[^;]*;)/g},n={rootElement:e.rootElement||document,include:e.include||'style,link[rel="stylesheet"]',exclude:e.exclude||null,filter:e.filter||null,useCSSOM:e.useCSSOM||!1,onBeforeSend:e.onBeforeSend||Function.prototype,onSuccess:e.onSuccess||Function.prototype,onError:e.onError||Function.prototype,onComplete:e.onComplete||Function.prototype},s=Array.apply(null,n.rootElement.querySelectorAll(n.include)).filter(function(e){return t=e,r=n.exclude,!(t.matches||t.matchesSelector||t.webkitMatchesSelector||t.mozMatchesSelector||t.msMatchesSelector||t.oMatchesSelector).call(t,r);var t,r}),a=Array.apply(null,Array(s.length)).map(function(e){return null});function c(){if(-1===a.indexOf(null)){var e=a.join("");n.onComplete(e,a,s)}}function i(e,t,o,s){var i=n.onSuccess(e,o,s);(function e(t,o,s,a){var c=arguments.length>4&&void 0!==arguments[4]?arguments[4]:[];var i=arguments.length>5&&void 0!==arguments[5]?arguments[5]:[];var l=u(t,s,i);l.rules.length?r(l.absoluteUrls,{onBeforeSend:function(e,t,r){n.onBeforeSend(e,o,t)},onSuccess:function(e,t,r){var s=n.onSuccess(e,o,t),a=u(e=!1===s?"":s||e,t,i);return a.rules.forEach(function(t,r){e=e.replace(t,a.absoluteRules[r])}),e},onError:function(r,n,u){c.push({xhr:r,url:n}),i.push(l.rules[u]),e(t,o,s,a,c,i)},onComplete:function(r){r.forEach(function(e,r){t=t.replace(l.rules[r],e)}),e(t,o,s,a,c,i)}}):a(t,c)})(e=void 0!==i&&!1===Boolean(i)?"":i||e,o,s,function(e,r){null===a[t]&&(r.forEach(function(e){return n.onError(e.xhr,o,e.url)}),!n.filter||n.filter.test(e)?a[t]=e:a[t]="",c())})}function u(e,r){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:[],s={};return s.rules=(e.replace(t.cssComments,"").match(t.cssImports)||[]).filter(function(e){return-1===n.indexOf(e)}),s.urls=s.rules.map(function(e){return e.replace(t.cssImports,"$1")}),s.absoluteUrls=s.urls.map(function(e){return o(e,r)}),s.absoluteRules=s.rules.map(function(e,t){var n=s.urls[t],a=o(s.absoluteUrls[t],r);return e.replace(n,a)}),s}s.length?s.forEach(function(e,t){var s=e.getAttribute("href"),u=e.getAttribute("rel"),l="LINK"===e.nodeName&&s&&u&&"stylesheet"===u.toLowerCase(),f="STYLE"===e.nodeName;if(l)r(s,{mimeType:"text/css",onBeforeSend:function(t,r,o){n.onBeforeSend(t,e,r)},onSuccess:function(r,n,a){var c=o(s,location.href);i(r,t,e,c)},onError:function(r,o,s){a[t]="",n.onError(r,e,o),c()}});else if(f){var d=e.textContent;n.useCSSOM&&(d=Array.apply(null,e.sheet.cssRules).map(function(e){return e.cssText}).join("")),i(d,t,e,location.href)}else a[t]="",c()}):n.onComplete("",[])}function o(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:location.href,r=document.implementation.createHTMLDocument(""),n=r.createElement("base"),o=r.createElement("a");return r.head.appendChild(n),r.body.appendChild(o),n.href=t,o.href=e,o.href}var s=a;function a(e,t,r){e instanceof RegExp&&(e=c(e,r)),t instanceof RegExp&&(t=c(t,r));var n=i(e,t,r);return n&&{start:n[0],end:n[1],pre:r.slice(0,n[0]),body:r.slice(n[0]+e.length,n[1]),post:r.slice(n[1]+t.length)}}function c(e,t){var r=t.match(e);return r?r[0]:null}function i(e,t,r){var n,o,s,a,c,i=r.indexOf(e),u=r.indexOf(t,i+1),l=i;if(i>=0&&u>0){for(n=[],s=r.length;l>=0&&!c;)l==i?(n.push(l),i=r.indexOf(e,l+1)):1==n.length?c=[n.pop(),u]:((o=n.pop())=0?i:u;n.length&&(c=[s,a])}return c}function u(t){var r=e({},{preserveStatic:!0,removeComments:!1},arguments.length>1&&void 0!==arguments[1]?arguments[1]:{});function n(e){throw new Error("CSS parse error: ".concat(e))}function o(e){var r=e.exec(t);if(r)return t=t.slice(r[0].length),r}function a(){return o(/^{\s*/)}function c(){return o(/^}/)}function i(){o(/^\s*/)}function u(){if(i(),"/"===t[0]&&"*"===t[1]){for(var e=2;t[e]&&("*"!==t[e]||"/"!==t[e+1]);)e++;if(!t[e])return n("end of comment is missing");var r=t.slice(2,e);return t=t.slice(e+2),{type:"comment",comment:r}}}function l(){for(var e,t=[];e=u();)t.push(e);return r.removeComments?[]:t}function f(){for(i();"}"===t[0];)n("extra closing bracket");var e=o(/^(("(?:\\"|[^"])*"|'(?:\\'|[^'])*'|[^{])+)/);if(e)return e[0].trim().replace(/\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*\/+/g,"").replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g,function(e){return e.replace(/,/g,"‌")}).split(/\s*(?![^(]*\)),\s*/).map(function(e){return e.replace(/\u200C/g,",")})}function d(){o(/^([;\s]*)+/);var e=/\/\*[^*]*\*+([^\/*][^*]*\*+)*\//g,t=o(/^(\*?[-#\/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/);if(t){if(t=t[0].trim(),!o(/^:\s*/))return n("property missing ':'");var r=o(/^((?:\/\*.*?\*\/|'(?:\\'|.)*?'|"(?:\\"|.)*?"|\((\s*'(?:\\'|.)*?'|"(?:\\"|.)*?"|[^)]*?)\s*\)|[^};])+)/),s={type:"declaration",property:t.replace(e,""),value:r?r[0].replace(e,"").trim():""};return o(/^[;\s]*/),s}}function p(){if(!a())return n("missing '{'");for(var e,t=l();e=d();)t.push(e),t=t.concat(l());return c()?t:n("missing '}'")}function m(){i();for(var e,t=[];e=o(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/);)t.push(e[1]),o(/^,\s*/);if(t.length)return{type:"keyframe",values:t,declarations:p()}}function v(){if(i(),"@"===t[0]){var e=function(){var e=o(/^@([-\w]+)?keyframes\s*/);if(e){var t=e[1];if(!(e=o(/^([-\w]+)\s*/)))return n("@keyframes missing name");var r,s=e[1];if(!a())return n("@keyframes missing '{'");for(var i=l();r=m();)i.push(r),i=i.concat(l());return c()?{type:"keyframes",name:s,vendor:t,keyframes:i}:n("@keyframes missing '}'")}}()||function(){var e=o(/^@supports *([^{]+)/);if(e)return{type:"supports",supports:e[1].trim(),rules:y()}}()||function(){if(o(/^@host\s*/))return{type:"host",rules:y()}}()||function(){var e=o(/^@media([^{]+)*/);if(e)return{type:"media",media:(e[1]||"").trim(),rules:y()}}()||function(){var e=o(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/);if(e)return{type:"custom-media",name:e[1].trim(),media:e[2].trim()}}()||function(){if(o(/^@page */))return{type:"page",selectors:f()||[],declarations:p()}}()||function(){var e=o(/^@([-\w]+)?document *([^{]+)/);if(e)return{type:"document",document:e[2].trim(),vendor:e[1]?e[1].trim():null,rules:y()}}()||function(){if(o(/^@font-face\s*/))return{type:"font-face",declarations:p()}}()||function(){var e=o(/^@(import|charset|namespace)\s*([^;]+);/);if(e)return{type:e[1],name:e[2].trim()}}();if(e&&!r.preserveStatic){var s=!1;if(e.declarations)s=e.declarations.some(function(e){return/var\(/.test(e.value)});else s=(e.keyframes||e.rules||[]).some(function(e){return(e.declarations||[]).some(function(e){return/var\(/.test(e.value)})});return s?e:{}}return e}}function h(){if(!r.preserveStatic){var e=s("{","}",t);if(e){var o=/:(?:root|host)(?![.:#(])/.test(e.pre)&&/--\S*\s*:/.test(e.body),a=/var\(/.test(e.body);if(!o&&!a)return t=t.slice(e.end+1),{}}}var c=f()||[],i=r.preserveStatic?p():p().filter(function(e){var t=c.some(function(e){return/:(?:root|host)(?![.:#(])/.test(e)})&&/^--\S/.test(e.property),r=/var\(/.test(e.value);return t||r});return c.length||n("selector missing"),{type:"rule",selectors:c,declarations:i}}function y(e){if(!e&&!a())return n("missing '{'");for(var r,o=l();t.length&&(e||"}"!==t[0])&&(r=v()||h());)r.type&&o.push(r),o=o.concat(l());return e||c()?o:n("missing '}'")}return{type:"stylesheet",stylesheet:{rules:y(!0),errors:[]}}}function l(t){var r=e({},{parseHost:!1,store:{},onWarning:function(){}},arguments.length>1&&void 0!==arguments[1]?arguments[1]:{}),n=new RegExp(":".concat(r.parseHost?"host":"root","(?![.:#(])"));return"string"==typeof t&&(t=u(t,r)),t.stylesheet.rules.forEach(function(e){"rule"===e.type&&e.selectors.some(function(e){return n.test(e)})&&e.declarations.forEach(function(e,t){var n=e.property,o=e.value;n&&0===n.indexOf("--")&&(r.store[n]=o)})}),r.store}function f(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",r=arguments.length>2?arguments[2]:void 0,n={charset:function(e){return"@charset "+e.name+";"},comment:function(e){return 0===e.comment.indexOf("__CSSVARSPONYFILL")?"/*"+e.comment+"*/":""},"custom-media":function(e){return"@custom-media "+e.name+" "+e.media+";"},declaration:function(e){return e.property+":"+e.value+";"},document:function(e){return"@"+(e.vendor||"")+"document "+e.document+"{"+o(e.rules)+"}"},"font-face":function(e){return"@font-face{"+o(e.declarations)+"}"},host:function(e){return"@host{"+o(e.rules)+"}"},import:function(e){return"@import "+e.name+";"},keyframe:function(e){return e.values.join(",")+"{"+o(e.declarations)+"}"},keyframes:function(e){return"@"+(e.vendor||"")+"keyframes "+e.name+"{"+o(e.keyframes)+"}"},media:function(e){return"@media "+e.media+"{"+o(e.rules)+"}"},namespace:function(e){return"@namespace "+e.name+";"},page:function(e){return"@page "+(e.selectors.length?e.selectors.join(", "):"")+"{"+o(e.declarations)+"}"},rule:function(e){var t=e.declarations;if(t.length)return e.selectors.join(",")+"{"+o(t)+"}"},supports:function(e){return"@supports "+e.supports+"{"+o(e.rules)+"}"}};function o(e){for(var o="",s=0;s1&&void 0!==arguments[1]?arguments[1]:{});return"string"==typeof t&&(t=u(t,r)),function e(t,r){t.rules.forEach(function(n){n.rules?e(n,r):n.keyframes?n.keyframes.forEach(function(e){"keyframe"===e.type&&r(e.declarations,n)}):n.declarations&&r(n.declarations,t)})}(t.stylesheet,function(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{},r=arguments.length>2?arguments[2]:void 0;if(-1===e.indexOf("var("))return e;var n=s("(",")",e);return n?"var"===n.pre.slice(-3)?0===n.body.trim().length?(t.onWarning("var() must contain a non-whitespace string"),e):n.pre.slice(0,-3)+function(e){var n=e.split(",")[0].replace(/[\s\n\t]/g,""),o=(e.match(/(?:\s*,\s*){1}(.*)?/)||[])[1],s=Object.prototype.hasOwnProperty.call(t.variables,n)?String(t.variables[n]):void 0,a=s||(o?String(o):void 0),c=r||e;return s||t.onWarning('variable "'.concat(n,'" is undefined')),a&&"undefined"!==a&&a.length>0?h(a,t,c):"var(".concat(c,")")}(n.body)+h(n.post,t):n.pre+"(".concat(h(n.body,t),")")+h(n.post,t):(-1!==e.indexOf("var(")&&t.onWarning('missing closing ")" in the value "'.concat(e,'"')),e)}var y="undefined"!=typeof window,g=y&&window.CSS&&window.CSS.supports&&window.CSS.supports("(--a: 0)"),S={group:0,job:0},b={rootElement:y?document:null,shadowDOM:!1,include:"style,link[rel=stylesheet]",exclude:"",variables:{},onlyLegacy:!0,preserveStatic:!0,preserveVars:!1,silent:!1,updateDOM:!0,updateURLs:!0,watch:null,onBeforeSend:function(){},onWarning:function(){},onError:function(){},onSuccess:function(){},onComplete:function(){}},E={cssComments:/\/\*[\s\S]+?\*\//g,cssKeyframes:/@(?:-\w*-)?keyframes/,cssMediaQueries:/@media[^{]+\{([\s\S]+?})\s*}/g,cssUrls:/url\((?!['"]?(?:data|http|\/\/):)['"]?([^'")]*)['"]?\)/g,cssVarDeclRules:/(?::(?:root|host)(?![.:#(])[\s,]*[^{]*{\s*[^}]*})/g,cssVarDecls:/(?:[\s;]*)(-{2}\w[\w-]*)(?:\s*:\s*)([^;]*);/g,cssVarFunc:/var\(\s*--[\w-]/,cssVars:/(?:(?::(?:root|host)(?![.:#(])[\s,]*[^{]*{\s*[^;]*;*\s*)|(?:var\(\s*))(--[^:)]+)(?:\s*[:)])/},w={dom:{},job:{},user:{}},C=!1,O=null,A=0,x=null,j=!1;function k(){var r=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},o="cssVars(): ",s=e({},b,r);function a(e,t,r,n){!s.silent&&window.console&&console.error("".concat(o).concat(e,"\n"),t),s.onError(e,t,r,n)}function c(e){!s.silent&&window.console&&console.warn("".concat(o).concat(e)),s.onWarning(e)}if(y){if(s.watch)return s.watch=b.watch,function(e){function t(e){return"LINK"===e.tagName&&-1!==(e.getAttribute("rel")||"").indexOf("stylesheet")&&!e.disabled}if(!window.MutationObserver)return;O&&(O.disconnect(),O=null);(O=new MutationObserver(function(r){r.some(function(r){var n,o=!1;return"attributes"===r.type?o=t(r.target):"childList"===r.type&&(n=r.addedNodes,o=Array.apply(null,n).some(function(e){var r=1===e.nodeType&&e.hasAttribute("data-cssvars"),n=function(e){return"STYLE"===e.tagName&&!e.disabled}(e)&&E.cssVars.test(e.textContent);return!r&&(t(e)||n)})||function(t){return Array.apply(null,t).some(function(t){var r=1===t.nodeType,n=r&&"out"===t.getAttribute("data-cssvars"),o=r&&"src"===t.getAttribute("data-cssvars"),s=o;if(o||n){var a=t.getAttribute("data-cssvars-group"),c=e.rootElement.querySelector('[data-cssvars-group="'.concat(a,'"]'));o&&(L(e.rootElement),w.dom={}),c&&c.parentNode.removeChild(c)}return s})}(r.removedNodes)),o})&&k(e)})).observe(document.documentElement,{attributes:!0,attributeFilter:["disabled","href"],childList:!0,subtree:!0})}(s),void k(s);if(!1===s.watch&&O&&(O.disconnect(),O=null),!s.__benchmark){if(C===s.rootElement)return void function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:100;clearTimeout(x),x=setTimeout(function(){e.__benchmark=null,k(e)},t)}(r);if(s.__benchmark=T(),s.exclude=[O?'[data-cssvars]:not([data-cssvars=""])':'[data-cssvars="out"]',s.exclude].filter(function(e){return e}).join(","),s.variables=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=/^-{2}/;return Object.keys(e).reduce(function(r,n){return r[t.test(n)?n:"--".concat(n.replace(/^-+/,""))]=e[n],r},{})}(s.variables),!O)if(Array.apply(null,s.rootElement.querySelectorAll('[data-cssvars="out"]')).forEach(function(e){var t=e.getAttribute("data-cssvars-group");(t?s.rootElement.querySelector('[data-cssvars="src"][data-cssvars-group="'.concat(t,'"]')):null)||e.parentNode.removeChild(e)}),A){var i=s.rootElement.querySelectorAll('[data-cssvars]:not([data-cssvars="out"])');i.length2&&void 0!==arguments[2]?arguments[2]:[],i={},d=s.updateDOM?w.dom:Object.keys(w.job).length?w.job:w.job=JSON.parse(JSON.stringify(w.dom)),p=!1;if(o.forEach(function(e,t){if(E.cssVars.test(n[t]))try{var r=u(n[t],{preserveStatic:s.preserveStatic,removeComments:!0});l(r,{parseHost:Boolean(s.rootElement.host),store:i,onWarning:c}),e.__cssVars={tree:r}}catch(t){a(t.message,e)}}),s.updateDOM&&e(w.user,s.variables),e(i,s.variables),p=Boolean((document.querySelector("[data-cssvars]")||Object.keys(w.dom).length)&&Object.keys(i).some(function(e){return i[e]!==d[e]})),e(d,w.user,i),p)L(s.rootElement),k(s);else{var v=[],h=[],y=!1;if(w.job={},s.updateDOM&&S.job++,o.forEach(function(t){var r=!t.__cssVars;if(t.__cssVars)try{m(t.__cssVars.tree,e({},s,{variables:d,onWarning:c}));var n=f(t.__cssVars.tree);if(s.updateDOM){if(t.getAttribute("data-cssvars")||t.setAttribute("data-cssvars","src"),n.length){var o=t.getAttribute("data-cssvars-group")||++S.group,i=n.replace(/\s/g,""),u=s.rootElement.querySelector('[data-cssvars="out"][data-cssvars-group="'.concat(o,'"]'))||document.createElement("style");y=y||E.cssKeyframes.test(n),u.hasAttribute("data-cssvars")||u.setAttribute("data-cssvars","out"),i===t.textContent.replace(/\s/g,"")?(r=!0,u&&u.parentNode&&(t.removeAttribute("data-cssvars-group"),u.parentNode.removeChild(u))):i!==u.textContent.replace(/\s/g,"")&&([t,u].forEach(function(e){e.setAttribute("data-cssvars-job",S.job),e.setAttribute("data-cssvars-group",o)}),u.textContent=n,v.push(n),h.push(u),u.parentNode||t.parentNode.insertBefore(u,t.nextSibling))}}else t.textContent.replace(/\s/g,"")!==n&&v.push(n)}catch(e){a(e.message,t)}r&&t.setAttribute("data-cssvars","skip"),t.hasAttribute("data-cssvars-job")||t.setAttribute("data-cssvars-job",S.job)}),A=s.rootElement.querySelectorAll('[data-cssvars]:not([data-cssvars="out"])').length,s.shadowDOM)for(var g,b=[s.rootElement].concat(t(s.rootElement.querySelectorAll("*"))),O=0;g=b[O];++O)if(g.shadowRoot&&g.shadowRoot.querySelector("style")){var x=e({},s,{rootElement:g.shadowRoot});k(x)}s.updateDOM&&y&&M(s.rootElement),C=!1,s.onComplete(v.join(""),h,JSON.parse(JSON.stringify(d)),T()-s.__benchmark)}}}));else document.addEventListener("DOMContentLoaded",function e(t){k(r),document.removeEventListener("DOMContentLoaded",e)})}}function M(e){var t=["animation-name","-moz-animation-name","-webkit-animation-name"].filter(function(e){return getComputedStyle(document.body)[e]})[0];if(t){for(var r=e.getElementsByTagName("*"),n=[],o=0,s=r.length;o1&&void 0!==arguments[1]?arguments[1]:location.href,r=document.implementation.createHTMLDocument(""),n=r.createElement("base"),o=r.createElement("a");return r.head.appendChild(n),r.body.appendChild(o),n.href=t,o.href=e,o.href}function T(){return y&&(window.performance||{}).now?window.performance.now():(new Date).getTime()}function L(e){Array.apply(null,e.querySelectorAll('[data-cssvars="skip"],[data-cssvars="src"]')).forEach(function(e){return e.setAttribute("data-cssvars","")})}return k.reset=function(){for(var e in C=!1,O&&(O.disconnect(),O=null),A=0,x=null,j=!1,w)w[e]={}},k}); +!(function (e, t) { + "object" == typeof exports && "undefined" != typeof module + ? (module.exports = t()) + : "function" == typeof define && define.amd + ? define(t) + : ((e = e || self).cssVars = t()); +})(this, function () { + "use strict"; + function e() { + return (e = + Object.assign || + function (e) { + for (var t = 1; t < arguments.length; t++) { + var r = arguments[t]; + for (var n in r) + Object.prototype.hasOwnProperty.call(r, n) && + (e[n] = r[n]); + } + return e; + }).apply(this, arguments); + } + function t(e) { + return ( + (function (e) { + if (Array.isArray(e)) { + for (var t = 0, r = new Array(e.length); t < e.length; t++) + r[t] = e[t]; + return r; + } + })(e) || + (function (e) { + if ( + Symbol.iterator in Object(e) || + "[object Arguments]" === Object.prototype.toString.call(e) + ) + return Array.from(e); + })(e) || + (function () { + throw new TypeError( + "Invalid attempt to spread non-iterable instance", + ); + })() + ); + } + function r(e) { + var t = + arguments.length > 1 && void 0 !== arguments[1] + ? arguments[1] + : {}, + r = { + mimeType: t.mimeType || null, + onBeforeSend: t.onBeforeSend || Function.prototype, + onSuccess: t.onSuccess || Function.prototype, + onError: t.onError || Function.prototype, + onComplete: t.onComplete || Function.prototype, + }, + n = Array.isArray(e) ? e : [e], + o = Array.apply(null, Array(n.length)).map(function (e) { + return null; + }); + function s() { + return !( + "<" === + (arguments.length > 0 && void 0 !== arguments[0] + ? arguments[0] + : "" + ) + .trim() + .charAt(0) + ); + } + function a(e, t) { + r.onError(e, n[t], t); + } + function c(e, t) { + var s = r.onSuccess(e, n[t], t); + (e = !1 === s ? "" : s || e), + (o[t] = e), + -1 === o.indexOf(null) && r.onComplete(o); + } + var i = document.createElement("a"); + n.forEach(function (e, t) { + if ( + (i.setAttribute("href", e), + (i.href = String(i.href)), + Boolean(document.all && !window.atob) && + i.host.split(":")[0] !== location.host.split(":")[0]) + ) { + if (i.protocol === location.protocol) { + var n = new XDomainRequest(); + n.open("GET", e), + (n.timeout = 0), + (n.onprogress = Function.prototype), + (n.ontimeout = Function.prototype), + (n.onload = function () { + s(n.responseText) ? c(n.responseText, t) : a(n, t); + }), + (n.onerror = function (e) { + a(n, t); + }), + setTimeout(function () { + n.send(); + }, 0); + } else + console.warn( + "Internet Explorer 9 Cross-Origin (CORS) requests must use the same protocol (".concat( + e, + ")", + ), + ), + a(null, t); + } else { + var o = new XMLHttpRequest(); + o.open("GET", e), + r.mimeType && + o.overrideMimeType && + o.overrideMimeType(r.mimeType), + r.onBeforeSend(o, e, t), + (o.onreadystatechange = function () { + 4 === o.readyState && + (200 === o.status && s(o.responseText) + ? c(o.responseText, t) + : a(o, t)); + }), + o.send(); + } + }); + } + function n(e) { + var t = { + cssComments: /\/\*[\s\S]+?\*\//g, + cssImports: + /(?:@import\s*)(?:url\(\s*)?(?:['"])([^'"]*)(?:['"])(?:\s*\))?(?:[^;]*;)/g, + }, + n = { + rootElement: e.rootElement || document, + include: e.include || 'style,link[rel="stylesheet"]', + exclude: e.exclude || null, + filter: e.filter || null, + useCSSOM: e.useCSSOM || !1, + onBeforeSend: e.onBeforeSend || Function.prototype, + onSuccess: e.onSuccess || Function.prototype, + onError: e.onError || Function.prototype, + onComplete: e.onComplete || Function.prototype, + }, + s = Array.apply( + null, + n.rootElement.querySelectorAll(n.include), + ).filter(function (e) { + return ( + (t = e), + (r = n.exclude), + !( + t.matches || + t.matchesSelector || + t.webkitMatchesSelector || + t.mozMatchesSelector || + t.msMatchesSelector || + t.oMatchesSelector + ).call(t, r) + ); + var t, r; + }), + a = Array.apply(null, Array(s.length)).map(function (e) { + return null; + }); + function c() { + if (-1 === a.indexOf(null)) { + var e = a.join(""); + n.onComplete(e, a, s); + } + } + function i(e, t, o, s) { + var i = n.onSuccess(e, o, s); + (function e(t, o, s, a) { + var c = + arguments.length > 4 && void 0 !== arguments[4] + ? arguments[4] + : []; + var i = + arguments.length > 5 && void 0 !== arguments[5] + ? arguments[5] + : []; + var l = u(t, s, i); + l.rules.length + ? r(l.absoluteUrls, { + onBeforeSend: function (e, t, r) { + n.onBeforeSend(e, o, t); + }, + onSuccess: function (e, t, r) { + var s = n.onSuccess(e, o, t), + a = u((e = !1 === s ? "" : s || e), t, i); + return ( + a.rules.forEach(function (t, r) { + e = e.replace(t, a.absoluteRules[r]); + }), + e + ); + }, + onError: function (r, n, u) { + c.push({ xhr: r, url: n }), + i.push(l.rules[u]), + e(t, o, s, a, c, i); + }, + onComplete: function (r) { + r.forEach(function (e, r) { + t = t.replace(l.rules[r], e); + }), + e(t, o, s, a, c, i); + }, + }) + : a(t, c); + })( + (e = void 0 !== i && !1 === Boolean(i) ? "" : i || e), + o, + s, + function (e, r) { + null === a[t] && + (r.forEach(function (e) { + return n.onError(e.xhr, o, e.url); + }), + !n.filter || n.filter.test(e) + ? (a[t] = e) + : (a[t] = ""), + c()); + }, + ); + } + function u(e, r) { + var n = + arguments.length > 2 && void 0 !== arguments[2] + ? arguments[2] + : [], + s = {}; + return ( + (s.rules = ( + e.replace(t.cssComments, "").match(t.cssImports) || [] + ).filter(function (e) { + return -1 === n.indexOf(e); + })), + (s.urls = s.rules.map(function (e) { + return e.replace(t.cssImports, "$1"); + })), + (s.absoluteUrls = s.urls.map(function (e) { + return o(e, r); + })), + (s.absoluteRules = s.rules.map(function (e, t) { + var n = s.urls[t], + a = o(s.absoluteUrls[t], r); + return e.replace(n, a); + })), + s + ); + } + s.length + ? s.forEach(function (e, t) { + var s = e.getAttribute("href"), + u = e.getAttribute("rel"), + l = + "LINK" === e.nodeName && + s && + u && + "stylesheet" === u.toLowerCase(), + f = "STYLE" === e.nodeName; + if (l) + r(s, { + mimeType: "text/css", + onBeforeSend: function (t, r, o) { + n.onBeforeSend(t, e, r); + }, + onSuccess: function (r, n, a) { + var c = o(s, location.href); + i(r, t, e, c); + }, + onError: function (r, o, s) { + (a[t] = ""), n.onError(r, e, o), c(); + }, + }); + else if (f) { + var d = e.textContent; + n.useCSSOM && + (d = Array.apply(null, e.sheet.cssRules) + .map(function (e) { + return e.cssText; + }) + .join("")), + i(d, t, e, location.href); + } else (a[t] = ""), c(); + }) + : n.onComplete("", []); + } + function o(e) { + var t = + arguments.length > 1 && void 0 !== arguments[1] + ? arguments[1] + : location.href, + r = document.implementation.createHTMLDocument(""), + n = r.createElement("base"), + o = r.createElement("a"); + return ( + r.head.appendChild(n), + r.body.appendChild(o), + (n.href = t), + (o.href = e), + o.href + ); + } + var s = a; + function a(e, t, r) { + e instanceof RegExp && (e = c(e, r)), + t instanceof RegExp && (t = c(t, r)); + var n = i(e, t, r); + return ( + n && { + start: n[0], + end: n[1], + pre: r.slice(0, n[0]), + body: r.slice(n[0] + e.length, n[1]), + post: r.slice(n[1] + t.length), + } + ); + } + function c(e, t) { + var r = t.match(e); + return r ? r[0] : null; + } + function i(e, t, r) { + var n, + o, + s, + a, + c, + i = r.indexOf(e), + u = r.indexOf(t, i + 1), + l = i; + if (i >= 0 && u > 0) { + for (n = [], s = r.length; l >= 0 && !c; ) + l == i + ? (n.push(l), (i = r.indexOf(e, l + 1))) + : 1 == n.length + ? (c = [n.pop(), u]) + : ((o = n.pop()) < s && ((s = o), (a = u)), + (u = r.indexOf(t, l + 1))), + (l = i < u && i >= 0 ? i : u); + n.length && (c = [s, a]); + } + return c; + } + function u(t) { + var r = e( + {}, + { preserveStatic: !0, removeComments: !1 }, + arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}, + ); + function n(e) { + throw new Error("CSS parse error: ".concat(e)); + } + function o(e) { + var r = e.exec(t); + if (r) return (t = t.slice(r[0].length)), r; + } + function a() { + return o(/^{\s*/); + } + function c() { + return o(/^}/); + } + function i() { + o(/^\s*/); + } + function u() { + if ((i(), "/" === t[0] && "*" === t[1])) { + for (var e = 2; t[e] && ("*" !== t[e] || "/" !== t[e + 1]); ) + e++; + if (!t[e]) return n("end of comment is missing"); + var r = t.slice(2, e); + return (t = t.slice(e + 2)), { type: "comment", comment: r }; + } + } + function l() { + for (var e, t = []; (e = u()); ) t.push(e); + return r.removeComments ? [] : t; + } + function f() { + for (i(); "}" === t[0]; ) n("extra closing bracket"); + var e = o(/^(("(?:\\"|[^"])*"|'(?:\\'|[^'])*'|[^{])+)/); + if (e) + return e[0] + .trim() + .replace( + /\/\*([^*]|[\r\n]|(\*+([^*\/]|[\r\n])))*\*\/+/g, + "", + ) + .replace(/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'/g, function (e) { + return e.replace(/,/g, "‌"); + }) + .split(/\s*(?![^(]*\)),\s*/) + .map(function (e) { + return e.replace(/\u200C/g, ","); + }); + } + function d() { + o(/^([;\s]*)+/); + var e = /\/\*[^*]*\*+([^\/*][^*]*\*+)*\//g, + t = o(/^(\*?[-#\/*\\\w]+(\[[0-9a-z_-]+\])?)\s*/); + if (t) { + if (((t = t[0].trim()), !o(/^:\s*/))) + return n("property missing ':'"); + var r = o( + /^((?:\/\*.*?\*\/|'(?:\\'|.)*?'|"(?:\\"|.)*?"|\((\s*'(?:\\'|.)*?'|"(?:\\"|.)*?"|[^)]*?)\s*\)|[^};])+)/, + ), + s = { + type: "declaration", + property: t.replace(e, ""), + value: r ? r[0].replace(e, "").trim() : "", + }; + return o(/^[;\s]*/), s; + } + } + function p() { + if (!a()) return n("missing '{'"); + for (var e, t = l(); (e = d()); ) t.push(e), (t = t.concat(l())); + return c() ? t : n("missing '}'"); + } + function m() { + i(); + for ( + var e, t = []; + (e = o(/^((\d+\.\d+|\.\d+|\d+)%?|[a-z]+)\s*/)); + ) + t.push(e[1]), o(/^,\s*/); + if (t.length) + return { type: "keyframe", values: t, declarations: p() }; + } + function v() { + if ((i(), "@" === t[0])) { + var e = + (function () { + var e = o(/^@([-\w]+)?keyframes\s*/); + if (e) { + var t = e[1]; + if (!(e = o(/^([-\w]+)\s*/))) + return n("@keyframes missing name"); + var r, + s = e[1]; + if (!a()) return n("@keyframes missing '{'"); + for (var i = l(); (r = m()); ) + i.push(r), (i = i.concat(l())); + return c() + ? { + type: "keyframes", + name: s, + vendor: t, + keyframes: i, + } + : n("@keyframes missing '}'"); + } + })() || + (function () { + var e = o(/^@supports *([^{]+)/); + if (e) + return { + type: "supports", + supports: e[1].trim(), + rules: y(), + }; + })() || + (function () { + if (o(/^@host\s*/)) return { type: "host", rules: y() }; + })() || + (function () { + var e = o(/^@media([^{]+)*/); + if (e) + return { + type: "media", + media: (e[1] || "").trim(), + rules: y(), + }; + })() || + (function () { + var e = o(/^@custom-media\s+(--[^\s]+)\s*([^{;]+);/); + if (e) + return { + type: "custom-media", + name: e[1].trim(), + media: e[2].trim(), + }; + })() || + (function () { + if (o(/^@page */)) + return { + type: "page", + selectors: f() || [], + declarations: p(), + }; + })() || + (function () { + var e = o(/^@([-\w]+)?document *([^{]+)/); + if (e) + return { + type: "document", + document: e[2].trim(), + vendor: e[1] ? e[1].trim() : null, + rules: y(), + }; + })() || + (function () { + if (o(/^@font-face\s*/)) + return { type: "font-face", declarations: p() }; + })() || + (function () { + var e = o(/^@(import|charset|namespace)\s*([^;]+);/); + if (e) return { type: e[1], name: e[2].trim() }; + })(); + if (e && !r.preserveStatic) { + var s = !1; + if (e.declarations) + s = e.declarations.some(function (e) { + return /var\(/.test(e.value); + }); + else + s = (e.keyframes || e.rules || []).some(function (e) { + return (e.declarations || []).some(function (e) { + return /var\(/.test(e.value); + }); + }); + return s ? e : {}; + } + return e; + } + } + function h() { + if (!r.preserveStatic) { + var e = s("{", "}", t); + if (e) { + var o = + /:(?:root|host)(?![.:#(])/.test(e.pre) && + /--\S*\s*:/.test(e.body), + a = /var\(/.test(e.body); + if (!o && !a) return (t = t.slice(e.end + 1)), {}; + } + } + var c = f() || [], + i = r.preserveStatic + ? p() + : p().filter(function (e) { + var t = + c.some(function (e) { + return /:(?:root|host)(?![.:#(])/.test( + e, + ); + }) && /^--\S/.test(e.property), + r = /var\(/.test(e.value); + return t || r; + }); + return ( + c.length || n("selector missing"), + { type: "rule", selectors: c, declarations: i } + ); + } + function y(e) { + if (!e && !a()) return n("missing '{'"); + for ( + var r, o = l(); + t.length && (e || "}" !== t[0]) && (r = v() || h()); + + ) + r.type && o.push(r), (o = o.concat(l())); + return e || c() ? o : n("missing '}'"); + } + return { type: "stylesheet", stylesheet: { rules: y(!0), errors: [] } }; + } + function l(t) { + var r = e( + {}, + { parseHost: !1, store: {}, onWarning: function () {} }, + arguments.length > 1 && void 0 !== arguments[1] + ? arguments[1] + : {}, + ), + n = new RegExp( + ":".concat(r.parseHost ? "host" : "root", "(?![.:#(])"), + ); + return ( + "string" == typeof t && (t = u(t, r)), + t.stylesheet.rules.forEach(function (e) { + "rule" === e.type && + e.selectors.some(function (e) { + return n.test(e); + }) && + e.declarations.forEach(function (e, t) { + var n = e.property, + o = e.value; + n && 0 === n.indexOf("--") && (r.store[n] = o); + }); + }), + r.store + ); + } + function f(e) { + var t = + arguments.length > 1 && void 0 !== arguments[1] + ? arguments[1] + : "", + r = arguments.length > 2 ? arguments[2] : void 0, + n = { + charset: function (e) { + return "@charset " + e.name + ";"; + }, + comment: function (e) { + return 0 === e.comment.indexOf("__CSSVARSPONYFILL") + ? "/*" + e.comment + "*/" + : ""; + }, + "custom-media": function (e) { + return "@custom-media " + e.name + " " + e.media + ";"; + }, + declaration: function (e) { + return e.property + ":" + e.value + ";"; + }, + document: function (e) { + return ( + "@" + + (e.vendor || "") + + "document " + + e.document + + "{" + + o(e.rules) + + "}" + ); + }, + "font-face": function (e) { + return "@font-face{" + o(e.declarations) + "}"; + }, + host: function (e) { + return "@host{" + o(e.rules) + "}"; + }, + import: function (e) { + return "@import " + e.name + ";"; + }, + keyframe: function (e) { + return e.values.join(",") + "{" + o(e.declarations) + "}"; + }, + keyframes: function (e) { + return ( + "@" + + (e.vendor || "") + + "keyframes " + + e.name + + "{" + + o(e.keyframes) + + "}" + ); + }, + media: function (e) { + return "@media " + e.media + "{" + o(e.rules) + "}"; + }, + namespace: function (e) { + return "@namespace " + e.name + ";"; + }, + page: function (e) { + return ( + "@page " + + (e.selectors.length ? e.selectors.join(", ") : "") + + "{" + + o(e.declarations) + + "}" + ); + }, + rule: function (e) { + var t = e.declarations; + if (t.length) + return e.selectors.join(",") + "{" + o(t) + "}"; + }, + supports: function (e) { + return "@supports " + e.supports + "{" + o(e.rules) + "}"; + }, + }; + function o(e) { + for (var o = "", s = 0; s < e.length; s++) { + var a = e[s]; + r && r(a); + var c = n[a.type](a); + c && ((o += c), c.length && a.selectors && (o += t)); + } + return o; + } + return o(e.stylesheet.rules); + } + a.range = i; + var d = "--", + p = "var"; + function m(t) { + var r = e( + {}, + { + preserveStatic: !0, + preserveVars: !1, + variables: {}, + onWarning: function () {}, + }, + arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}, + ); + return ( + "string" == typeof t && (t = u(t, r)), + (function e(t, r) { + t.rules.forEach(function (n) { + n.rules + ? e(n, r) + : n.keyframes + ? n.keyframes.forEach(function (e) { + "keyframe" === e.type && + r(e.declarations, n); + }) + : n.declarations && r(n.declarations, t); + }); + })(t.stylesheet, function (e, t) { + for (var n = 0; n < e.length; n++) { + var o = e[n], + s = o.type, + a = o.property, + c = o.value; + if ("declaration" === s) + if (r.preserveVars || !a || 0 !== a.indexOf(d)) { + if (-1 !== c.indexOf(p + "(")) { + var i = h(c, r); + i !== o.value && + ((i = v(i)), + r.preserveVars + ? (e.splice(n, 0, { + type: s, + property: a, + value: i, + }), + n++) + : (o.value = i)); + } + } else e.splice(n, 1), n--; + } + }), + f(t) + ); + } + function v(e) { + return ( + (e.match(/calc\(([^)]+)\)/g) || []).forEach(function (t) { + var r = "calc".concat(t.split("calc").join("")); + e = e.replace(t, r); + }), + e + ); + } + function h(e) { + var t = + arguments.length > 1 && void 0 !== arguments[1] + ? arguments[1] + : {}, + r = arguments.length > 2 ? arguments[2] : void 0; + if (-1 === e.indexOf("var(")) return e; + var n = s("(", ")", e); + return n + ? "var" === n.pre.slice(-3) + ? 0 === n.body.trim().length + ? (t.onWarning( + "var() must contain a non-whitespace string", + ), + e) + : n.pre.slice(0, -3) + + (function (e) { + var n = e.split(",")[0].replace(/[\s\n\t]/g, ""), + o = (e.match(/(?:\s*,\s*){1}(.*)?/) || [])[1], + s = Object.prototype.hasOwnProperty.call( + t.variables, + n, + ) + ? String(t.variables[n]) + : void 0, + a = s || (o ? String(o) : void 0), + c = r || e; + return ( + s || + t.onWarning( + 'variable "'.concat( + n, + '" is undefined', + ), + ), + a && "undefined" !== a && a.length > 0 + ? h(a, t, c) + : "var(".concat(c, ")") + ); + })(n.body) + + h(n.post, t) + : n.pre + "(".concat(h(n.body, t), ")") + h(n.post, t) + : (-1 !== e.indexOf("var(") && + t.onWarning( + 'missing closing ")" in the value "'.concat(e, '"'), + ), + e); + } + var y = "undefined" != typeof window, + g = + y && + window.CSS && + window.CSS.supports && + window.CSS.supports("(--a: 0)"), + S = { group: 0, job: 0 }, + b = { + rootElement: y ? document : null, + shadowDOM: !1, + include: "style,link[rel=stylesheet]", + exclude: "", + variables: {}, + onlyLegacy: !0, + preserveStatic: !0, + preserveVars: !1, + silent: !1, + updateDOM: !0, + updateURLs: !0, + watch: null, + onBeforeSend: function () {}, + onWarning: function () {}, + onError: function () {}, + onSuccess: function () {}, + onComplete: function () {}, + }, + E = { + cssComments: /\/\*[\s\S]+?\*\//g, + cssKeyframes: /@(?:-\w*-)?keyframes/, + cssMediaQueries: /@media[^{]+\{([\s\S]+?})\s*}/g, + cssUrls: /url\((?!['"]?(?:data|http|\/\/):)['"]?([^'")]*)['"]?\)/g, + cssVarDeclRules: + /(?::(?:root|host)(?![.:#(])[\s,]*[^{]*{\s*[^}]*})/g, + cssVarDecls: /(?:[\s;]*)(-{2}\w[\w-]*)(?:\s*:\s*)([^;]*);/g, + cssVarFunc: /var\(\s*--[\w-]/, + cssVars: + /(?:(?::(?:root|host)(?![.:#(])[\s,]*[^{]*{\s*[^;]*;*\s*)|(?:var\(\s*))(--[^:)]+)(?:\s*[:)])/, + }, + w = { dom: {}, job: {}, user: {} }, + C = !1, + O = null, + A = 0, + x = null, + j = !1; + function k() { + var r = + arguments.length > 0 && void 0 !== arguments[0] + ? arguments[0] + : {}, + o = "cssVars(): ", + s = e({}, b, r); + function a(e, t, r, n) { + !s.silent && + window.console && + console.error("".concat(o).concat(e, "\n"), t), + s.onError(e, t, r, n); + } + function c(e) { + !s.silent && window.console && console.warn("".concat(o).concat(e)), + s.onWarning(e); + } + if (y) { + if (s.watch) + return ( + (s.watch = b.watch), + (function (e) { + function t(e) { + return ( + "LINK" === e.tagName && + -1 !== + (e.getAttribute("rel") || "").indexOf( + "stylesheet", + ) && + !e.disabled + ); + } + if (!window.MutationObserver) return; + O && (O.disconnect(), (O = null)); + (O = new MutationObserver(function (r) { + r.some(function (r) { + var n, + o = !1; + return ( + "attributes" === r.type + ? (o = t(r.target)) + : "childList" === r.type && + ((n = r.addedNodes), + (o = + Array.apply(null, n).some( + function (e) { + var r = + 1 === + e.nodeType && + e.hasAttribute( + "data-cssvars", + ), + n = + (function (e) { + return ( + "STYLE" === + e.tagName && + !e.disabled + ); + })(e) && + E.cssVars.test( + e.textContent, + ); + return ( + !r && (t(e) || n) + ); + }, + ) || + (function (t) { + return Array.apply( + null, + t, + ).some(function (t) { + var r = + 1 === + t.nodeType, + n = + r && + "out" === + t.getAttribute( + "data-cssvars", + ), + o = + r && + "src" === + t.getAttribute( + "data-cssvars", + ), + s = o; + if (o || n) { + var a = + t.getAttribute( + "data-cssvars-group", + ), + c = + e.rootElement.querySelector( + '[data-cssvars-group="'.concat( + a, + '"]', + ), + ); + o && + (L( + e.rootElement, + ), + (w.dom = {})), + c && + c.parentNode.removeChild( + c, + ); + } + return s; + }); + })(r.removedNodes))), + o + ); + }) && k(e); + })).observe(document.documentElement, { + attributes: !0, + attributeFilter: ["disabled", "href"], + childList: !0, + subtree: !0, + }); + })(s), + void k(s) + ); + if ( + (!1 === s.watch && O && (O.disconnect(), (O = null)), + !s.__benchmark) + ) { + if (C === s.rootElement) + return void (function (e) { + var t = + arguments.length > 1 && void 0 !== arguments[1] + ? arguments[1] + : 100; + clearTimeout(x), + (x = setTimeout(function () { + (e.__benchmark = null), k(e); + }, t)); + })(r); + if ( + ((s.__benchmark = T()), + (s.exclude = [ + O + ? '[data-cssvars]:not([data-cssvars=""])' + : '[data-cssvars="out"]', + s.exclude, + ] + .filter(function (e) { + return e; + }) + .join(",")), + (s.variables = (function () { + var e = + arguments.length > 0 && void 0 !== arguments[0] + ? arguments[0] + : {}, + t = /^-{2}/; + return Object.keys(e).reduce(function (r, n) { + return ( + (r[ + t.test(n) + ? n + : "--".concat(n.replace(/^-+/, "")) + ] = e[n]), + r + ); + }, {}); + })(s.variables)), + !O) + ) + if ( + (Array.apply( + null, + s.rootElement.querySelectorAll( + '[data-cssvars="out"]', + ), + ).forEach(function (e) { + var t = e.getAttribute("data-cssvars-group"); + (t + ? s.rootElement.querySelector( + '[data-cssvars="src"][data-cssvars-group="'.concat( + t, + '"]', + ), + ) + : null) || e.parentNode.removeChild(e); + }), + A) + ) { + var i = s.rootElement.querySelectorAll( + '[data-cssvars]:not([data-cssvars="out"])', + ); + i.length < A && ((A = i.length), (w.dom = {})); + } + } + if ("loading" !== document.readyState) + if (g && s.onlyLegacy) { + if (s.updateDOM) { + var d = + s.rootElement.host || + (s.rootElement === document + ? document.documentElement + : s.rootElement); + Object.keys(s.variables).forEach(function (e) { + d.style.setProperty(e, s.variables[e]); + }); + } + } else + !j && + (s.shadowDOM || + s.rootElement.shadowRoot || + s.rootElement.host) + ? n({ + rootElement: b.rootElement, + include: b.include, + exclude: s.exclude, + onSuccess: function (e, t, r) { + return ( + (e = ( + (e = e + .replace(E.cssComments, "") + .replace( + E.cssMediaQueries, + "", + )).match(E.cssVarDeclRules) || + [] + ).join("")) || !1 + ); + }, + onComplete: function (e, t, r) { + l(e, { store: w.dom, onWarning: c }), + (j = !0), + k(s); + }, + }) + : ((C = s.rootElement), + n({ + rootElement: s.rootElement, + include: s.include, + exclude: s.exclude, + onBeforeSend: s.onBeforeSend, + onError: function (e, t, r) { + var n = + e.responseURL || + _(r, location.href), + o = e.statusText + ? "(".concat(e.statusText, ")") + : "Unspecified Error" + + (0 === e.status + ? " (possibly CORS related)" + : ""); + a( + "CSS XHR Error: " + .concat(n, " ") + .concat(e.status, " ") + .concat(o), + t, + e, + n, + ); + }, + onSuccess: function (e, t, r) { + var n = s.onSuccess(e, t, r); + return ( + (e = + void 0 !== n && !1 === Boolean(n) + ? "" + : n || e), + s.updateURLs && + (e = (function (e, t) { + return ( + ( + e + .replace( + E.cssComments, + "", + ) + .match(E.cssUrls) || + [] + ).forEach(function (r) { + var n = r.replace( + E.cssUrls, + "$1", + ), + o = _(n, t); + e = e.replace( + r, + r.replace(n, o), + ); + }), + e + ); + })(e, r)), + e + ); + }, + onComplete: function (r, n) { + var o = + arguments.length > 2 && + void 0 !== arguments[2] + ? arguments[2] + : [], + i = {}, + d = s.updateDOM + ? w.dom + : Object.keys(w.job).length + ? w.job + : (w.job = JSON.parse( + JSON.stringify(w.dom), + )), + p = !1; + if ( + (o.forEach(function (e, t) { + if (E.cssVars.test(n[t])) + try { + var r = u(n[t], { + preserveStatic: + s.preserveStatic, + removeComments: !0, + }); + l(r, { + parseHost: Boolean( + s.rootElement.host, + ), + store: i, + onWarning: c, + }), + (e.__cssVars = { + tree: r, + }); + } catch (t) { + a(t.message, e); + } + }), + s.updateDOM && e(w.user, s.variables), + e(i, s.variables), + (p = Boolean( + (document.querySelector( + "[data-cssvars]", + ) || + Object.keys(w.dom).length) && + Object.keys(i).some( + function (e) { + return i[e] !== d[e]; + }, + ), + )), + e(d, w.user, i), + p) + ) + L(s.rootElement), k(s); + else { + var v = [], + h = [], + y = !1; + if ( + ((w.job = {}), + s.updateDOM && S.job++, + o.forEach(function (t) { + var r = !t.__cssVars; + if (t.__cssVars) + try { + m( + t.__cssVars.tree, + e({}, s, { + variables: d, + onWarning: c, + }), + ); + var n = f( + t.__cssVars.tree, + ); + if (s.updateDOM) { + if ( + (t.getAttribute( + "data-cssvars", + ) || + t.setAttribute( + "data-cssvars", + "src", + ), + n.length) + ) { + var o = + t.getAttribute( + "data-cssvars-group", + ) || + ++S.group, + i = + n.replace( + /\s/g, + "", + ), + u = + s.rootElement.querySelector( + '[data-cssvars="out"][data-cssvars-group="'.concat( + o, + '"]', + ), + ) || + document.createElement( + "style", + ); + (y = + y || + E.cssKeyframes.test( + n, + )), + u.hasAttribute( + "data-cssvars", + ) || + u.setAttribute( + "data-cssvars", + "out", + ), + i === + t.textContent.replace( + /\s/g, + "", + ) + ? ((r = + !0), + u && + u.parentNode && + (t.removeAttribute( + "data-cssvars-group", + ), + u.parentNode.removeChild( + u, + ))) + : i !== + u.textContent.replace( + /\s/g, + "", + ) && + ([ + t, + u, + ].forEach( + function ( + e, + ) { + e.setAttribute( + "data-cssvars-job", + S.job, + ), + e.setAttribute( + "data-cssvars-group", + o, + ); + }, + ), + (u.textContent = + n), + v.push( + n, + ), + h.push( + u, + ), + u.parentNode || + t.parentNode.insertBefore( + u, + t.nextSibling, + )); + } + } else + t.textContent.replace( + /\s/g, + "", + ) !== n && + v.push(n); + } catch (e) { + a(e.message, t); + } + r && + t.setAttribute( + "data-cssvars", + "skip", + ), + t.hasAttribute( + "data-cssvars-job", + ) || + t.setAttribute( + "data-cssvars-job", + S.job, + ); + }), + (A = s.rootElement.querySelectorAll( + '[data-cssvars]:not([data-cssvars="out"])', + ).length), + s.shadowDOM) + ) + for ( + var g, + b = [s.rootElement].concat( + t( + s.rootElement.querySelectorAll( + "*", + ), + ), + ), + O = 0; + (g = b[O]); + ++O + ) + if ( + g.shadowRoot && + g.shadowRoot.querySelector( + "style", + ) + ) { + var x = e({}, s, { + rootElement: + g.shadowRoot, + }); + k(x); + } + s.updateDOM && y && M(s.rootElement), + (C = !1), + s.onComplete( + v.join(""), + h, + JSON.parse(JSON.stringify(d)), + T() - s.__benchmark, + ); + } + }, + })); + else + document.addEventListener("DOMContentLoaded", function e(t) { + k(r), document.removeEventListener("DOMContentLoaded", e); + }); + } + } + function M(e) { + var t = [ + "animation-name", + "-moz-animation-name", + "-webkit-animation-name", + ].filter(function (e) { + return getComputedStyle(document.body)[e]; + })[0]; + if (t) { + for ( + var r = e.getElementsByTagName("*"), + n = [], + o = 0, + s = r.length; + o < s; + o++ + ) { + var a = r[o]; + "none" !== getComputedStyle(a)[t] && + ((a.style[t] += "__CSSVARSPONYFILL-KEYFRAMES__"), + n.push(a)); + } + document.body.offsetHeight; + for (var c = 0, i = n.length; c < i; c++) { + var u = n[c].style; + u[t] = u[t].replace("__CSSVARSPONYFILL-KEYFRAMES__", ""); + } + } + } + function _(e) { + var t = + arguments.length > 1 && void 0 !== arguments[1] + ? arguments[1] + : location.href, + r = document.implementation.createHTMLDocument(""), + n = r.createElement("base"), + o = r.createElement("a"); + return ( + r.head.appendChild(n), + r.body.appendChild(o), + (n.href = t), + (o.href = e), + o.href + ); + } + function T() { + return y && (window.performance || {}).now + ? window.performance.now() + : new Date().getTime(); + } + function L(e) { + Array.apply( + null, + e.querySelectorAll('[data-cssvars="skip"],[data-cssvars="src"]'), + ).forEach(function (e) { + return e.setAttribute("data-cssvars", ""); + }); + } + return ( + (k.reset = function () { + for (var e in ((C = !1), + O && (O.disconnect(), (O = null)), + (A = 0), + (x = null), + (j = !1), + w)) + w[e] = {}; + }), + k + ); +}); // Default values cssVars({ - // Targets - rootElement: document, - shadowDOM: false, + // Targets + rootElement: document, + shadowDOM: false, - // Sources - include: 'link[rel=stylesheet],style', - exclude: '', - variables: {}, + // Sources + include: "link[rel=stylesheet],style", + exclude: "", + variables: {}, - // Options - onlyLegacy: true, - preserveStatic: true, - preserveVars: false, - silent: false, - updateDOM: true, - updateURLs: true, - watch: false, + // Options + onlyLegacy: true, + preserveStatic: true, + preserveVars: false, + silent: false, + updateDOM: true, + updateURLs: true, + watch: false, - // Callbacks - onBeforeSend(xhr, elm, url) { - // ... - }, - onWarning(message) { - // ... - }, - onError(message, elm, xhr, url) { - // ... - }, - onSuccess(cssText, elm, url) { - // ... - }, - onComplete(cssText, styleElms, cssVariables, benchmark) { - // ... - } + // Callbacks + onBeforeSend(xhr, elm, url) { + // ... + }, + onWarning(message) { + // ... + }, + onError(message, elm, xhr, url) { + // ... + }, + onSuccess(cssText, elm, url) { + // ... + }, + onComplete(cssText, styleElms, cssVariables, benchmark) { + // ... + }, }); diff --git a/docs/src/assets/js/focus-visible.js b/docs/src/assets/js/focus-visible.js index c95845112cf4..da377d4b3331 100644 --- a/docs/src/assets/js/focus-visible.js +++ b/docs/src/assets/js/focus-visible.js @@ -1,4 +1,3 @@ - /** * Applies the :focus-visible polyfill at the given scope. * A scope in this case is either the top-level Document or a Shadow Root. @@ -7,299 +6,299 @@ * @see https://github.com/WICG/focus-visible */ function applyFocusVisiblePolyfill(scope) { - var hadKeyboardEvent = true; - var hadFocusVisibleRecently = false; - var hadFocusVisibleRecentlyTimeout = null; - - var inputTypesWhitelist = { - text: true, - search: true, - url: true, - tel: true, - email: true, - password: true, - number: true, - date: true, - month: true, - week: true, - time: true, - datetime: true, - 'datetime-local': true - }; - - /** - * Helper function for legacy browsers and iframes which sometimes focus - * elements like document, body, and non-interactive SVG. - * @param {Element} el - */ - function isValidFocusTarget(el) { - if ( - el && - el !== document && - el.nodeName !== 'HTML' && - el.nodeName !== 'BODY' && - 'classList' in el && - 'contains' in el.classList - ) { - return true; - } - return false; - } - - /** - * Computes whether the given element should automatically trigger the - * `focus-visible` class being added, i.e. whether it should always match - * `:focus-visible` when focused. - * @param {Element} el - * @return {boolean} - */ - function focusTriggersKeyboardModality(el) { - var type = el.type; - var tagName = el.tagName; - - if (tagName === 'INPUT' && inputTypesWhitelist[type] && !el.readOnly) { - return true; - } - - if (tagName === 'TEXTAREA' && !el.readOnly) { - return true; - } - - if (el.isContentEditable) { - return true; - } - - return false; - } - - /** - * Add the `focus-visible` class to the given element if it was not added by - * the author. - * @param {Element} el - */ - function addFocusVisibleClass(el) { - if (el.classList.contains('focus-visible')) { - return; - } - el.classList.add('focus-visible'); - el.setAttribute('data-focus-visible-added', ''); - } - - /** - * Remove the `focus-visible` class from the given element if it was not - * originally added by the author. - * @param {Element} el - */ - function removeFocusVisibleClass(el) { - if (!el.hasAttribute('data-focus-visible-added')) { - return; - } - el.classList.remove('focus-visible'); - el.removeAttribute('data-focus-visible-added'); - } - - /** - * If the most recent user interaction was via the keyboard; - * and the key press did not include a meta, alt/option, or control key; - * then the modality is keyboard. Otherwise, the modality is not keyboard. - * Apply `focus-visible` to any current active element and keep track - * of our keyboard modality state with `hadKeyboardEvent`. - * @param {KeyboardEvent} e - */ - function onKeyDown(e) { - if (e.metaKey || e.altKey || e.ctrlKey) { - return; - } - - if (isValidFocusTarget(scope.activeElement)) { - addFocusVisibleClass(scope.activeElement); - } - - hadKeyboardEvent = true; - } - - /** - * If at any point a user clicks with a pointing device, ensure that we change - * the modality away from keyboard. - * This avoids the situation where a user presses a key on an already focused - * element, and then clicks on a different element, focusing it with a - * pointing device, while we still think we're in keyboard modality. - * @param {Event} e - */ - function onPointerDown(e) { - hadKeyboardEvent = false; - } - - /** - * On `focus`, add the `focus-visible` class to the target if: - * - the target received focus as a result of keyboard navigation, or - * - the event target is an element that will likely require interaction - * via the keyboard (e.g. a text box) - * @param {Event} e - */ - function onFocus(e) { - // Prevent IE from focusing the document or HTML element. - if (!isValidFocusTarget(e.target)) { - return; - } - - if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) { - addFocusVisibleClass(e.target); - } - } - - /** - * On `blur`, remove the `focus-visible` class from the target. - * @param {Event} e - */ - function onBlur(e) { - if (!isValidFocusTarget(e.target)) { - return; - } - - if ( - e.target.classList.contains('focus-visible') || - e.target.hasAttribute('data-focus-visible-added') - ) { - // To detect a tab/window switch, we look for a blur event followed - // rapidly by a visibility change. - // If we don't see a visibility change within 100ms, it's probably a - // regular focus change. - hadFocusVisibleRecently = true; - window.clearTimeout(hadFocusVisibleRecentlyTimeout); - hadFocusVisibleRecentlyTimeout = window.setTimeout(function() { - hadFocusVisibleRecently = false; - window.clearTimeout(hadFocusVisibleRecentlyTimeout); - }, 100); - removeFocusVisibleClass(e.target); - } - } - - /** - * If the user changes tabs, keep track of whether or not the previously - * focused element had .focus-visible. - * @param {Event} e - */ - function onVisibilityChange(e) { - if (document.visibilityState === 'hidden') { - // If the tab becomes active again, the browser will handle calling focus - // on the element (Safari actually calls it twice). - // If this tab change caused a blur on an element with focus-visible, - // re-apply the class when the user switches back to the tab. - if (hadFocusVisibleRecently) { - hadKeyboardEvent = true; - } - addInitialPointerMoveListeners(); - } - } - - /** - * Add a group of listeners to detect usage of any pointing devices. - * These listeners will be added when the polyfill first loads, and anytime - * the window is blurred, so that they are active when the window regains - * focus. - */ - function addInitialPointerMoveListeners() { - document.addEventListener('mousemove', onInitialPointerMove); - document.addEventListener('mousedown', onInitialPointerMove); - document.addEventListener('mouseup', onInitialPointerMove); - document.addEventListener('pointermove', onInitialPointerMove); - document.addEventListener('pointerdown', onInitialPointerMove); - document.addEventListener('pointerup', onInitialPointerMove); - document.addEventListener('touchmove', onInitialPointerMove); - document.addEventListener('touchstart', onInitialPointerMove); - document.addEventListener('touchend', onInitialPointerMove); - } - - function removeInitialPointerMoveListeners() { - document.removeEventListener('mousemove', onInitialPointerMove); - document.removeEventListener('mousedown', onInitialPointerMove); - document.removeEventListener('mouseup', onInitialPointerMove); - document.removeEventListener('pointermove', onInitialPointerMove); - document.removeEventListener('pointerdown', onInitialPointerMove); - document.removeEventListener('pointerup', onInitialPointerMove); - document.removeEventListener('touchmove', onInitialPointerMove); - document.removeEventListener('touchstart', onInitialPointerMove); - document.removeEventListener('touchend', onInitialPointerMove); - } - - /** - * When the polyfill first loads, assume the user is in keyboard modality. - * If any event is received from a pointing device (e.g. mouse, pointer, - * touch), turn off keyboard modality. - * This accounts for situations where focus enters the page from the URL bar. - * @param {Event} e - */ - function onInitialPointerMove(e) { - // Work around a Safari quirk that fires a mousemove on whenever the - // window blurs, even if you're tabbing out of the page. ¯\_(ツ)_/¯ - if (e.target.nodeName && e.target.nodeName.toLowerCase() === 'html') { - return; - } - - hadKeyboardEvent = false; - removeInitialPointerMoveListeners(); - } - - // For some kinds of state, we are interested in changes at the global scope - // only. For example, global pointer input, global key presses and global - // visibility change should affect the state at every scope: - document.addEventListener('keydown', onKeyDown, true); - document.addEventListener('mousedown', onPointerDown, true); - document.addEventListener('pointerdown', onPointerDown, true); - document.addEventListener('touchstart', onPointerDown, true); - document.addEventListener('visibilitychange', onVisibilityChange, true); - - addInitialPointerMoveListeners(); - - // For focus and blur, we specifically care about state changes in the local - // scope. This is because focus / blur events that originate from within a - // shadow root are not re-dispatched from the host element if it was already - // the active element in its own scope: - scope.addEventListener('focus', onFocus, true); - scope.addEventListener('blur', onBlur, true); - - // We detect that a node is a ShadowRoot by ensuring that it is a - // DocumentFragment and also has a host property. This check covers native - // implementation and polyfill implementation transparently. If we only cared - // about the native implementation, we could just check if the scope was - // an instance of a ShadowRoot. - if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) { - // Since a ShadowRoot is a special kind of DocumentFragment, it does not - // have a root element to add a class to. So, we add this attribute to the - // host element instead: - scope.host.setAttribute('data-js-focus-visible', ''); - } else if (scope.nodeType === Node.DOCUMENT_NODE) { - document.documentElement.classList.add('js-focus-visible'); - } + var hadKeyboardEvent = true; + var hadFocusVisibleRecently = false; + var hadFocusVisibleRecentlyTimeout = null; + + var inputTypesWhitelist = { + text: true, + search: true, + url: true, + tel: true, + email: true, + password: true, + number: true, + date: true, + month: true, + week: true, + time: true, + datetime: true, + "datetime-local": true, + }; + + /** + * Helper function for legacy browsers and iframes which sometimes focus + * elements like document, body, and non-interactive SVG. + * @param {Element} el + */ + function isValidFocusTarget(el) { + if ( + el && + el !== document && + el.nodeName !== "HTML" && + el.nodeName !== "BODY" && + "classList" in el && + "contains" in el.classList + ) { + return true; + } + return false; + } + + /** + * Computes whether the given element should automatically trigger the + * `focus-visible` class being added, i.e. whether it should always match + * `:focus-visible` when focused. + * @param {Element} el + * @return {boolean} + */ + function focusTriggersKeyboardModality(el) { + var type = el.type; + var tagName = el.tagName; + + if (tagName === "INPUT" && inputTypesWhitelist[type] && !el.readOnly) { + return true; + } + + if (tagName === "TEXTAREA" && !el.readOnly) { + return true; + } + + if (el.isContentEditable) { + return true; + } + + return false; + } + + /** + * Add the `focus-visible` class to the given element if it was not added by + * the author. + * @param {Element} el + */ + function addFocusVisibleClass(el) { + if (el.classList.contains("focus-visible")) { + return; + } + el.classList.add("focus-visible"); + el.setAttribute("data-focus-visible-added", ""); + } + + /** + * Remove the `focus-visible` class from the given element if it was not + * originally added by the author. + * @param {Element} el + */ + function removeFocusVisibleClass(el) { + if (!el.hasAttribute("data-focus-visible-added")) { + return; + } + el.classList.remove("focus-visible"); + el.removeAttribute("data-focus-visible-added"); + } + + /** + * If the most recent user interaction was via the keyboard; + * and the key press did not include a meta, alt/option, or control key; + * then the modality is keyboard. Otherwise, the modality is not keyboard. + * Apply `focus-visible` to any current active element and keep track + * of our keyboard modality state with `hadKeyboardEvent`. + * @param {KeyboardEvent} e + */ + function onKeyDown(e) { + if (e.metaKey || e.altKey || e.ctrlKey) { + return; + } + + if (isValidFocusTarget(scope.activeElement)) { + addFocusVisibleClass(scope.activeElement); + } + + hadKeyboardEvent = true; + } + + /** + * If at any point a user clicks with a pointing device, ensure that we change + * the modality away from keyboard. + * This avoids the situation where a user presses a key on an already focused + * element, and then clicks on a different element, focusing it with a + * pointing device, while we still think we're in keyboard modality. + * @param {Event} e + */ + function onPointerDown(e) { + hadKeyboardEvent = false; + } + + /** + * On `focus`, add the `focus-visible` class to the target if: + * - the target received focus as a result of keyboard navigation, or + * - the event target is an element that will likely require interaction + * via the keyboard (e.g. a text box) + * @param {Event} e + */ + function onFocus(e) { + // Prevent IE from focusing the document or HTML element. + if (!isValidFocusTarget(e.target)) { + return; + } + + if (hadKeyboardEvent || focusTriggersKeyboardModality(e.target)) { + addFocusVisibleClass(e.target); + } + } + + /** + * On `blur`, remove the `focus-visible` class from the target. + * @param {Event} e + */ + function onBlur(e) { + if (!isValidFocusTarget(e.target)) { + return; + } + + if ( + e.target.classList.contains("focus-visible") || + e.target.hasAttribute("data-focus-visible-added") + ) { + // To detect a tab/window switch, we look for a blur event followed + // rapidly by a visibility change. + // If we don't see a visibility change within 100ms, it's probably a + // regular focus change. + hadFocusVisibleRecently = true; + window.clearTimeout(hadFocusVisibleRecentlyTimeout); + hadFocusVisibleRecentlyTimeout = window.setTimeout(function () { + hadFocusVisibleRecently = false; + window.clearTimeout(hadFocusVisibleRecentlyTimeout); + }, 100); + removeFocusVisibleClass(e.target); + } + } + + /** + * If the user changes tabs, keep track of whether or not the previously + * focused element had .focus-visible. + * @param {Event} e + */ + function onVisibilityChange(e) { + if (document.visibilityState === "hidden") { + // If the tab becomes active again, the browser will handle calling focus + // on the element (Safari actually calls it twice). + // If this tab change caused a blur on an element with focus-visible, + // re-apply the class when the user switches back to the tab. + if (hadFocusVisibleRecently) { + hadKeyboardEvent = true; + } + addInitialPointerMoveListeners(); + } + } + + /** + * Add a group of listeners to detect usage of any pointing devices. + * These listeners will be added when the polyfill first loads, and anytime + * the window is blurred, so that they are active when the window regains + * focus. + */ + function addInitialPointerMoveListeners() { + document.addEventListener("mousemove", onInitialPointerMove); + document.addEventListener("mousedown", onInitialPointerMove); + document.addEventListener("mouseup", onInitialPointerMove); + document.addEventListener("pointermove", onInitialPointerMove); + document.addEventListener("pointerdown", onInitialPointerMove); + document.addEventListener("pointerup", onInitialPointerMove); + document.addEventListener("touchmove", onInitialPointerMove); + document.addEventListener("touchstart", onInitialPointerMove); + document.addEventListener("touchend", onInitialPointerMove); + } + + function removeInitialPointerMoveListeners() { + document.removeEventListener("mousemove", onInitialPointerMove); + document.removeEventListener("mousedown", onInitialPointerMove); + document.removeEventListener("mouseup", onInitialPointerMove); + document.removeEventListener("pointermove", onInitialPointerMove); + document.removeEventListener("pointerdown", onInitialPointerMove); + document.removeEventListener("pointerup", onInitialPointerMove); + document.removeEventListener("touchmove", onInitialPointerMove); + document.removeEventListener("touchstart", onInitialPointerMove); + document.removeEventListener("touchend", onInitialPointerMove); + } + + /** + * When the polyfill first loads, assume the user is in keyboard modality. + * If any event is received from a pointing device (e.g. mouse, pointer, + * touch), turn off keyboard modality. + * This accounts for situations where focus enters the page from the URL bar. + * @param {Event} e + */ + function onInitialPointerMove(e) { + // Work around a Safari quirk that fires a mousemove on whenever the + // window blurs, even if you're tabbing out of the page. ¯\_(ツ)_/¯ + if (e.target.nodeName && e.target.nodeName.toLowerCase() === "html") { + return; + } + + hadKeyboardEvent = false; + removeInitialPointerMoveListeners(); + } + + // For some kinds of state, we are interested in changes at the global scope + // only. For example, global pointer input, global key presses and global + // visibility change should affect the state at every scope: + document.addEventListener("keydown", onKeyDown, true); + document.addEventListener("mousedown", onPointerDown, true); + document.addEventListener("pointerdown", onPointerDown, true); + document.addEventListener("touchstart", onPointerDown, true); + document.addEventListener("visibilitychange", onVisibilityChange, true); + + addInitialPointerMoveListeners(); + + // For focus and blur, we specifically care about state changes in the local + // scope. This is because focus / blur events that originate from within a + // shadow root are not re-dispatched from the host element if it was already + // the active element in its own scope: + scope.addEventListener("focus", onFocus, true); + scope.addEventListener("blur", onBlur, true); + + // We detect that a node is a ShadowRoot by ensuring that it is a + // DocumentFragment and also has a host property. This check covers native + // implementation and polyfill implementation transparently. If we only cared + // about the native implementation, we could just check if the scope was + // an instance of a ShadowRoot. + if (scope.nodeType === Node.DOCUMENT_FRAGMENT_NODE && scope.host) { + // Since a ShadowRoot is a special kind of DocumentFragment, it does not + // have a root element to add a class to. So, we add this attribute to the + // host element instead: + scope.host.setAttribute("data-js-focus-visible", ""); + } else if (scope.nodeType === Node.DOCUMENT_NODE) { + document.documentElement.classList.add("js-focus-visible"); + } } // It is important to wrap all references to global window and document in // these checks to support server-side rendering use cases // @see https://github.com/WICG/focus-visible/issues/199 -if (typeof window !== 'undefined' && typeof document !== 'undefined') { - // Make the polyfill helper globally available. This can be used as a signal - // to interested libraries that wish to coordinate with the polyfill for e.g., - // applying the polyfill to a shadow root: - window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill; - - // Notify interested libraries of the polyfill's presence, in case the - // polyfill was loaded lazily: - var event; - - try { - event = new CustomEvent('focus-visible-polyfill-ready'); - } catch (error) { - // IE11 does not support using CustomEvent as a constructor directly: - event = document.createEvent('CustomEvent'); - event.initCustomEvent('focus-visible-polyfill-ready', false, false, {}); - } - - window.dispatchEvent(event); +if (typeof window !== "undefined" && typeof document !== "undefined") { + // Make the polyfill helper globally available. This can be used as a signal + // to interested libraries that wish to coordinate with the polyfill for e.g., + // applying the polyfill to a shadow root: + window.applyFocusVisiblePolyfill = applyFocusVisiblePolyfill; + + // Notify interested libraries of the polyfill's presence, in case the + // polyfill was loaded lazily: + var event; + + try { + event = new CustomEvent("focus-visible-polyfill-ready"); + } catch (error) { + // IE11 does not support using CustomEvent as a constructor directly: + event = document.createEvent("CustomEvent"); + event.initCustomEvent("focus-visible-polyfill-ready", false, false, {}); + } + + window.dispatchEvent(event); } -if (typeof document !== 'undefined') { - // Apply the polyfill to the global document, so that no JavaScript - // coordination is required to use the polyfill in the top-level document: - applyFocusVisiblePolyfill(document); +if (typeof document !== "undefined") { + // Apply the polyfill to the global document, so that no JavaScript + // coordination is required to use the polyfill in the top-level document: + applyFocusVisiblePolyfill(document); } diff --git a/docs/src/assets/js/inert-polyfill.js b/docs/src/assets/js/inert-polyfill.js index 11ae095ccf60..34e90291d3e4 100644 --- a/docs/src/assets/js/inert-polyfill.js +++ b/docs/src/assets/js/inert-polyfill.js @@ -1,23 +1,96 @@ -/* inert polyfill +/* inert polyfill * source: https://cdn.rawgit.com/GoogleChrome/inert-polyfill/v0.1.0/inert-polyfill.min.js */ window.addEventListener("load", function () { - function h(a, b, c) { if (0 > b) { if (a.previousElementSibling) { for (a = a.previousElementSibling; a.lastElementChild;)a = a.lastElementChild; return a } return a.parentElement } if (a != c && a.firstElementChild) return a.firstElementChild; for (; null != a;) { if (a.nextElementSibling) return a.nextElementSibling; a = a.parentElement } return null } function g(a) { for (; a && a !== document.documentElement;) { if (a.hasAttribute("inert")) return a; a = a.parentElement } return null } (function (a) { - var b = document.createElement("style"); - b.type = "text/css"; b.styleSheet ? b.styleSheet.cssText = a : b.appendChild(document.createTextNode(a)); document.body.appendChild(b) - })("/*[inert]*/[inert]{position:relative!important;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}[inert]::before{content:'';display:block;position:absolute;top:0;left:0;right:0;bottom:0}"); var c = 0; document.addEventListener("keydown", function (a) { c = 9 === a.keyCode ? a.shiftKey ? -1 : 1 : 0 }); document.addEventListener("mousedown", - function () { c = 0 }); document.body.addEventListener("focus", function (a) { - var b = a.target, f = g(b); if (f) { - if (document.hasFocus() && 0 !== c) { - var d = document.activeElement, e = new KeyboardEvent("keydown", { keyCode: 9, which: 9, key: "Tab", code: "Tab", keyIdentifier: "U+0009", shiftKey: !!(0 > c), bubbles: !0 }); Object.defineProperty(e, "keyCode", { value: 9 }); document.activeElement.dispatchEvent(e); if (d != document.activeElement) return; for (d = f; ;) { - d = h(d, c, f); if (!d) break; a: { - e = b; if (!(0 > d.tabIndex) && (d.focus(), document.activeElement !== e)) { - e = - !0; break a - } e = !1 - } if (e) return - } - } b.blur(); a.preventDefault(); a.stopPropagation() - } - }, !0); document.addEventListener("click", function (a) { g(a.target) && (a.preventDefault(), a.stopPropagation()) }, !0) -}); \ No newline at end of file + function h(a, b, c) { + if (0 > b) { + if (a.previousElementSibling) { + for (a = a.previousElementSibling; a.lastElementChild; ) + a = a.lastElementChild; + return a; + } + return a.parentElement; + } + if (a != c && a.firstElementChild) return a.firstElementChild; + for (; null != a; ) { + if (a.nextElementSibling) return a.nextElementSibling; + a = a.parentElement; + } + return null; + } + function g(a) { + for (; a && a !== document.documentElement; ) { + if (a.hasAttribute("inert")) return a; + a = a.parentElement; + } + return null; + } + (function (a) { + var b = document.createElement("style"); + b.type = "text/css"; + b.styleSheet + ? (b.styleSheet.cssText = a) + : b.appendChild(document.createTextNode(a)); + document.body.appendChild(b); + })( + "/*[inert]*/[inert]{position:relative!important;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none}[inert]::before{content:'';display:block;position:absolute;top:0;left:0;right:0;bottom:0}", + ); + var c = 0; + document.addEventListener("keydown", function (a) { + c = 9 === a.keyCode ? (a.shiftKey ? -1 : 1) : 0; + }); + document.addEventListener("mousedown", function () { + c = 0; + }); + document.body.addEventListener( + "focus", + function (a) { + var b = a.target, + f = g(b); + if (f) { + if (document.hasFocus() && 0 !== c) { + var d = document.activeElement, + e = new KeyboardEvent("keydown", { + keyCode: 9, + which: 9, + key: "Tab", + code: "Tab", + keyIdentifier: "U+0009", + shiftKey: !!(0 > c), + bubbles: !0, + }); + Object.defineProperty(e, "keyCode", { value: 9 }); + document.activeElement.dispatchEvent(e); + if (d != document.activeElement) return; + for (d = f; ; ) { + d = h(d, c, f); + if (!d) break; + a: { + e = b; + if ( + !(0 > d.tabIndex) && + (d.focus(), document.activeElement !== e) + ) { + e = !0; + break a; + } + e = !1; + } + if (e) return; + } + } + b.blur(); + a.preventDefault(); + a.stopPropagation(); + } + }, + !0, + ); + document.addEventListener( + "click", + function (a) { + g(a.target) && (a.preventDefault(), a.stopPropagation()); + }, + !0, + ); +}); diff --git a/docs/src/assets/js/main.js b/docs/src/assets/js/main.js index 80168a136c91..83599a5d32d1 100644 --- a/docs/src/assets/js/main.js +++ b/docs/src/assets/js/main.js @@ -1,303 +1,304 @@ (function () { - // for sticky table of contents - const tocBody = document.querySelector(".docs-aside #js-toc-panel"); - const options = { - root: null, - rootMargin: `0px 0px -90% 0px`, - threshold: 1.0, - }; - const activeClassName = "active"; - const observer = new IntersectionObserver((entries) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - const activeAnchor = tocBody.querySelector( - `a.${activeClassName}` - ); - if (activeAnchor) { - activeAnchor.parentNode.classList.remove(activeClassName); - activeAnchor.classList.remove(activeClassName); - } - - const nextActiveAnchor = tocBody.querySelector( - `a[href="#${entry.target.id}"]` - ); - if (nextActiveAnchor) { - nextActiveAnchor.parentNode.classList.add(activeClassName); - nextActiveAnchor.classList.add(activeClassName); - } - } - }); - }, options); - if (window.matchMedia("(min-width: 1400px)").matches) { - document - .querySelectorAll( - "#main > div > h2[id], #main > div > h3[id], #main > div > h4[id]" // only h2, h3, h4 are shown in toc - ) - .forEach((el) => observer.observe(el)); - } + // for sticky table of contents + const tocBody = document.querySelector(".docs-aside #js-toc-panel"); + const options = { + root: null, + rootMargin: `0px 0px -90% 0px`, + threshold: 1.0, + }; + const activeClassName = "active"; + const observer = new IntersectionObserver(entries => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const activeAnchor = tocBody.querySelector( + `a.${activeClassName}`, + ); + if (activeAnchor) { + activeAnchor.parentNode.classList.remove(activeClassName); + activeAnchor.classList.remove(activeClassName); + } + + const nextActiveAnchor = tocBody.querySelector( + `a[href="#${entry.target.id}"]`, + ); + if (nextActiveAnchor) { + nextActiveAnchor.parentNode.classList.add(activeClassName); + nextActiveAnchor.classList.add(activeClassName); + } + } + }); + }, options); + if (window.matchMedia("(min-width: 1400px)").matches) { + document + .querySelectorAll( + "#main > div > h2[id], #main > div > h3[id], #main > div > h4[id]", // only h2, h3, h4 are shown in toc + ) + .forEach(el => observer.observe(el)); + } })(); -(function() { - var toc_trigger = document.getElementById("js-toc-label"), - toc = document.getElementById("js-toc-panel"), - body = document.getElementsByTagName("body")[0], - open = false; - - if (toc && matchMedia) { - const mq = window.matchMedia("(max-width: 1023px)"); - mq.addEventListener('change', WidthChange); - WidthChange(mq); - } - - // media query change - function WidthChange(mq) { - if (mq.matches && toc_trigger) { - let text = toc_trigger.innerText; - let headingButton = document.createElement("button"); - headingButton.setAttribute("aria-expanded", "false"); - headingButton.innerText = text; - toc_trigger.innerHTML = ""; - - toc_trigger.appendChild(headingButton); - headingButton.innerHTML += ``; - - toc.setAttribute("data-open", "false"); - toc_trigger.setAttribute("aria-expanded", "false"); - headingButton.addEventListener("click", toggleTOC, true); - } else { - toc_trigger.innerHTML = 'Table of Contents'; - toc.setAttribute("data-open", "true"); - } - - } - - function toggleTOC(e) { - if (!open) { - this.setAttribute("aria-expanded", "true"); - toc.setAttribute("data-open", "true"); - open = true; - } else { - this.setAttribute("aria-expanded", "false"); - toc.setAttribute("data-open", "false"); - open = false; - } - } +(function () { + var toc_trigger = document.getElementById("js-toc-label"), + toc = document.getElementById("js-toc-panel"), + body = document.getElementsByTagName("body")[0], + open = false; + + if (toc && matchMedia) { + const mq = window.matchMedia("(max-width: 1023px)"); + mq.addEventListener("change", WidthChange); + WidthChange(mq); + } + + // media query change + function WidthChange(mq) { + if (mq.matches && toc_trigger) { + let text = toc_trigger.innerText; + let headingButton = document.createElement("button"); + headingButton.setAttribute("aria-expanded", "false"); + headingButton.innerText = text; + toc_trigger.innerHTML = ""; + + toc_trigger.appendChild(headingButton); + headingButton.innerHTML += ``; + + toc.setAttribute("data-open", "false"); + toc_trigger.setAttribute("aria-expanded", "false"); + headingButton.addEventListener("click", toggleTOC, true); + } else { + toc_trigger.innerHTML = "Table of Contents"; + toc.setAttribute("data-open", "true"); + } + } + + function toggleTOC(e) { + if (!open) { + this.setAttribute("aria-expanded", "true"); + toc.setAttribute("data-open", "true"); + open = true; + } else { + this.setAttribute("aria-expanded", "false"); + toc.setAttribute("data-open", "false"); + open = false; + } + } })(); -(function() { - var nav_trigger = document.getElementById("nav-toggle"), - nav = document.getElementById("nav-panel"), - body = document.getElementsByTagName("body")[0], - open = false; - - if (matchMedia) { - const mq = window.matchMedia("(max-width: 1023px)"); - mq.addEventListener('change', WidthChange); - WidthChange(mq); - } - - // media query change - function WidthChange(mq) { - if (mq.matches) { - nav.setAttribute("data-open", "false"); - nav_trigger.removeAttribute("hidden"); - nav_trigger.setAttribute("aria-expanded", "false"); - nav_trigger.addEventListener("click", togglenav, false); - } else { - nav.setAttribute("data-open", "true"); - nav_trigger.setAttribute("hidden", ""); - nav_trigger.setAttribute("aria-expanded", "true"); - } - - } - - function togglenav(e) { - if (!open) { - this.setAttribute("aria-expanded", "true"); - nav.setAttribute("data-open", "true"); - open = true; - } else { - this.setAttribute("aria-expanded", "false"); - nav.setAttribute("data-open", "false"); - open = false; - } - } +(function () { + var nav_trigger = document.getElementById("nav-toggle"), + nav = document.getElementById("nav-panel"), + body = document.getElementsByTagName("body")[0], + open = false; + + if (matchMedia) { + const mq = window.matchMedia("(max-width: 1023px)"); + mq.addEventListener("change", WidthChange); + WidthChange(mq); + } + + // media query change + function WidthChange(mq) { + if (mq.matches) { + nav.setAttribute("data-open", "false"); + nav_trigger.removeAttribute("hidden"); + nav_trigger.setAttribute("aria-expanded", "false"); + nav_trigger.addEventListener("click", togglenav, false); + } else { + nav.setAttribute("data-open", "true"); + nav_trigger.setAttribute("hidden", ""); + nav_trigger.setAttribute("aria-expanded", "true"); + } + } + + function togglenav(e) { + if (!open) { + this.setAttribute("aria-expanded", "true"); + nav.setAttribute("data-open", "true"); + open = true; + } else { + this.setAttribute("aria-expanded", "false"); + nav.setAttribute("data-open", "false"); + open = false; + } + } })(); -(function() { - var index_trigger = document.getElementById("js-docs-index-toggle"), - index = document.getElementById("js-docs-index-panel"), - body = document.getElementsByTagName("body")[0], - open = false; - - if (matchMedia) { - const mq = window.matchMedia("(max-width: 1023px)"); - mq.addEventListener('change', WidthChange); - WidthChange(mq); - } - - function WidthChange(mq) { - initIndex(); - } - - function toggleindex(e) { - if (!open) { - this.setAttribute("aria-expanded", "true"); - index.setAttribute("data-open", "true"); - open = true; - } else { - this.setAttribute("aria-expanded", "false"); - index.setAttribute("data-open", "false"); - open = false; - } - } - - function initIndex() { - if(index_trigger) { - - index_trigger.removeAttribute("hidden"); - index_trigger.setAttribute("aria-expanded", "false"); - index.setAttribute("data-open", "false"); - - index.setAttribute("data-open", "false"); - index_trigger.addEventListener("click", toggleindex, false); - } - } +(function () { + var index_trigger = document.getElementById("js-docs-index-toggle"), + index = document.getElementById("js-docs-index-panel"), + body = document.getElementsByTagName("body")[0], + open = false; + + if (matchMedia) { + const mq = window.matchMedia("(max-width: 1023px)"); + mq.addEventListener("change", WidthChange); + WidthChange(mq); + } + + function WidthChange(mq) { + initIndex(); + } + + function toggleindex(e) { + if (!open) { + this.setAttribute("aria-expanded", "true"); + index.setAttribute("data-open", "true"); + open = true; + } else { + this.setAttribute("aria-expanded", "false"); + index.setAttribute("data-open", "false"); + open = false; + } + } + + function initIndex() { + if (index_trigger) { + index_trigger.removeAttribute("hidden"); + index_trigger.setAttribute("aria-expanded", "false"); + index.setAttribute("data-open", "false"); + + index.setAttribute("data-open", "false"); + index_trigger.addEventListener("click", toggleindex, false); + } + } })(); - - -(function() { - var switchers = document.querySelectorAll('.switcher'), - fallbacks = document.querySelectorAll('.switcher-fallback'); - - if (fallbacks != null) { - fallbacks.forEach(el => { - el.setAttribute('hidden', ''); - }); - } - - if (switchers != null) { - switchers.forEach(element => { - element.removeAttribute('hidden'); - const select = element.querySelector('select'); - - select.addEventListener('change', function() { - var selected = this.options[this.selectedIndex]; - url = selected.getAttribute('data-url'); - - window.location.href = url; - }) - }); - } +(function () { + var switchers = document.querySelectorAll(".switcher"), + fallbacks = document.querySelectorAll(".switcher-fallback"); + + if (fallbacks != null) { + fallbacks.forEach(el => { + el.setAttribute("hidden", ""); + }); + } + + if (switchers != null) { + switchers.forEach(element => { + element.removeAttribute("hidden"); + const select = element.querySelector("select"); + + select.addEventListener("change", function () { + var selected = this.options[this.selectedIndex]; + url = selected.getAttribute("data-url"); + + window.location.href = url; + }); + }); + } })(); // add utilities var util = { - keyCodes: { - UP: 38, - DOWN: 40, - LEFT: 37, - RIGHT: 39, - HOME: 36, - END: 35, - ENTER: 13, - SPACE: 32, - DELETE: 46, - TAB: 9, - }, - - generateID: function(base) { - return base + Math.floor(Math.random() * 999); - }, - - getDirectChildren: function(elm, selector) { - return Array.prototype.filter.call(elm.children, function(child) { - return child.matches(selector); - }); - }, + keyCodes: { + UP: 38, + DOWN: 40, + LEFT: 37, + RIGHT: 39, + HOME: 36, + END: 35, + ENTER: 13, + SPACE: 32, + DELETE: 46, + TAB: 9, + }, + + generateID: function (base) { + return base + Math.floor(Math.random() * 999); + }, + + getDirectChildren: function (elm, selector) { + return Array.prototype.filter.call(elm.children, function (child) { + return child.matches(selector); + }); + }, }; -(function(w, doc, undefined) { - var CollapsibleIndexOptions = { - allCollapsed: false, - icon: '', - }; - var CollapsibleIndex = function(inst, options) { - var _options = Object.assign(CollapsibleIndexOptions, options); - var el = inst; - var indexToggles = el.querySelectorAll(".docs-index .docs__index__panel > ul > .docs-index__item[data-has-children] > a"); // only top-most level - var indexPanels = el.querySelectorAll(".docs-index .docs__index__panel > ul > .docs-index__item>[data-child-list]"); // the list - var accID = util.generateID("c-index-"); - - var init = function() { - el.classList.add("index-js"); - - setupindexToggles(indexToggles); - setupindexPanels(indexPanels); - }; - - - var setupindexToggles = function(indexToggles) { - Array.from(indexToggles).forEach(function(item, index) { - var $this = item; - - $this.setAttribute('role', 'button'); - $this.setAttribute("id", accID + "__item-" + index); - $this.innerHTML += _options.icon; - - if (_options.allCollapsed) $this.setAttribute("aria-expanded", "false"); - else $this.setAttribute("aria-expanded", "true"); - - $this.addEventListener("click", function(e) { - e.preventDefault(); - togglePanel($this); - }); - }); - }; - - var setupindexPanels = function(indexPanels) { - Array.from(indexPanels).forEach(function(item, index) { - let $this = item; - - $this.setAttribute("id", accID + "__list-" + index); - $this.setAttribute( - "aria-labelledby", - accID + "__item-" + index - ); - if (_options.allCollapsed) $this.setAttribute("aria-hidden", "true"); - else $this.setAttribute("aria-hidden", "false"); - }); - }; - - var togglePanel = function(toggleButton) { - var thepanel = toggleButton.nextElementSibling; - - if (toggleButton.getAttribute("aria-expanded") == "true") { - toggleButton.setAttribute("aria-expanded", "false"); - thepanel.setAttribute("aria-hidden", "true"); - } else { - toggleButton.setAttribute("aria-expanded", "true"); - thepanel.setAttribute("aria-hidden", "false"); - } - }; - - - init.call(this); - return this; - }; // CollapsibleIndex() - - w.CollapsibleIndex = CollapsibleIndex; +(function (w, doc, undefined) { + var CollapsibleIndexOptions = { + allCollapsed: false, + icon: '', + }; + var CollapsibleIndex = function (inst, options) { + var _options = Object.assign(CollapsibleIndexOptions, options); + var el = inst; + var indexToggles = el.querySelectorAll( + ".docs-index .docs__index__panel > ul > .docs-index__item[data-has-children] > a", + ); // only top-most level + var indexPanels = el.querySelectorAll( + ".docs-index .docs__index__panel > ul > .docs-index__item>[data-child-list]", + ); // the list + var accID = util.generateID("c-index-"); + + var init = function () { + el.classList.add("index-js"); + + setupindexToggles(indexToggles); + setupindexPanels(indexPanels); + }; + + var setupindexToggles = function (indexToggles) { + Array.from(indexToggles).forEach(function (item, index) { + var $this = item; + + $this.setAttribute("role", "button"); + $this.setAttribute("id", accID + "__item-" + index); + $this.innerHTML += _options.icon; + + if (_options.allCollapsed) + $this.setAttribute("aria-expanded", "false"); + else $this.setAttribute("aria-expanded", "true"); + + $this.addEventListener("click", function (e) { + e.preventDefault(); + togglePanel($this); + }); + }); + }; + + var setupindexPanels = function (indexPanels) { + Array.from(indexPanels).forEach(function (item, index) { + let $this = item; + + $this.setAttribute("id", accID + "__list-" + index); + $this.setAttribute( + "aria-labelledby", + accID + "__item-" + index, + ); + if (_options.allCollapsed) + $this.setAttribute("aria-hidden", "true"); + else $this.setAttribute("aria-hidden", "false"); + }); + }; + + var togglePanel = function (toggleButton) { + var thepanel = toggleButton.nextElementSibling; + + if (toggleButton.getAttribute("aria-expanded") == "true") { + toggleButton.setAttribute("aria-expanded", "false"); + thepanel.setAttribute("aria-hidden", "true"); + } else { + toggleButton.setAttribute("aria-expanded", "true"); + thepanel.setAttribute("aria-hidden", "false"); + } + }; + + init.call(this); + return this; + }; // CollapsibleIndex() + + w.CollapsibleIndex = CollapsibleIndex; })(window, document); // init -var index = document.getElementById('docs-index'); +var index = document.getElementById("docs-index"); if (index) { - index = new CollapsibleIndex(index, { - allCollapsed: false - }); + index = new CollapsibleIndex(index, { + allCollapsed: false, + }); } document.addEventListener("DOMContentLoaded", () => { - anchors.add(".docs-content h2:not(.c-toc__label), .docs-content h3, .docs-content h4"); + anchors.add( + ".docs-content h2:not(.c-toc__label), .docs-content h3, .docs-content h4", + ); }); diff --git a/docs/src/assets/js/scroll-up-btn.js b/docs/src/assets/js/scroll-up-btn.js index cb77af1bcbe4..9a3cf4ed2ee5 100644 --- a/docs/src/assets/js/scroll-up-btn.js +++ b/docs/src/assets/js/scroll-up-btn.js @@ -1,13 +1,16 @@ (function () { - const scrollUpBtn = document.getElementById("scroll-up-btn"); + const scrollUpBtn = document.getElementById("scroll-up-btn"); - if(window.innerWidth < 1400) { - window.addEventListener("scroll", function () { - if(document.body.scrollTop > 500 || document.documentElement.scrollTop > 500) { - scrollUpBtn.style.display = "flex"; - } else { - scrollUpBtn.style.display = "none"; - } - }); - } -})(); \ No newline at end of file + if (window.innerWidth < 1400) { + window.addEventListener("scroll", function () { + if ( + document.body.scrollTop > 500 || + document.documentElement.scrollTop > 500 + ) { + scrollUpBtn.style.display = "flex"; + } else { + scrollUpBtn.style.display = "none"; + } + }); + } +})(); diff --git a/docs/src/assets/js/search.js b/docs/src/assets/js/search.js index 6d8eaa7b1b2b..ac7a3492cbcb 100644 --- a/docs/src/assets/js/search.js +++ b/docs/src/assets/js/search.js @@ -14,16 +14,20 @@ import algoliasearch from "./algoliasearch.js"; //----------------------------------------------------------------------------- // search -const client = algoliasearch('L633P0C2IR', 'bb6bbd2940351f3afc18844a6b06a6e8'); -const index = client.initIndex('eslint'); +const client = algoliasearch("L633P0C2IR", "bb6bbd2940351f3afc18844a6b06a6e8"); +const index = client.initIndex("eslint"); // page -const resultsElement = document.querySelector('#search-results'); -const resultsLiveRegion = document.querySelector('#search-results-announcement'); -const searchInput = document.querySelector('#search'); -const searchClearBtn = document.querySelector('#search__clear-btn'); +const resultsElement = document.querySelector("#search-results"); +const resultsLiveRegion = document.querySelector( + "#search-results-announcement", +); +const searchInput = document.querySelector("#search"); +const searchClearBtn = document.querySelector("#search__clear-btn"); +const poweredByLink = document.querySelector(".search_powered-by-wrapper"); let activeIndex = -1; let searchQuery; +let caretPosition = 0; //----------------------------------------------------------------------------- // Helpers @@ -35,20 +39,45 @@ let searchQuery; * @returns {Promise>} The search results. */ function fetchSearchResults(query) { - return index.search(query, { - // facetFilters: ["tags:docs"] - }).then(({ hits }) => hits); + return index + .search(query, { + facetFilters: ["tags:docs"], + }) + .then(({ hits }) => hits); } /** - * Removes any current search results from the display. - * @returns {void} + * Clears the search results from the display. + * If the removeEventListener flag is true, removes the click event listener from the document. + * @param {boolean} [removeEventListener=false] - Optional flag to indicate if the click event listener should be removed. Default is false. + * @returns {void} - This function doesn't return anything. + */ +function clearSearchResults(removeEventListener = false) { + resultsElement.innerHTML = ""; + if (removeEventListener && document.clickEventAdded) { + document.removeEventListener("click", handleDocumentClick); + document.clickEventAdded = false; + } +} + +/** + * Displays a "No results found" message in both the live region and results display area. + * This is typically used when no matching results are found in the search. + * @returns {void} - This function doesn't return anything. + */ +function showNoResults() { + resultsLiveRegion.innerHTML = "No results found."; + resultsElement.innerHTML = "No results found."; + resultsElement.setAttribute("data-results", "false"); +} + +/** + * Clears any "No results found" message from the live region and results display area. + * @returns {void} - This function doesn't return anything. */ -function clearSearchResults() { - while (resultsElement.firstChild) { - resultsElement.removeChild(resultsElement.firstChild); - } - resultsElement.innerHTML = ""; +function clearNoResults() { + resultsLiveRegion.innerHTML = ""; + resultsElement.innerHTML = ""; } /** @@ -57,58 +86,53 @@ function clearSearchResults() { * @returns {void} */ function displaySearchResults(results) { - - clearSearchResults(); - - if (results.length) { - - const list = document.createElement("ul"); - list.setAttribute('role', 'list'); - list.classList.add('search-results__list'); - resultsElement.append(list); - resultsElement.setAttribute('data-results', 'true'); - activeIndex = -1; - - for (const result of results) { - const listItem = document.createElement('li'); - listItem.classList.add('search-results__item'); - const maxLvl = Math.max(...Object.keys(result._highlightResult.hierarchy).map(k => Number(k.substring(3)))); - listItem.innerHTML = ` + clearSearchResults(); + + if (results.length) { + const list = document.createElement("ul"); + list.setAttribute("role", "list"); + list.classList.add("search-results__list"); + resultsElement.append(list); + resultsElement.setAttribute("data-results", "true"); + activeIndex = -1; + + for (const result of results) { + const listItem = document.createElement("li"); + listItem.classList.add("search-results__item"); + const maxLvl = Math.max( + ...Object.keys(result._highlightResult.hierarchy).map(k => + Number(k.substring(3)), + ), + ); + listItem.innerHTML = `

${result.hierarchy.lvl0}

-

${typeof result._highlightResult.content !== 'undefined' ? result._highlightResult.content.value : result._highlightResult.hierarchy[`lvl${maxLvl}`].value}

+

${typeof result._highlightResult.content !== "undefined" ? result._highlightResult.content.value : result._highlightResult.hierarchy[`lvl${maxLvl}`].value}

`.trim(); - list.append(listItem); - } - - } else { - resultsLiveRegion.innerHTML = "No results found."; - resultsElement.innerHTML = "No results found."; - resultsElement.setAttribute('data-results', 'false'); - } - + list.append(listItem); + } + } else { + showNoResults(); + } } - // Check if an element is currently scrollable function isScrollable(element) { - return element && element.clientHeight < element.scrollHeight; + return element && element.clientHeight < element.scrollHeight; } // Ensure given child element is within the parent's visible scroll area function maintainScrollVisibility(activeElement, scrollParent) { - const { offsetHeight, offsetTop } = activeElement; - const { offsetHeight: parentOffsetHeight, scrollTop } = scrollParent; + const { offsetHeight, offsetTop } = activeElement; + const { offsetHeight: parentOffsetHeight, scrollTop } = scrollParent; - const isAbove = offsetTop < scrollTop; - const isBelow = (offsetTop + offsetHeight) > (scrollTop + parentOffsetHeight); - - if (isAbove) { - scrollParent.scrollTo(0, offsetTop); - } - else if (isBelow) { - scrollParent.scrollTo(0, offsetTop - parentOffsetHeight + offsetHeight); - } + const isAbove = offsetTop < scrollTop; + const isBelow = offsetTop + offsetHeight > scrollTop + parentOffsetHeight; + if (isAbove) { + scrollParent.scrollTo(0, offsetTop); + } else if (isBelow) { + scrollParent.scrollTo(0, offsetTop - parentOffsetHeight + offsetHeight); + } } /** @@ -118,92 +142,153 @@ function maintainScrollVisibility(activeElement, scrollParent) { * @returns {Function} Returns the new debounced function. */ function debounce(callback, delay) { - let timer; - return (...args) => { - if (timer) clearTimeout(timer); - timer = setTimeout(() => callback.apply(this, args), delay); - } + let timer; + return (...args) => { + if (timer) clearTimeout(timer); + timer = setTimeout(() => callback.apply(this, args), delay); + }; } -const debouncedFetchSearchResults = debounce((query) => { - fetchSearchResults(query) - .then(displaySearchResults) - .catch(clearSearchResults); +/** + * Debounced function to fetch search results after 300ms of inactivity. + * Calls `fetchSearchResults` to retrieve data and `displaySearchResults` to show them. + * If an error occurs, clears the search results. + * @param {string} query - The search query. + * @returns {void} - No return value. + * @see debounce - Limits the number of requests during rapid typing. + */ +const debouncedFetchSearchResults = debounce(query => { + fetchSearchResults(query) + .then(displaySearchResults) + .catch(() => { + clearSearchResults(true); + }); }, 300); +/** + * Handles the document click event to clear search results if the user clicks outside of the search input or results element. + * @param {MouseEvent} e - The event object representing the click event. + * @returns {void} - This function does not return any value. It directly interacts with the UI by clearing search results. + */ +const handleDocumentClick = e => { + if (e.target !== resultsElement && e.target !== searchInput) { + clearSearchResults(true); + } +}; + //----------------------------------------------------------------------------- // Event Handlers //----------------------------------------------------------------------------- // listen for input changes if (searchInput) - searchInput.addEventListener('keyup', function (e) { - const query = searchInput.value; - - if (query === searchQuery) return; - - if (query.length) searchClearBtn.removeAttribute('hidden'); - else searchClearBtn.setAttribute('hidden', ''); - - if (query.length > 2) { - - debouncedFetchSearchResults(query); - - document.addEventListener('click', function (e) { - if (e.target !== resultsElement) clearSearchResults(); - }); - } else { - clearSearchResults(); - } - - searchQuery = query - - }); - - -if (searchClearBtn) - searchClearBtn.addEventListener('click', function (e) { - searchInput.value = ''; - searchInput.focus(); - clearSearchResults(); - searchClearBtn.setAttribute('hidden', ''); - }); - -document.addEventListener('keydown', function (e) { - - const searchResults = Array.from(document.querySelectorAll('.search-results__item')); - - if (e.key === 'Escape') { - e.preventDefault(); - if (searchResults.length) { - clearSearchResults(); - searchInput.focus(); - } - } - - if ((e.metaKey || e.ctrlKey) && e.key === 'k') { - e.preventDefault(); - searchInput.focus(); - document.querySelector('.search').scrollIntoView({ behavior: "smooth", block: "start" }); - } - - if (!searchResults.length) return; - - switch (e.key) { - case "ArrowUp": - e.preventDefault(); - activeIndex = activeIndex - 1 < 0 ? searchResults.length - 1 : activeIndex - 1; - break; - case "ArrowDown": - e.preventDefault(); - activeIndex = activeIndex + 1 < searchResults.length ? activeIndex + 1 : 0; - break; - } - - if (activeIndex === -1) return; - const activeSearchResult = searchResults[activeIndex]; - activeSearchResult.querySelector('a').focus(); - if (isScrollable(resultsElement)) { - maintainScrollVisibility(activeSearchResult, resultsElement); - } + searchInput.addEventListener("keyup", function (e) { + const query = searchInput.value; + + if (query === searchQuery) return; + + if (query.length) searchClearBtn.removeAttribute("hidden"); + else searchClearBtn.setAttribute("hidden", ""); + + if (query.length > 2) { + debouncedFetchSearchResults(query); + if (!document.clickEventAdded) { + document.addEventListener("click", handleDocumentClick); + document.clickEventAdded = true; + } + } else { + clearSearchResults(true); + } + + searchQuery = query; + }); + +if (searchClearBtn) { + searchClearBtn.addEventListener("click", function () { + searchInput.value = ""; + searchInput.focus(); + clearSearchResults(true); + searchClearBtn.setAttribute("hidden", ""); + }); + + searchInput.addEventListener("blur", function () { + caretPosition = searchInput.selectionStart; + }); + + searchInput.addEventListener("focus", function () { + if (searchInput.selectionStart !== caretPosition) { + searchInput.setSelectionRange(caretPosition, caretPosition); + } + }); +} + +if (poweredByLink) { + poweredByLink.addEventListener("focus", function () { + clearSearchResults(); + }); +} + +if (resultsElement) { + resultsElement.addEventListener("keydown", e => { + if ( + e.key !== "ArrowUp" && + e.key !== "ArrowDown" && + e.key !== "Tab" && + e.key !== "Shift" && + e.key !== "Enter" + ) { + searchInput.focus(); + } + }); +} + +document.addEventListener("keydown", function (e) { + const searchResults = Array.from( + document.querySelectorAll(".search-results__item"), + ); + const isArrowKey = e.key === "ArrowUp" || e.key === "ArrowDown"; + + if (e.key === "Escape") { + e.preventDefault(); + if (searchResults.length) { + clearSearchResults(true); + searchInput.focus(); + } else if (document.activeElement === searchInput) { + clearNoResults(); + searchInput.blur(); + } + } + + if ((e.metaKey || e.ctrlKey) && e.key === "k") { + e.preventDefault(); + searchInput.focus(); + document + .querySelector(".search") + .scrollIntoView({ behavior: "smooth", block: "start" }); + } + + if (!searchResults.length) return; + + if (isArrowKey) { + e.preventDefault(); + + if (e.key === "ArrowUp") { + activeIndex = + activeIndex - 1 < 0 + ? searchResults.length - 1 + : activeIndex - 1; + } else if (e.key === "ArrowDown") { + activeIndex = + activeIndex + 1 < searchResults.length ? activeIndex + 1 : 0; + } + + if (activeIndex !== -1) { + const activeSearchResult = searchResults[activeIndex]; + activeSearchResult.querySelector("a").focus(); + + if (isScrollable(resultsElement)) { + maintainScrollVisibility(activeSearchResult, resultsElement); + } + } + } }); diff --git a/docs/src/assets/js/tabs.js b/docs/src/assets/js/tabs.js index a22159385389..a8613c7a0d75 100644 --- a/docs/src/assets/js/tabs.js +++ b/docs/src/assets/js/tabs.js @@ -1,337 +1,339 @@ "use strict"; if (typeof Object.assign != "function") { - // Must be writable: true, enumerable: false, configurable: true - Object.defineProperty(Object, "assign", { - value: function assign(target, varArgs) { - // .length of function is 2 - - if (target == null) { - // TypeError if undefined or null - throw new TypeError( - "Cannot convert undefined or null to object" - ); - } - - var to = Object(target); - - for (var index = 1; index < arguments.length; index++) { - var nextSource = arguments[index]; - - if (nextSource != null) { - // Skip over if undefined or null - for (var nextKey in nextSource) { - // Avoid bugs when hasOwnProperty is shadowed - if ( - Object.prototype.hasOwnProperty.call( - nextSource, - nextKey - ) - ) { - to[nextKey] = nextSource[nextKey]; - } - } - } - } - return to; - }, - writable: true, - configurable: true - }); + // Must be writable: true, enumerable: false, configurable: true + Object.defineProperty(Object, "assign", { + value: function assign(target, varArgs) { + // .length of function is 2 + + if (target == null) { + // TypeError if undefined or null + throw new TypeError( + "Cannot convert undefined or null to object", + ); + } + + var to = Object(target); + + for (var index = 1; index < arguments.length; index++) { + var nextSource = arguments[index]; + + if (nextSource != null) { + // Skip over if undefined or null + for (var nextKey in nextSource) { + // Avoid bugs when hasOwnProperty is shadowed + if ( + Object.prototype.hasOwnProperty.call( + nextSource, + nextKey, + ) + ) { + to[nextKey] = nextSource[nextKey]; + } + } + } + } + return to; + }, + writable: true, + configurable: true, + }); } // add utilities; borrowed from: https://scottaohara.github.io/a11y_tab_widget/ var util = { - keyCodes: { - UP: 38, - DOWN: 40, - LEFT: 37, - RIGHT: 39, - HOME: 36, - END: 35, - ENTER: 13, - SPACE: 32, - DELETE: 46, - TAB: 9 - }, - - generateID: function (base) { - return base + Math.floor(Math.random() * 999); - }, - - - getUrlHash: function () { - return window.location.hash.replace('#', ''); - }, - - /** - * Use history.replaceState so clicking through Tabs - * does not create dozens of new history entries. - * Browser back should navigate to the previous page - * regardless of how many Tabs were activated. - * - * @param {string} hash - */ - setUrlHash: function (hash) { - if (history.replaceState) { - history.replaceState(null, '', '#' + hash); - } else { - location.hash = hash; - } - } + keyCodes: { + UP: 38, + DOWN: 40, + LEFT: 37, + RIGHT: 39, + HOME: 36, + END: 35, + ENTER: 13, + SPACE: 32, + DELETE: 46, + TAB: 9, + }, + + generateID: function (base) { + return base + Math.floor(Math.random() * 999); + }, + + getUrlHash: function () { + return window.location.hash.replace("#", ""); + }, + + /** + * Use history.replaceState so clicking through Tabs + * does not create dozens of new history entries. + * Browser back should navigate to the previous page + * regardless of how many Tabs were activated. + * + * @param {string} hash + */ + setUrlHash: function (hash) { + if (history.replaceState) { + history.replaceState(null, "", "#" + hash); + } else { + location.hash = hash; + } + }, }; - - - (function (w, doc, undefined) { - - var ARIAaccOptions = { - manual: true, - open: 0 - } - - var ARIAtabs = function (inst, options) { - var _options = Object.assign(ARIAaccOptions, options); - var el = inst; - var tablist = el.querySelector("[data-tablist]"); - var tabs = Array.from(el.querySelectorAll("[data-tab]")); - var tabpanels = Array.from(el.querySelectorAll("[data-tabpanel]")); - var tabsID = util.generateID('ps__tabs-'); - var orientation = el.getAttribute('data-tabs-orientation'); - var currentIndex = _options.open; - var selectedTab = currentIndex; - var manual = _options.manual; - - el.setAttribute('id', tabsID); - - var init = function () { - el.classList.add('js-tabs'); - tablist.removeAttribute('hidden'); - setupTabList(); - setupTabs(); - setupTabPanels(); - }; - - var setupTabList = function () { - tablist.setAttribute("role", "tablist"); - if (orientation == 'vertical') tablist.setAttribute("aria-orientation", "vertical"); - } - - var setupTabs = function () { - - tabs.forEach((tab, index) => { - tab.setAttribute('role', 'tab'); - // each tab needs an ID that will be used to label its corresponding panel - tab.setAttribute('id', tabsID + '__tab-' + index); - tab.setAttribute('data-controls', tabpanels[index].getAttribute('id')); - - // first tab is initially active - if (index === currentIndex) { - selectTab(tab); - // updateUrlHash(); - } - - if (tab.getAttribute('data-controls') === util.getUrlHash()) { - currentIndex = index; - selectedTab = index; - selectTab(tab); - } - - tab.addEventListener('click', (e) => { - e.preventDefault(); - currentIndex = index; - selectedTab = index; - focusCurrentTab(); - selectTab(tab); - // updateUrlHash(); - }, false); - - tab.addEventListener('keydown', (e) => { - tabKeyboardRespond(e, tab); - }, false); - }); - } - - var focusCurrentTab = function () { - tabs[currentIndex].focus(); - } - - var updateUrlHash = function () { - var active = tabs[selectedTab]; - util.setUrlHash(active.getAttribute('data-controls')); - }; - - var selectTab = function (tab) { - // unactivate all other tabs - tabs.forEach(tab => { - tab.setAttribute('aria-selected', 'false'); - tab.setAttribute('tabindex', '-1'); - }); - //activate current tab - tab.setAttribute('aria-selected', 'true'); - tab.setAttribute('tabindex', '0'); - - // activate corresponding panel - showTabpanel(tab); - } - - var setupTabPanels = function () { - tabpanels.forEach((tabpanel, index) => { - tabpanel.setAttribute('role', 'tabpanel'); - tabpanel.setAttribute('tabindex', '-1'); - tabpanel.setAttribute('hidden', ''); - - if (index == currentIndex) { - tabpanel.removeAttribute('hidden'); - } - - tabpanel.addEventListener('keydown', (e) => { - panelKeyboardRespond(e); - }, false); - - tabpanel.addEventListener("blur", () => { - tabpanel.setAttribute('tabindex', '-1'); - }, false); - }); - } - - - var panelKeyboardRespond = function (e) { - var keyCode = e.keyCode || e.which; - - switch (keyCode) { - case util.keyCodes.TAB: - tabpanels[currentIndex].setAttribute('tabindex', '-1'); - break; - - default: - break; - } - } - - - var showTabpanel = function (tab) { - tabpanels.forEach((tabpanel, index) => { - tabpanel.setAttribute('hidden', ''); - tabpanel.removeAttribute('tabindex'); - - if (index == currentIndex) { - tabpanel.removeAttribute('hidden'); - tabpanel.setAttribute('aria-labelledby', tabs[currentIndex].getAttribute('id')); - tabpanel.setAttribute('tabindex', '0'); - } - }); - } - - var incrementcurrentIndex = function () { - if (currentIndex < tabs.length - 1) { - return ++currentIndex; - } - else { - currentIndex = 0; - return currentIndex; - } - }; - - - var decrementcurrentIndex = function () { - if (currentIndex > 0) { - return --currentIndex; - } - else { - currentIndex = tabs.length - 1; - return currentIndex; - } - }; - - - - var tabKeyboardRespond = function (e, tab) { - var firstTab = tabs[0]; - var lastTab = tabs[tabs.length - 1]; - - var keyCode = e.keyCode || e.which; - - switch (keyCode) { - case util.keyCodes.UP: - case util.keyCodes.LEFT: - e.preventDefault(); - decrementcurrentIndex(); - focusCurrentTab(); - - if (!manual) { - selectedTab = currentIndex; - selectTab(tabs[selectedTab]); - // updateUrlHash(); - } - - break; - - - case util.keyCodes.DOWN: - case util.keyCodes.RIGHT: - e.preventDefault(); - incrementcurrentIndex(); - focusCurrentTab(); - - if (!manual) { - selectedTab = currentIndex; - selectTab(tabs[selectedTab]); - // updateUrlHash(); - } - - break; - - - case util.keyCodes.ENTER: - case util.keyCodes.SPACE: - e.preventDefault(); - selectedTab = currentIndex; - selectTab(tabs[selectedTab]); - // updateUrlHash(); - - break; - - - case util.keyCodes.TAB: - tabpanels[selectedTab].setAttribute('tabindex', '0'); - currentIndex = selectedTab; - - break; - - - case util.keyCodes.HOME: - e.preventDefault(); - firstTab.focus(); - // updateUrlHash(); - - break; - - - case util.keyCodes.END: - e.preventDefault(); - lastTab.focus(); - // updateUrlHash(); - - break; - } - - } - - init.call(this); - return this; - }; // ARIAtabs() - - w.ARIAtabs = ARIAtabs; - + var ARIAaccOptions = { + manual: true, + open: 0, + }; + + var ARIAtabs = function (inst, options) { + var _options = Object.assign(ARIAaccOptions, options); + var el = inst; + var tablist = el.querySelector("[data-tablist]"); + var tabs = Array.from(el.querySelectorAll("[data-tab]")); + var tabpanels = Array.from(el.querySelectorAll("[data-tabpanel]")); + var tabsID = util.generateID("ps__tabs-"); + var orientation = el.getAttribute("data-tabs-orientation"); + var currentIndex = _options.open; + var selectedTab = currentIndex; + var manual = _options.manual; + + el.setAttribute("id", tabsID); + + var init = function () { + el.classList.add("js-tabs"); + tablist.removeAttribute("hidden"); + setupTabList(); + setupTabs(); + setupTabPanels(); + }; + + var setupTabList = function () { + tablist.setAttribute("role", "tablist"); + if (orientation == "vertical") + tablist.setAttribute("aria-orientation", "vertical"); + }; + + var setupTabs = function () { + tabs.forEach((tab, index) => { + tab.setAttribute("role", "tab"); + // each tab needs an ID that will be used to label its corresponding panel + tab.setAttribute("id", tabsID + "__tab-" + index); + tab.setAttribute( + "data-controls", + tabpanels[index].getAttribute("id"), + ); + + // first tab is initially active + if (index === currentIndex) { + selectTab(tab); + // updateUrlHash(); + } + + if (tab.getAttribute("data-controls") === util.getUrlHash()) { + currentIndex = index; + selectedTab = index; + selectTab(tab); + } + + tab.addEventListener( + "click", + e => { + e.preventDefault(); + currentIndex = index; + selectedTab = index; + focusCurrentTab(); + selectTab(tab); + // updateUrlHash(); + }, + false, + ); + + tab.addEventListener( + "keydown", + e => { + tabKeyboardRespond(e, tab); + }, + false, + ); + }); + }; + + var focusCurrentTab = function () { + tabs[currentIndex].focus(); + }; + + var updateUrlHash = function () { + var active = tabs[selectedTab]; + util.setUrlHash(active.getAttribute("data-controls")); + }; + + var selectTab = function (tab) { + // unactivate all other tabs + tabs.forEach(tab => { + tab.setAttribute("aria-selected", "false"); + tab.setAttribute("tabindex", "-1"); + }); + //activate current tab + tab.setAttribute("aria-selected", "true"); + tab.setAttribute("tabindex", "0"); + + // activate corresponding panel + showTabpanel(tab); + }; + + var setupTabPanels = function () { + tabpanels.forEach((tabpanel, index) => { + tabpanel.setAttribute("role", "tabpanel"); + tabpanel.setAttribute("tabindex", "-1"); + tabpanel.setAttribute("hidden", ""); + + if (index == currentIndex) { + tabpanel.removeAttribute("hidden"); + } + + tabpanel.addEventListener( + "keydown", + e => { + panelKeyboardRespond(e); + }, + false, + ); + + tabpanel.addEventListener( + "blur", + () => { + tabpanel.setAttribute("tabindex", "-1"); + }, + false, + ); + }); + }; + + var panelKeyboardRespond = function (e) { + var keyCode = e.keyCode || e.which; + + switch (keyCode) { + case util.keyCodes.TAB: + tabpanels[currentIndex].setAttribute("tabindex", "-1"); + break; + + default: + break; + } + }; + + var showTabpanel = function (tab) { + tabpanels.forEach((tabpanel, index) => { + tabpanel.setAttribute("hidden", ""); + tabpanel.removeAttribute("tabindex"); + + if (index == currentIndex) { + tabpanel.removeAttribute("hidden"); + tabpanel.setAttribute( + "aria-labelledby", + tabs[currentIndex].getAttribute("id"), + ); + tabpanel.setAttribute("tabindex", "0"); + } + }); + }; + + var incrementcurrentIndex = function () { + if (currentIndex < tabs.length - 1) { + return ++currentIndex; + } else { + currentIndex = 0; + return currentIndex; + } + }; + + var decrementcurrentIndex = function () { + if (currentIndex > 0) { + return --currentIndex; + } else { + currentIndex = tabs.length - 1; + return currentIndex; + } + }; + + var tabKeyboardRespond = function (e, tab) { + var firstTab = tabs[0]; + var lastTab = tabs[tabs.length - 1]; + + var keyCode = e.keyCode || e.which; + + switch (keyCode) { + case util.keyCodes.UP: + case util.keyCodes.LEFT: + e.preventDefault(); + decrementcurrentIndex(); + focusCurrentTab(); + + if (!manual) { + selectedTab = currentIndex; + selectTab(tabs[selectedTab]); + // updateUrlHash(); + } + + break; + + case util.keyCodes.DOWN: + case util.keyCodes.RIGHT: + e.preventDefault(); + incrementcurrentIndex(); + focusCurrentTab(); + + if (!manual) { + selectedTab = currentIndex; + selectTab(tabs[selectedTab]); + // updateUrlHash(); + } + + break; + + case util.keyCodes.ENTER: + case util.keyCodes.SPACE: + e.preventDefault(); + selectedTab = currentIndex; + selectTab(tabs[selectedTab]); + // updateUrlHash(); + + break; + + case util.keyCodes.TAB: + tabpanels[selectedTab].setAttribute("tabindex", "0"); + currentIndex = selectedTab; + + break; + + case util.keyCodes.HOME: + e.preventDefault(); + firstTab.focus(); + // updateUrlHash(); + + break; + + case util.keyCodes.END: + e.preventDefault(); + lastTab.focus(); + // updateUrlHash(); + + break; + } + }; + + init.call(this); + return this; + }; // ARIAtabs() + + w.ARIAtabs = ARIAtabs; })(window, document); - var tabsInstance = "[data-tabs]"; var els = document.querySelectorAll(tabsInstance); var allTabs = []; // Generate all tabs instances for (var i = 0; i < els.length; i++) { - var nTabs = new ARIAtabs(els[i], { manual: true }); // if manual is set to false, the tabs open on focus without needing an ENTER or SPACE press - allTabs.push(nTabs); + var nTabs = new ARIAtabs(els[i], { manual: true }); // if manual is set to false, the tabs open on focus without needing an ENTER or SPACE press + allTabs.push(nTabs); } diff --git a/docs/src/assets/js/themes.js b/docs/src/assets/js/themes.js index e6071b21983a..d63c9d4b4b6b 100644 --- a/docs/src/assets/js/themes.js +++ b/docs/src/assets/js/themes.js @@ -1,48 +1,78 @@ -/* theme toggle buttons */ -(function() { - var enableToggle = function(btn) { - btn.setAttribute("aria-pressed", "true"); - } - - var disableToggle = function(btn) { - btn.setAttribute("aria-pressed", "false"); - } - - document.addEventListener('DOMContentLoaded', function() { - var switcher = document.getElementById('js-theme-switcher'); - switcher.removeAttribute('hidden'); - - var light_theme_toggle = document.getElementById('light-theme-toggle'), - dark_theme_toggle = document.getElementById('dark-theme-toggle'); - - // get any previously-chosen themes - var theme = document.documentElement.getAttribute('data-theme'); - - if (theme == "light") { - enableToggle(light_theme_toggle); - disableToggle(dark_theme_toggle); - } else if (theme == "dark") { - enableToggle(dark_theme_toggle); - disableToggle(light_theme_toggle); - } - - light_theme_toggle.addEventListener("click", function() { - enableToggle(light_theme_toggle); - theme = this.getAttribute('data-theme'); - document.documentElement.setAttribute('data-theme', theme); - window.localStorage.setItem("theme", theme); - - disableToggle(dark_theme_toggle); - }, false); - - dark_theme_toggle.addEventListener("click", function() { - enableToggle(dark_theme_toggle); - theme = this.getAttribute('data-theme'); - document.documentElement.setAttribute('data-theme', theme); - window.localStorage.setItem("theme", theme); - - disableToggle(light_theme_toggle); - }, false); - }, false); +(function () { + var enableToggle = function (btn) { + btn.setAttribute("aria-pressed", "true"); + }; + var disableToggle = function (btns) { + btns.forEach(btn => btn.setAttribute("aria-pressed", "false")); + }; + + var setTheme = function (theme) { + if (theme === "system") { + var systemTheme = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light"; + document.documentElement.setAttribute("data-theme", systemTheme); + } else { + document.documentElement.setAttribute("data-theme", theme); + } + window.localStorage.setItem("theme", theme); + }; + + var initializeThemeSwitcher = function () { + var theme = window.localStorage.getItem("theme") || "system"; + var switcher = document.getElementById("js-theme-switcher"); + switcher.removeAttribute("hidden"); + + var lightThemeToggle = document.getElementById("light-theme-toggle"); + var darkThemeToggle = document.getElementById("dark-theme-toggle"); + var systemThemeToggle = document.getElementById("system-theme-toggle"); + + var toggleButtons = [ + lightThemeToggle, + darkThemeToggle, + systemThemeToggle, + ]; + + toggleButtons.forEach(function (btn) { + btn.addEventListener("click", function () { + enableToggle(btn); + var theme = this.getAttribute("data-theme"); + setTheme(theme); + if (btn === systemThemeToggle) { + disableToggle([lightThemeToggle, darkThemeToggle]); + } else if (btn === lightThemeToggle) { + disableToggle([systemThemeToggle, darkThemeToggle]); + } else if (btn === darkThemeToggle) { + disableToggle([systemThemeToggle, lightThemeToggle]); + } + }); + }); + + if (theme === "system") { + enableToggle(systemThemeToggle); + disableToggle([lightThemeToggle, darkThemeToggle]); + } else if (theme === "light") { + enableToggle(lightThemeToggle); + disableToggle([systemThemeToggle, darkThemeToggle]); + } else if (theme === "dark") { + enableToggle(darkThemeToggle); + disableToggle([systemThemeToggle, lightThemeToggle]); + } + + // Update theme on system preference change + window + .matchMedia("(prefers-color-scheme: dark)") + .addEventListener("change", function () { + var currentTheme = window.localStorage.getItem("theme"); + if (currentTheme === "system" || !currentTheme) { + enableToggle(systemThemeToggle); + disableToggle([lightThemeToggle, darkThemeToggle]); + setTheme("system"); + } + }); + }; + + document.addEventListener("DOMContentLoaded", initializeThemeSwitcher); })(); diff --git a/docs/src/assets/scss/ads.scss b/docs/src/assets/scss/ads.scss new file mode 100644 index 000000000000..cc4ed68030c5 --- /dev/null +++ b/docs/src/assets/scss/ads.scss @@ -0,0 +1,154 @@ +.hero-ad { + @media all and (max-width: 800px) { + display: none; + } +} + +.docs-ad { + height: 290px; +} + +/* + * Carbon Ads + * https://www.carbonads.net/ + */ + +#carbonads * { + margin: initial; + padding: initial; +} + +#carbonads { + display: inline-block; + margin: 2rem 0; + padding: 0.6em; + font-size: 1rem; + overflow: hidden; + background-color: var(--body-background-color); + border: 1px solid var(--border-color); + border-radius: 4px; + border-radius: var(--border-radius); + box-shadow: 0 1px 4px 1px hsla(0, 0%, 0%, 0.1); + + .docs-main & { + margin: 0 0 2rem; + } + + @media all and (max-width: 800px) { + display: none !important; + } +} + +.jumbotron #carbonads { + border: solid 1px hsla(250, 20%, 50%, 0.6); + background-color: hsla(0, 0%, 70%, 0.15); +} + +#carbonads a { + font-weight: 500; + color: inherit; + text-decoration: none; +} + +#carbonads a:hover { + text-decoration: none; + color: var(--link-color); +} + +.jumbotron #carbonads a { + color: #eee; +} + +.jumbotron #carbonads a:hover { + color: #ccc; +} + +#carbonads span { + display: block; + position: relative; + overflow: hidden; +} + +#carbonads .carbon-wrap { + display: flex; + flex-direction: column; + max-width: 130px; +} + +#carbonads .carbon-img img { + display: block; +} + +#carbonads .carbon-text { + margin-top: 10px; + line-height: 1rem; + font-size: 0.7em; + font-weight: 500; + text-align: left; +} + +#carbonads .carbon-poweredby { + display: block; + margin-top: 10px; + font-size: 0.5rem; + font-weight: 500; + line-height: 1; + letter-spacing: 0.1ch; + text-transform: uppercase; +} + +@media only screen and (min-width: 320px) and (max-width: 759px) { + #carbonads { + margin-top: 0; + font-size: 12px; + } + + #carbonads .carbon-wrap { + display: flex; + flex-direction: row; + max-width: 330px; + } + + #carbonads .carbon-text { + margin: 0 0 14px 10px; + font-size: 14px; + text-align: left; + } + + #carbonads .carbon-poweredby { + position: absolute; + bottom: 0; + left: 142px; + font-size: 8px; + } +} + +/* + * Ethical Ads + */ + +[data-ea-publisher].loaded .ea-content, +[data-ea-type].loaded .ea-content { + background-color: var(--body-background-color) !important; + border: 1px solid var(--border-color) !important; +} + +[data-ea-publisher].loaded .ea-content a:link, +[data-ea-type].loaded .ea-content a:link { + color: var(--body-text-color) !important; +} + +[data-ea-publisher].loaded .ea-callout a:link, +[data-ea-type].loaded .ea-callout a:link { + color: var(--body-text-color) !important; +} + +.jumbotron [data-ea-publisher].loaded .ea-content a, +.jumbotron [data-ea-type].loaded .ea-content a { + color: #eee; +} + +.jumbotron [data-ea-publisher].loaded .ea-content a:hover, +.jumbotron [data-ea-type].loaded .ea-content a:hover { + color: #ccc; +} diff --git a/docs/src/assets/scss/carbon-ads.scss b/docs/src/assets/scss/carbon-ads.scss deleted file mode 100644 index bd7ea8e660cb..000000000000 --- a/docs/src/assets/scss/carbon-ads.scss +++ /dev/null @@ -1,115 +0,0 @@ -.hero-ad { - @media all and (max-width: 800px) { - display: none; - } -} - -#carbonads * { - margin: initial; - padding: initial; -} - -#carbonads { - display: inline-block; - margin: 2rem 0; - padding: .6em; - font-size: 1rem; - overflow: hidden; - background-color: var(--body-background-color); - border: 1px solid var(--border-color); - border-radius: 4px; - border-radius: var(--border-radius); - box-shadow: 0 1px 4px 1px hsla(0, 0%, 0%, 0.1); - - .docs-main & { - margin: 0 0 2rem; - } - - @media all and (max-width: 800px) { - display: none !important; - } -} - -.jumbotron #carbonads { - border: solid 1px hsla(250, 20%, 50%, 0.6); - background-color: hsla(0, 0%, 70%, 0.15); -} - -#carbonads a { - font-weight: 500; - color: inherit; - text-decoration: none; -} - -#carbonads a:hover { - text-decoration: none; - color: var(--link-color); -} - -.jumbotron #carbonads a { - color: #eee; -} - -.jumbotron #carbonads a:hover { - color: #ccc; -} - -#carbonads span { - display: block; - position: relative; - overflow: hidden; -} - -#carbonads .carbon-wrap { - display: flex; - flex-direction: column; - max-width: 130px; -} - -#carbonads .carbon-img img { - display: block; -} - -#carbonads .carbon-text { - margin-top: 10px; - line-height: 1rem; - font-size: .7em; - font-weight: 500; - text-align: left; -} - -#carbonads .carbon-poweredby { - display: block; - margin-top: 10px; - font-size: 0.5rem; - font-weight: 500; - line-height: 1; - letter-spacing: .1ch; - text-transform: uppercase; -} - -@media only screen and (min-width: 320px) and (max-width: 759px) { - #carbonads { - margin-top: 0; - font-size: 12px; - } - - #carbonads .carbon-wrap { - display: flex; - flex-direction: row; - max-width: 330px; - } - - #carbonads .carbon-text { - margin: 0 0 14px 10px; - font-size: 14px; - text-align: left; - } - - #carbonads .carbon-poweredby { - position: absolute; - bottom: 0; - left: 142px; - font-size: 8px; - } -} diff --git a/docs/src/assets/scss/components/alert.scss b/docs/src/assets/scss/components/alert.scss index 8235c1429c90..1f61eeec3d68 100644 --- a/docs/src/assets/scss/components/alert.scss +++ b/docs/src/assets/scss/components/alert.scss @@ -1,119 +1,89 @@ .alert { - position: relative; - display: grid; - grid-template-columns: auto 1fr; - padding: 1rem; - gap: .75rem; - margin-bottom: 1.5rem; - margin-block-end: 1.5rem; - align-items: start; - font-size: .875rem; - border: 1px solid currentColor; - border-radius: var(--border-radius); - - &.alert--warning { - background-color: var(--color-rose-25); - color: var(--color-rose-600); - - [data-theme="dark"] & { - border: 1px solid var(--color-rose-300); - color: var(--color-rose-300); - background-color: var(--color-rose-900); - } - } - - &.alert--important { - background-color: var(--color-warning-25); - color: var(--color-warning-600); - - [data-theme="dark"] & { - color: var(--color-warning-300); - border: 1px solid var(--color-warning-300); - background-color: var(--color-warning-900); - } - } - - &.alert--tip { - background-color: var(--color-success-25); - color: var(--color-success-600); - - [data-theme="dark"] & { - color: var(--color-success-300); - border: 1px solid var(--color-success-300); - background-color: var(--color-success-900); - } - } + position: relative; + display: grid; + grid-template-columns: auto 1fr; + padding: 1rem; + gap: 0.75rem; + margin-bottom: 1.5rem; + margin-block-end: 1.5rem; + align-items: start; + font-size: 0.875rem; + border: 1px solid currentColor; + border-radius: var(--border-radius); + + &.alert--warning { + background-color: var(--alert-warning-background-color); + color: var(--alert-warning-color); + } + + &.alert--important { + background-color: var(--alert-important-background-color); + color: var(--alert-important-color); + } + + &.alert--tip { + background-color: var(--alert-tip-background-color); + color: var(--alert-tip-color); + } } .alert__icon { - color: inherit; - position: relative; - top: 2px; - offset-block-start: 2px; + color: inherit; + position: relative; + top: 2px; + offset-block-start: 2px; } .alert__text > p { - margin: 0; + margin: 0; } .alert__type { - display: block; - font-weight: 500; - margin-bottom: .25rem; - margin-block-end: .25rem; - - .alert--warning & { - color: var(--color-rose-700); - - [data-theme="dark"] & { - color: var(--color-rose-200); - } - } - - .alert--important & { - color: var(--color-warning-700); - - [data-theme="dark"] & { - color: var(--color-warning-200); - } - } - - .alert--tip & { - color: var(--color-success-700); - - [data-theme="dark"] & { - color: var(--color-success-200); - } - } + display: block; + font-weight: 500; + margin-bottom: 0.25rem; + margin-block-end: 0.25rem; + + .alert--warning & { + color: var(--alert-warning-heading-color); + } + + .alert--important & { + color: var(--alert-important-heading-color); + } + + .alert--tip & { + color: var(--alert-tip-heading-color); + } } .alert__learn-more { - display: block; - font-weight: 500; - margin-top: .75rem; - margin-block-start: .75rem; - - .alert--warning & { - color: var(--color-rose-700); - - [data-theme="dark"] & { - color: var(--color-rose-200); - } - } - - .alert--important & { - color: var(--color-warning-700); - - [data-theme="dark"] & { - color: var(--color-warning-200); - } - } - - .alert--tip & { - color: var(--color-success-700); - - [data-theme="dark"] & { - color: var(--color-success-200); - } - } + display: block; + font-weight: 500; + margin-top: 0.75rem; + margin-block-start: 0.75rem; + + .alert--warning & { + color: var(--color-rose-700); + + [data-theme="dark"] & { + color: var(--color-rose-200); + } + } + + .alert--important & { + color: var(--color-warning-700); + + [data-theme="dark"] & { + color: var(--color-warning-200); + } + } + + .alert--tip & { + color: var(--color-success-700); + + [data-theme="dark"] & { + color: var(--color-success-200); + } + } } diff --git a/docs/src/assets/scss/components/buttons.scss b/docs/src/assets/scss/components/buttons.scss index ca0aa72a726c..f081cf87c632 100644 --- a/docs/src/assets/scss/components/buttons.scss +++ b/docs/src/assets/scss/components/buttons.scss @@ -1,77 +1,79 @@ button { - border: none; - background: none; - font: inherit; - cursor: pointer; - line-height: inherit; - display: inline-flex; - align-items: center; - justify-content: center; + border: none; + background: none; + font: inherit; + cursor: pointer; + line-height: inherit; + display: inline-flex; + align-items: center; + justify-content: center; } .c-btn { - background: none; - border: none; - font: inherit; - font-family: var(--text-font); - cursor: pointer; - line-height: inherit; - font-weight: 500; - font-size: var(--step-0); - display: inline-flex; - padding: .75em 1.125em; - align-items: center; - justify-content: center; - border-radius: var(--border-radius); - transition: background-color .2s linear, border-color .2s linear; + background: none; + border: none; + font: inherit; + font-family: var(--text-font); + cursor: pointer; + line-height: inherit; + font-weight: 500; + font-size: var(--step-0); + display: inline-flex; + padding: 0.75em 1.125em; + align-items: center; + justify-content: center; + border-radius: var(--border-radius); + transition: + background-color 0.2s linear, + border-color 0.2s linear; - svg { - color: inherit; - } + svg { + color: inherit; + } } .c-btn--large { - font-size: 1.125rem; - padding: .88em 1.5em; + font-size: 1.125rem; + padding: 0.88em 1.5em; } .c-btn--block { - display: flex; - width: 100%; + display: flex; + width: 100%; } a.c-btn { - text-decoration: none; - display: inline-flex; - flex-wrap: wrap; - gap: .5rem; - align-items: center; + text-decoration: none; + display: inline-flex; + flex-wrap: wrap; + gap: 0.5rem; + align-items: center; } .c-btn--primary { - background-color: var(--primary-button-background-color); - color: var(--primary-button-text-color); + background-color: var(--primary-button-background-color); + color: var(--primary-button-text-color); - &:hover { - background-color: var(--primary-button-hover-color); - } + &:hover { + background-color: var(--primary-button-hover-color); + } } .c-btn--secondary { - background-color: var(--secondary-button-background-color); - color: var(--secondary-button-text-color); - box-shadow: 0 1px 2px rgba(16, 24, 40, 0.1); + background-color: var(--secondary-button-background-color); + color: var(--secondary-button-text-color); + box-shadow: 0 1px 2px rgba(16, 24, 40, 0.1); - &:hover { - background-color: var(--secondary-button-hover-color); - } + &:hover { + background-color: var(--secondary-button-hover-color); + } } .c-btn--ghost { - color: var(--body-text-color); - border: 1px solid var(--border-color); + color: var(--body-text-color); + border: 1px solid var(--border-color); - &:hover { - border-color: var(--link-color); - } + &:hover { + border-color: var(--link-color); + } } diff --git a/docs/src/assets/scss/components/docs-index.scss b/docs/src/assets/scss/components/docs-index.scss index 22e156eb39d8..0e0abbe4d911 100644 --- a/docs/src/assets/scss/components/docs-index.scss +++ b/docs/src/assets/scss/components/docs-index.scss @@ -1,164 +1,164 @@ .docs-index .docs-index__list { - a { - border-radius: var(--border-radius); - text-decoration: none; - display: flex; - justify-content: space-between; - align-items: center; - padding: .5rem .75rem; - margin-left: -.75rem; - margin-inline-start: -.75rem; - color: var(--headings-color); - - &:hover, - &[aria-current="true"] { - background-color: var(--docs-lightest-background-color); - color: var(--link-color); - } - - @media all and (max-width: 1023px) { - padding: .5rem 1rem; - margin-left: 0; - margin-inline-start: 0; - } - } + a { + border-radius: var(--border-radius); + text-decoration: none; + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0.75rem; + margin-left: -0.75rem; + margin-inline-start: -0.75rem; + color: var(--headings-color); + + &:hover, + &[aria-current="true"] { + background-color: var(--docs-lightest-background-color); + color: var(--link-color); + } + + @media all and (max-width: 1023px) { + padding: 0.5rem 1rem; + margin-left: 0; + margin-inline-start: 0; + } + } } .docs-index__item { - margin: 0; + margin: 0; - ul ul { - padding-left: .75rem; - } + ul ul { + padding-left: 0.75rem; + } - &[data-has-children] { - margin-bottom: .5rem; - } + &[data-has-children] { + margin-bottom: 0.5rem; + } } .docs-index__list > .docs-index__item { - margin-top: 1.5rem; - margin-block-start: 1.5rem; - - > a { - color: var(--icon-color); - text-transform: uppercase; - letter-spacing: 1px; - font-size: .875rem; - font-weight: 500; - } + margin-top: 1.5rem; + margin-block-start: 1.5rem; + + > a { + color: var(--icon-color); + text-transform: uppercase; + letter-spacing: 1px; + font-size: 0.875rem; + font-weight: 500; + } } /* Styles for the accordion icon */ .index-js .index-icon { - display: block !important; - width: 0.75rem; - height: 0.5rem; - transform-origin: 50% 50%; - transition: all 0.1s linear; - color: inherit; + display: block !important; + width: 0.75rem; + height: 0.5rem; + transform-origin: 50% 50%; + transition: all 0.1s linear; + color: inherit; } .index-js [aria-expanded="true"] .index-icon { - transform: rotate(180deg); + transform: rotate(180deg); } .index-js ul[aria-hidden="true"] { - display: none; + display: none; } .index-js ul[aria-hidden="false"] { - display: block; + display: block; } .docs__index__panel { - &[data-open="false"] { - display: none; + &[data-open="false"] { + display: none; - @media all and (min-width: 1024px) { - display: block; - } - } + @media all and (min-width: 1024px) { + display: block; + } + } - &[data-open="true"] { - display: block; + &[data-open="true"] { + display: block; - @media all and (min-width: 1024px) { - display: block; - } - } + @media all and (min-width: 1024px) { + display: block; + } + } } .docs-index-toggle { - cursor: pointer; - display: flex; - width: 100%; - padding: .75rem 1.125rem; - align-items: center; - justify-content: space-between; - gap: .5rem; - font-weight: 500; - border: 1px solid var(--border-color); - border-radius: var(--border-radius); - background-color: var(--secondary-button-background-color); - color: var(--secondary-button-text-color); - box-shadow: 0 1px 2px rgba(16, 24, 40, 0.1); - - &:hover { - background-color: var(--secondary-button-hover-color); - } - - @media all and (min-width: 1024px) { - display: none; - } - - svg { - width: 1.5em; - height: 1.5em; - color: inherit; - fill: none; - stroke-width: 4; - stroke-linecap: round; - stroke-linejoin: round; - } - - #ham-top, - #ham-middle, - #ham-bottom { - transition: all .2s linear; - } - - #ham-top { - transform-origin: 30px 37px; - } - - #ham-bottom { - transform-origin: 30px 63px; - } - - &[aria-expanded="true"] { - #ham-middle { - opacity: 0; - } - - #ham-top { - transform: rotate(41deg); - } - - #ham-bottom { - transform: rotate(-41deg); - } - } + cursor: pointer; + display: flex; + width: 100%; + padding: 0.75rem 1.125rem; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + font-weight: 500; + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + background-color: var(--secondary-button-background-color); + color: var(--secondary-button-text-color); + box-shadow: 0 1px 2px rgba(16, 24, 40, 0.1); + + &:hover { + background-color: var(--secondary-button-hover-color); + } + + @media all and (min-width: 1024px) { + display: none; + } + + svg { + width: 1.5em; + height: 1.5em; + color: inherit; + fill: none; + stroke-width: 4; + stroke-linecap: round; + stroke-linejoin: round; + } + + #ham-top, + #ham-middle, + #ham-bottom { + transition: all 0.2s linear; + } + + #ham-top { + transform-origin: 30px 37px; + } + + #ham-bottom { + transform-origin: 30px 63px; + } + + &[aria-expanded="true"] { + #ham-middle { + opacity: 0; + } + + #ham-top { + transform: rotate(41deg); + } + + #ham-bottom { + transform: rotate(-41deg); + } + } } .eslint-actions { - display: inline-flex; - flex-wrap: wrap; - flex-direction: column; - width: 100%; - gap: 1rem; - - @media all and (min-width: 640px) { - flex-direction: row; - } + display: inline-flex; + flex-wrap: wrap; + flex-direction: column; + width: 100%; + gap: 1rem; + + @media all and (min-width: 640px) { + flex-direction: row; + } } diff --git a/docs/src/assets/scss/components/docs-navigation.scss b/docs/src/assets/scss/components/docs-navigation.scss index f47fce3a0a50..900fa1a3eee5 100644 --- a/docs/src/assets/scss/components/docs-navigation.scss +++ b/docs/src/assets/scss/components/docs-navigation.scss @@ -1,147 +1,147 @@ .docs-site-nav { - display: flex; - flex-direction: column; - flex: 1; - grid-column: 1 / -1; - grid-row: 1; - - ul { - list-style: none; - font-size: var(--step-1); - margin-top: 1rem; - margin-block-start: 1rem; - margin-bottom: 2rem; - margin-block-end: 2rem; - - @media all and (min-width: 1024px) { - font-size: var(--step-0); - margin-top: 0; - margin-block-start: 0; - margin-bottom: 0; - margin-block-end: 0; - align-items: center; - display: flex; - } - } - - .flexer { - display: flex; - justify-self: flex-end; - align-self: flex-end; - } - - a:not(.c-btn) { - text-decoration: none; - color: inherit; - transition: color .2s linear; - display: block; - - &:hover { - color: var(--link-color); - } - } - - a:not(.c-btn)[aria-current="page"], - a:not(.c-btn)[aria-current="true"] { - color: var(--link-color); - text-decoration: none; - font-weight: 500; - } + display: flex; + flex-direction: column; + flex: 1; + grid-column: 1 / -1; + grid-row: 1; + + ul { + list-style: none; + font-size: var(--step-1); + margin-top: 1rem; + margin-block-start: 1rem; + margin-bottom: 2rem; + margin-block-end: 2rem; + + @media all and (min-width: 1024px) { + font-size: var(--step-0); + margin-top: 0; + margin-block-start: 0; + margin-bottom: 0; + margin-block-end: 0; + align-items: center; + display: flex; + } + } + + .flexer { + display: flex; + justify-self: flex-end; + align-self: flex-end; + } + + a:not(.c-btn) { + text-decoration: none; + color: inherit; + transition: color 0.2s linear; + display: block; + + &:hover { + color: var(--link-color); + } + } + + a:not(.c-btn)[aria-current="page"], + a:not(.c-btn)[aria-current="true"] { + color: var(--link-color); + text-decoration: none; + font-weight: 500; + } } .docs-nav-panel { - @media all and (min-width: 1024px) { - display: flex; - flex-direction: row; - justify-content: center; - } - - &[data-open="false"] { - display: none; - } - - &[data-open="true"] { - @media all and (min-width: 1024px) { - display: flex; - flex-direction: row; - justify-content: center; - } - } + @media all and (min-width: 1024px) { + display: flex; + flex-direction: row; + justify-content: center; + } + + &[data-open="false"] { + display: none; + } + + &[data-open="true"] { + @media all and (min-width: 1024px) { + display: flex; + flex-direction: row; + justify-content: center; + } + } } .docs-nav-panel .mobile-only { - @media all and (min-width: 1024px) { - display: none; - } + @media all and (min-width: 1024px) { + display: none; + } } .docs-site-nav-toggle { - cursor: pointer; - display: inline-flex; - align-items: center; - margin-left: .5rem; - margin-right: -10px; - margin-inline-start: .5rem; - margin-inline-end: -10px; - - svg { - width: 40px; - height: 40px; - color: var(--headings-color); - fill: none; - stroke-width: 4; - stroke-linecap: round; - stroke-linejoin: round; - } - - #ham-top, - #ham-middle, - #ham-bottom { - transition: all .2s linear; - } - - #ham-top { - transform-origin: 30px 37px; - } - - #ham-bottom { - transform-origin: 30px 63px; - } - - &[aria-expanded="true"] { - #ham-middle { - opacity: 0; - } - - #ham-top { - transform: rotate(41deg); - } - - #ham-bottom { - transform: rotate(-41deg); - } - } + cursor: pointer; + display: inline-flex; + align-items: center; + margin-left: 0.5rem; + margin-right: -10px; + margin-inline-start: 0.5rem; + margin-inline-end: -10px; + + svg { + width: 40px; + height: 40px; + color: var(--headings-color); + fill: none; + stroke-width: 4; + stroke-linecap: round; + stroke-linejoin: round; + } + + #ham-top, + #ham-middle, + #ham-bottom { + transition: all 0.2s linear; + } + + #ham-top { + transform-origin: 30px 37px; + } + + #ham-bottom { + transform-origin: 30px 63px; + } + + &[aria-expanded="true"] { + #ham-middle { + opacity: 0; + } + + #ham-top { + transform: rotate(41deg); + } + + #ham-bottom { + transform: rotate(-41deg); + } + } } @media all and (min-width: 1024px) { - .docs-site-nav { - flex-direction: row; - grid-column: auto; - gap: 2rem; - - ul { - display: flex; - gap: 2rem; - font-size: var(--step-0); - - li { - margin-bottom: 0; - margin-block-end: 0; - } - } - - .flexer { - order: 1; - } - } + .docs-site-nav { + flex-direction: row; + grid-column: auto; + gap: 2rem; + + ul { + display: flex; + gap: 2rem; + font-size: var(--step-0); + + li { + margin-bottom: 0; + margin-block-end: 0; + } + } + + .flexer { + order: 1; + } + } } diff --git a/docs/src/assets/scss/components/hero.scss b/docs/src/assets/scss/components/hero.scss index 44a7390e0270..5a24c7348573 100644 --- a/docs/src/assets/scss/components/hero.scss +++ b/docs/src/assets/scss/components/hero.scss @@ -1,64 +1,64 @@ .hero .grid { - @media all and (min-width: 800px) { - display: grid; - grid-template-columns: 2fr 1fr; - grid-gap: 2rem; - align-items: center; - } + @media all and (min-width: 800px) { + display: grid; + grid-template-columns: 2fr 1fr; + grid-gap: 2rem; + align-items: center; + } - .span-1-7 { - grid-column: 1 / 2; - } + .span-1-7 { + grid-column: 1 / 2; + } - .span-10-12 { - grid-column: 2 / 3; - justify-self: end; - } + .span-10-12 { + grid-column: 2 / 3; + justify-self: end; + } } .hero { - border-bottom: 1px solid var(--divider-color); - border-block-end: 1px solid var(--divider-color); - background-color: var(--hero-background-color); + border-bottom: 1px solid var(--divider-color); + border-block-end: 1px solid var(--divider-color); + background-color: var(--hero-background-color); - @media all and (min-width: 800px) { - // when the ad is displayed - min-height: calc(285px + var(--space-xl-4xl)); - } + @media all and (min-width: 800px) { + // when the ad is displayed + min-height: calc(285px + var(--space-xl-4xl)); + } - .content-container { - padding: var(--space-xl-4xl) 0; - margin: 0; - } + .content-container { + padding: var(--space-xl-4xl) 0; + margin: 0; + } - >.content-container { - margin: 0 auto; - padding: 0 calc(1rem + 1vw); - padding-bottom: 0; - align-items: center; - max-width: 1700px; + > .content-container { + margin: 0 auto; + padding: 0 calc(1rem + 1vw); + padding-bottom: 0; + align-items: center; + max-width: 1700px; - @media all and (min-width: 1700px) { - margin: auto; - } - } + @media all and (min-width: 1700px) { + margin: auto; + } + } } .hero--homepage { - .section-title { - margin-bottom: 1.5rem; - margin-block-end: 1.5rem; - } + .section-title { + margin-bottom: 1.5rem; + margin-block-end: 1.5rem; + } - .section-supporting-text { - margin: 0; - font-size: var(--step-1); - text-align: left; - } + .section-supporting-text { + margin: 0; + font-size: var(--step-1); + text-align: left; + } - .eslint-actions { - font-size: var(--step-1); - margin-top: 3rem; - margin-block-start: 3rem; - } + .eslint-actions { + font-size: var(--step-1); + margin-top: 3rem; + margin-block-start: 3rem; + } } diff --git a/docs/src/assets/scss/components/index.scss b/docs/src/assets/scss/components/index.scss index 5989e1f48e7a..e009644ac587 100644 --- a/docs/src/assets/scss/components/index.scss +++ b/docs/src/assets/scss/components/index.scss @@ -1,109 +1,109 @@ .index { - margin-bottom: 4rem; - margin-block-end: 4rem; + margin-bottom: 4rem; + margin-block-end: 4rem; } .index__item { - margin: 0; - - a { - display: block; - color: inherit; - text-decoration: none; - padding: .625rem .875rem; - font-size: var(--step-0); - border-radius: var(--border-radius); - - &:hover { - color: var(--link-color); - } - } - - a[aria-current="page"] { - color: var(--link-color); - background-color: var(--lightest-background-color); - font-weight: 500; - } + margin: 0; + + a { + display: block; + color: inherit; + text-decoration: none; + padding: 0.625rem 0.875rem; + font-size: var(--step-0); + border-radius: var(--border-radius); + + &:hover { + color: var(--link-color); + } + } + + a[aria-current="page"] { + color: var(--link-color); + background-color: var(--lightest-background-color); + font-weight: 500; + } } .index__toggle { - cursor: pointer; - display: flex; - width: 100%; - padding: .75rem 1.125rem; - align-items: center; - justify-content: space-between; - gap: .5rem; - font-weight: 500; - border: 1px solid var(--border-color); - border-radius: var(--border-radius); - background-color: var(--secondary-button-background-color); - color: var(--secondary-button-text-color); - box-shadow: 0 1px 2px rgba(16, 24, 40, 0.1); - - &:hover { - background-color: var(--secondary-button-hover-color); - } - - @media all and (min-width: 1024px) { - display: none; - } - - svg { - width: 1.5em; - height: 1.5em; - color: inherit; - fill: none; - stroke-width: 4; - stroke-linecap: round; - stroke-linejoin: round; - } - - #ham-top, - #ham-middle, - #ham-bottom { - transition: all .2s linear; - } - - #ham-top { - transform-origin: 30px 37px; - } - - #ham-bottom { - transform-origin: 30px 63px; - } - - &[aria-expanded="true"] { - #ham-middle { - opacity: 0; - } - - #ham-top { - transform: rotate(41deg); - } - - #ham-bottom { - transform: rotate(-41deg); - } - } + cursor: pointer; + display: flex; + width: 100%; + padding: 0.75rem 1.125rem; + align-items: center; + justify-content: space-between; + gap: 0.5rem; + font-weight: 500; + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + background-color: var(--secondary-button-background-color); + color: var(--secondary-button-text-color); + box-shadow: 0 1px 2px rgba(16, 24, 40, 0.1); + + &:hover { + background-color: var(--secondary-button-hover-color); + } + + @media all and (min-width: 1024px) { + display: none; + } + + svg { + width: 1.5em; + height: 1.5em; + color: inherit; + fill: none; + stroke-width: 4; + stroke-linecap: round; + stroke-linejoin: round; + } + + #ham-top, + #ham-middle, + #ham-bottom { + transition: all 0.2s linear; + } + + #ham-top { + transform-origin: 30px 37px; + } + + #ham-bottom { + transform-origin: 30px 63px; + } + + &[aria-expanded="true"] { + #ham-middle { + opacity: 0; + } + + #ham-top { + transform: rotate(41deg); + } + + #ham-bottom { + transform: rotate(-41deg); + } + } } .index__list { - display: block; + display: block; - &[data-open="false"] { - display: none; + &[data-open="false"] { + display: none; - @media all and (min-width: 1024px) { - display: block; - } - } + @media all and (min-width: 1024px) { + display: block; + } + } - &[data-open="true"] { - display: block; + &[data-open="true"] { + display: block; - @media all and (min-width: 1024px) { - display: block; - } - } + @media all and (min-width: 1024px) { + display: block; + } + } } diff --git a/docs/src/assets/scss/components/language-switcher.scss b/docs/src/assets/scss/components/language-switcher.scss index 364f23fed6cc..92c05f1e04ea 100644 --- a/docs/src/assets/scss/components/language-switcher.scss +++ b/docs/src/assets/scss/components/language-switcher.scss @@ -1,31 +1,31 @@ .switcher--language { - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - gap: .25rem .5rem; - position: relative; - width: 100%; - padding: 0; - font-size: inherit; + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + gap: 0.25rem 0.5rem; + position: relative; + width: 100%; + padding: 0; + font-size: inherit; - @media all and (min-width: 800px) { - justify-content: flex-start; - } + @media all and (min-width: 800px) { + justify-content: flex-start; + } } .switcher--language .label__text { - flex: 1 0 10ch; + flex: 1 0 10ch; } .switcher--language .switcher__select { - flex: 1 0 12rem; + flex: 1 0 12rem; - @media all and (max-width: 799px) { - max-width: 250px; - } + @media all and (max-width: 799px) { + max-width: 250px; + } } .language-switcher { - display: inline-flex; + display: inline-flex; } diff --git a/docs/src/assets/scss/components/logo.scss b/docs/src/assets/scss/components/logo.scss new file mode 100644 index 000000000000..351e6653dbe8 --- /dev/null +++ b/docs/src/assets/scss/components/logo.scss @@ -0,0 +1,12 @@ +.logo-component { + fill: var(--logo-color); +} + +.logo-title { + fill: var(--headings-color); +} + +#logo-center { + opacity: var(--logo-center-opacity); + fill: var(--logo-center-color); +} diff --git a/docs/src/assets/scss/components/resources.scss b/docs/src/assets/scss/components/resources.scss index 4ee2616d8db7..3a59d162c6b1 100644 --- a/docs/src/assets/scss/components/resources.scss +++ b/docs/src/assets/scss/components/resources.scss @@ -1,67 +1,68 @@ .resource { - display: flex; - border-radius: var(--border-radius); - border: 1px solid var(--divider-color); - background-color: var(--lightest-background-color); - align-items: stretch; - overflow: hidden; - margin-bottom: .5rem; - margin-block-end: .5rem; - position: relative; - transition: all .2s linear; + display: flex; + border-radius: var(--border-radius); + border: 1px solid var(--divider-color); + background-color: var(--lightest-background-color); + align-items: stretch; + overflow: hidden; + margin-bottom: 0.5rem; + margin-block-end: 0.5rem; + position: relative; + transition: all 0.2s linear; - &:hover { - background-color: var(--lighter-background-color); - } + &:hover { + background-color: var(--lighter-background-color); + } } .resource__image { - flex: 1 0 5.5rem; - max-width: 5.5rem; - overflow: hidden; - padding: .25rem; + flex: 1 0 5.5rem; + max-width: 5.5rem; + overflow: hidden; + padding: 0.25rem; - img { - display: block; - height: 100%; - width: 100%; - object-fit: contain; - } + img { + display: block; + height: 100%; + width: 100%; + object-fit: contain; + } } .resource__content { - flex: 4; - padding: .75rem; - align-self: center; + flex: 4; + padding: 0.75rem; + align-self: center; } -.resource__title { // a - text-decoration: none; - color: var(--headings-color); - font-weight: 500; - margin-bottom: .125rem; +.resource__title { + // a + text-decoration: none; + color: var(--headings-color); + font-weight: 500; + margin-bottom: 0.125rem; - &::after { - content: ""; - position: absolute; - left: 0; - offset-inline-start: 0; - top: 0; - offset-block-start: 0; - width: 100%; - height: 100%; - } + &::after { + content: ""; + position: absolute; + left: 0; + offset-inline-start: 0; + top: 0; + offset-block-start: 0; + width: 100%; + height: 100%; + } } .resource__domain, .resource__domain a { - text-decoration: none; - color: var(--body-text-color); - font-size: .875rem; + text-decoration: none; + color: var(--body-text-color); + font-size: 0.875rem; } .resource__icon { - color: var(--headings-color); - margin: 1rem; - align-self: center; + color: var(--headings-color); + margin: 1rem; + align-self: center; } diff --git a/docs/src/assets/scss/components/rules.scss b/docs/src/assets/scss/components/rules.scss index 4e0f4619c211..6754851337fc 100644 --- a/docs/src/assets/scss/components/rules.scss +++ b/docs/src/assets/scss/components/rules.scss @@ -1,201 +1,207 @@ .rule-categories { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 0; - margin-bottom: 3rem; - background-color: var(--lightest-background-color); - border: 1px solid var(--divider-color); - border-radius: var(--border-radius); - - .rule-category { - margin: 0; - padding: 1rem; - background: none; - border: none; - - @media screen and (min-width: 768px) { - &:not(:first-child)::after { - content: ""; - display: block; - padding: 1px; - border-left: 1px solid var(--divider-color); - left: 0; - } - } - - @media screen and (min-width: 768px) and (max-width: 1023px), screen and (min-width: 1440px) { - &:not(:first-child)::after { - height: 70%; - position: absolute; - } - } - - @media screen and (min-width: 1024px) and (max-width: 1439px) { - &:nth-child(2)::after { - height: 70%; - position: absolute; - } - } - } - - .rule-category__description { - flex: 1 1 45ch; - } + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 0; + margin-bottom: 3rem; + background-color: var(--lightest-background-color); + border: 1px solid var(--divider-color); + border-radius: var(--border-radius); + + .rule-category { + margin: 0; + padding: 1rem; + background: none; + border: none; + + @media screen and (min-width: 768px) { + &:not(:first-child)::after { + content: ""; + display: block; + border-left: 1px solid var(--divider-color); + left: 0; + } + + &:nth-child(2)::after { + height: 70%; + position: absolute; + } + } + + @media screen and (min-width: 768px) and (max-width: 799px), + screen and (min-width: 854px) and (max-width: 1023px), + screen and (min-width: 1256px) { + &:nth-child(3)::after { + height: 70%; + position: absolute; + } + } + + @media screen and (min-width: 800px) and (max-width: 853px), + screen and (min-width: 1024px) and (max-width: 1255px), + screen and (min-width: 1654px) and (max-width: 1982px) { + &:nth-child(4)::after { + height: 70%; + position: absolute; + } + } + } + + .rule-category__description { + height: 100%; + } } .rule-category { - font-size: var(--step--1); - display: flex; - position: relative; - flex-wrap: wrap; - align-items: flex-start; - gap: 1rem; - padding: 1rem; - margin: 1.5rem 0; - border-radius: var(--border-radius); - border: 1px solid var(--divider-color); - background-color: var(--lightest-background-color); - - p { - margin: 0; - } - - .rule-category__description { - flex: 1 1 30ch; - } + font-size: var(--step--1); + display: flex; + position: relative; + flex-wrap: wrap; + align-items: flex-start; + gap: 1rem; + padding: 1rem; + margin: 1.5rem 0; + border-radius: var(--border-radius); + border: 1px solid var(--divider-color); + background-color: var(--lightest-background-color); + + p { + margin: 0; + } } .rule:not(.token) { - border-radius: var(--border-radius); - background-color: var(--lightest-background-color); - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 1rem; - padding: 1rem; - margin: .5rem 0; - position: relative; - - p:last-of-type { - margin: 0; - } + border-radius: var(--border-radius); + background-color: var(--lightest-background-color); + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 1rem; + padding: 1rem; + margin: 0.5rem 0; + position: relative; + + p:last-of-type { + margin: 0; + } } -.rule--deprecated, -.rule--removed { - // opacity: .5; +.rule__content { + flex: 1 1 35ch; + overflow-x: auto; } -.rule__content { - flex: 1 1 35ch; +.rule__name_wrapper { + display: flex; + align-items: center; + gap: 0.5rem; + + .frozen { + font-size: 0.875rem; + } } .rule__name { - font-weight: 500; - font-size: .875rem; - margin-bottom: .25rem; - margin-block-end: .25rem; + font-weight: 500; + font-size: 0.875rem; + margin-bottom: 0.25rem; + margin-block-end: 0.25rem; } a.rule__name { - text-decoration: none; - - &:hover { - text-decoration: underline; - } - - &::after { - position: absolute; - content: ""; - width: 100%; - height: 100%; - top: 0; - offset-block-start: 0; - left: 0; - offset-inline-start: 0; - } + text-decoration: none; + + &:hover { + text-decoration: underline; + } + + &::after { + position: absolute; + content: ""; + width: 100%; + height: 100%; + top: 0; + offset-block-start: 0; + left: 0; + offset-inline-start: 0; + } } .rule__description { - font-size: var(--step--1); + font-size: var(--step--1); } .rule__categories { - font-size: .875rem; - display: flex; - align-items: center; - gap: 1rem; - border-radius: var(--border-radius); - padding: 2px 4px; - - p { - display: inline-flex; - margin: 0; - align-items: center; - } - - [data-theme="dark"] & { - background: var(--body-background-color); - } + font-size: 0.875rem; + display: flex; + align-items: center; + gap: 1rem; + border-radius: var(--border-radius); + padding: 2px 4px; + + p { + display: inline-flex; + margin: 0; + align-items: center; + } + + [data-theme="dark"] & { + background: var(--body-background-color); + } } .rule__status { - color: var(--color-rose-500); - background: var(--color-rose-50); - border-radius: var(--border-radius); - display: inline-block; - font-weight: normal; - margin-left: .5rem; - margin-inline-start: .5rem; - font-size: var(--step--1); - padding: 0 .5rem; - - [data-theme="dark"] & { - background: var(--body-background-color); - } + color: var(--color-rose-500); + background: var(--rule-status-background-color); + border-radius: var(--border-radius); + display: inline-block; + font-weight: normal; + margin-left: 0.5rem; + margin-inline-start: 0.5rem; + font-size: var(--step--1); + padding: 0 0.5rem; } .rule__categories__type { - &[aria-hidden="true"] { - opacity: .25; - } + &[aria-hidden="true"] { + opacity: 0.25; + } } /* related rules */ .related-rules__list { - display: flex; - gap: .5rem; - flex-wrap: wrap; - justify-content: start; + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + justify-content: start; } .related-rules__list__item { - svg { - color: inherit; - } - - a { - text-decoration: none; - color: var(--headings-color); - padding: .625rem; - display: inline-flex; - gap: .5rem; - align-items: center; - border: 1px solid var(--divider-color); - border-radius: var(--border-radius); - background-color: var(--lightest-background-color); - - &:hover { - color: var(--link-color); - background-color: var(--lighter-background-color); - } - } + svg { + color: inherit; + } + + a { + text-decoration: none; + color: var(--headings-color); + padding: 0.625rem; + display: inline-flex; + gap: 0.5rem; + align-items: center; + border: 1px solid var(--divider-color); + border-radius: var(--border-radius); + background-color: var(--lightest-background-color); + + &:hover { + color: var(--link-color); + background-color: var(--lighter-background-color); + } + } } a.rule-list-item + a.rule-list-item::before { - content: ","; - display: inline-block; - margin-left: 5px; - margin-right: 5px; + content: ","; + display: inline-block; + margin-left: 5px; + margin-right: 5px; } diff --git a/docs/src/assets/scss/components/search.scss b/docs/src/assets/scss/components/search.scss index 4b90582c4c6f..9f942657e70f 100644 --- a/docs/src/assets/scss/components/search.scss +++ b/docs/src/assets/scss/components/search.scss @@ -1,163 +1,184 @@ [type="search"]::-webkit-search-cancel-button, [type="search"]::-webkit-search-decoration { - appearance: none; + appearance: none; } [type="search"]::-ms-clear, [type="search"]::-ms-reveal { - display: none; - width: 0; - height: 0; + display: none; + width: 0; + height: 0; } .search { - margin: 1rem 0; - position: relative; + margin: 1rem 0; + position: relative; } .search__input-wrapper, .search__inner-input-wrapper { - position: relative; + position: relative; } .search__clear-btn { - color: var(--body-text-color); - position: absolute; - display: flex; - top: 50%; - offset-block-start: 50%; - transform: translateY(-50%); - right: 1.5rem; - offset-inline-end: 1.5rem; - z-index: 3; - padding: 0; - - svg { - color: inherit; - width: 1rem; - height: 1rem; - border: 1px solid; - border-radius: 50%; - } + color: var(--body-text-color); + position: absolute; + display: flex; + top: 25%; + offset-block-start: 25%; + transform: translateY(-25%); + right: 1rem; + offset-inline-end: 1.5rem; + z-index: 3; + padding: 0; + + svg { + color: inherit; + width: 1rem; + height: 1rem; + border: 1px solid; + border-radius: 50%; + } } .search__input { - padding-left: 2.5rem; - padding-inline-start: 2.5rem; - outline-offset: 1px; - width: 100%; + padding-left: 2.5rem; + padding-inline-start: 2.5rem; + outline-offset: 1px; + width: 100%; + padding-right: 2.5rem; + padding-inline-end: 2.5rem; } .search__icon { - color: var(--body-text-color); - position: absolute; - display: block; - top: 50%; - offset-block-start: 50%; - transform: translateY(-50%); - left: .75rem; - offset-inline-start: .75rem; - z-index: 3; + color: var(--body-text-color); + position: absolute; + display: block; + top: 0.75rem; + left: 0.75rem; + offset-inline-start: 0.75rem; + z-index: 3; +} + +.search__inner-input-wrapper { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +.search_powered-by-wrapper { + text-decoration: none; + + .search__powered-by { + display: flex; + padding: 10px 6px 0 0; + align-items: center; + + .powered_by-text { + color: var(--body-text-color); + margin-right: 5px; + margin-top: -2px; + } + + .algolia-logo { + fill: var(--body-text-color); + } + } } /* search results */ .search .search-results { - font-size: .875rem; - background-color: var(--body-background-color); - z-index: 10; - width: 100%; - border-radius: 0 0 var(--border-radius) var(--border-radius); - border: 1px solid var(--divider-color); - position: relative; - top: .25rem; - max-height: 400px; - overflow-y: auto; - - @media all and (min-width: 1024px) { - box-shadow: var(--shadow-lg); - position: absolute; - top: calc(100% + .25rem); - } - - &[data-results="true"] { - padding: 0; - } - - &[data-results="false"] { - padding: 1rem; - } - - &:empty { - display: none; - } + font-size: 0.875rem; + background-color: var(--body-background-color); + z-index: 10; + width: 100%; + border-radius: 0 0 var(--border-radius) var(--border-radius); + border: 1px solid var(--divider-color); + position: absolute; + top: calc(100% - 1.5rem); + max-height: 400px; + overflow-y: auto; + box-shadow: var(--shadow-lg); + + &[data-results="true"] { + padding: 0; + } + + &[data-results="false"] { + padding: 1rem; + } + + &:empty { + display: none; + } } .search-results__list { - list-style: none; - margin: 0; - padding: 0; + list-style: none; + margin: 0; + padding: 0; } .search .search-results__item { - margin: 0; - padding: .875rem; - border-bottom: 1px solid var(--lightest-background-color); - border-block-end: 1px solid var(--lightest-background-color); - position: relative; - - &:hover { - background-color: var(--lightest-background-color); - } - - &:focus-within { - background-color: var(--lightest-background-color); - } + margin: 0; + padding: 0.875rem; + border-bottom: 1px solid var(--lightest-background-color); + border-block-end: 1px solid var(--lightest-background-color); + position: relative; + + &:hover { + background-color: var(--lightest-background-color); + } + + &:focus-within { + background-color: var(--lightest-background-color); + } } .search .search-results__item__title { - font-size: var(--step-0); - font-size: .875rem; - margin-bottom: 0; - font-family: var(--text-font); - - a { - display: block; - text-decoration: none; - color: var(--link-color); - font: inherit; - padding: .25rem .75rem; - - &:hover { - background-color: inherit; - color: var(--link-color); - } - - &::after { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - content: ""; - } - } + font-size: var(--step-0); + font-size: 0.875rem; + margin-bottom: 0; + font-family: var(--text-font); + + a { + display: block; + text-decoration: none; + color: var(--link-color); + font: inherit; + padding: 0.25rem 0.75rem; + + &:hover { + background-color: inherit; + color: var(--link-color); + } + + &::after { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + content: ""; + } + } } .search-results__item__context { - margin: 0; - font-size: .875rem; - padding-left: 1rem; + margin: 0; + font-size: 0.875rem; + padding-left: 1rem; } .algolia-docsearch-suggestion--highlight { - background-color: var(--color-brand); - color: #fff; - display: inline-block; - padding: 0 2px; - border-radius: 2px; - - [data-theme="dark"] & { - background-color: var(--link-color); - color: var(--color-neutral-900); - } + background-color: var(--color-brand); + color: #fff; + display: inline-block; + padding: 0 2px; + border-radius: 2px; + + [data-theme="dark"] & { + background-color: var(--link-color); + color: var(--color-neutral-900); + } } diff --git a/docs/src/assets/scss/components/social-icons.scss b/docs/src/assets/scss/components/social-icons.scss index eddd47f3ec77..24048d5ce7b9 100644 --- a/docs/src/assets/scss/components/social-icons.scss +++ b/docs/src/assets/scss/components/social-icons.scss @@ -1,22 +1,26 @@ .eslint-social-icons { - margin-bottom: -1rem; - margin-block-end: -1rem; + margin-bottom: -1rem; + margin-block-end: -1rem; - ul { - margin: 0; - padding: 0; - margin-left: -1rem; - margin-inline-start: -1rem; - display: inline-flex; + ul { + margin: 0; + padding: 0; + margin-left: -1rem; + margin-inline-start: -1rem; + display: inline-flex; - li { - margin: 0; - align-items: center; + li { + margin: 0; + align-items: center; - a { - display: flex; - padding: 1rem .75rem; - } - } - } + a { + display: flex; + padding: 1rem 0.75rem; + } + } + + @media screen and (min-width: 800px) and (max-width: 1500px) { + flex-wrap: wrap; + } + } } diff --git a/docs/src/assets/scss/components/tabs.scss b/docs/src/assets/scss/components/tabs.scss index 8a7d866c514c..67cdfc89be0c 100644 --- a/docs/src/assets/scss/components/tabs.scss +++ b/docs/src/assets/scss/components/tabs.scss @@ -1,63 +1,66 @@ .c-tabs { - pre { - margin-top: 0; - margin-block-start: 0; - } + pre { + margin-top: 0; + margin-block-start: 0; + } } .c-tabs__tablist { - .js-tabs & { - display: flex; - justify-content: start; - } + .js-tabs & { + display: flex; + justify-content: start; + } } .c-tabs__tab { - background: none; - border: none; - margin: 0; - color: inherit; - font: inherit; - cursor: pointer; - line-height: inherit; - font-weight: 500; - font-size: var(--step-0); - display: inline-flex; - padding: .75rem 1.125rem; - align-items: center; - justify-content: center; - border-radius: var(--border-radius) var(--border-radius) 0 0; - transition: background-color .2s linear, border-color .2s linear; - - &:hover { - color: var(--link-color); - } - - &[aria-selected="true"] { - color: var(--link-color); - background-color: var(--lightest-background-color); - } + background: none; + border: none; + margin: 0; + color: inherit; + font: inherit; + cursor: pointer; + line-height: inherit; + font-weight: 500; + font-size: var(--step-0); + display: inline-flex; + padding: 0.75rem 1.125rem; + align-items: center; + justify-content: center; + border-radius: var(--border-radius) var(--border-radius) 0 0; + transition: + background-color 0.2s linear, + border-color 0.2s linear; + + &:hover { + color: var(--link-color); + } + + &[aria-selected="true"] { + color: var(--link-color); + background-color: var(--lightest-background-color); + } } .c-tabs__tabpanel { - margin-bottom: 2rem; - margin-block-end: 2rem; - background-color: var(--lightest-background-color); - border-radius: 0 var(--border-radius) var(--border-radius) var(--border-radius); - - .js-tabs & { - margin-bottom: 0; - margin-block-end: 0; - } + margin-bottom: 2rem; + margin-block-end: 2rem; + background-color: var(--lightest-background-color); + border-radius: 0 var(--border-radius) var(--border-radius) + var(--border-radius); + + .js-tabs & { + margin-bottom: 0; + margin-block-end: 0; + } } .c-tabs__tabpanel__title { - margin-bottom: 1.5rem; - margin-block-end: 1.5rem; + margin-bottom: 1.5rem; + margin-block-end: 1.5rem; } // when the js is enabled, the tabpanels are labelled by their tabs // you may choose to hide or keep the headings inside of them visible .js-tabs .c-tabs__tabpanel__title { - display: none; + display: none; } diff --git a/docs/src/assets/scss/components/theme-switcher.scss b/docs/src/assets/scss/components/theme-switcher.scss index d44aa9009d94..69277fa2c1f6 100644 --- a/docs/src/assets/scss/components/theme-switcher.scss +++ b/docs/src/assets/scss/components/theme-switcher.scss @@ -1,77 +1,77 @@ .theme-switcher { - display: inline-flex; - align-items: center; - gap: .5rem; - position: relative; + display: inline-flex; + align-items: center; + gap: 0.5rem; + position: relative; } .theme-switcher-label.theme-switcher-label { - color: inherit; - font: inherit; - font-family: var(--text-font); - margin: 0; + color: inherit; + font: inherit; + font-family: var(--text-font); + margin: 0; } .theme-switcher__buttons { - display: flex; - border: 1px solid var(--border-color); - border-radius: var(--border-radius); - background-color: var(--body-background-color); + display: flex; + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + background-color: var(--body-background-color); } .theme-switcher__button { - flex-wrap: wrap; - box-shadow: var(--shadow-xs); - padding: .625rem .875rem; - display: inline-flex; - align-items: center; - margin: 0; - gap: .25rem; - color: inherit; + flex-wrap: wrap; + box-shadow: var(--shadow-xs); + padding: 0.625rem 0.675rem; + display: inline-flex; + align-items: center; + margin: 0; + gap: 0.25rem; + color: inherit; - &:first-of-type { - border-right: .5px solid var(--border-color); - border-inline-end: .5px solid var(--border-color); - } + &:first-of-type { + border-right: 0.5px solid var(--border-color); + border-inline-end: 0.5px solid var(--border-color); + } - &:last-of-type { - border-left: .5px solid var(--border-color); - border-inline-start: .5px solid var(--border-color); - } + &:last-of-type { + border-left: 0.5px solid var(--border-color); + border-inline-start: 0.5px solid var(--border-color); + } - .theme-switcher__icon { - color: var(--icon-color); - } + .theme-switcher__icon { + color: var(--icon-color); + } - &:hover { - .theme-switcher__icon { - color: var(--link-color); - } - } + &:hover { + .theme-switcher__icon { + color: var(--link-color); + } + } } .theme-switcher__button[aria-pressed="true"] { - color: var(--link-color); + color: var(--link-color); - .theme-switcher__icon { - color: var(--link-color); - } + .theme-switcher__icon { + color: var(--link-color); + } - &:hover { - .theme-switcher__icon { - color: var(--link-color); - } - } + &:hover { + .theme-switcher__icon { + color: var(--link-color); + } + } } .theme-switcher__button[aria-pressed="false"] { - .theme-switcher__icon { - color: var(--icon-color); - } + .theme-switcher__icon { + color: var(--icon-color); + } - &:hover { - .theme-switcher__icon { - color: var(--link-color); - } - } + &:hover { + .theme-switcher__icon { + color: var(--link-color); + } + } } diff --git a/docs/src/assets/scss/components/toc.scss b/docs/src/assets/scss/components/toc.scss index 96647b4c70f0..10440effd068 100644 --- a/docs/src/assets/scss/components/toc.scss +++ b/docs/src/assets/scss/components/toc.scss @@ -1,135 +1,135 @@ .docs-toc { - margin: 2rem 0; + margin: 2rem 0; - @media all and (min-width: 1400px) { - display: none; - } + @media all and (min-width: 1400px) { + display: none; + } - .docs-aside & { - display: none; + .docs-aside & { + display: none; - @media all and (min-width: 1400px) { - display: block; - } - } + @media all and (min-width: 1400px) { + display: block; + } + } } .docs-aside { - // for sticky table of contents in sidebar - .docs-toc.c-toc { - background-color: var(--body-background-color); - @media all and (min-width: 1400px) { - position: sticky; - top: 20px; - overflow-y: auto; // show scrollbar when toc is higher than viewport - padding-right: 5px; // push scrollbar away from content - max-height: calc(100vh - 32px); // minus element's margin-top - a.active { - color: var(--link-color); - font-weight: 500; - } - } - } - - .c-toc ol li.active::before { - @media all and (min-width: 1400px) { - color: var(--link-color); - } - } + // for sticky table of contents in sidebar + .docs-toc.c-toc { + background-color: var(--body-background-color); + @media all and (min-width: 1400px) { + position: sticky; + top: 20px; + overflow-y: auto; // show scrollbar when toc is higher than viewport + padding-right: 5px; // push scrollbar away from content + max-height: calc(100vh - 32px); // minus element's margin-top + a.active { + color: var(--link-color); + font-weight: 500; + } + } + } + + .c-toc ol li.active::before { + @media all and (min-width: 1400px) { + color: var(--link-color); + } + } } .c-toc { - ol { - margin: 0; - - li { - position: relative; - margin-bottom: .25rem; - margin-block-end: .25rem; - padding-left: 1rem; - padding-inline-start: 1rem; - - >ol { - margin-top: .25rem; - } - } - - li::before { - content: "└"; - color: var(--icon-color); - position: absolute; - left: -.4rem; - offset-inline-start: -.4rem; - } - } - - a { - text-decoration: none; - color: var(--headings-color); - - &:hover { - color: var(--link-color); - } - } + ol { + margin: 0; + + li { + position: relative; + margin-bottom: 0.25rem; + margin-block-end: 0.25rem; + padding-left: 1rem; + padding-inline-start: 1rem; + + > ol { + margin-top: 0.25rem; + } + } + + li::before { + content: "└"; + color: var(--icon-color); + position: absolute; + left: -0.4rem; + offset-inline-start: -0.4rem; + } + } + + a { + text-decoration: none; + color: var(--headings-color); + + &:hover { + color: var(--link-color); + } + } } .c-toc__label.c-toc__label { - font-size: var(--step-0); - color: var(--body-text-color); - font-family: var(--text-font); - margin-bottom: .5rem; - margin-block-end: .5rem; + font-size: var(--step-0); + color: var(--body-text-color); + font-family: var(--text-font); + margin-bottom: 0.5rem; + margin-block-end: 0.5rem; } .c-toc__label { - width: fit-content; - - button { - color: var(--link-color); - cursor: pointer; - display: flex; - align-items: center; - justify-content: space-between; - font: inherit; - font-size: inherit; - font-weight: 500; - width: 100%; - height: 100%; - text-align: left; - line-height: 1.5; - padding: 0; - border-radius: 0; - position: relative; - transition: outline 0.1s linear; - - svg { - flex: none; - } - } + width: fit-content; + + button { + color: var(--link-color); + cursor: pointer; + display: flex; + align-items: center; + justify-content: space-between; + font: inherit; + font-size: inherit; + font-weight: 500; + width: 100%; + height: 100%; + text-align: left; + line-height: 1.5; + padding: 0; + border-radius: 0; + position: relative; + transition: outline 0.1s linear; + + svg { + flex: none; + } + } } /* Styles for the accordion icon */ .toc-trigger-icon { - display: block !important; // to override aria-hidden - width: 0.75rem; - height: 0.5rem; - transform-origin: 50% 50%; - margin-left: 2rem; - margin-inline-start: 2rem; - transition: all 0.1s linear; - color: var(--color-neutral-400); - - [aria-expanded="true"] & { - transform: rotate(180deg); - } + display: block !important; // to override aria-hidden + width: 0.75rem; + height: 0.5rem; + transform-origin: 50% 50%; + margin-left: 2rem; + margin-inline-start: 2rem; + transition: all 0.1s linear; + color: var(--color-neutral-400); + + [aria-expanded="true"] & { + transform: rotate(180deg); + } } .c-toc__panel { - &[data-open="false"] { - display: none; - } + &[data-open="false"] { + display: none; + } - &[data-open="true"] { - display: block; - } + &[data-open="true"] { + display: block; + } } diff --git a/docs/src/assets/scss/components/version-switcher.scss b/docs/src/assets/scss/components/version-switcher.scss index 606b802395cb..95079922f5d0 100644 --- a/docs/src/assets/scss/components/version-switcher.scss +++ b/docs/src/assets/scss/components/version-switcher.scss @@ -1,4 +1,4 @@ .version-switcher { - margin-bottom: .5rem; - margin-block-end: .5rem; + margin-bottom: 0.5rem; + margin-block-end: 0.5rem; } diff --git a/docs/src/assets/scss/docs-footer.scss b/docs/src/assets/scss/docs-footer.scss index 347afd3978e6..994004fb3e3a 100644 --- a/docs/src/assets/scss/docs-footer.scss +++ b/docs/src/assets/scss/docs-footer.scss @@ -1,50 +1,50 @@ .docs-footer { - display: flex; - flex-direction: column; - gap: 2rem; - justify-content: space-between; - align-items: baseline; - font-size: .875rem; - - @media all and (max-width: 800px) { - padding: 1.5rem 0 4rem; - align-items: center; - } + display: flex; + flex-direction: column; + gap: 2rem; + justify-content: space-between; + align-items: baseline; + font-size: 0.875rem; + + @media all and (max-width: 800px) { + padding: 1.5rem 0 4rem; + align-items: center; + } } .copyright p { - margin: 0; + margin: 0; } .docs-socials-and-legal { - display: flex; - flex-direction: column; - gap: 1rem; + display: flex; + flex-direction: column; + gap: 1rem; - @media all and (max-width: 800px) { - text-align: center; - } + @media all and (max-width: 800px) { + text-align: center; + } } .docs-switchers { - display: flex; - flex-wrap: wrap; - gap: 1.5rem; - - .theme-switcher, - .language-switcher { - flex: 1 1 240px; - } - - .theme-switcher { - @media all and (max-width: 800px) { - justify-content: center; - } - } - - .language-switcher { - @media all and (max-width: 800px) { - justify-content: center; - } - } + display: flex; + flex-wrap: wrap; + gap: 1.5rem; + + .theme-switcher, + .language-switcher { + flex: 1 1 240px; + } + + .theme-switcher { + @media all and (max-width: 800px) { + justify-content: center; + } + } + + .language-switcher { + @media all and (max-width: 800px) { + justify-content: center; + } + } } diff --git a/docs/src/assets/scss/docs-header.scss b/docs/src/assets/scss/docs-header.scss index 15f21cf47eef..50cc4ad7bc3b 100644 --- a/docs/src/assets/scss/docs-header.scss +++ b/docs/src/assets/scss/docs-header.scss @@ -1,43 +1,43 @@ .site-header { - padding: .75rem 0; - border-top: 4px solid var(--link-color); - border-bottom: 1px solid var(--divider-color); - border-block-start: 4px solid var(--link-color); - border-block-end: 1px solid var(--divider-color); + padding: 0.75rem 0; + border-top: 4px solid var(--link-color); + border-bottom: 1px solid var(--divider-color); + border-block-start: 4px solid var(--link-color); + border-block-end: 1px solid var(--divider-color); - .docs-wrapper { - display: grid; - align-items: start; - padding-top: 0; - padding-bottom: 0; - padding-block-start: 0; - padding-block-end: 0; - max-width: 1700px; + .docs-wrapper { + display: grid; + align-items: start; + padding-top: 0; + padding-bottom: 0; + padding-block-start: 0; + padding-block-end: 0; + max-width: 1700px; - @media all and (min-width: 1024px) { - justify-content: space-between; - } - @media all and (min-width: 1700px) { - margin: auto; - } - } + @media all and (min-width: 1024px) { + justify-content: space-between; + } + @media all and (min-width: 1700px) { + margin: auto; + } + } } .logo-link { - display: inline-flex; - justify-self: start; - flex: none; - place-content: center; - grid-column: 1 / -1; - grid-row: 1; - padding: .5rem 0; + display: inline-flex; + justify-self: start; + flex: none; + place-content: center; + grid-column: 1 / -1; + grid-row: 1; + padding: 0.5rem 0; } .logo svg { - display: inline-block; - margin-bottom: -4px; - margin-block-end: -4px; - width: 100%; - max-width: 100px; - height: auto; + display: inline-block; + margin-bottom: -4px; + margin-block-end: -4px; + width: 100%; + max-width: 100px; + height: auto; } diff --git a/docs/src/assets/scss/docs.scss b/docs/src/assets/scss/docs.scss index e981d8cc6af3..5068172a4899 100644 --- a/docs/src/assets/scss/docs.scss +++ b/docs/src/assets/scss/docs.scss @@ -1,216 +1,274 @@ /* docs layout styles */ html { - scroll-behavior: smooth; + scroll-behavior: smooth; } .docs-aside__content { - flex: 1; + flex: 1; } .docs-wrapper { - padding: 0 var(--space-s-l); - flex: 1; - display: flex; - flex-direction: column; - max-width: 1700px; - - @media all and (min-width: 1024px) { - display: grid; - grid-template-columns: minmax(250px, 1fr) minmax(0, 3.5fr); - align-items: stretch; - } - @media all and (min-width: 1700px) { - margin: auto; - } + padding: 0 var(--space-s-l); + flex: 1; + display: flex; + flex-direction: column; + max-width: 1700px; + + @media all and (min-width: 1024px) { + display: grid; + grid-template-columns: minmax(250px, 1fr) minmax(0, 3.5fr); + align-items: stretch; + } + @media all and (min-width: 1700px) { + margin: auto; + } } .docs-nav { - grid-column: 1 / 2; - grid-row: 1 / 2; - padding-top: var(--space-l-xl); - padding-block-start: var(--space-l-xl); - font-size: 0.875rem; - display: grid; - grid-auto-rows: max-content; - align-items: start; - - @media all and (min-width: 1024px) { - padding: var(--space-l-xl) 0; - padding-right: var(--space-s-l); - padding-inline-end: var(--space-s-l); - border-right: 1px solid var(--divider-color); - border-inline-end: 1px solid var(--divider-color); - } + grid-column: 1 / 2; + grid-row: 1 / 2; + padding-top: var(--space-l-xl); + padding-block-start: var(--space-l-xl); + font-size: 0.875rem; + display: grid; + grid-auto-rows: max-content; + align-items: start; + + @media all and (min-width: 1024px) { + padding: var(--space-l-xl) 0; + padding-right: var(--space-s-l); + padding-inline-end: var(--space-s-l); + border-right: 1px solid var(--divider-color); + border-inline-end: 1px solid var(--divider-color); + } } .docs-content { - grid-column: 2 / 3; - padding: var(--space-l-xl) 0; - flex: 1; - - @media all and (min-width: 800px) { - display: grid; - grid-template-columns: minmax(0, 4fr) minmax(160px, 1fr); - grid-gap: 1rem; - } - - @media all and (min-width: 1024px) { - padding: 0; - } - - @media all and (min-width: 1300px) { - grid-gap: 2rem; - } + grid-column: 2 / 3; + padding: var(--space-l-xl) 0; + flex: 1; + + @media all and (min-width: 800px) { + display: grid; + grid-template-columns: minmax(0, 4fr) minmax(160px, 1fr); + grid-gap: 1rem; + } + + @media all and (min-width: 1024px) { + padding: 0; + } + + @media all and (min-width: 1300px) { + grid-gap: 2rem; + } } .docs-main { - flex: 1 1 68ch; - - @media all and (min-width: 800px) { - padding-right: var(--space-s-l); - padding-inline-end: var(--space-s-l); - border-right: 1px solid var(--divider-color); - border-inline-end: 1px solid var(--divider-color); - } - - @media all and (min-width: 1024px) { - padding: var(--space-l-xl) var(--space-l-2xl); - } + flex: 1 1 68ch; + + @media all and (min-width: 800px) { + padding-right: var(--space-s-l); + padding-inline-end: var(--space-s-l); + border-right: 1px solid var(--divider-color); + border-inline-end: 1px solid var(--divider-color); + } + + @media all and (min-width: 1024px) { + padding: var(--space-l-xl) var(--space-l-2xl); + } } .docs-aside { - grid-column: 2 / 3; - display: flex; - flex-direction: column; + grid-column: 2 / 3; + display: flex; + flex-direction: column; - @media all and (min-width: 800px) { - padding: var(--space-l-xl) 0; - } + @media all and (min-width: 800px) { + padding: var(--space-l-xl) 0; + } } .docs-toc { - flex: 1; - align-self: center; + flex: 1; + align-self: center; } .docs-edit-link { - border-top: 1px solid var(--divider-color); - padding-top: 1.5rem; - padding-block-start: 1.5rem; - margin: 3rem 0; + border-top: 1px solid var(--divider-color); + padding-top: 1.5rem; + padding-block-start: 1.5rem; + margin: 3rem 0; } div.correct, div.incorrect { - position: relative; - - &::after { - position: absolute; - top: -22px; - right: -22px; - offset-inline-end: -22px; - offset-block-start: -22px; - } - - // Add space to the bottom if there is a Playground button. - .c-btn.c-btn--playground ~ pre.line-numbers-mode { - padding-bottom: 4.5rem; - } + position: relative; + + &::after { + position: absolute; + top: -22px; + right: -22px; + offset-inline-end: -22px; + offset-block-start: -22px; + } + + // Add space to the bottom if there is a Playground button. + .c-btn.c-btn--playground ~ pre.line-numbers-mode { + padding-bottom: 4.5rem; + } } div.correct { - &::after { - content: url("data:image/svg+xml,%3Csvg width='45' height='44' viewBox='0 0 45 44' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1.5' y='1' width='42' height='42' rx='21' fill='%23ECFDF3'/%3E%3Cpath d='M30.5 16L19.5 27L14.5 22' stroke='%2312B76A' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Crect x='1.5' y='1' width='42' height='42' rx='21' stroke='white' stroke-width='2'/%3E%3C/svg%3E%0A"); - } + &::after { + content: url("data:image/svg+xml,%3Csvg width='45' height='44' viewBox='0 0 45 44' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1.5' y='1' width='42' height='42' rx='21' fill='%23ECFDF3'/%3E%3Cpath d='M30.5 16L19.5 27L14.5 22' stroke='%2312B76A' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Crect x='1.5' y='1' width='42' height='42' rx='21' stroke='white' stroke-width='2'/%3E%3C/svg%3E%0A"); + } } div.incorrect { - &::after { - content: url("data:image/svg+xml,%3Csvg width='45' height='44' viewBox='0 0 45 44' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1.5' y='1' width='42' height='42' rx='21' fill='%23FFF1F3'/%3E%3Cpath d='M28.5 16L16.5 28M16.5 16L28.5 28' stroke='%23F63D68' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Crect x='1.5' y='1' width='42' height='42' rx='21' stroke='white' stroke-width='2'/%3E%3C/svg%3E%0A"); - } + &::after { + content: url("data:image/svg+xml,%3Csvg width='45' height='44' viewBox='0 0 45 44' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='1.5' y='1' width='42' height='42' rx='21' fill='%23FFF1F3'/%3E%3Cpath d='M28.5 16L16.5 28M16.5 16L28.5 28' stroke='%23F63D68' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3Crect x='1.5' y='1' width='42' height='42' rx='21' stroke='white' stroke-width='2'/%3E%3C/svg%3E%0A"); + } } div.img-container { - background-color: var(--img-background-color); - border-radius: var(--border-radius); + background-color: var(--img-background-color); + border-radius: var(--border-radius); - img { - margin: 0 auto; - } + img { + margin: 0 auto; + } } pre[class*="language-"] { - position: relative; + position: relative; } .c-btn.c-btn--playground { - position: absolute; - font-size: var(--step--1); - bottom: 1rem; - right: 1rem; - z-index: 1; - - @media all and (min-width: 768px) { - bottom: 1.5rem; - } + position: absolute; + font-size: var(--step--1); + bottom: 1rem; + right: 1rem; + z-index: 1; + + @media all and (min-width: 768px) { + bottom: 1.5rem; + } } @media (hover: none) { - .anchorjs-link { - opacity: 1; - } + .anchorjs-link { + opacity: 1; + } } #scroll-up-btn { - width: 50px; - height: 50px; - display: none; - position: fixed; - right: 19.8vw; - bottom: 35px; - z-index: 1; - font-size: 1.5rem; - border-radius: 50%; - color: var(--body-background-color); - text-decoration: none; - justify-content: center; - align-items: center; - background-color: var(--link-color); - - @media (max-width: 1299px) { - right: 18.99vw; - } - - @media (max-width: 1100px) { - right: 19.4vw; - } - - @media (max-width: 1060px) { - right: 19.9vw; - } - - @media (max-width: 1024px) { - right: 22vw; - } - - @media (max-width: 860px) { - right: 22.2vw; - } - - @media (max-width: 850px) { - right: 22.6vw; - } - - @media (max-width: 820px) { - right: 23.4vw; - } - - @media (max-width: 799px) { - right: 35px; - } - - @media (max-width: 600px) { - right: 25px; - } + width: 50px; + height: 50px; + display: none; + position: fixed; + right: 19.8vw; + bottom: 35px; + z-index: 1; + font-size: 1.5rem; + border-radius: 50%; + color: var(--body-background-color); + text-decoration: none; + justify-content: center; + align-items: center; + background-color: var(--link-color); + + @media (max-width: 1299px) { + right: 18.99vw; + } + + @media (max-width: 1100px) { + right: 19.4vw; + } + + @media (max-width: 1060px) { + right: 19.9vw; + } + + @media (max-width: 1024px) { + right: 22vw; + } + + @media (max-width: 860px) { + right: 22.2vw; + } + + @media (max-width: 850px) { + right: 22.6vw; + } + + @media (max-width: 820px) { + right: 23.4vw; + } + + @media (max-width: 799px) { + right: 35px; + } + + @media (max-width: 600px) { + right: 25px; + } +} + +.code-wrapper { + position: relative; + + > pre { + padding-right: 3.5rem; + } +} + +.copy-btn { + position: absolute; + top: 1rem; + right: 1rem; + padding: 0.5rem; + border-radius: var(--border-radius); + color: var(--body-text-color); + background-color: var(--color-neutral-100); + border: 1px solid var(--border-color); + transition: background-color 0.1s linear; + + &:hover { + background-color: var(--color-neutral-200); + } + + [data-theme="dark"] & { + background-color: var(--color-neutral-700); + + &:hover { + background-color: var(--color-neutral-600); + } + } + + &::after { + content: "Copied!"; + padding: 0.5rem 0.75rem; + font-size: var(--step--1); + border-radius: var(--border-radius); + position: absolute; + top: 0; + right: 2.5rem; + height: 2.25rem; + display: flex; + align-items: center; + justify-content: center; + background-color: var(--color-neutral-100); + border: 1px solid var(--border-color); + opacity: 0; + pointer-events: none; + + [data-theme="dark"] & { + background-color: var(--color-neutral-700); + } + } + + &[data-copied="true"]::after { + opacity: 1; + } } diff --git a/docs/src/assets/scss/eslint-site-footer.scss b/docs/src/assets/scss/eslint-site-footer.scss index 6ecb430c7038..e9b25d685b9d 100644 --- a/docs/src/assets/scss/eslint-site-footer.scss +++ b/docs/src/assets/scss/eslint-site-footer.scss @@ -1,64 +1,64 @@ .site-footer { - text-align: center; - background-color: var(--footer-background-color); - border-top: 1px solid var(--divider-color); - border-block-start: 1px solid var(--divider-color); + text-align: center; + background-color: var(--footer-background-color); + border-top: 1px solid var(--divider-color); + border-block-start: 1px solid var(--divider-color); } .footer-cta { - .logo { - margin-bottom: 2.5rem; - margin-block-end: 2.5rem; - } + .logo { + margin-bottom: 2.5rem; + margin-block-end: 2.5rem; + } - .section-supporting-text { - margin-bottom: 2.5rem; - margin-block-end: 2.5rem; - } + .section-supporting-text { + margin-bottom: 2.5rem; + margin-block-end: 2.5rem; + } - .eslint-actions { - justify-content: center; - } + .eslint-actions { + justify-content: center; + } } .footer-legal-links { - ul { - li { - display: inline-block; - margin-right: .5rem; - margin-inline-end: .5rem; + ul { + li { + display: inline-block; + margin-right: 0.5rem; + margin-inline-end: 0.5rem; - &:not(:last-of-type)::after { - content: "|"; - margin-left: .5rem; - margin-inline-start: .5rem; - } - } - } + &:not(:last-of-type)::after { + content: "|"; + margin-left: 0.5rem; + margin-inline-start: 0.5rem; + } + } + } } .footer-legal-section { - font-size: var(--step--1); - padding: 2rem 1rem; + font-size: var(--step--1); + padding: 2rem 1rem; } .copyright { - max-width: 1100px; - margin: 0 auto; + max-width: 1100px; + margin: 0 auto; } .footer-middle { - padding-top: 2rem; - padding-bottom: 2rem; - padding-block-start: 2rem; - padding-block-end: 2rem; - display: flex; - flex-direction: column; - align-items: center; - gap: 2rem; + padding-top: 2rem; + padding-bottom: 2rem; + padding-block-start: 2rem; + padding-block-end: 2rem; + display: flex; + flex-direction: column; + align-items: center; + gap: 2rem; - @media all and (min-width: 768px) { - flex-direction: row; - justify-content: space-between; - } + @media all and (min-width: 768px) { + flex-direction: row; + justify-content: space-between; + } } diff --git a/docs/src/assets/scss/eslint-site-header.scss b/docs/src/assets/scss/eslint-site-header.scss index 892ebc7e6250..b970e673efea 100644 --- a/docs/src/assets/scss/eslint-site-header.scss +++ b/docs/src/assets/scss/eslint-site-header.scss @@ -1,40 +1,40 @@ .site-header { - padding: .75rem 0; - border-top: 4px solid var(--link-color); - border-block-start: 4px solid var(--link-color); - border-bottom: 1px solid var(--divider-color); - border-block-end: 1px solid var(--divider-color); + padding: 0.75rem 0; + border-top: 4px solid var(--link-color); + border-block-start: 4px solid var(--link-color); + border-bottom: 1px solid var(--divider-color); + border-block-end: 1px solid var(--divider-color); - .content-container { - display: grid; - align-items: start; - padding-top: 0; - padding-bottom: 0; - padding-block-start: 0; - padding-block-end: 0; + .content-container { + display: grid; + align-items: start; + padding-top: 0; + padding-bottom: 0; + padding-block-start: 0; + padding-block-end: 0; - @media all and (min-width: 680px) { - justify-content: space-between; - } - } + @media all and (min-width: 680px) { + justify-content: space-between; + } + } } .logo-link { - display: inline-flex; - justify-self: start; - flex: none; - place-content: center; - grid-column: 1 / -1; - grid-row: 1; - padding: .5rem 0; - z-index: 2; + display: inline-flex; + justify-self: start; + flex: none; + place-content: center; + grid-column: 1 / -1; + grid-row: 1; + padding: 0.5rem 0; + z-index: 2; } .logo svg { - display: inline-block; - margin-bottom: -4px; - margin-block-end: -4px; - width: 100%; - max-width: 100px; - height: auto; + display: inline-block; + margin-bottom: -4px; + margin-block-end: -4px; + width: 100%; + max-width: 100px; + height: auto; } diff --git a/docs/src/assets/scss/forms.scss b/docs/src/assets/scss/forms.scss index 3ca145257342..28dfde5750b0 100644 --- a/docs/src/assets/scss/forms.scss +++ b/docs/src/assets/scss/forms.scss @@ -1,49 +1,58 @@ .c-custom-select { - appearance: none; - box-sizing: border-box; - display: block; - width: 100%; - max-width: 100%; - min-width: 0; - padding: .625rem .875rem; - padding-right: calc(.875rem * 2.5); - padding-inline-end: calc(.875rem * 2.5); - font: inherit; - color: var(--body-text-color); - line-height: 1.3; - border: 1px solid var(--border-color); - border-radius: var(--border-radius); - box-shadow: var(--shadow-xs); - background-color: var(--body-background-color); - background-image: url("data:image/svg+xml,%3Csvg width='20' height='21' viewBox='0 0 20 21' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 7.60938L10 12.6094L15 7.60938' stroke='%23667085' stroke-width='1.66667' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A"), linear-gradient(to bottom, var(--body-background-color) 0%, var(--body-background-color) 100%); - background-repeat: no-repeat, repeat; - background-position: right .875rem top 50%, 0 0; - background-size: 1em auto, 100%; + appearance: none; + box-sizing: border-box; + display: block; + width: 100%; + max-width: 100%; + min-width: 0; + padding: 0.625rem 0.875rem; + padding-right: calc(0.875rem * 2.5); + padding-inline-end: calc(0.875rem * 2.5); + font: inherit; + color: var(--body-text-color); + line-height: 1.3; + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + box-shadow: var(--shadow-xs); + background-color: var(--body-background-color); + background-image: url("data:image/svg+xml,%3Csvg width='20' height='21' viewBox='0 0 20 21' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M5 7.60938L10 12.6094L15 7.60938' stroke='%23667085' stroke-width='1.66667' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E%0A"), + linear-gradient( + to bottom, + var(--body-background-color) 0%, + var(--body-background-color) 100% + ); + background-repeat: no-repeat, repeat; + background-position: + right 0.875rem top 50%, + 0 0; + background-size: + 1em auto, + 100%; } .label__text.label__text { - display: flex; - align-items: center; - gap: .5rem; - font-size: .875rem; - font-family: var(--text-font); - color: inherit; - font-weight: 400; - line-height: 1.5; - margin-bottom: .25rem; - margin-block-end: .25rem; + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; + font-family: var(--text-font); + color: inherit; + font-weight: 400; + line-height: 1.5; + margin-bottom: 0.25rem; + margin-block-end: 0.25rem; } input { - border: 1px solid var(--border-color); - border-radius: var(--border-radius); - padding: .625rem .875rem; - font: inherit; - font-size: 1rem; - display: block; - min-width: 0; - line-height: 1.3; - max-width: 100%; - background-color: var(--body-background-color); - color: inherit; + border: 1px solid var(--border-color); + border-radius: var(--border-radius); + padding: 0.625rem 0.875rem; + font: inherit; + font-size: 1rem; + display: block; + min-width: 0; + line-height: 1.3; + max-width: 100%; + background-color: var(--body-background-color); + color: inherit; } diff --git a/docs/src/assets/scss/foundations.scss b/docs/src/assets/scss/foundations.scss index 68e44651f4fb..6237fef1d022 100644 --- a/docs/src/assets/scss/foundations.scss +++ b/docs/src/assets/scss/foundations.scss @@ -1,6 +1,6 @@ ::selection { - background-color: var(--color-brand); - color: #fff; + background-color: var(--color-brand); + color: #fff; } h1:target, @@ -9,230 +9,231 @@ h3:target, h4:target, h5:target, h6:target { - background-color: var(--lighter-background-color); + background-color: var(--lighter-background-color); + border-radius: var(--border-radius); } *:focus { - outline: none; + outline: none; } *:focus-visible { - outline: 2px solid var(--outline-color); - outline-offset: 3px; + outline: 2px solid var(--outline-color); + outline-offset: 3px; } *.focus-visible { - outline: 2px solid var(--outline-color); - outline-offset: 3px; + outline: 2px solid var(--outline-color); + outline-offset: 3px; } *:focus:not(:focus-visible) { - outline: 1px solid transparent; - box-shadow: none; + outline: 1px solid transparent; + box-shadow: none; } .js-focus-visible *:focus:not(.focus-visible) { - outline: 1px solid transparent; - box-shadow: none; + outline: 1px solid transparent; + box-shadow: none; } input:focus-visible { - outline: 2px solid var(--link-color); - border-color: var(--border-color); + outline: 2px solid var(--link-color); + border-color: var(--border-color); } input:focus { - outline: 2px solid transparent; - box-shadow: 0 0 0 2px var(--link-color); + outline: 2px solid transparent; + box-shadow: 0 0 0 2px var(--link-color); } *, *::before, *::after { - box-sizing: border-box; + box-sizing: border-box; } html { - accent-color: var(--link-color); - background-color: var(--body-background-color); - height: 100%; - font-family: var(--text-font); - overflow-x: hidden; - caret-color: var(--link-color); + accent-color: var(--link-color); + background-color: var(--body-background-color); + height: 100%; + font-family: var(--text-font); + overflow-x: hidden; + caret-color: var(--link-color); } body { - font-size: var(--step-0); - position: relative; - margin: 0 auto; - line-height: 1.5; - display: flex; - flex-direction: column; - min-height: 100%; - background-color: var(--body-background-color); - color: var(--body-text-color); + font-size: var(--step-0); + position: relative; + margin: 0 auto; + line-height: 1.5; + display: flex; + flex-direction: column; + min-height: 100%; + background-color: var(--body-background-color); + color: var(--body-text-color); } #skip-link { - position: fixed; - top: -30em; - left: 0; - right: auto; - offset-block-start: -30em; - offset-inline-start: 0; - offset-inline-end: auto; - z-index: 999; - transition: top .1s linear; - - &:focus { - outline: 2px solid transparent; - top: 2px; - offset-block-start: 2px; - } - - &:focus-visible { - outline: 2px solid transparent; - top: 2px; - offset-block-start: 2px; - } + position: fixed; + top: -30em; + left: 0; + right: auto; + offset-block-start: -30em; + offset-inline-start: 0; + offset-inline-end: auto; + z-index: 999; + transition: top 0.1s linear; + + &:focus { + outline: 2px solid transparent; + top: 2px; + offset-block-start: 2px; + } + + &:focus-visible { + outline: 2px solid transparent; + top: 2px; + offset-block-start: 2px; + } } main { - flex: 1; + flex: 1; - &:focus { - outline: none; - } + &:focus { + outline: none; + } - &:target { - outline: none; - } + &:target { + outline: none; + } } hr { - border: none; - border-top: 1px solid var(--divider-color); - border-block-start: 1px solid var(--divider-color); - background: none; - height: 0; - margin: 2rem 0; + border: none; + border-top: 1px solid var(--divider-color); + border-block-start: 1px solid var(--divider-color); + background: none; + height: 0; + margin: 2rem 0; } .content-container { - width: 100%; - margin: 0 auto; - padding: var(--space-xl-3xl) calc(1rem + 1vw); - max-width: 1700px; + width: 100%; + margin: 0 auto; + padding: var(--space-xl-3xl) calc(1rem + 1vw); + max-width: 1700px; - @media all and (min-width: 1700px) { - margin: auto; - } + @media all and (min-width: 1700px) { + margin: auto; + } } .section-head { - .section-supporting-text { - text-align: center; - max-width: 768px; - margin: 0 auto var(--space-l-2xl); - } + .section-supporting-text { + text-align: center; + max-width: 768px; + margin: 0 auto var(--space-l-2xl); + } } .section-foot { - margin-top: var(--space-l-2xl); - margin-block-start: var(--space-l-2xl); + margin-top: var(--space-l-2xl); + margin-block-start: var(--space-l-2xl); - .section-supporting-text { - text-align: center; - font-size: var(--step--1); - max-width: 768px; - margin: 0 auto; - } + .section-supporting-text { + text-align: center; + font-size: var(--step--1); + max-width: 768px; + margin: 0 auto; + } } .section-title { - margin-bottom: 1rem; - margin-block-end: 1rem; + margin-bottom: 1rem; + margin-block-end: 1rem; } .section-supporting-text { - font-size: var(--step-1); + font-size: var(--step-1); } code, pre { - font-family: var(--mono-font); - font-variant-ligatures: none; + font-family: var(--mono-font); + font-variant-ligatures: none; } code { - color: var(--link-color); + color: var(--link-color); - pre & { - color: unset; - } + pre & { + color: unset; + } } .c-icon { - color: var(--icon-color); - flex: none; - transition: all .2s linear; + color: var(--icon-color); + flex: none; + transition: all 0.2s linear; - @media (-ms-high-contrast: active) { - color: windowText; - } + @media (-ms-high-contrast: active) { + color: windowText; + } - @media (forced-colors: active) { - color: canvasText; - } + @media (forced-colors: active) { + color: canvasText; + } } table { - width: 100%; - margin: 2.5rem 0; - border-collapse: collapse; - border: 1px solid var(--divider-color); + width: 100%; + margin: 2.5rem 0; + border-collapse: collapse; + border: 1px solid var(--divider-color); - td { - padding: .25rem .5rem; - border: 1px solid var(--divider-color); - } + td { + padding: 0.25rem 0.5rem; + border: 1px solid var(--divider-color); + } - th { - background-color: var(--lightest-background-color); - padding: .25rem .5rem; - } + th { + background-color: var(--lightest-background-color); + padding: 0.25rem 0.5rem; + } } .c-btn, button, a { - .c-icon:hover { - color: var(--link-color); - } + .c-icon:hover { + color: var(--link-color); + } } a { - color: var(--link-color); - transition: color .1s linear; + color: var(--link-color); + transition: color 0.1s linear; - .side-header & { - color: inherit; - text-decoration: none; - } + .side-header & { + color: inherit; + text-decoration: none; + } } svg { - flex: none; - transition: color .1s linear; + flex: none; + transition: color 0.1s linear; } p { - margin: 0 0 1.5em; + margin: 0 0 1.5em; - :matches(nav, .posts-collection) & { - margin-bottom: .75em; - margin-block-end: .75em; - } + :matches(nav, .posts-collection) & { + margin-bottom: 0.75em; + margin-block-end: 0.75em; + } } p, @@ -242,114 +243,114 @@ h3, h4, h5, h6 { - overflow-wrap: break-word; + overflow-wrap: break-word; } ul, ol { - margin-top: 0; - margin-block-start: 0; + margin-top: 0; + margin-block-start: 0; - li { - margin: 0 0 .75em; - } + li { + margin: 0 0 0.75em; + } - .person__bio & { - padding-left: 1.5rem; - padding-inline-start: 1.5rem; - } + .person__bio & { + padding-left: 1.5rem; + padding-inline-start: 1.5rem; + } } .docs-main ul, .post-main ul, .docs-main ol, .post-main ol { - margin: 1rem 0; + margin: 1rem 0; } ul[role="list"] { - list-style: none; - margin: 0; - padding: 0; + list-style: none; + margin: 0; + padding: 0; - li { - margin: 0; - } + li { + margin: 0; + } } ol { - list-style: decimal; + list-style: decimal; - li::marker { - color: var(--link-color); - } + li::marker { + color: var(--link-color); + } } p:empty { - margin: 0; - display: none; + margin: 0; + display: none; } figure { - margin-bottom: 4rem; - margin-block-end: 4rem; + margin-bottom: 4rem; + margin-block-end: 4rem; - img { - margin-bottom: 1rem; - margin-block-end: 1rem; - } + img { + margin-bottom: 1rem; + margin-block-end: 1rem; + } - figcaption { - color: var(--grey); - } + figcaption { + color: var(--grey); + } } img { - display: block; - position: relative; - max-width: 100%; - height: auto; + display: block; + position: relative; + max-width: 100%; + height: auto; } nav { - /* rarely do we display bullets for lists in navigation */ - ol, - ul { - list-style: none; - margin: 0; - padding: 0; - } + /* rarely do we display bullets for lists in navigation */ + ol, + ul { + list-style: none; + margin: 0; + padding: 0; + } } .video { - width: 90%; - max-width: 1400px; - margin: 2em auto; + width: 90%; + max-width: 1400px; + margin: 2em auto; - iframe { - aspect-ratio: 16 / 9; - width: 100%; - height: auto; - } + iframe { + aspect-ratio: 16 / 9; + width: 100%; + height: auto; + } } @media (prefers-reduced-motion: no-preference) { - *:focus-visible, - *.focus-visible { - transition: outline-offset .15s linear; - outline-offset: 3px; - } + *:focus-visible, + *.focus-visible { + transition: outline-offset 0.15s linear; + outline-offset: 3px; + } } /* typography */ .eyebrow { - color: var(--link-color); - font-size: 1rem; - font-weight: 500; - display: block; - margin-bottom: 1.5rem; - margin-block-end: 1.5rem; + color: var(--link-color); + font-size: 1rem; + font-weight: 500; + display: block; + margin-bottom: 1.5rem; + margin-block-end: 1.5rem; } h1, @@ -358,11 +359,12 @@ h3, h4, h5, h6 { - font-family: var(--display-font); - color: var(--headings-color); - font-weight: 500; - margin-top: 0; - margin-block-start: 0; + font-family: var(--display-font); + color: var(--headings-color); + font-weight: 500; + margin-top: 0; + margin-block-start: 0; + padding: 0.25rem 0; } h2, @@ -370,61 +372,61 @@ h3, h4, h5, h6 { - .docs-main &, - .components-main & { - margin-top: 3rem; - margin-bottom: 1.5rem; - margin-block-start: 3rem; - margin-block-end: 1.5rem; + .docs-main &, + .components-main & { + margin-top: 3rem; + margin-bottom: 1.5rem; + margin-block-start: 3rem; + margin-block-end: 1.5rem; - &:first-child { - margin-top: 0; - margin-block-start: 0; - } - } + &:first-child { + margin-top: 0; + margin-block-start: 0; + } + } } small, caption, cite, figcaption { - font-size: var(--step--1); + font-size: var(--step--1); } h6, .h6 { - font-size: var(--step-0); + font-size: var(--step-0); } h5, .h5 { - font-size: var(--step-0); // 20 + font-size: var(--step-0); // 20 } h4, .h4 { - font-size: var(--step-1); // 24 + font-size: var(--step-1); // 24 } h3, .h3 { - font-size: var(--step-2); - line-height: 1.2; + font-size: var(--step-2); + line-height: 1.2; } h2, .h2 { - font-size: var(--step-3); - line-height: 1.2; + font-size: var(--step-3); + line-height: 1.2; } h1, .h1 { - font-size: var(--step-4); - line-height: 1.2; + font-size: var(--step-4); + line-height: 1.2; } .h0 { - font-size: var(--step-6); - line-height: 1.2; + font-size: var(--step-6); + line-height: 1.2; } diff --git a/docs/src/assets/scss/languages.scss b/docs/src/assets/scss/languages.scss index 9b29097f0d49..468d55b319b4 100644 --- a/docs/src/assets/scss/languages.scss +++ b/docs/src/assets/scss/languages.scss @@ -1,55 +1,55 @@ .languages-list { - margin: 0; - padding: 0; - font-size: var(--step-0); - - li { - margin: 0; - - &:last-of-type a { - border-bottom: 0; - } - } - - a { - color: inherit; - width: 100%; - padding: .75rem .1rem; - text-decoration: none; - display: block; - display: flex; - align-items: center; - border-bottom: 1px solid var(--divider-color); - border-block-end: 1px solid var(--divider-color); - - &[aria-current="true"] { - font-weight: 500; - color: var(--link-color); - - &::after { - content: " âœ”ī¸"; - white-space: pre; - color: rgba(100%, 0%, 0%, 0); - text-shadow: 0 0 0 var(--headings-color); - } - } - - &:hover { - color: var(--link-color); - } - } + margin: 0; + padding: 0; + font-size: var(--step-0); + + li { + margin: 0; + + &:last-of-type a { + border-bottom: 0; + } + } + + a { + color: inherit; + width: 100%; + padding: 0.75rem 0.1rem; + text-decoration: none; + display: block; + display: flex; + align-items: center; + border-bottom: 1px solid var(--divider-color); + border-block-end: 1px solid var(--divider-color); + + &[aria-current="true"] { + font-weight: 500; + color: var(--link-color); + + &::after { + content: " âœ”ī¸"; + white-space: pre; + color: rgba(100%, 0%, 0%, 0); + text-shadow: 0 0 0 var(--headings-color); + } + } + + &:hover { + color: var(--link-color); + } + } } .languages-section .flag { - font-size: 2em; - margin-right: .5rem; - margin-inline-end: .5rem; + font-size: 2em; + margin-right: 0.5rem; + margin-inline-end: 0.5rem; } .languages-section .languages-list { - font-size: var(--step-1); - border-left: 4px solid var(--tab-border-color); - padding-left: 1rem; - border-inline-start: 4px solid var(--tab-border-color); - padding-inline-start: 1rem; + font-size: var(--step-1); + border-left: 4px solid var(--tab-border-color); + padding-left: 1rem; + border-inline-start: 4px solid var(--tab-border-color); + padding-inline-start: 1rem; } diff --git a/docs/src/assets/scss/print.scss b/docs/src/assets/scss/print.scss index 39dcc9470cde..b87f598cd837 100644 --- a/docs/src/assets/scss/print.scss +++ b/docs/src/assets/scss/print.scss @@ -6,34 +6,34 @@ p::first-line, div::first-line, blockquote::first-line, li::first-line { - background: transparent !important; - color: #000 !important; - box-shadow: none !important; - text-shadow: none !important; + background: transparent !important; + color: #000 !important; + box-shadow: none !important; + text-shadow: none !important; } body { - width: 100% !important; - margin: 0 !important; - padding: 0 !important; - line-height: 1.45; - font-family: Helvetica, sans-serif; - color: #000; - background: none; - font-size: 14pt; + width: 100% !important; + margin: 0 !important; + padding: 0 !important; + line-height: 1.45; + font-family: Helvetica, sans-serif; + color: #000; + background: none; + font-size: 14pt; } .grid { - display: block; + display: block; } main, .docs-content, .docs-wrapper { - display: block; - width: 100%; - max-width: 75ch; - margin: 1cm auto; + display: block; + width: 100%; + max-width: 75ch; + margin: 1cm auto; } /* Headings */ @@ -43,64 +43,66 @@ h3, h4, h5, h6 { - page-break-after: avoid; + page-break-after: avoid; } h1 { - font-size: 19pt; + font-size: 19pt; } h2 { - font-size: 17pt; + font-size: 17pt; } h3 { - font-size: 15pt; + font-size: 15pt; } h4, h5, h6 { - font-size: 14pt; + font-size: 14pt; } p, h2, h3 { - orphans: 3; - widows: 3; + orphans: 3; + widows: 3; } code { - font: 12pt Courier, monospace; + font: + 12pt Courier, + monospace; } blockquote { - margin: 1.2em; - padding: 1em; - font-size: 12pt; + margin: 1.2em; + padding: 1em; + font-size: 12pt; } hr { - background-color: #ccc; + background-color: #ccc; } /* Images */ img { - max-width: 100% !important; + max-width: 100% !important; } a img { - border: none; + border: none; } /* Links */ a:link, a:visited { - background: transparent; - font-weight: 700; - text-decoration: underline; - color: #333; + background: transparent; + font-weight: 700; + text-decoration: underline; + color: #333; } // a:link[href^="http://"]:after, @@ -110,104 +112,105 @@ a:visited { // } abbr[title]::after { - content: " ("attr(title) ")"; + content: " (" attr(title) ")"; } /* Don't show linked images */ -a[href^="http://"] { - color: #000; +a[href^="http://"] +{ + color: #000; } a[href$=".jpg"]::after, a[href$=".jpeg"]::after, a[href$=".gif"]::after, a[href$=".png"]::after { - content: " ("attr(href) ") "; - display: none; + content: " (" attr(href) ") "; + display: none; } /* Don't show links that are fragment identifiers, or use the `javascript:` pseudo protocol .. taken from html5boilerplate */ a[href^="#"]::after, a[href^="javascript:"]::after { - content: ""; + content: ""; } /* Table */ table { - margin: 1px; - text-align: left; + margin: 1px; + text-align: left; } th { - border-bottom: 1px solid #333; - font-weight: bold; + border-bottom: 1px solid #333; + font-weight: bold; } td { - border-bottom: 1px solid #333; + border-bottom: 1px solid #333; } th, td { - padding: 4px 10px 4px 0; + padding: 4px 10px 4px 0; } tfoot { - font-style: italic; + font-style: italic; } caption { - background: #fff; - margin-bottom: 2em; - text-align: left; + background: #fff; + margin-bottom: 2em; + text-align: left; } thead { - display: table-header-group; + display: table-header-group; } img, tr { - page-break-inside: avoid; + page-break-inside: avoid; } body > *:not(main), aside, *[class*="sidebar"] { - display: none; + display: none; } button, .c-btn.c-btn--playground, .docs-edit-link { - display: none; + display: none; } a[href^="http"]:not([href*="eslint.org"])::after { - content: " ("attr(href) ")"; + content: " (" attr(href) ")"; } .resource a::after { - display: none; + display: none; } ul { - page-break-inside: avoid; + page-break-inside: avoid; } .docs-toc, .docs-index, .docs-aside, #skip-link { - display: none; + display: none; } @media print { - @page { - margin: 1cm; - } + @page { + margin: 1cm; + } } #scroll-up-btn { - display: none; + display: none; } diff --git a/docs/src/assets/scss/styles.scss b/docs/src/assets/scss/styles.scss index 8907a6c4bf9f..7b4b4b43c5c9 100644 --- a/docs/src/assets/scss/styles.scss +++ b/docs/src/assets/scss/styles.scss @@ -1,35 +1,37 @@ -@import "tokens/themes"; -@import "tokens/spacing"; -@import "tokens/typography"; -@import "tokens/ui"; +@use "tokens/themes"; +@use "tokens/spacing"; +@use "tokens/typography"; +@use "tokens/ui"; +@use "tokens/opacity"; -@import "foundations"; -@import "syntax-highlighter"; -@import "docs-header"; -@import "docs-footer"; -@import "eslint-site-footer"; -@import "eslint-site-header"; -@import "forms"; -@import "docs"; -@import "versions"; -@import "languages"; +@use "foundations"; +@use "syntax-highlighter"; +@use "docs-header"; +@use "docs-footer"; +@use "eslint-site-footer"; +@use "eslint-site-header"; +@use "forms"; +@use "docs"; +@use "versions"; +@use "languages"; -@import "components/buttons"; -@import "components/docs-navigation"; -@import "components/toc"; -@import "components/search"; -@import "components/alert"; -@import "components/rules"; -@import "components/social-icons"; -@import "components/hero"; -@import "components/theme-switcher"; -@import "components/version-switcher"; -@import "components/language-switcher"; -@import "components/docs-index"; // docs index on the main docs pages -@import "components/index"; // used in component library -@import "components/tabs"; -@import "components/resources"; +@use "components/buttons"; +@use "components/docs-navigation"; +@use "components/toc"; +@use "components/search"; +@use "components/alert"; +@use "components/rules"; +@use "components/social-icons"; +@use "components/hero"; +@use "components/theme-switcher"; +@use "components/version-switcher"; +@use "components/language-switcher"; +@use "components/docs-index"; // docs index on the main docs pages +@use "components/index"; // used in component library +@use "components/tabs"; +@use "components/resources"; +@use "components/logo"; -@import "carbon-ads"; +@use "ads"; -@import "utilities"; +@use "utilities"; diff --git a/docs/src/assets/scss/syntax-highlighter.scss b/docs/src/assets/scss/syntax-highlighter.scss index cb744db0e381..868a96fcc722 100644 --- a/docs/src/assets/scss/syntax-highlighter.scss +++ b/docs/src/assets/scss/syntax-highlighter.scss @@ -1,74 +1,61 @@ code[class*="language-"], pre[class*="language-"] { - font-family: - var(--mono-font), - Consolas, - Monaco, - "Andale Mono", - "Ubuntu Mono", - monospace; - font-size: 1em; - text-align: left; - white-space: pre; - word-spacing: normal; - word-break: normal; - word-wrap: normal; - line-height: 1.5; - font-variant-ligatures: none; - tab-size: 4; - hyphens: none; + font-family: var(--mono-font), Consolas, Monaco, "Andale Mono", + "Ubuntu Mono", monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + font-variant-ligatures: none; + tab-size: 4; + hyphens: none; } @media print { - code[class*="language-"], - pre[class*="language-"] { - text-shadow: none; - } + code[class*="language-"], + pre[class*="language-"] { + text-shadow: none; + } } /* Code blocks */ pre[class*="language-"] { - padding: 1.5rem; - margin: 1.5rem 0; - overflow: auto; - border-radius: var(--border-radius); - background-color: var(--lightest-background-color); - color: var(--color-neutral-900); - - [data-theme="dark"] & { - color: var(--color-neutral-100); - } - - &.line-numbers-mode { - padding-left: calc(1.5rem + 2.4em + 1.2rem); - } + padding: 1.5rem; + margin: 1.5rem 0; + overflow: auto; + border-radius: var(--border-radius); + background-color: var(--lightest-background-color); + color: var(--code-text-color); + + &.line-numbers-mode { + padding-left: calc(1.5rem + 2.4em + 1.2rem); + } } :not(pre) > code[class*="language-"], pre[class*="language-"] { - background-color: var(--lightest-background-color); + background-color: var(--lightest-background-color); } /* Inline code */ :not(pre) > code[class*="language-"] { - padding: 0.1em; - border-radius: 0.3em; - white-space: normal; + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; } .token.comment, .token.prolog, .token.doctype, .token.cdata { - color: #6e7f8e; - - [data-theme="dark"] & { - color: #8e9fae; - } + color: var(--code-comments-color); } .token.namespace { - opacity: 0.7; + opacity: 0.7; } .token.selector, @@ -77,46 +64,91 @@ pre[class*="language-"] { .token.char, .token.builtin, .token.inserted { - color: var(--link-color); + color: var(--link-color); } .token.atrule, .token.attr-value, .token.keyword { - color: var(--link-color); + color: var(--link-color); } .token.important, .token.bold { - font-weight: bold; + font-weight: bold; } .token.italic { - font-style: italic; + font-style: italic; } .token.entity { - cursor: help; + cursor: help; +} + +.token.eslint-marked { + /* Draw the wavy line. */ + background: url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%206%203'%20enable-background%3D'new%200%200%206%203'%20height%3D'3'%20width%3D'6'%3E%3Cg%20fill%3D'%23f14c4c'%3E%3Cpolygon%20points%3D'5.5%2C0%202.5%2C3%201.1%2C3%204.1%2C0'%2F%3E%3Cpolygon%20points%3D'4%2C0%206%2C2%206%2C0.6%205.4%2C0'%2F%3E%3Cpolygon%20points%3D'0%2C2%201%2C3%202.4%2C3%200%2C0.6'%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E") + repeat-x bottom left; + + /* + * Since the character width of the token span is not constant, + * if we use it as is, we may see a shift in the border. + * To make the border shift less noticeable, draw it with a smaller width. + */ + background-size: 4.4px auto; +} + +.token.eslint-marked-on-line-feed { + /* Use `padding` to give it width so the marker on line feed code is visible. */ + padding-right: 8px; +} + +.token.eslint-marked:not(.eslint-marked-on-line-feed) + + .token.eslint-marked-on-line-feed { + /* + * If there is a marker before the same line, + * there is no need to make visible the marker on the line feed code. + */ + padding-right: 0; +} + +.token.eslint-marked-on-zero-width { + position: relative; + + /* Delete the wavy line. */ + background: none; +} + +.token.eslint-marked-on-zero-width::before { + content: ""; + position: absolute; + bottom: 0; + left: -2px; + border-left: 3px solid transparent; + border-right: 3px solid transparent; + border-bottom: 4px solid #d11; } .line-numbers-wrapper { - position: absolute; - top: 0; - left: 1.5rem; - text-align: right; - padding-top: 1.5rem; - font-size: 1em; - font-family: var(--mono-font), Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; - line-height: 1.5; - color: var(--icon-color); - font-variant-ligatures: none; - - .line-number { - user-select: none; - color: var(--icon-color); - display: inline-block; - font-variant-numeric: tabular-nums; - text-align: right; - width: 1.2em; - } + position: absolute; + top: 0; + left: 1.5rem; + text-align: right; + padding-top: 1.5rem; + font-size: 1em; + font-family: var(--mono-font), Consolas, Monaco, "Andale Mono", + "Ubuntu Mono", monospace; + line-height: 1.5; + color: var(--icon-color); + font-variant-ligatures: none; + + .line-number { + user-select: none; + color: var(--icon-color); + display: inline-block; + font-variant-numeric: tabular-nums; + text-align: right; + width: 1.2em; + } } diff --git a/docs/src/assets/scss/tokens/opacity.scss b/docs/src/assets/scss/tokens/opacity.scss new file mode 100644 index 000000000000..2a6fb240ed6f --- /dev/null +++ b/docs/src/assets/scss/tokens/opacity.scss @@ -0,0 +1,18 @@ +:root { + --opacity-100: 1; + --opacity-60: 0.6; +} + +@media (prefers-color-scheme: "dark") { + :root { + --logo-center-opacity: var(--opacity-60); + } +} + +html[data-theme="light"] { + --logo-center-opacity: var(--opacity-100); +} + +html[data-theme="dark"] { + --logo-center-opacity: var(--opacity-60); +} diff --git a/docs/src/assets/scss/tokens/spacing.scss b/docs/src/assets/scss/tokens/spacing.scss index 2bc542459b52..47e63745c918 100644 --- a/docs/src/assets/scss/tokens/spacing.scss +++ b/docs/src/assets/scss/tokens/spacing.scss @@ -1,69 +1,144 @@ /* @link https://utopia.fyi/space/calculator?c=320,16,1.125,1023,16,1.25,6,2,&s=0.75|0.5|0.25,1.5|2|3|4|6|8,l-2xl|xl-3xl|xl-4xl|l-3xl|s-l */ :root { - --fluid-min-width: 320; - --fluid-max-width: 1023; + --fluid-min-width: 320; + --fluid-max-width: 1023; - --fluid-screen: 100vw; - --fluid-bp: calc((var(--fluid-screen) - var(--fluid-min-width) / 16 * 1rem) / (var(--fluid-max-width) - var(--fluid-min-width))); + --fluid-screen: 100vw; + --fluid-bp: calc( + (var(--fluid-screen) - var(--fluid-min-width) / 16 * 1rem) / + (var(--fluid-max-width) - var(--fluid-min-width)) + ); - --fc-3xs-min: (var(--fc-s-min) * 0.25); - --fc-3xs-max: (var(--fc-s-max) * 0.25); + --fc-3xs-min: (var(--fc-s-min) * 0.25); + --fc-3xs-max: (var(--fc-s-max) * 0.25); - --fc-2xs-min: (var(--fc-s-min) * 0.5); - --fc-2xs-max: (var(--fc-s-max) * 0.5); + --fc-2xs-min: (var(--fc-s-min) * 0.5); + --fc-2xs-max: (var(--fc-s-max) * 0.5); - --fc-xs-min: (var(--fc-s-min) * 0.75); - --fc-xs-max: (var(--fc-s-max) * 0.75); + --fc-xs-min: (var(--fc-s-min) * 0.75); + --fc-xs-max: (var(--fc-s-max) * 0.75); - --fc-s-min: (var(--f-0-min, 16)); - --fc-s-max: (var(--f-0-max, 16)); + --fc-s-min: (var(--f-0-min, 16)); + --fc-s-max: (var(--f-0-max, 16)); - --fc-m-min: (var(--fc-s-min) * 1.5); - --fc-m-max: (var(--fc-s-max) * 1.5); + --fc-m-min: (var(--fc-s-min) * 1.5); + --fc-m-max: (var(--fc-s-max) * 1.5); - --fc-l-min: (var(--fc-s-min) * 2); - --fc-l-max: (var(--fc-s-max) * 2); + --fc-l-min: (var(--fc-s-min) * 2); + --fc-l-max: (var(--fc-s-max) * 2); - --fc-xl-min: (var(--fc-s-min) * 3); - --fc-xl-max: (var(--fc-s-max) * 3); + --fc-xl-min: (var(--fc-s-min) * 3); + --fc-xl-max: (var(--fc-s-max) * 3); - --fc-2xl-min: (var(--fc-s-min) * 4); - --fc-2xl-max: (var(--fc-s-max) * 4); + --fc-2xl-min: (var(--fc-s-min) * 4); + --fc-2xl-max: (var(--fc-s-max) * 4); - --fc-3xl-min: (var(--fc-s-min) * 6); - --fc-3xl-max: (var(--fc-s-max) * 6); + --fc-3xl-min: (var(--fc-s-min) * 6); + --fc-3xl-max: (var(--fc-s-max) * 6); - --fc-4xl-min: (var(--fc-s-min) * 8); - --fc-4xl-max: (var(--fc-s-max) * 8); + --fc-4xl-min: (var(--fc-s-min) * 8); + --fc-4xl-max: (var(--fc-s-max) * 8); - /* T-shirt sizes */ - --space-3xs: calc(((var(--fc-3xs-min) / 16) * 1rem) + (var(--fc-3xs-max) - var(--fc-3xs-min)) * var(--fluid-bp)); - --space-2xs: calc(((var(--fc-2xs-min) / 16) * 1rem) + (var(--fc-2xs-max) - var(--fc-2xs-min)) * var(--fluid-bp)); - --space-xs: calc(((var(--fc-xs-min) / 16) * 1rem) + (var(--fc-xs-max) - var(--fc-xs-min)) * var(--fluid-bp)); - --space-s: calc(((var(--fc-s-min) / 16) * 1rem) + (var(--fc-s-max) - var(--fc-s-min)) * var(--fluid-bp)); - --space-m: calc(((var(--fc-m-min) / 16) * 1rem) + (var(--fc-m-max) - var(--fc-m-min)) * var(--fluid-bp)); - --space-l: calc(((var(--fc-l-min) / 16) * 1rem) + (var(--fc-l-max) - var(--fc-l-min)) * var(--fluid-bp)); - --space-xl: calc(((var(--fc-xl-min) / 16) * 1rem) + (var(--fc-xl-max) - var(--fc-xl-min)) * var(--fluid-bp)); - --space-2xl: calc(((var(--fc-2xl-min) / 16) * 1rem) + (var(--fc-2xl-max) - var(--fc-2xl-min)) * var(--fluid-bp)); - --space-3xl: calc(((var(--fc-3xl-min) / 16) * 1rem) + (var(--fc-3xl-max) - var(--fc-3xl-min)) * var(--fluid-bp)); - --space-4xl: calc(((var(--fc-4xl-min) / 16) * 1rem) + (var(--fc-4xl-max) - var(--fc-4xl-min)) * var(--fluid-bp)); + /* T-shirt sizes */ + --space-3xs: calc( + ((var(--fc-3xs-min) / 16) * 1rem) + + (var(--fc-3xs-max) - var(--fc-3xs-min)) * var(--fluid-bp) + ); + --space-2xs: calc( + ((var(--fc-2xs-min) / 16) * 1rem) + + (var(--fc-2xs-max) - var(--fc-2xs-min)) * var(--fluid-bp) + ); + --space-xs: calc( + ((var(--fc-xs-min) / 16) * 1rem) + (var(--fc-xs-max) - var(--fc-xs-min)) * + var(--fluid-bp) + ); + --space-s: calc( + ((var(--fc-s-min) / 16) * 1rem) + (var(--fc-s-max) - var(--fc-s-min)) * + var(--fluid-bp) + ); + --space-m: calc( + ((var(--fc-m-min) / 16) * 1rem) + (var(--fc-m-max) - var(--fc-m-min)) * + var(--fluid-bp) + ); + --space-l: calc( + ((var(--fc-l-min) / 16) * 1rem) + (var(--fc-l-max) - var(--fc-l-min)) * + var(--fluid-bp) + ); + --space-xl: calc( + ((var(--fc-xl-min) / 16) * 1rem) + (var(--fc-xl-max) - var(--fc-xl-min)) * + var(--fluid-bp) + ); + --space-2xl: calc( + ((var(--fc-2xl-min) / 16) * 1rem) + + (var(--fc-2xl-max) - var(--fc-2xl-min)) * var(--fluid-bp) + ); + --space-3xl: calc( + ((var(--fc-3xl-min) / 16) * 1rem) + + (var(--fc-3xl-max) - var(--fc-3xl-min)) * var(--fluid-bp) + ); + --space-4xl: calc( + ((var(--fc-4xl-min) / 16) * 1rem) + + (var(--fc-4xl-max) - var(--fc-4xl-min)) * var(--fluid-bp) + ); - /* One-up pairs */ - --space-3xs-2xs: calc(((var(--fc-3xs-min) / 16) * 1rem) + (var(--fc-2xs-max) - var(--fc-3xs-min)) * var(--fluid-bp)); - --space-2xs-xs: calc(((var(--fc-2xs-min) / 16) * 1rem) + (var(--fc-xs-max) - var(--fc-2xs-min)) * var(--fluid-bp)); - --space-xs-s: calc(((var(--fc-xs-min) / 16) * 1rem) + (var(--fc-s-max) - var(--fc-xs-min)) * var(--fluid-bp)); - --space-s-m: calc(((var(--fc-s-min) / 16) * 1rem) + (var(--fc-m-max) - var(--fc-s-min)) * var(--fluid-bp)); - --space-m-l: calc(((var(--fc-m-min) / 16) * 1rem) + (var(--fc-l-max) - var(--fc-m-min)) * var(--fluid-bp)); - --space-l-xl: calc(((var(--fc-l-min) / 16) * 1rem) + (var(--fc-xl-max) - var(--fc-l-min)) * var(--fluid-bp)); - --space-xl-2xl: calc(((var(--fc-xl-min) / 16) * 1rem) + (var(--fc-2xl-max) - var(--fc-xl-min)) * var(--fluid-bp)); - --space-2xl-3xl: calc(((var(--fc-2xl-min) / 16) * 1rem) + (var(--fc-3xl-max) - var(--fc-2xl-min)) * var(--fluid-bp)); - --space-3xl-4xl: calc(((var(--fc-3xl-min) / 16) * 1rem) + (var(--fc-4xl-max) - var(--fc-3xl-min)) * var(--fluid-bp)); + /* One-up pairs */ + --space-3xs-2xs: calc( + ((var(--fc-3xs-min) / 16) * 1rem) + + (var(--fc-2xs-max) - var(--fc-3xs-min)) * var(--fluid-bp) + ); + --space-2xs-xs: calc( + ((var(--fc-2xs-min) / 16) * 1rem) + + (var(--fc-xs-max) - var(--fc-2xs-min)) * var(--fluid-bp) + ); + --space-xs-s: calc( + ((var(--fc-xs-min) / 16) * 1rem) + (var(--fc-s-max) - var(--fc-xs-min)) * + var(--fluid-bp) + ); + --space-s-m: calc( + ((var(--fc-s-min) / 16) * 1rem) + (var(--fc-m-max) - var(--fc-s-min)) * + var(--fluid-bp) + ); + --space-m-l: calc( + ((var(--fc-m-min) / 16) * 1rem) + (var(--fc-l-max) - var(--fc-m-min)) * + var(--fluid-bp) + ); + --space-l-xl: calc( + ((var(--fc-l-min) / 16) * 1rem) + (var(--fc-xl-max) - var(--fc-l-min)) * + var(--fluid-bp) + ); + --space-xl-2xl: calc( + ((var(--fc-xl-min) / 16) * 1rem) + + (var(--fc-2xl-max) - var(--fc-xl-min)) * var(--fluid-bp) + ); + --space-2xl-3xl: calc( + ((var(--fc-2xl-min) / 16) * 1rem) + + (var(--fc-3xl-max) - var(--fc-2xl-min)) * var(--fluid-bp) + ); + --space-3xl-4xl: calc( + ((var(--fc-3xl-min) / 16) * 1rem) + + (var(--fc-4xl-max) - var(--fc-3xl-min)) * var(--fluid-bp) + ); - /* Custom pairs */ - --space-l-2xl: calc(((var(--fc-l-min) / 16) * 1rem) + (var(--fc-2xl-max) - var(--fc-l-min)) * var(--fluid-bp)); - --space-xl-3xl: calc(((var(--fc-xl-min) / 16) * 1rem) + (var(--fc-3xl-max) - var(--fc-xl-min)) * var(--fluid-bp)); - --space-xl-4xl: calc(((var(--fc-xl-min) / 16) * 1rem) + (var(--fc-4xl-max) - var(--fc-xl-min)) * var(--fluid-bp)); - --space-l-3xl: calc(((var(--fc-l-min) / 16) * 1rem) + (var(--fc-3xl-max) - var(--fc-l-min)) * var(--fluid-bp)); - --space-s-l: calc(((var(--fc-s-min) / 16) * 1rem) + (var(--fc-l-max) - var(--fc-s-min)) * var(--fluid-bp)); + /* Custom pairs */ + --space-l-2xl: calc( + ((var(--fc-l-min) / 16) * 1rem) + (var(--fc-2xl-max) - var(--fc-l-min)) * + var(--fluid-bp) + ); + --space-xl-3xl: calc( + ((var(--fc-xl-min) / 16) * 1rem) + + (var(--fc-3xl-max) - var(--fc-xl-min)) * var(--fluid-bp) + ); + --space-xl-4xl: calc( + ((var(--fc-xl-min) / 16) * 1rem) + + (var(--fc-4xl-max) - var(--fc-xl-min)) * var(--fluid-bp) + ); + --space-l-3xl: calc( + ((var(--fc-l-min) / 16) * 1rem) + (var(--fc-3xl-max) - var(--fc-l-min)) * + var(--fluid-bp) + ); + --space-s-l: calc( + ((var(--fc-s-min) / 16) * 1rem) + (var(--fc-l-max) - var(--fc-s-min)) * + var(--fluid-bp) + ); } diff --git a/docs/src/assets/scss/tokens/themes.scss b/docs/src/assets/scss/tokens/themes.scss index a8104a5b3469..6fb58f6bb43a 100644 --- a/docs/src/assets/scss/tokens/themes.scss +++ b/docs/src/assets/scss/tokens/themes.scss @@ -1,158 +1,240 @@ :root { - /* Tier 1 variables */ - // colors - --color-neutral-25: #fcfcfd; - --color-neutral-50: #f9fafb; - --color-neutral-100: #f2f4f7; - --color-neutral-200: #e4e7ec; - --color-neutral-300: #d0d5dd; - --color-neutral-400: #98a2b3; - --color-neutral-500: #667085; - --color-neutral-600: #475467; - --color-neutral-700: #344054; - --color-neutral-800: #1d2939; - --color-neutral-900: #101828; - - --color-primary-25: #fbfbff; - --color-primary-50: #f6f6fe; - --color-primary-100: #ececfd; - --color-primary-200: #dedeff; - --color-primary-300: #ccccfa; - --color-primary-400: #b7b7ff; - --color-primary-500: #a0a0f5; - --color-primary-600: #8080f2; - --color-primary-700: #6358d4; - --color-primary-800: #4b32c3; - --color-primary-900: #341bab; - - --color-warning-25: #fffcf5; - --color-warning-50: #fffaeb; - --color-warning-100: #fef0c7; - --color-warning-200: #fedf89; - --color-warning-300: #fec84b; - --color-warning-400: #fdb022; - --color-warning-500: #f79009; - --color-warning-600: #dc6803; - --color-warning-700: #b54708; - --color-warning-800: #93370d; - --color-warning-900: #7a2e0e; - - --color-success-25: #f6fef9; - --color-success-50: #ecfdf3; - --color-success-100: #d1fadf; - --color-success-200: #a6f4c5; - --color-success-300: #6ce9a6; - --color-success-400: #32d583; - --color-success-500: #12b76a; - --color-success-600: #039855; - --color-success-700: #027a48; - --color-success-800: #05603a; - --color-success-900: #054f31; - - --color-rose-25: #fff5f6; - --color-rose-50: #fff1f3; - --color-rose-100: #ffe4e8; - --color-rose-200: #fecdd6; - --color-rose-300: #fea3b4; - --color-rose-400: #fd6f8e; - --color-rose-500: #f63d68; - --color-rose-600: #e31b54; - --color-rose-700: #c01048; - --color-rose-800: #a11043; - --color-rose-900: #89123e; - - /* Tier 2 variables */ - --primary-button-background-color: var(--color-primary-800); - --primary-button-hover-color: var(--color-primary-900); - --primary-button-text-color: #fff; - --secondary-button-background-color: var(--color-primary-50); - --secondary-button-hover-color: var(--color-primary-100); - --secondary-button-text-color: var(--color-brand); - --ghost-button-background-color: var(--color-primary-50); - --ghost-button-text-color: var(--color-brand); - - --color-brand: var(--color-primary-800); - --body-background-color: #fff; - --body-text-color: var(--color-neutral-500); - --headings-color: var(--color-neutral-900); - - --border-color: var(--color-neutral-300); - --divider-color: var(--color-neutral-200); - - --icon-color: var(--color-neutral-400); - --dark-icon-color: var(--color-neutral-500); - --link-color: var(--color-primary-800); - - --lighter-background-color: var(--color-neutral-100); - --lightest-background-color: var(--color-neutral-50); - --docs-lightest-background-color: var(--color-primary-50); - --hero-background-color: var(--color-neutral-25); - --footer-background-color: var(--color-neutral-25); - --outline-color: var(--color-brand); + /* Tier 1 variables */ + // colors + --color-neutral-25: #fcfcfd; + --color-neutral-50: #f9fafb; + --color-neutral-100: #f2f4f7; + --color-neutral-200: #e4e7ec; + --color-neutral-300: #d0d5dd; + --color-neutral-400: #98a2b3; + --color-neutral-500: #667085; + --color-neutral-600: #475467; + --color-neutral-700: #344054; + --color-neutral-800: #1d2939; + --color-neutral-900: #101828; + + --color-primary-25: #fbfbff; + --color-primary-50: #f6f6fe; + --color-primary-100: #ececfd; + --color-primary-200: #dedeff; + --color-primary-300: #ccccfa; + --color-primary-400: #b7b7ff; + --color-primary-500: #a0a0f5; + --color-primary-600: #8080f2; + --color-primary-700: #6358d4; + --color-primary-800: #4b32c3; + --color-primary-900: #341bab; + + --color-warning-25: #fffcf5; + --color-warning-50: #fffaeb; + --color-warning-100: #fef0c7; + --color-warning-200: #fedf89; + --color-warning-300: #fec84b; + --color-warning-400: #fdb022; + --color-warning-500: #f79009; + --color-warning-600: #dc6803; + --color-warning-700: #b54708; + --color-warning-800: #93370d; + --color-warning-900: #7a2e0e; + + --color-success-25: #f6fef9; + --color-success-50: #ecfdf3; + --color-success-100: #d1fadf; + --color-success-200: #a6f4c5; + --color-success-300: #6ce9a6; + --color-success-400: #32d583; + --color-success-500: #12b76a; + --color-success-600: #039855; + --color-success-700: #027a48; + --color-success-800: #05603a; + --color-success-900: #054f31; + + --color-rose-25: #fff5f6; + --color-rose-50: #fff1f3; + --color-rose-100: #ffe4e8; + --color-rose-200: #fecdd6; + --color-rose-300: #fea3b4; + --color-rose-400: #fd6f8e; + --color-rose-500: #f63d68; + --color-rose-600: #e31b54; + --color-rose-700: #c01048; + --color-rose-800: #a11043; + --color-rose-900: #89123e; + + /* Tier 2 variables */ + --primary-button-background-color: var(--color-primary-800); + --primary-button-hover-color: var(--color-primary-900); + --primary-button-text-color: #fff; + --secondary-button-background-color: var(--color-primary-50); + --secondary-button-hover-color: var(--color-primary-100); + --secondary-button-text-color: var(--color-brand); + --ghost-button-background-color: var(--color-primary-50); + --ghost-button-text-color: var(--color-brand); + + --color-brand: var(--color-primary-800); + --body-background-color: #fff; + --body-text-color: var(--color-neutral-500); + --code-comments-color: var(--color-neutral-500); + --headings-color: var(--color-neutral-900); + + --border-color: var(--color-neutral-300); + --divider-color: var(--color-neutral-200); + + --icon-color: var(--color-neutral-400); + --dark-icon-color: var(--color-neutral-500); + --link-color: var(--color-primary-800); + + --lighter-background-color: var(--color-neutral-100); + --lightest-background-color: var(--color-neutral-50); + --docs-lightest-background-color: var(--color-primary-50); + --hero-background-color: var(--color-neutral-25); + --footer-background-color: var(--color-neutral-25); + --outline-color: var(--color-brand); + --img-background-color: #fff; + + --code-text-color: var(--color-neutral-900); + + --logo-color: var(--color-primary-800); + --logo-center-color: var(--color-primary-600); + + --alert-tip-heading-color: var(--color-success-700); + --alert-tip-color: var(--color-success-600); + --alert-tip-background-color: var(--color-success-25); + + --alert-important-heading-color: var(--color-warning-700); + --alert-important-color: var(--color-warning-600); + --alert-important-background-color: var(--color-warning-25); + + --alert-warning-heading-color: var(--color-rose-700); + --alert-warning-color: var(--color-rose-600); + --alert-warning-background-color: var(--color-rose-25); + + --rule-status-background-color: var(--color-rose-50); } @media (prefers-color-scheme: dark) { - :root { - --body-background-color: var(--color-neutral-900); - --body-text-color: var(--color-neutral-300); - --headings-color: #fff; - - --divider-color: var(--color-neutral-600); - --border-color: var(--color-neutral-500); - - --icon-color: var(--body-text-color); - --dark-icon-color: #fff; - --link-color: var(--color-primary-400); - - --lighter-background-color: var(--color-neutral-800); - --lightest-background-color: var(--color-neutral-800); - --docs-lightest-background-color: var(--color-neutral-800); - --hero-background-color: var(--color-neutral-800); - --footer-background-color: var(--color-neutral-800); - --outline-color: #fff; - } + :root { + --body-background-color: var(--color-neutral-900); + --body-text-color: var(--color-neutral-300); + --code-comments-color: var(--color-neutral-400); + --headings-color: #fff; + + --divider-color: var(--color-neutral-600); + --border-color: var(--color-neutral-500); + + --icon-color: var(--body-text-color); + --dark-icon-color: #fff; + --link-color: var(--color-primary-400); + + --lighter-background-color: var(--color-neutral-800); + --lightest-background-color: var(--color-neutral-800); + --docs-lightest-background-color: var(--color-neutral-800); + --hero-background-color: var(--color-neutral-800); + --footer-background-color: var(--color-neutral-800); + --outline-color: #fff; + --img-background-color: var(--color-neutral-300); + + --code-text-color: var(--color-neutral-100); + + --logo-color: #fff; + --logo-center-color: #fff; + + --alert-tip-heading-color: var(--color-success-200); + --alert-tip-color: var(--color-success-300); + --alert-tip-background-color: var(--color-success-900); + + --alert-important-heading-color: var(--color-warning-200); + --alert-important-color: var(--color-warning-300); + --alert-important-background-color: var(--color-warning-900); + + --alert-warning-heading-color: var(--color-rose-200); + --alert-warning-color: var(--color-rose-300); + --alert-warning-background-color: var(--color-rose-900); + + --rule-status-background-color: var(--color-neutral-900); + } } html[data-theme="light"] { - --body-background-color: #fff; - --body-text-color: var(--color-neutral-500); - --headings-color: var(--color-neutral-900); - - --border-color: var(--color-neutral-300); - --divider-color: var(--color-neutral-200); - - --icon-color: var(--color-neutral-400); - --dark-icon-color: var(--color-neutral-500); - --link-color: var(--color-primary-800); - - --lighter-background-color: var(--color-neutral-100); - --lightest-background-color: var(--color-neutral-50); - --docs-lightest-background-color: var(--color-primary-50); - --hero-background-color: var(--color-neutral-25); - --footer-background-color: var(--color-neutral-25); - --outline-color: var(--color-brand); - --img-background-color: #fff; + --body-background-color: #fff; + --body-text-color: var(--color-neutral-500); + --code-comments-color: var(--color-neutral-500); + --headings-color: var(--color-neutral-900); + + --border-color: var(--color-neutral-300); + --divider-color: var(--color-neutral-200); + + --icon-color: var(--color-neutral-400); + --dark-icon-color: var(--color-neutral-500); + --link-color: var(--color-primary-800); + + --lighter-background-color: var(--color-neutral-100); + --lightest-background-color: var(--color-neutral-50); + --docs-lightest-background-color: var(--color-primary-50); + --hero-background-color: var(--color-neutral-25); + --footer-background-color: var(--color-neutral-25); + --outline-color: var(--color-brand); + --img-background-color: #fff; + + --code-text-color: var(--color-neutral-900); + + --logo-color: var(--color-primary-800); + --logo-center-color: var(--color-primary-600); + + --alert-tip-heading-color: var(--color-success-700); + --alert-tip-color: var(--color-success-600); + --alert-tip-background-color: var(--color-success-25); + + --alert-important-heading-color: var(--color-warning-700); + --alert-important-color: var(--color-warning-600); + --alert-important-background-color: var(--color-warning-25); + + --alert-warning-heading-color: var(--color-rose-700); + --alert-warning-color: var(--color-rose-600); + --alert-warning-background-color: var(--color-rose-25); + + --rule-status-background-color: var(--color-rose-50); } html[data-theme="dark"] { - color-scheme: dark; - - --body-background-color: var(--color-neutral-900); - --body-text-color: var(--color-neutral-300); - --headings-color: #fff; - - --divider-color: var(--color-neutral-600); - --border-color: var(--color-neutral-500); - - --icon-color: var(--body-text-color); - --dark-icon-color: #fff; - --link-color: var(--color-primary-400); - - --lighter-background-color: var(--color-neutral-800); - --lightest-background-color: var(--color-neutral-800); - --docs-lightest-background-color: var(--color-neutral-800); - --hero-background-color: var(--color-neutral-800); - --footer-background-color: var(--color-neutral-800); - --outline-color: #fff; - --img-background-color: var(--color-neutral-300); + color-scheme: dark; + + --body-background-color: var(--color-neutral-900); + --body-text-color: var(--color-neutral-300); + --code-comments-color: var(--color-neutral-400); + --headings-color: #fff; + + --divider-color: var(--color-neutral-600); + --border-color: var(--color-neutral-500); + + --icon-color: var(--body-text-color); + --dark-icon-color: #fff; + --link-color: var(--color-primary-400); + + --lighter-background-color: var(--color-neutral-800); + --lightest-background-color: var(--color-neutral-800); + --docs-lightest-background-color: var(--color-neutral-800); + --hero-background-color: var(--color-neutral-800); + --footer-background-color: var(--color-neutral-800); + --outline-color: #fff; + --img-background-color: var(--color-neutral-300); + + --code-text-color: var(--color-neutral-100); + + --logo-color: #fff; + --logo-center-color: #fff; + + --alert-tip-heading-color: var(--color-success-200); + --alert-tip-color: var(--color-success-300); + --alert-tip-background-color: var(--color-success-900); + + --alert-important-heading-color: var(--color-warning-200); + --alert-important-color: var(--color-warning-300); + --alert-important-background-color: var(--color-warning-900); + + --alert-warning-heading-color: var(--color-rose-200); + --alert-warning-color: var(--color-rose-300); + --alert-warning-background-color: var(--color-rose-900); + + --rule-status-background-color: var(--color-neutral-900); } diff --git a/docs/src/assets/scss/tokens/typography.scss b/docs/src/assets/scss/tokens/typography.scss index a9e935b2a01f..a03390682767 100644 --- a/docs/src/assets/scss/tokens/typography.scss +++ b/docs/src/assets/scss/tokens/typography.scss @@ -1,78 +1,89 @@ /* @link https://utopia.fyi/type/calculator?c=320,16,1.125,1280,16,1.25,6,2,&s=0.75|0.5|0.25,1.5|2|3|4|6,s-l */ @media screen and (min-width: 1280px) { - :root { - --fluid-screen: calc(var(--fluid-max-width) * 1px); - } + :root { + --fluid-screen: calc(var(--fluid-max-width) * 1px); + } } :root { - --fluid-min-width: 320; - --fluid-max-width: 1280; + --fluid-min-width: 320; + --fluid-max-width: 1280; - --fluid-screen: 100vw; - --fluid-bp: calc((var(--fluid-screen) - var(--fluid-min-width) / 16 * 1rem) / (var(--fluid-max-width) - var(--fluid-min-width))); + --fluid-screen: 100vw; + --fluid-bp: calc( + (var(--fluid-screen) - var(--fluid-min-width) / 16 * 1rem) / + (var(--fluid-max-width) - var(--fluid-min-width)) + ); - --f--2-min: 12.64; - --f--2-max: 10.24; - --step--2: calc(((var(--f--2-min) / 16) * 1rem) + (var(--f--2-max) - var(--f--2-min)) * var(--fluid-bp)); + --f--2-min: 12.64; + --f--2-max: 10.24; + --step--2: calc( + ((var(--f--2-min) / 16) * 1rem) + (var(--f--2-max) - var(--f--2-min)) * + var(--fluid-bp) + ); - --f--1-min: 14.22; - --f--1-max: 12.80; - --step--1: calc(((var(--f--1-min) / 16) * 1rem) + (var(--f--1-max) - var(--f--1-min)) * var(--fluid-bp)); + --f--1-min: 14.22; + --f--1-max: 12.8; + --step--1: calc( + ((var(--f--1-min) / 16) * 1rem) + (var(--f--1-max) - var(--f--1-min)) * + var(--fluid-bp) + ); - --f-0-min: 16.00; - --f-0-max: 16.00; - --step-0: calc(((var(--f-0-min) / 16) * 1rem) + (var(--f-0-max) - var(--f-0-min)) * var(--fluid-bp)); + --f-0-min: 16; + --f-0-max: 16; + --step-0: calc( + ((var(--f-0-min) / 16) * 1rem) + (var(--f-0-max) - var(--f-0-min)) * + var(--fluid-bp) + ); - --f-1-min: 18.00; - --f-1-max: 20.00; - --step-1: calc(((var(--f-1-min) / 16) * 1rem) + (var(--f-1-max) - var(--f-1-min)) * var(--fluid-bp)); + --f-1-min: 18; + --f-1-max: 20; + --step-1: calc( + ((var(--f-1-min) / 16) * 1rem) + (var(--f-1-max) - var(--f-1-min)) * + var(--fluid-bp) + ); - --f-2-min: 20.25; - --f-2-max: 25.00; - --step-2: calc(((var(--f-2-min) / 16) * 1rem) + (var(--f-2-max) - var(--f-2-min)) * var(--fluid-bp)); + --f-2-min: 20.25; + --f-2-max: 25; + --step-2: calc( + ((var(--f-2-min) / 16) * 1rem) + (var(--f-2-max) - var(--f-2-min)) * + var(--fluid-bp) + ); - --f-3-min: 22.78; - --f-3-max: 31.25; - --step-3: calc(((var(--f-3-min) / 16) * 1rem) + (var(--f-3-max) - var(--f-3-min)) * var(--fluid-bp)); + --f-3-min: 22.78; + --f-3-max: 31.25; + --step-3: calc( + ((var(--f-3-min) / 16) * 1rem) + (var(--f-3-max) - var(--f-3-min)) * + var(--fluid-bp) + ); - --f-4-min: 25.63; - --f-4-max: 39.06; - --step-4: calc(((var(--f-4-min) / 16) * 1rem) + (var(--f-4-max) - var(--f-4-min)) * var(--fluid-bp)); + --f-4-min: 25.63; + --f-4-max: 39.06; + --step-4: calc( + ((var(--f-4-min) / 16) * 1rem) + (var(--f-4-max) - var(--f-4-min)) * + var(--fluid-bp) + ); - --f-5-min: 28.83; - --f-5-max: 48.83; - --step-5: calc(((var(--f-5-min) / 16) * 1rem) + (var(--f-5-max) - var(--f-5-min)) * var(--fluid-bp)); + --f-5-min: 28.83; + --f-5-max: 48.83; + --step-5: calc( + ((var(--f-5-min) / 16) * 1rem) + (var(--f-5-max) - var(--f-5-min)) * + var(--fluid-bp) + ); - --f-6-min: 32.44; - --f-6-max: 61.04; - --step-6: calc(((var(--f-6-min) / 16) * 1rem) + (var(--f-6-max) - var(--f-6-min)) * var(--fluid-bp)); + --f-6-min: 32.44; + --f-6-max: 61.04; + --step-6: calc( + ((var(--f-6-min) / 16) * 1rem) + (var(--f-6-max) - var(--f-6-min)) * + var(--fluid-bp) + ); - --mono-font: "Mono Punctuators", "Space Mono", monospace; - --text-font: - "Inter", - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - Roboto, - Helvetica, - Arial, - sans-serif, - "Apple Color Emoji", - "Twemoji Country Flags", - "Segoe UI Emoji", - "Segoe UI Symbol"; - --display-font: - "Space Grotesk", - -apple-system, - BlinkMacSystemFont, - "Segoe UI", - Roboto, - Helvetica, - Arial, - sans-serif, - "Apple Color Emoji", - "Segoe UI Emoji", - "Segoe UI Symbol"; + --mono-font: "Mono Punctuators", "Space Mono", monospace; + --text-font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + Helvetica, Arial, sans-serif, "Apple Color Emoji", + "Twemoji Country Flags", "Segoe UI Emoji", "Segoe UI Symbol"; + --display-font: "Space Grotesk", -apple-system, BlinkMacSystemFont, + "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol"; } diff --git a/docs/src/assets/scss/tokens/ui.scss b/docs/src/assets/scss/tokens/ui.scss index 49380e12da85..7865467affa6 100644 --- a/docs/src/assets/scss/tokens/ui.scss +++ b/docs/src/assets/scss/tokens/ui.scss @@ -1,9 +1,8 @@ :root { - // elevations - --shadow-lg: - 0 12px 16px -4px rgba(16, 24, 40, 0.1), - 0 4px 6px -2px rgba(16, 24, 40, 0.05); - --shadow-xs: 0 1px 2px rgba(16, 24, 40, 0.05); + // elevations + --shadow-lg: 0 12px 16px -4px rgba(16, 24, 40, 0.1), + 0 4px 6px -2px rgba(16, 24, 40, 0.05); + --shadow-xs: 0 1px 2px rgba(16, 24, 40, 0.05); - --border-radius: .5rem; + --border-radius: 0.5rem; } diff --git a/docs/src/assets/scss/utilities.scss b/docs/src/assets/scss/utilities.scss index c296358837ee..5705bed3a068 100644 --- a/docs/src/assets/scss/utilities.scss +++ b/docs/src/assets/scss/utilities.scss @@ -1,171 +1,171 @@ .grid { - @media all and (min-width: 1024px) { - display: grid; - grid-template-columns: repeat(12, 1fr); - grid-gap: 2rem; - align-items: start; - } + @media all and (min-width: 1024px) { + display: grid; + grid-template-columns: repeat(12, 1fr); + grid-gap: 2rem; + align-items: start; + } } .visually-hidden { - clip: rect(0 0 0 0); - clip-path: inset(100%); - height: 1px; - overflow: hidden; - position: absolute; - width: 1px; - white-space: nowrap; + clip: rect(0 0 0 0); + clip-path: inset(100%); + height: 1px; + overflow: hidden; + position: absolute; + width: 1px; + white-space: nowrap; } [hidden] { - display: none !important; + display: none !important; } .mobile-only { - @media all and (min-width: 1024px) { - display: none; - } + @media all and (min-width: 1024px) { + display: none; + } } .desktop-only { - @media all and (max-width: 1023px) { - display: none; - } + @media all and (max-width: 1023px) { + display: none; + } } .text.text { - color: inherit; - font: inherit; - font-family: var(--text-font); - margin: 0; + color: inherit; + font: inherit; + font-family: var(--text-font); + margin: 0; } .color-brand { - color: var(--link-color); + color: var(--link-color); } .font-weight-medium { - font-weight: 500; + font-weight: 500; } .center-text { - text-align: center; - grid-column: 1 / -1; + text-align: center; + grid-column: 1 / -1; } .text-dark { - color: var(--headings-color); + color: var(--headings-color); } .divider { - border-bottom: 1px solid var(--divider-color); - border-block-end: 1px solid var(--divider-color); + border-bottom: 1px solid var(--divider-color); + border-block-end: 1px solid var(--divider-color); } .fs-step--1 { - font-size: .875rem; + font-size: 0.875rem; } .fs-step-0 { - font-size: var(--step-0); + font-size: var(--step-0); } .fs-step-1 { - font-size: var(--step-1); + font-size: var(--step-1); } .fs-step-2 { - font-size: var(--step-2); + font-size: var(--step-2); } .fs-step-3 { - font-size: var(--step-3); + font-size: var(--step-3); } .fs-step-4 { - font-size: var(--step-4); + font-size: var(--step-4); } .fs-step-5 { - font-size: var(--step-5); + font-size: var(--step-5); } .fs-step-6 { - font-size: var(--step-6); + font-size: var(--step-6); } .grid--center-items { - align-items: center; + align-items: center; } .span-1-3 { - grid-column: 1 / 4; + grid-column: 1 / 4; } .span-1-4 { - grid-column: 1 / 5; + grid-column: 1 / 5; } .span-1-5 { - grid-column: 1 / 6; + grid-column: 1 / 6; } .span-1-6 { - grid-column: 1 / 7; + grid-column: 1 / 7; } .span-1-7 { - grid-column: 1 / 8; + grid-column: 1 / 8; } .span-1-12 { - grid-column: 1 / -1; + grid-column: 1 / -1; } .span-4-12 { - grid-column: 4 / 13; + grid-column: 4 / 13; } .span-6-12 { - grid-column: 6 / 13; + grid-column: 6 / 13; } .span-7-12 { - grid-column: 7 / 13; + grid-column: 7 / 13; } .span-8-12 { - grid-column: 8 / 13; + grid-column: 8 / 13; } .span-10-12 { - grid-column: 10 / 13; + grid-column: 10 / 13; } .span-11-12 { - grid-column: 11 / 13; + grid-column: 11 / 13; } .span-4-9 { - grid-column: 4 / 10; + grid-column: 4 / 10; } .span-4-11 { - grid-column: 4 / 11; + grid-column: 4 / 11; } .span-5-12 { - grid-column: 5 / 12; + grid-column: 5 / 12; } .span-3-10 { - grid-column: 3 / 11; + grid-column: 3 / 11; } .span-6-7 { - grid-column: 6 / 8; + grid-column: 6 / 8; } .span-5-8 { - grid-column: 5 / 9; + grid-column: 5 / 9; } diff --git a/docs/src/assets/scss/versions.scss b/docs/src/assets/scss/versions.scss index f0979c64f4ea..bcaac9511469 100644 --- a/docs/src/assets/scss/versions.scss +++ b/docs/src/assets/scss/versions.scss @@ -1,50 +1,50 @@ .versions-list { - margin: 0; - padding: 0; - font-size: var(--step-1); + margin: 0; + padding: 0; + font-size: var(--step-1); - li { - margin: 0; + li { + margin: 0; - &:last-of-type a { - border-bottom: 0; - border-block-end: 0; - } - } + &:last-of-type a { + border-bottom: 0; + border-block-end: 0; + } + } - a { - color: var(--link-color); - width: 100%; - padding: 1rem .5rem; - text-decoration: none; - display: block; - display: flex; - align-items: center; - border-bottom: 1px solid var(--divider-color); - border-block-end: 1px solid var(--divider-color); + a { + color: var(--link-color); + width: 100%; + padding: 1rem 0.5rem; + text-decoration: none; + display: block; + display: flex; + align-items: center; + border-bottom: 1px solid var(--divider-color); + border-block-end: 1px solid var(--divider-color); - &[data-current="true"] { - font-weight: 500; - color: var(--link-color); + &[data-current="true"] { + font-weight: 500; + color: var(--link-color); - &::after { - content: " âœ”ī¸"; - white-space: pre; - color: rgba(100%, 0%, 0%, 0); - text-shadow: 0 0 0 var(--headings-color); - } - } + &::after { + content: " âœ”ī¸"; + white-space: pre; + color: rgba(100%, 0%, 0%, 0); + text-shadow: 0 0 0 var(--headings-color); + } + } - &:hover { - background-color: var(--lightest-background-color); - } - } + &:hover { + background-color: var(--lightest-background-color); + } + } } .versions-section .versions-list { - font-size: var(--step-1); - border-left: 4px solid var(--tab-border-color); - padding-left: 1rem; - border-inline-start: 4px solid var(--tab-border-color); - padding-inline-start: 1rem; + font-size: var(--step-1); + border-left: 4px solid var(--tab-border-color); + padding-left: 1rem; + border-inline-start: 4px solid var(--tab-border-color); + padding-inline-start: 1rem; } diff --git a/docs/src/contribute/architecture/index.md b/docs/src/contribute/architecture/index.md index 3370e6df4480..992fce89d425 100644 --- a/docs/src/contribute/architecture/index.md +++ b/docs/src/contribute/architecture/index.md @@ -13,14 +13,14 @@ eleventyNavigation: At a high level, there are a few key parts to ESLint: -* `bin/eslint.js` - this is the file that actually gets executed with the command line utility. It's a dumb wrapper that does nothing more than bootstrap ESLint, passing the command line arguments to `cli`. This is intentionally small so as not to require heavy testing. -* `lib/api.js` - this is the entry point of `require("eslint")`. This file exposes an object that contains public classes `Linter`, `ESLint`, `RuleTester`, and `SourceCode`. -* `lib/cli.js` - this is the heart of the ESLint CLI. It takes an array of arguments and then uses `eslint` to execute the commands. By keeping this as a separate utility, it allows others to effectively call ESLint from within another Node.js program as if it were done on the command line. The main call is `cli.execute()`. This is also the part that does all the file reading, directory traversing, input, and output. -* `lib/cli-engine/` - this module is `CLIEngine` class that finds source code files and configuration files then does code verifying with the `Linter` class. This includes the loading logic of configuration files, parsers, plugins, and formatters. -* `lib/linter/` - this module is the core `Linter` class that does code verifying based on configuration options. This file does no file I/O and does not interact with the `console` at all. For other Node.js programs that have JavaScript text to verify, they would be able to use this interface directly. -* `lib/rule-tester/` - this module is `RuleTester` class that is a wrapper around Mocha so that rules can be unit tested. This class lets us write consistently formatted tests for each rule that is implemented and be confident that each of the rules work. The RuleTester interface was modeled after Mocha and works with Mocha's global testing methods. RuleTester can also be modified to work with other testing frameworks. -* `lib/source-code/` - this module is `SourceCode` class that is used to represent the parsed source code. It takes in source code and the Program node of the AST representing the code. -* `lib/rules/` - this contains built-in rules that verify source code. +- `bin/eslint.js` - this is the file that actually gets executed with the command line utility. It's a dumb wrapper that does nothing more than bootstrap ESLint, passing the command line arguments to `cli`. This is intentionally small so as not to require heavy testing. +- `lib/api.js` - this is the entry point of `require("eslint")`. This file exposes an object that contains public classes `Linter`, `ESLint`, `RuleTester`, and `SourceCode`. +- `lib/cli.js` - this is the heart of the ESLint CLI. It takes an array of arguments and then uses `eslint` to execute the commands. By keeping this as a separate utility, it allows others to effectively call ESLint from within another Node.js program as if it were done on the command line. The main call is `cli.execute()`. This is also the part that does all the file reading, directory traversing, input, and output. +- `lib/cli-engine/` - this module is `CLIEngine` class that finds source code files and configuration files then does code verifying with the `Linter` class. This includes the loading logic of configuration files, parsers, plugins, and formatters. +- `lib/linter/` - this module is the core `Linter` class that does code verifying based on configuration options. This file does no file I/O and does not interact with the `console` at all. For other Node.js programs that have JavaScript text to verify, they would be able to use this interface directly. +- `lib/rule-tester/` - this module is `RuleTester` class that is a wrapper around Mocha so that rules can be unit tested. This class lets us write consistently formatted tests for each rule that is implemented and be confident that each of the rules work. The RuleTester interface was modeled after Mocha and works with Mocha's global testing methods. RuleTester can also be modified to work with other testing frameworks. +- `lib/source-code/` - this module is `SourceCode` class that is used to represent the parsed source code. It takes in source code and the Program node of the AST representing the code. +- `lib/rules/` - this contains built-in rules that verify source code. ## The `cli` object @@ -30,17 +30,17 @@ The main method is `cli.execute()`, which accepts an array of strings that repre This object's responsibilities include: -* Interpreting command line arguments -* Reading from the file system -* Outputting to the console -* Outputting to the filesystem -* Use a formatter -* Returning the correct exit code +- Interpreting command line arguments. +- Reading from the file system. +- Outputting to the console. +- Outputting to the filesystem. +- Use a formatter. +- Returning the correct exit code. This object may not: -* Call `process.exit()` directly -* Perform any asynchronous operations +- Call `process.exit()` directly. +- Perform any asynchronous operations. ## The `CLIEngine` object @@ -50,16 +50,16 @@ The main method of the `CLIEngine` is `executeOnFiles()`, which accepts an array This object's responsibilities include: -* Managing the execution environment for `Linter` -* Reading from the file system -* Reading configuration information from config files (including `.eslintrc` and `package.json`) +- Managing the execution environment for `Linter`. +- Reading from the file system. +- Reading configuration information from config files (including `.eslintrc` and `package.json`). This object may not: -* Call `process.exit()` directly -* Perform any asynchronous operations -* Output to the console -* Use formatters +- Call `process.exit()` directly. +- Perform any asynchronous operations. +- Output to the console. +- Use formatters. ## The `Linter` object @@ -69,18 +69,18 @@ Once the AST is available, `estraverse` is used to traverse the AST from top to This object's responsibilities include: -* Inspecting JavaScript code strings -* Creating an AST for the code -* Executing rules on the AST -* Reporting back the results of the execution +- Inspecting JavaScript code strings. +- Creating an AST for the code. +- Executing rules on the AST. +- Reporting back the results of the execution. This object may not: -* Call `process.exit()` directly -* Perform any asynchronous operations -* Use Node.js-specific features -* Access the file system -* Call `console.log()` or any other similar method +- Call `process.exit()` directly. +- Perform any asynchronous operations. +- Use Node.js-specific features. +- Access the file system. +- Call `console.log()` or any other similar method. ## Rules @@ -88,13 +88,13 @@ Individual rules are the most specialized part of the ESLint architecture. Rules These objects' responsibilities are: -* Inspect the AST for specific patterns -* Reporting warnings when certain patterns are found +- Inspect the AST for specific patterns. +- Reporting warnings when certain patterns are found. These objects may not: -* Call `process.exit()` directly -* Perform any asynchronous operations -* Use Node.js-specific features -* Access the file system -* Call `console.log()` or any other similar method +- Call `process.exit()` directly. +- Perform any asynchronous operations. +- Use Node.js-specific features. +- Access the file system. +- Call `console.log()` or any other similar method. diff --git a/docs/src/contribute/core-rules.md b/docs/src/contribute/core-rules.md index 93b7647f52be..e5dd8991c5a6 100644 --- a/docs/src/contribute/core-rules.md +++ b/docs/src/contribute/core-rules.md @@ -20,9 +20,9 @@ For full reference information on writing rules, refer to [Custom Rules](../exte Each core rule in ESLint has three files named with its identifier (for example, `no-extra-semi`). -* in the `lib/rules` directory: a source file (for example, `no-extra-semi.js`) -* in the `tests/lib/rules` directory: a test file (for example, `no-extra-semi.js`) -* in the `docs/src/rules` directory: a Markdown documentation file (for example, `no-extra-semi.md`) +- in the `lib/rules` directory: a source file (for example, `no-extra-semi.js`). +- in the `tests/lib/rules` directory: a test file (for example, `no-extra-semi.js`). +- in the `docs/src/rules` directory: a Markdown documentation file (for example, `no-extra-semi.md`). **Important:** If you submit a core rule to the ESLint repository, you **must** follow the conventions explained below. @@ -40,24 +40,24 @@ Here is the basic format of the source file for a rule: // Rule Definition //------------------------------------------------------------------------------ -/** @type {import('../shared/types').Rule} */ +/** @type {import('../types').Rule.RuleModule} */ module.exports = { - meta: { - type: "suggestion", - - docs: { - description: "disallow unnecessary semicolons", - recommended: true, - url: "https://eslint.org/docs/rules/no-extra-semi" - }, - fixable: "code", - schema: [] // no options - }, - create: function(context) { - return { - // callback functions - }; - } + meta: { + type: "suggestion", + + docs: { + description: "disallow unnecessary semicolons", + recommended: true, + url: "https://eslint.org/docs/rules/no-extra-semi", + }, + fixable: "code", + schema: [], // no options + }, + create: function (context) { + return { + // callback functions + }; + }, }; ``` @@ -105,6 +105,19 @@ Performance budget ok: 1443.736547ms (limit: 3409.090909090909ms) The rule naming conventions for ESLint are as follows: -* Use dashes between words. -* If your rule only disallows something, prefix it with `no-` such as `no-eval` for disallowing `eval()` and `no-debugger` for disallowing `debugger`. -* If your rule is enforcing the inclusion of something, use a short name without a special prefix. +- Use dashes between words. +- If your rule only disallows something, prefix it with `no-` such as `no-eval` for disallowing `eval()` and `no-debugger` for disallowing `debugger`. +- If your rule is enforcing the inclusion of something, use a short name without a special prefix. + +## Frozen Rules + +When rules are feature complete, they are marked as frozen (indicated with â„ī¸ in the documentation). Rules are considered feature complete when the intended purpose of the rule has been fully implemented such that it catches 80% or more of expected violations and covers the majority of common exceptions. After that point, we expect users to use [disable comments](../use/configure/rules#using-configuration-comments-1) when they find an edge case that isn't covered. + +When a rule is frozen, it means: + +- **Bug fixes**: We will still fix confirmed bugs. +- **New ECMAScript features**: We will ensure compatibility with new ECMAScript features, meaning the rule will not break on new syntax. +- **TypeScript support**: We will ensure compatibility with TypeScript syntax, meaning the rule will not break on TypeScript syntax and violations are appropriate for TypeScript. +- **New options**: We will **not** add any new options unless an option is the only way to fix a bug or support a newly-added ECMAScript feature. + +If you find that a frozen rule would work better for you with a change, we recommend copying the rule source code and modifying it to fit your needs. diff --git a/docs/src/contribute/development-environment.md b/docs/src/contribute/development-environment.md index b84af4e32743..b156daf2f0c0 100644 --- a/docs/src/contribute/development-environment.md +++ b/docs/src/contribute/development-environment.md @@ -7,6 +7,8 @@ eleventyNavigation: order: 6 --- +{%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} + ESLint has a very lightweight development environment that makes updating code fast and easy. This is a step-by-step guide to setting up a local development environment that will let you contribute back to the project. ## Step 1: Install Node.js @@ -22,23 +24,28 @@ Go to and click the "Fork" button. Follow the Clone your fork: ```shell -git clone https://github.com//eslint +git clone https://github.com//eslint ``` Once you've cloned the repository, run `npm install` to get all the necessary dependencies: ```shell cd eslint -npm install ``` +{{ npm_tabs({ + command: "install", + packages: [], + args: [] +}) }} + You must be connected to the Internet for this step to work. You'll see a lot of utilities being downloaded. **Note:** It's a good idea to re-run `npm install` whenever you pull from the main repository to ensure you have the latest development dependencies. ## Step 3: Add the Upstream Source -The *upstream source* is the main ESLint repository where active development happens. While you won't have push access to upstream, you will have pull access, allowing you to pull in the latest code whenever you want. +The _upstream source_ is the main ESLint repository where active development happens. While you won't have push access to upstream, you will have pull access, allowing you to pull in the latest code whenever you want. To add the upstream source for ESLint, run the following in your repository: @@ -52,15 +59,19 @@ Now, the remote `upstream` points to the upstream source. [Yeoman](https://yeoman.io) is a scaffold generator that ESLint uses to help streamline development of new rules. If you don't already have Yeoman installed, you can install it via npm: -```shell -npm install -g yo -``` +{{ npm_tabs({ + command: "install", + packages: ["yo"], + args: ["--global"] +}) }} Then, you can install the ESLint Yeoman generator: -```shell -npm install -g generator-eslint -``` +{{ npm_tabs({ + command: "install", + packages: ["generator-eslint"], + args: ["--global"] +}) }} Please see the [generator documentation](https://github.com/eslint/generator-eslint) for instructions on how to use it. @@ -80,16 +91,16 @@ The testing takes a few minutes to complete. If any tests fail, that likely mean The ESLint directory and file structure is as follows: -* `bin` - executable files that are available when ESLint is installed -* `conf` - default configuration information -* `docs` - documentation for the project -* `lib` - contains the source code - * `formatters` - all source files defining formatters - * `rules` - all source files defining rules -* `tests` - the main unit test folder - * `lib` - tests for the source code - * `formatters` - tests for the formatters - * `rules` - tests for the rules +- `bin` - executable files that are available when ESLint is installed. +- `conf` - default configuration information. +- `docs` - documentation for the project. +- `lib` - contains the source code. + - `formatters` - all source files defining formatters. + - `rules` - all source files defining rules. +- `tests` - the main unit test folder. + - `lib` - tests for the source code. + - `formatters` - tests for the formatters. + - `rules` - tests for the rules. ### Workflow @@ -103,11 +114,11 @@ ESLint has several build scripts that help with various parts of development. The primary script to use is `npm test`, which does several things: -1. Lints all JavaScript (including tests) and JSON -1. Runs all tests on Node.js -1. Checks code coverage targets -1. Generates `build/eslint.js` for use in a browser -1. Runs a subset of tests in PhantomJS +1. Lints all JavaScript (including tests) and JSON. +1. Runs all tests on Node.js. +1. Checks code coverage targets. +1. Generates `build/eslint.js` for use in a browser. +1. Runs a subset of tests in PhantomJS. Be sure to run this after making changes and before sending a pull request with your changes. diff --git a/docs/src/contribute/governance.md b/docs/src/contribute/governance.md index 3f761fb2a513..9f206ed1f6e2 100644 --- a/docs/src/contribute/governance.md +++ b/docs/src/contribute/governance.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: contribute to eslint title: Governance order: 12 - --- ESLint is an open source project that depends on contributions from the community. Anyone may contribute to the project at any time by submitting code, participating in discussions, making suggestions, or any other contribution they see fit. This document describes how various types of contributors work within the ESLint project. @@ -30,20 +29,25 @@ As Contributors gain experience and familiarity with the project, their profile Website Team Members are community members who have shown that they are committed to the continued maintenance of [eslint.org](https://eslint.org/) through ongoing engagement with the community. Website Team Members are given push access to the `eslint.org` GitHub repository and must abide by the project's [Contribution Guidelines](../contribute/). - Website Team Members: +Website Team Members: -* Are expected to work on public branches of the source repository and submit pull requests from that branch to the master branch. -* Are expected to delete their public branches when they are no longer necessary. -* Must submit pull requests for all changes. -* Have their work reviewed by Reviewers and TSC members before acceptance into the repository. -* May label and close website-related issues (see [Manage Issues](../maintain/manage-issues)) -* May merge some pull requests (see [Review Pull Requests](../maintain/review-pull-requests)) +- Are expected to work at least one hour per week triaging issues and reviewing pull requests. +- Are expected to work at least two hours total per week on ESLint. +- May invoice for the hours they spend working on ESLint at a rate of $50 USD per hour. +- Are expected to check in on the `#team` Discord channel once per week day (excluding holidays and other time off) for team updates. +- Are expected to work on public branches of the source repository and submit pull requests from that branch to the master branch. +- Are expected to delete their public branches when they are no longer necessary. +- Must submit pull requests for all changes. +- Have their work reviewed by Reviewers and TSC members before acceptance into the repository. +- May label and close website-related issues (see [Manage Issues](../maintain/manage-issues)). +- May merge some pull requests (see [Review Pull Requests](../maintain/review-pull-requests)). +- May take time off whenever they want, and are expected to post in the `#team` Discord channel when they will be away for more than a couple of days. -To become a Website Team Member: +To become a Website Team Member: -* One must have shown a willingness and ability to participate in the maintenance of [eslint.org](https://eslint.org/) as a team player. Typically, a potential Website Team Member will need to show that they have an understanding of the structure of the website and how it fits into the larger ESLint project's objectives and strategy. -* Website Team Members are expected to be respectful of every community member and to work collaboratively in the spirit of inclusion. -* Have submitted a minimum of 10 website-related pull requests. What's a website-related pull request? One that is made to the `eslint.org` repository or the `docs` directory in the `eslint` repository and requires little effort to accept because it's well documented and tested. +- One must have shown a willingness and ability to participate in the maintenance of [eslint.org](https://eslint.org/) as a team player. Typically, a potential Website Team Member will need to show that they have an understanding of the structure of the website and how it fits into the larger ESLint project's objectives and strategy. +- Website Team Members are expected to be respectful of every community member and to work collaboratively in the spirit of inclusion. +- Have submitted a minimum of 10 website-related pull requests. What's a website-related pull request? One that is made to the `eslint.org` repository or the `docs` directory in the `eslint` repository and requires little effort to accept because it's well documented and tested. New Website Team Members can be nominated by any existing Website Team Member or Committer. Once they have been nominated, there will be a vote by the TSC members. @@ -55,18 +59,25 @@ Committers are community members who have shown that they are committed to the c Committers: -* Are expected to work on public branches of the source repository and submit pull requests from that branch to the master branch. -* Are expected to delete their public branches when they are no longer necessary. -* Must submit pull requests for all changes. -* Have their work reviewed by TSC members before acceptance into the repository. -* May label and close issues (see [Manage Issues](../maintain/manage-issues)) -* May merge some pull requests (see [Review Pull Requests](../maintain/review-pull-requests)) +- Are expected to work at least one hour per week triaging issues and reviewing pull requests. +- Are expected to work at least two hours total per week on ESLint. +- May invoice for the hours they spend working on ESLint at a rate of $50 USD per hour. +- Are expected to check in on the `#team` Discord channel once per week day (excluding holidays and other time off) for team updates. +- Are expected to work on public branches of the source repository and submit pull requests from that branch to the master branch. +- Are expected to delete their public branches when they are no longer necessary. +- Are expected to provide feedback on issues in the "Feedback Needed" column of the [Triage Board](https://github.com/orgs/eslint/projects/3/views/1). +- Are expected to work on at least one issue in the "Ready to Implement" column of the [Triage Board](https://github.com/orgs/eslint/projects/3/views/1) that they didn't create each month. +- Must submit pull requests for all changes. +- Have their work reviewed by TSC members before acceptance into the repository. +- May label and close issues (see [Manage Issues](../maintain/manage-issues)). +- May merge some pull requests (see [Review Pull Requests](../maintain/review-pull-requests)). +- May take time off whenever they want, and are expected to post in the `#team` Discord channel when they will be away for more than a couple of days. To become a Committer: -* One must have shown a willingness and ability to participate in the project as a team player. Typically, a potential Committer will need to show that they have an understanding of and alignment with the project, its objectives, and its strategy. -* Committers are expected to be respectful of every community member and to work collaboratively in the spirit of inclusion. -* Have submitted a minimum of 10 qualifying pull requests. What's a qualifying pull request? One that carries significant technical weight and requires little effort to accept because it's well documented and tested. +- One must have shown a willingness and ability to participate in the project as a team player. Typically, a potential Committer will need to show that they have an understanding of and alignment with the project, its objectives, and its strategy. +- Committers are expected to be respectful of every community member and to work collaboratively in the spirit of inclusion. +- Have submitted a minimum of 10 qualifying pull requests. What's a qualifying pull request? One that carries significant technical weight and requires little effort to accept because it's well documented and tested. New Committers can be nominated by any existing Committer. Once they have been nominated, there will be a vote by the TSC members. @@ -74,47 +85,35 @@ It is important to recognize that committership is a privilege, not a right. Tha A Committer who shows an above-average level of contribution to the project, particularly with respect to its strategic direction and long-term health, may be nominated to become a reviewer, described below. -#### Process for Adding Committers - -1. Send email congratulating the new committer and confirming that they would like to accept. This should also outline the responsibilities of a committer with a link to the maintainer guide. -1. Add the GitHub user to the "ESLint Team" team -1. Add committer email to the ESLint team mailing list -1. Invite to Discord team channel -1. Tweet congratulations to the new committer from the ESLint Twitter account - ### Reviewers Reviewers are community members who have contributed a significant amount of time to the project through triaging of issues, fixing bugs, implementing enhancements/features, and are trusted community leaders. Reviewers may perform all of the duties of Committers, and also: -* May merge external pull requests for accepted issues upon reviewing and approving the changes. -* May merge their own pull requests once they have collected the feedback they deem necessary. (No pull request should be merged without at least one Committer/Reviewer/TSC member comment stating they've looked at the code.) +- May merge external pull requests for accepted issues upon reviewing and approving the changes. +- May merge their own pull requests once they have collected the feedback they deem necessary. (No pull request should be merged without at least one Committer/Reviewer/TSC member comment stating they've looked at the code.) +- May invoice for the hours they spend working on ESLint at a rate of $80 USD per hour. To become a Reviewer: -* Work in a helpful and collaborative way with the community. -* Have given good feedback on others' submissions and displayed an overall understanding of the code quality standards for the project. -* Commit to being a part of the community for the long-term. -* Have submitted a minimum of 50 qualifying pull requests. +- Work in a helpful and collaborative way with the community. +- Have given good feedback on others' submissions and displayed an overall understanding of the code quality standards for the project. +- Commit to being a part of the community for the long-term. +- Have submitted a minimum of 50 qualifying pull requests. A Committer is invited to become a Reviewer by existing Reviewers and TSC members. A nomination will result in discussion and then a decision by the TSC. -#### Process for Adding Reviewers - -1. Add the GitHub user to the "ESLint Reviewers" GitHub team -1. Tweet congratulations to the new Reviewer from the ESLint Twitter account - ### Technical Steering Committee (TSC) The ESLint project is jointly governed by a Technical Steering Committee (TSC) which is responsible for high-level guidance of the project. The TSC has final authority over this project including: -* Technical direction -* Project governance and process (including this policy) -* Contribution policy -* GitHub repository hosting +- Technical direction +- Project governance and process (including this policy) +- Contribution policy +- GitHub repository hosting TSC seats are not time-limited. The size of the TSC can not be larger than five members. This size ensures adequate coverage of important areas of expertise balanced with the ability to make decisions efficiently. @@ -130,27 +129,15 @@ TSC members have additional responsibilities over and above those of a Reviewer. TSC members may perform all of the duties of Reviewers, and also: -* May release new versions of all ESLint projects. -* May participate in TSC meetings. -* May propose budget items. -* May propose new ESLint projects. +- May release new versions of all ESLint projects. +- May participate in TSC meetings. +- May propose budget items. +- May propose new ESLint projects. There is no specific set of requirements or qualifications for TSC members beyond those that are expected of Reviewers. A Reviewer is invited to become a TSC member by existing TSC members. A nomination will result in discussion and then a decision by the TSC. -#### Process for Adding TSC Members - -1. Add the GitHub user to the "ESLint TSC" GitHub team -1. Set the GitHub user to be have the "Owner" role for the ESLint organization -1. Send a welcome email with a link to the [Maintain ESLint documentation](../maintain/) and instructions for npm 2FA. -1. Invite to the Discord TSC channel -1. Make the TSC member an admin on the ESLint team mailing list -1. Add the TSC member to the recurring TSC meeting event on Google Calendar -1. Add the TSC member as an admin to ESLint Twitter Account on Tweetdeck -1. Add the TSC member to the ESLint TSC mailing list as an "Owner" -1. Tweet congratulations to the new TSC member from the ESLint Twitter account - #### TSC Meetings The TSC meets every other week in the TSC Meeting [Discord](https://eslint.org/chat) channel. The meeting is run by a designated moderator approved by the TSC. @@ -197,7 +184,7 @@ either a closing vote or a vote to table the issue to the next meeting. The call for a vote must be approved by a majority of the TSC or else the discussion will continue. Simple majority wins. ----- +--- This work is a derivative of [YUI Contributor Model](https://github.com/yui/yui3/wiki/Contributor-Model) and the [Node.js Project Governance Model](https://github.com/nodejs/node/blob/master/GOVERNANCE.md). diff --git a/docs/src/contribute/index.md b/docs/src/contribute/index.md index 3b0a6d8f5c53..8bf55b30d302 100644 --- a/docs/src/contribute/index.md +++ b/docs/src/contribute/index.md @@ -60,7 +60,7 @@ Describes the governance policy for ESLint, including the rights and privileges ## [Report a Security Vulnerability](report-security-vulnerability) -To report a security vulnerability in ESLint, please create an advisory on Github. +To report a security vulnerability in ESLint, please create an advisory on GitHub. ## Sign the CLA diff --git a/docs/src/contribute/package-json-conventions.md b/docs/src/contribute/package-json-conventions.md index a030dc3f3dc9..16327785b46a 100644 --- a/docs/src/contribute/package-json-conventions.md +++ b/docs/src/contribute/package-json-conventions.md @@ -1,6 +1,5 @@ --- title: Package.json Conventions -edit_link: https://github.com/eslint/eslint/edit/main/docs/src/contribute/package-json-conventions.md eleventyNavigation: key: package.json conventions parent: contribute to eslint @@ -19,7 +18,7 @@ Here is a summary of the proposal in ABNF. ```abnf name = life-cycle / main target? option* ":watch"? life-cycle = "prepare" / "preinstall" / "install" / "postinstall" / "prepublish" / "preprepare" / "prepare" / "postprepare" / "prepack" / "postpack" / "prepublishOnly" -main = "build" / "lint" ":fix"? / "release" / "start" / "test" / "fetch" +main = "build" / "lint" ":fix"? / "fmt" ":check"? / "release" / "start" / "test" / "fetch" target = ":" word ("-" word)* / extension ("+" extension)* option = ":" word ("-" word)* word = ALPHA + @@ -28,7 +27,7 @@ extension = ( ALPHA / DIGIT )+ ## Order -The script names MUST appear in the package.json file in alphabetical order. The other conventions outlined in this document ensure that alphabetical order will coincide with logical groupings. +The script names MUST appear in the `package.json` file in alphabetical order. The other conventions outlined in this document ensure that alphabetical order will coincide with logical groupings. ## Main Script Names @@ -48,7 +47,7 @@ If a package contains any `fetch:*` scripts, there MAY be a script named `fetch` ### Release -Scripts that have public side effects (publishing the web site, committing to Git, etc.) MUST begin with `release`. +Scripts that have public side effects (publishing the website, committing to Git, etc.) MUST begin with `release`. ### Lint @@ -58,6 +57,12 @@ If a package contains any `lint:*` scripts, there SHOULD be a script named `lint If fixing is available, a linter MUST NOT apply fixes UNLESS the script contains the `:fix` modifier (see below). +### Fmt + +Scripts that format source code MUST have names that begin with `fmt`. + +If a package contains any `fmt:*` scripts, there SHOULD be a script named `fmt` that applies formatting fixes to all source files. There SHOULD also be a script named `fmt:check` that validates code formatting without modifying files and exits non-zero if any files are out of compliance. + ### Start A `start` script is used to start a server. As of this writing, no ESLint package has more than one `start` script, so there's no need `start` to have any modifiers. @@ -74,19 +79,23 @@ A test script SHOULD report test coverage when possible. ## Modifiers -One or more of the following modifiers MAY be appended to the standard script names above. If a target has modifiers, they MUST be in the order in which they appear below (e.g. `lint:fix:js:watch` not `lint:watch:js:fix`) +One or more of the following modifiers MAY be appended to the standard script names above. If a target has modifiers, they MUST be in the order in which they appear below (e.g. `lint:fix:js:watch` not `lint:watch:js:fix`). ### Fix If it's possible for a linter to fix problems that it finds, add a copy of the script with `:fix` appended to the end that also fixes. +### Check + +If a script validates code or artifacts without making any modifications, append `:check` to the script name. This modifier is typically used for formatters (e.g., `fmt:check`) to verify that files conform to the expected format and to exit with a non-zero status if any issues are found. Scripts with the `:check` modifier MUST NOT alter any files or outputs. + ### Target The name of the target of the action being run. In the case of a `build` script, it SHOULD identify the build artifact(s), e.g. "javascript" or "css" or "website". In the case of a `lint` or `test` script, it SHOULD identify the item(s) being linted or tested. In the case of a `start` script, it SHOULD identify which server is starting. A target MAY refer to a list of affected file extensions (such as `cjs` or `less`) delimited by a `+`. If there is more than one extension, the list SHOULD be alphabetized. When a file extension has variants (such as `cjs` for CommonJS and `mjs` for ESM), the common part of the extension MAY be used instead of explicitly listing out all of the variants (e.g. `js` instead of `cjs+jsx+mjs`). -The target SHOULD NOT refer to name of the name of the tool that's performing the action (`eleventy`, `webpack`, etc.) +The target SHOULD NOT refer to name of the name of the tool that's performing the action (`eleventy`, `webpack`, etc.). ### Options diff --git a/docs/src/contribute/propose-new-rule.md b/docs/src/contribute/propose-new-rule.md index 7aa9d3a0e7cc..bdd152d2989f 100644 --- a/docs/src/contribute/propose-new-rule.md +++ b/docs/src/contribute/propose-new-rule.md @@ -34,9 +34,9 @@ We need all of this information in order to determine whether or not the rule is In order for a rule to be accepted in the ESLint core, it must: -1. Fulfill all the criteria listed in the "Core Rule Guidelines" section -1. Have an ESLint team member champion inclusion of the rule -1. Be related to an ECMAScript feature that has reached stage 4 in the preceding 12 months +1. Fulfill all the criteria listed in the "Core Rule Guidelines" section. +1. Have an ESLint team member champion inclusion of the rule. +1. Be related to an ECMAScript feature that has reached stage 4 in the preceding 12 months. Keep in mind that we have over 200 rules, and that is daunting both for end users and the ESLint team (who has to maintain them). As such, any new rules must be deemed of high importance to be considered for inclusion in ESLint. diff --git a/docs/src/contribute/propose-rule-change.md b/docs/src/contribute/propose-rule-change.md index d2d198b19bde..8bb85a110a33 100644 --- a/docs/src/contribute/propose-rule-change.md +++ b/docs/src/contribute/propose-rule-change.md @@ -19,9 +19,9 @@ We need all of this information in order to determine whether or not the change In order for a rule change to be accepted into ESLint, it must: -1. Adhere to the [Core Rule Guidelines](propose-new-rule#core-rule-guidelines) -1. Have an ESLint team member champion the change -1. Be important enough that rule is deemed incomplete without this change +1. Adhere to the [Core Rule Guidelines](propose-new-rule#core-rule-guidelines). +1. Have an ESLint team member champion the change. +1. Be important enough that rule is deemed incomplete without this change. ## Implementation is Your Responsibility diff --git a/docs/src/contribute/pull-requests.md b/docs/src/contribute/pull-requests.md index 578118603752..6b5f395b1ce9 100644 --- a/docs/src/contribute/pull-requests.md +++ b/docs/src/contribute/pull-requests.md @@ -22,13 +22,13 @@ After that, you're ready to start working on code. The process of submitting a pull request is fairly straightforward and generally follows the same pattern each time: -1. [Create a new branch](#step1) -2. [Make your changes](#step2) -3. [Rebase onto upstream](#step3) -4. [Run the tests](#step4) -5. [Double check your submission](#step5) -6. [Push your changes](#step6) -7. [Submit the pull request](#step7) +1. [Create a new branch](#step1). +2. [Make your changes](#step2). +3. [Rebase onto upstream](#step3). +4. [Run the tests](#step4). +5. [Double check your submission](#step5). +6. [Push your changes](#step6). +7. [Submit the pull request](#step7). Details about each step are found below. @@ -53,7 +53,7 @@ git add -A git commit ``` -All ESLint projects follow [Conventional Commits](https://www.conventionalcommits.org/) for our commit messages. (Note: we don’t support the optional scope in messages.) Here's an example commit message: +All ESLint projects follow [Conventional Commits](https://www.conventionalcommits.org/) for our commit messages. (Note: we don’t support the optional scope in messages.) Here's an example commit message: ```txt tag: Short description of what you did @@ -67,17 +67,17 @@ The first line of the commit message (the summary) must have a specific format. The `tag` is one of the following: -* `fix` - for a bug fix. -* `feat` - either for a backwards-compatible enhancement or for a rule change that adds reported problems. -* `fix!` - for a backwards-incompatible bug fix. -* `feat!` - for a backwards-incompatible enhancement or feature. -* `docs` - changes to documentation only. -* `chore` - for changes that aren't user-facing. -* `build` - changes to build process only. -* `refactor` - a change that doesn't affect APIs or user experience. -* `test` - just changes to test files. -* `ci` - changes to our CI configuration files and scripts. -* `perf` - a code change that improves performance. +- `fix` - for a bug fix. +- `feat` - either for a backwards-compatible enhancement or for a rule change that adds reported problems. +- `fix!` - for a backwards-incompatible bug fix. +- `feat!` - for a backwards-incompatible enhancement or feature. +- `docs` - changes to documentation only. +- `chore` - for changes that aren't user-facing. +- `build` - changes to build process only. +- `refactor` - a change that doesn't affect APIs or user experience. +- `test` - just changes to test files. +- `ci` - changes to our CI configuration files and scripts. +- `perf` - a code change that improves performance. Use the [labels of the issue you are working on](work-on-issue#issue-labels) to determine the best tag. @@ -114,12 +114,12 @@ If there are any failing tests, update your code until all tests pass. With your code ready to go, this is a good time to double-check your submission to make sure it follows our conventions. Here are the things to check: -* The commit message is properly formatted. -* The change introduces no functional regression. Be sure to run `npm test` to verify your changes before submitting a pull request. -* Make separate pull requests for unrelated changes. Large pull requests with multiple unrelated changes may be closed without merging. -* All changes must be accompanied by tests, even if the feature you're working on previously had no tests. -* All user-facing changes must be accompanied by appropriate documentation. -* Follow the [Code Conventions](./code-conventions). +- The commit message is properly formatted. +- The change introduces no functional regression. Be sure to run `npm test` to verify your changes before submitting a pull request. +- Make separate pull requests for unrelated changes. Large pull requests with multiple unrelated changes may be closed without merging. +- All changes must be accompanied by tests, even if the feature you're working on previously had no tests. +- All user-facing changes must be accompanied by appropriate documentation. +- Follow the [Code Conventions](./code-conventions). ### Step 6: Push your changes @@ -139,7 +139,7 @@ git push -f origin issue1234 Now you're ready to send the pull request. Go to your ESLint fork and then follow the [GitHub documentation](https://help.github.com/articles/creating-a-pull-request) on how to send a pull request. -In order to submit code or documentation to an ESLint project, you’ll be asked to sign our CLA when you send your first pull request. (Read more about the Open JS Foundation CLA process at .) +In order to submit code or documentation to an ESLint project, you’ll be asked to sign our CLA when you send your first pull request. (Read more about the OpenJS Foundation CLA process at .) The pull request title is autogenerated from the summary of the first commit, but it can be edited before the pull request is submitted. diff --git a/docs/src/contribute/tests.md b/docs/src/contribute/tests.md index 9ad25500d82b..1fca848bc29b 100644 --- a/docs/src/contribute/tests.md +++ b/docs/src/contribute/tests.md @@ -29,18 +29,18 @@ If you want to run just one or a subset of `RuleTester` test cases, add `only: t ```js ruleTester.run("my-rule", myRule, { - valid: [ - RuleTester.only("const valid = 42;"), - // Other valid cases - ], - invalid: [ - { - code: "const invalid = 42;", - only: true, - }, - // Other invalid cases - ] -}) + valid: [ + RuleTester.only("const valid = 42;"), + // Other valid cases + ], + invalid: [ + { + code: "const invalid = 42;", + only: true, + }, + // Other invalid cases + ], +}); ``` Running individual tests is useful when you're working on a specific bug and iterating on the solution. You should be sure to run `npm test` before submitting a pull request. `npm test` uses Mocha's `--forbid-only` option to prevent `only` tests from passing full test runs. diff --git a/docs/src/contribute/work-on-issue.md b/docs/src/contribute/work-on-issue.md index d258aa4eb156..8232a1ef1d9a 100644 --- a/docs/src/contribute/work-on-issue.md +++ b/docs/src/contribute/work-on-issue.md @@ -13,9 +13,10 @@ Our public [issues tracker](https://github.com/eslint/eslint/issues) lists all o We use labels to indicate the status of issues. The most complete documentation on the labels is found in the [Maintain ESLint documentation](../maintain/manage-issues#when-an-issue-or-pull-request-is-opened), but most contributors should find the information on this page sufficient. The most important questions that labels can help you, as a contributor, answer are: -1. Is this issue available for me to work on? If you have little or no experience contributing to ESLint, the [`good first issue`](https://github.com/eslint/eslint/labels/good%20first%20issue) label marks appropriate issues. Otherwise, the [`help wanted`](https://github.com/eslint/eslint/labels/help%20wanted) label is an invitation to work on the issue. If you have more experience, you can try working on other issues labeled [`accepted`](https://github.com/eslint/eslint/labels/accepted). Conversely, issues not yet ready to work on are labeled `triage`, `evaluating`, and/or `needs bikeshedding`, and issues that cannot currently be worked on because of something else, such as a bug in a dependency, are labeled `blocked`. -1. What is this issue about? Labels describing the nature of issues include `bug`, `enhancement`, `feature`, `question`, `rule`, `documentation`, `core`, `build`, `cli`, `infrastructure`, `breaking`, and `chore`. These are documented in [Maintain ESLint](../maintain/manage-issues#types-of-issues-and-pull-requests). -1. What is the priority of this issue? Because we have a lot of issues, we prioritize certain issues above others. The following is the list of priorities, from highest to lowest: +1. **Is this issue ready for a pull request?** Issues that are ready for pull requests have the [`accepted`](https://github.com/eslint/eslint/labels/accepted) label, which indicates that the team has agreed to accept a pull request. Please do not send pull requests for issues that have not been marked as accepted. +2. **Is this issue right for a beginner?** If you have little or no experience contributing to ESLint, the [`good first issue`](https://github.com/eslint/eslint/labels/good%20first%20issue) label marks appropriate issues. Otherwise, the [`help wanted`](https://github.com/eslint/eslint/labels/help%20wanted) label is an invitation to work on the issue. If you have more experience, you can try working on other issues labeled [`accepted`](https://github.com/eslint/eslint/labels/accepted). +3. **What is this issue about?** Labels describing the nature of issues include `bug`, `enhancement`, `feature`, `question`, `rule`, `documentation`, `core`, `build`, `cli`, `infrastructure`, `breaking`, and `chore`. These are documented in [Maintain ESLint](../maintain/manage-issues#types-of-issues-and-pull-requests). +4. **What is the priority of this issue?** Because we have a lot of issues, we prioritize certain issues above others. The following is the list of priorities, from highest to lowest: 1. **Bugs** - problems with the project are actively affecting users. We want to get these resolved as quickly as possible. 1. **Documentation** - documentation issues are a type of bug in that they actively affect current users. As such, we want to address documentation issues as quickly as possible. @@ -23,21 +24,38 @@ We use labels to indicate the status of issues. The most complete documentation 1. **Enhancements** - requested improvements for existing functionality. 1. **Other** - anything else. - Some issues have had monetary rewards attached to them. Those are labeled `bounty`. Bounties are assigned via [BountySource](https://www.bountysource.com/teams/eslint/issues). - ## Starting Work -If you're going to work on an issue, please add a comment to that issue saying so and indicating when you think you will complete it. It will help us to avoid duplication of effort. Some examples of good comments are: +::: important +Before starting to work on an existing issue, please check if the issue has been assigned to anyone. If it has, then that person is already responsible for submitting a pull request and you should choose a different issue to work on. +::: + +### Claiming an issue + +If you're going to work on an issue, please _claim_ the issue by adding a comment saying you're working on it and indicating when you think you will complete it. This helps us to avoid duplication of effort. Some examples of good claim comments are: + +- "I'll take a look at this over the weekend." +- "I'm going to do this, give me two weeks." +- "Working on this" (as in, I'm working on it right now) + +The team will validate your claim by assigning the issue to you. + +### Offering help on a claimed issue + +If an issue has an assignee or has already been claimed by someone, please be respectful of that person's desire to complete the work and don't work on it unless you verify that they are no longer interested or would welcome the help. If there hasn't been activity on the issue after two weeks, you can express your interest in helping with the issue. For example: + +- "Are you still working on this? If not, I'd love to work on it." +- "Do you need any help on this? I'm interested." + +It is up to the assignee to decide if they're going to continue working on the issue or if they'd like your help. -* "I'll take a look at this over the weekend." -* "I'm going to do this, give me two weeks." -* "Working on this" (as in, I'm working on it right now) +If there is no response after a week, please contact a team member for help. -If an issue has already been claimed by someone, please be respectful of that person's desire to complete the work and don't work on it unless you verify that they are no longer interested. +### Unclaiming an issue -If you find you can't finish the work, then simply add a comment letting people know, for example: +If you claimed an issue and find you can't finish the work, then add a comment letting people know, for example: -* "Sorry, it looks like I don't have time to do this." -* "I thought I knew enough to fix this, but it turns out I don't." +- "Sorry, it looks like I don't have time to do this." +- "I thought I knew enough to fix this, but it turns out I don't." No one will blame you for backing out of an issue if you are unable to complete it. We just want to keep the process moving along as efficiently as possible. diff --git a/docs/src/extend/code-path-analysis.md b/docs/src/extend/code-path-analysis.md index 879119574907..9bcbe73da1e4 100644 --- a/docs/src/extend/code-path-analysis.md +++ b/docs/src/extend/code-path-analysis.md @@ -1,6 +1,5 @@ --- title: Code Path Analysis Details - --- ESLint's rules can use code paths. @@ -9,7 +8,7 @@ It forks/joins at such as `if` statements. ```js if (a && b) { - foo(); + foo(); } bar(); ``` @@ -18,6 +17,10 @@ bar(); ![Code Path Example](../assets/images/code-path-analysis/helo.svg) ::: +::: tip +You can view code path diagrams for any JavaScript code using [Code Explorer](http://explorer.eslint.org). +::: + ## Objects Program is expressed with several code paths. @@ -31,15 +34,14 @@ This has references of both the initial segment and the final segments of a code `CodePath` has the following properties: -* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path. -* `origin` (`string`) - The reason that the code path was started. May be `"program"`, `"function"`, `"class-field-initializer"`, or `"class-static-block"`. -* `initialSegment` (`CodePathSegment`) - The initial segment of this code path. -* `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown. -* `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned. -* `thrownSegments` (`CodePathSegment[]`) - The final segments which includes only thrown. -* `currentSegments` (`CodePathSegment[]`) - **Deprecated.** Segments of the current traversal position. -* `upper` (`CodePath|null`) - The code path of the upper function/global scope. -* `childCodePaths` (`CodePath[]`) - Code paths of functions this code path contains. +- `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each code path. +- `origin` (`string`) - The reason that the code path was started. May be `"program"`, `"function"`, `"class-field-initializer"`, or `"class-static-block"`. +- `initialSegment` (`CodePathSegment`) - The initial segment of this code path. +- `finalSegments` (`CodePathSegment[]`) - The final segments which includes both returned and thrown. +- `returnedSegments` (`CodePathSegment[]`) - The final segments which includes only returned. +- `thrownSegments` (`CodePathSegment[]`) - The final segments which includes only thrown. +- `upper` (`CodePath|null`) - The code path of the upper function/global scope. +- `childCodePaths` (`CodePath[]`) - Code paths of functions this code path contains. ### `CodePathSegment` @@ -49,10 +51,10 @@ Difference from doubly linked list is what there are forking and merging (the ne `CodePathSegment` has the following properties: -* `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each segment. -* `nextSegments` (`CodePathSegment[]`) - The next segments. If forking, there are two or more. If final, there is nothing. -* `prevSegments` (`CodePathSegment[]`) - The previous segments. If merging, there are two or more. If initial, there is nothing. -* `reachable` (`boolean`) - A flag which shows whether or not it's reachable. This becomes `false` when preceded by `return`, `throw`, `break`, or `continue`. +- `id` (`string`) - A unique string. Respective rules can use `id` to save additional information for each segment. +- `nextSegments` (`CodePathSegment[]`) - The next segments. If forking, there are two or more. If final, there is nothing. +- `prevSegments` (`CodePathSegment[]`) - The previous segments. If merging, there are two or more. If initial, there is nothing. +- `reachable` (`boolean`) - A flag which shows whether or not it's reachable. This becomes `false` when preceded by `return`, `throw`, `break`, or `continue`. ## Events @@ -60,106 +62,104 @@ There are seven events related to code paths, and you can define event handlers ```js module.exports = { - meta: { - // ... - }, - create(context) { - - return { - /** - * This is called at the start of analyzing a code path. - * In this time, the code path object has only the initial segment. - * - * @param {CodePath} codePath - The new code path. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - onCodePathStart(codePath, node) { - // do something with codePath - }, - - /** - * This is called at the end of analyzing a code path. - * In this time, the code path object is complete. - * - * @param {CodePath} codePath - The completed code path. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - onCodePathEnd(codePath, node) { - // do something with codePath - }, - - /** - * This is called when a reachable code path segment was created. - * It meant the code path is forked or merged. - * In this time, the segment has the previous segments and has been - * judged reachable or not. - * - * @param {CodePathSegment} segment - The new code path segment. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - onCodePathSegmentStart(segment, node) { - // do something with segment - }, - - /** - * This is called when a reachable code path segment was left. - * In this time, the segment does not have the next segments yet. - * - * @param {CodePathSegment} segment - The left code path segment. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - onCodePathSegmentEnd(segment, node) { - // do something with segment - }, - - /** - * This is called when an unreachable code path segment was created. - * It meant the code path is forked or merged. - * In this time, the segment has the previous segments and has been - * judged reachable or not. - * - * @param {CodePathSegment} segment - The new code path segment. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - onUnreachableCodePathSegmentStart(segment, node) { - // do something with segment - }, - - /** - * This is called when an unreachable code path segment was left. - * In this time, the segment does not have the next segments yet. - * - * @param {CodePathSegment} segment - The left code path segment. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - onUnreachableCodePathSegmentEnd(segment, node) { - // do something with segment - }, - - /** - * This is called when a code path segment was looped. - * Usually segments have each previous segments when created, - * but when looped, a segment is added as a new previous segment into a - * existing segment. - * - * @param {CodePathSegment} fromSegment - A code path segment of source. - * @param {CodePathSegment} toSegment - A code path segment of destination. - * @param {ASTNode} node - The current node. - * @returns {void} - */ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - // do something with segment - } - }; - - } -} + meta: { + // ... + }, + create(context) { + return { + /** + * This is called at the start of analyzing a code path. + * In this time, the code path object has only the initial segment. + * + * @param {CodePath} codePath - The new code path. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathStart(codePath, node) { + // do something with codePath + }, + + /** + * This is called at the end of analyzing a code path. + * In this time, the code path object is complete. + * + * @param {CodePath} codePath - The completed code path. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathEnd(codePath, node) { + // do something with codePath + }, + + /** + * This is called when a reachable code path segment was created. + * It meant the code path is forked or merged. + * In this time, the segment has the previous segments and has been + * judged reachable or not. + * + * @param {CodePathSegment} segment - The new code path segment. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathSegmentStart(segment, node) { + // do something with segment + }, + + /** + * This is called when a reachable code path segment was left. + * In this time, the segment does not have the next segments yet. + * + * @param {CodePathSegment} segment - The left code path segment. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathSegmentEnd(segment, node) { + // do something with segment + }, + + /** + * This is called when an unreachable code path segment was created. + * It meant the code path is forked or merged. + * In this time, the segment has the previous segments and has been + * judged reachable or not. + * + * @param {CodePathSegment} segment - The new code path segment. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onUnreachableCodePathSegmentStart(segment, node) { + // do something with segment + }, + + /** + * This is called when an unreachable code path segment was left. + * In this time, the segment does not have the next segments yet. + * + * @param {CodePathSegment} segment - The left code path segment. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onUnreachableCodePathSegmentEnd(segment, node) { + // do something with segment + }, + + /** + * This is called when a code path segment was looped. + * Usually segments have each previous segments when created, + * but when looped, a segment is added as a new previous segment into a + * existing segment. + * + * @param {CodePathSegment} fromSegment - A code path segment of source. + * @param {CodePathSegment} toSegment - A code path segment of destination. + * @param {ASTNode} node - The current node. + * @returns {void} + */ + onCodePathSegmentLoop(fromSegment, toSegment, node) { + // do something with segment + }, + }; + }, +}; ``` ### About `onCodePathSegmentLoop` @@ -171,7 +171,7 @@ For Example 1: ```js while (a) { - a = foo(); + a = foo(); } bar(); ``` @@ -179,7 +179,7 @@ bar(); 1. First, the analysis advances to the end of loop. :::img-container - ![Loop Event's Example 1](../assets/images/code-path-analysis/loop-event-example-while-1.svg) +![Loop Event's Example 1](../assets/images/code-path-analysis/loop-event-example-while-1.svg) ::: 2. Second, it creates the looping path. @@ -187,20 +187,20 @@ bar(); It fires `onCodePathSegmentLoop` instead. :::img-container - ![Loop Event's Example 2](../assets/images/code-path-analysis/loop-event-example-while-2.svg) +![Loop Event's Example 2](../assets/images/code-path-analysis/loop-event-example-while-2.svg) ::: 3. Last, it advances to the end. :::img-container - ![Loop Event's Example 3](../assets/images/code-path-analysis/loop-event-example-while-3.svg) +![Loop Event's Example 3](../assets/images/code-path-analysis/loop-event-example-while-3.svg) ::: For example 2: ```js for (let i = 0; i < 10; ++i) { - foo(i); + foo(i); } bar(); ``` @@ -210,7 +210,7 @@ bar(); The `update` segment is hovered at first. :::img-container - ![Loop Event's Example 1](../assets/images/code-path-analysis/loop-event-example-for-1.svg) +![Loop Event's Example 1](../assets/images/code-path-analysis/loop-event-example-for-1.svg) ::: 2. Second, it advances to `ForStatement.body`. @@ -218,7 +218,7 @@ bar(); It keeps the `update` segment hovering. :::img-container - ![Loop Event's Example 2](../assets/images/code-path-analysis/loop-event-example-for-2.svg) +![Loop Event's Example 2](../assets/images/code-path-analysis/loop-event-example-for-2.svg) ::: 3. Third, it creates the looping path from `body` segment to `update` segment. @@ -226,7 +226,7 @@ bar(); It fires `onCodePathSegmentLoop` instead. :::img-container - ![Loop Event's Example 3](../assets/images/code-path-analysis/loop-event-example-for-3.svg) +![Loop Event's Example 3](../assets/images/code-path-analysis/loop-event-example-for-3.svg) ::: 4. Fourth, also it creates the looping path from `update` segment to `test` segment. @@ -234,13 +234,13 @@ bar(); It fires `onCodePathSegmentLoop` instead. :::img-container - ![Loop Event's Example 4](../assets/images/code-path-analysis/loop-event-example-for-4.svg) +![Loop Event's Example 4](../assets/images/code-path-analysis/loop-event-example-for-4.svg) ::: 5. Last, it advances to the end. :::img-container - ![Loop Event's Example 5](../assets/images/code-path-analysis/loop-event-example-for-5.svg) +![Loop Event's Example 5](../assets/images/code-path-analysis/loop-event-example-for-5.svg) ::: ## Usage Examples @@ -251,51 +251,48 @@ To track the current code path segment position, you can define a rule like this ```js module.exports = { - meta: { - // ... - }, - create(context) { - - // tracks the code path we are currently in - let currentCodePath; - - // tracks the segments we've traversed in the current code path - let currentSegments; - - // tracks all current segments for all open paths - const allCurrentSegments = []; - - return { - - onCodePathStart(codePath) { - currentCodePath = codePath; - allCurrentSegments.push(currentSegments); - currentSegments = new Set(); - }, - - onCodePathEnd(codePath) { - currentCodePath = codePath.upper; - currentSegments = allCurrentSegments.pop(); - }, - - onCodePathSegmentStart(segment) { - currentSegments.add(segment); - }, - - onCodePathSegmentEnd(segment) { - currentSegments.delete(segment); - }, - - onUnreachableCodePathSegmentStart(segment) { - currentSegments.add(segment); - }, - - onUnreachableCodePathSegmentEnd(segment) { - currentSegments.delete(segment); - } - }; - - } + meta: { + // ... + }, + create(context) { + // tracks the code path we are currently in + let currentCodePath; + + // tracks the segments we've traversed in the current code path + let currentSegments; + + // tracks all current segments for all open paths + const allCurrentSegments = []; + + return { + onCodePathStart(codePath) { + currentCodePath = codePath; + allCurrentSegments.push(currentSegments); + currentSegments = new Set(); + }, + + onCodePathEnd(codePath) { + currentCodePath = codePath.upper; + currentSegments = allCurrentSegments.pop(); + }, + + onCodePathSegmentStart(segment) { + currentSegments.add(segment); + }, + + onCodePathSegmentEnd(segment) { + currentSegments.delete(segment); + }, + + onUnreachableCodePathSegmentStart(segment) { + currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + currentSegments.delete(segment); + }, + }; + }, }; ``` @@ -309,70 +306,65 @@ To find an unreachable node, track the current segment position and then use a n ```js function areAnySegmentsReachable(segments) { - for (const segment of segments) { - if (segment.reachable) { - return true; - } - } + for (const segment of segments) { + if (segment.reachable) { + return true; + } + } - return false; + return false; } module.exports = { - meta: { - // ... - }, - create(context) { - - // tracks the code path we are currently in - let currentCodePath; - - // tracks the segments we've traversed in the current code path - let currentSegments; - - // tracks all current segments for all open paths - const allCurrentSegments = []; - - return { - - onCodePathStart(codePath) { - currentCodePath = codePath; - allCurrentSegments.push(currentSegments); - currentSegments = new Set(); - }, - - onCodePathEnd(codePath) { - currentCodePath = codePath.upper; - currentSegments = allCurrentSegments.pop(); - }, - - onCodePathSegmentStart(segment) { - currentSegments.add(segment); - }, - - onCodePathSegmentEnd(segment) { - currentSegments.delete(segment); - }, - - onUnreachableCodePathSegmentStart(segment) { - currentSegments.add(segment); - }, - - onUnreachableCodePathSegmentEnd(segment) { - currentSegments.delete(segment); - }, - - ExpressionStatement(node) { - - // check all the code path segments that led to this node - if (!areAnySegmentsReachable(currentSegments)) { - context.report({ message: "Unreachable!", node }); - } - } - - }; - - } + meta: { + // ... + }, + create(context) { + // tracks the code path we are currently in + let currentCodePath; + + // tracks the segments we've traversed in the current code path + let currentSegments; + + // tracks all current segments for all open paths + const allCurrentSegments = []; + + return { + onCodePathStart(codePath) { + currentCodePath = codePath; + allCurrentSegments.push(currentSegments); + currentSegments = new Set(); + }, + + onCodePathEnd(codePath) { + currentCodePath = codePath.upper; + currentSegments = allCurrentSegments.pop(); + }, + + onCodePathSegmentStart(segment) { + currentSegments.add(segment); + }, + + onCodePathSegmentEnd(segment) { + currentSegments.delete(segment); + }, + + onUnreachableCodePathSegmentStart(segment) { + currentSegments.add(segment); + }, + + onUnreachableCodePathSegmentEnd(segment) { + currentSegments.delete(segment); + }, + + ExpressionStatement(node) { + // check all the code path segments that led to this node + if (!areAnySegmentsReachable(currentSegments)) { + context.report({ message: "Unreachable!", node }); + } + }, + }; + }, }; ``` @@ -390,114 +382,113 @@ Please use a map of information instead. ```js function hasCb(node, context) { - if (node.type.indexOf("Function") !== -1) { - const sourceCode = context.sourceCode; - return sourceCode.getDeclaredVariables(node).some(function(v) { - return v.type === "Parameter" && v.name === "cb"; - }); - } - return false; + if (node.type.indexOf("Function") !== -1) { + const sourceCode = context.sourceCode; + return sourceCode.getDeclaredVariables(node).some(function (v) { + return v.type === "Parameter" && v.name === "cb"; + }); + } + return false; } function isCbCalled(info) { - return info.cbCalled; + return info.cbCalled; } module.exports = { - meta: { - // ... - }, - create(context) { - - let funcInfo; - const funcInfoStack = []; - const segmentInfoMap = Object.create(null); - - return { - // Checks `cb`. - onCodePathStart(codePath, node) { - funcInfoStack.push(funcInfo); - - funcInfo = { - codePath: codePath, - hasCb: hasCb(node, context), - currentSegments: new Set() - }; - }, - - onCodePathEnd(codePath, node) { - funcInfo = funcInfoStack.pop(); - - // Checks `cb` was called in every paths. - const cbCalled = codePath.finalSegments.every(function(segment) { - const info = segmentInfoMap[segment.id]; - return info.cbCalled; - }); - - if (!cbCalled) { - context.report({ - message: "`cb` should be called in every path.", - node: node - }); - } - }, - - // Manages state of code paths and tracks traversed segments - onCodePathSegmentStart(segment) { - - funcInfo.currentSegments.add(segment); - - // Ignores if `cb` doesn't exist. - if (!funcInfo.hasCb) { - return; - } - - // Initialize state of this path. - const info = segmentInfoMap[segment.id] = { - cbCalled: false - }; - - // If there are the previous paths, merges state. - // Checks `cb` was called in every previous path. - if (segment.prevSegments.length > 0) { - info.cbCalled = segment.prevSegments.every(isCbCalled); - } - }, - - // Tracks unreachable segment traversal - onUnreachableCodePathSegmentStart(segment) { - funcInfo.currentSegments.add(segment); - }, - - // Tracks reachable segment traversal - onCodePathSegmentEnd(segment) { - funcInfo.currentSegments.delete(segment); - }, - - // Tracks unreachable segment traversal - onUnreachableCodePathSegmentEnd(segment) { - funcInfo.currentSegments.delete(segment); - }, - - // Checks reachable or not. - CallExpression(node) { - - // Ignores if `cb` doesn't exist. - if (!funcInfo.hasCb) { - return; - } - - // Sets marks that `cb` was called. - const callee = node.callee; - if (callee.type === "Identifier" && callee.name === "cb") { - funcInfo.currentSegments.forEach(segment => { - const info = segmentInfoMap[segment.id]; - info.cbCalled = true; - }); - } - } - }; - } + meta: { + // ... + }, + create(context) { + let funcInfo; + const funcInfoStack = []; + const segmentInfoMap = Object.create(null); + + return { + // Checks `cb`. + onCodePathStart(codePath, node) { + funcInfoStack.push(funcInfo); + + funcInfo = { + codePath: codePath, + hasCb: hasCb(node, context), + currentSegments: new Set(), + }; + }, + + onCodePathEnd(codePath, node) { + funcInfo = funcInfoStack.pop(); + + // Checks `cb` was called in every paths. + const cbCalled = codePath.finalSegments.every( + function (segment) { + const info = segmentInfoMap[segment.id]; + return info.cbCalled; + }, + ); + + if (!cbCalled) { + context.report({ + message: "`cb` should be called in every path.", + node: node, + }); + } + }, + + // Manages state of code paths and tracks traversed segments + onCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + + // Ignores if `cb` doesn't exist. + if (!funcInfo.hasCb) { + return; + } + + // Initialize state of this path. + const info = (segmentInfoMap[segment.id] = { + cbCalled: false, + }); + + // If there are the previous paths, merges state. + // Checks `cb` was called in every previous path. + if (segment.prevSegments.length > 0) { + info.cbCalled = segment.prevSegments.every(isCbCalled); + } + }, + + // Tracks unreachable segment traversal + onUnreachableCodePathSegmentStart(segment) { + funcInfo.currentSegments.add(segment); + }, + + // Tracks reachable segment traversal + onCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + // Tracks unreachable segment traversal + onUnreachableCodePathSegmentEnd(segment) { + funcInfo.currentSegments.delete(segment); + }, + + // Checks reachable or not. + CallExpression(node) { + // Ignores if `cb` doesn't exist. + if (!funcInfo.hasCb) { + return; + } + + // Sets marks that `cb` was called. + const callee = node.callee; + if (callee.type === "Identifier" && callee.name === "cb") { + funcInfo.currentSegments.forEach(segment => { + const info = segmentInfoMap[segment.id]; + info.cbCalled = true; + }); + } + }, + }; + }, }; ``` @@ -521,9 +512,9 @@ console.log("Hello world!"); ```js if (a) { - foo(); + foo(); } else { - bar(); + bar(); } ``` @@ -535,11 +526,11 @@ if (a) { ```js if (a) { - foo(); + foo(); } else if (b) { - bar(); + bar(); } else if (c) { - hoge(); + hoge(); } ``` @@ -551,18 +542,18 @@ if (a) { ```js switch (a) { - case 0: - foo(); - break; - - case 1: - case 2: - bar(); - // fallthrough - - case 3: - hoge(); - break; + case 0: + foo(); + break; + + case 1: + case 2: + bar(); + // fallthrough + + case 3: + hoge(); + break; } ``` @@ -574,22 +565,22 @@ switch (a) { ```js switch (a) { - case 0: - foo(); - break; - - case 1: - case 2: - bar(); - // fallthrough - - case 3: - hoge(); - break; - - default: - fuga(); - break; + case 0: + foo(); + break; + + case 1: + case 2: + bar(); + // fallthrough + + case 3: + hoge(); + break; + + default: + fuga(); + break; } ``` @@ -601,22 +592,22 @@ switch (a) { ```js try { - foo(); - if (a) { - throw new Error(); - } - bar(); + foo(); + if (a) { + throw new Error(); + } + bar(); } catch (err) { - hoge(err); + hoge(err); } last(); ``` It creates the paths from `try` block to `catch` block at: -* `throw` statements. -* The first throwable node (e.g. a function call) in the `try` block. -* The end of the `try` block. +- `throw` statements. +- The first throwable node (e.g. a function call) in the `try` block. +- The end of the `try` block. :::img-container ![`TryStatement` (try-catch)](../assets/images/code-path-analysis/example-trystatement-try-catch.svg) @@ -626,16 +617,16 @@ It creates the paths from `try` block to `catch` block at: ```js try { - foo(); - bar(); + foo(); + bar(); } finally { - fuga(); + fuga(); } last(); ``` If there is not `catch` block, `finally` block has two current segments. -At this time, `CodePath.currentSegments.length` is `2`. +At this time when running the previous example to find unreachable nodes, `currentSegments.length` is `2`. One is the normal path, and another is the leaving path (`throw` or `return`). :::img-container @@ -646,12 +637,12 @@ One is the normal path, and another is the leaving path (`throw` or `return`). ```js try { - foo(); - bar(); + foo(); + bar(); } catch (err) { - hoge(err); + hoge(err); } finally { - fuga(); + fuga(); } last(); ``` @@ -664,11 +655,11 @@ last(); ```js while (a) { - foo(); - if (b) { - continue; - } - bar(); + foo(); + if (b) { + continue; + } + bar(); } ``` @@ -680,8 +671,8 @@ while (a) { ```js do { - foo(); - bar(); + foo(); + bar(); } while (a); ``` @@ -693,11 +684,11 @@ do { ```js for (let i = 0; i < 10; ++i) { - foo(); - if (b) { - break; - } - bar(); + foo(); + if (b) { + break; + } + bar(); } ``` @@ -709,7 +700,7 @@ for (let i = 0; i < 10; ++i) { ```js for (;;) { - foo(); + foo(); } bar(); ``` @@ -722,7 +713,7 @@ bar(); ```js for (let key in obj) { - foo(key); + foo(key); } ``` @@ -734,10 +725,10 @@ for (let key in obj) { ```js function foo(a) { - if (a) { - return; - } - bar(); + if (a) { + return; + } + bar(); } foo(false); @@ -745,14 +736,14 @@ foo(false); It creates two code paths. -* The global's +- The global's :::img-container - ![When there is a function](../assets/images/code-path-analysis/example-when-there-is-a-function-g.svg) +![When there is a function](../assets/images/code-path-analysis/example-when-there-is-a-function-g.svg) ::: -* The function's +- The function's :::img-container - ![When there is a function](../assets/images/code-path-analysis/example-when-there-is-a-function-f.svg) +![When there is a function](../assets/images/code-path-analysis/example-when-there-is-a-function-f.svg) ::: diff --git a/docs/src/extend/custom-formatters.md b/docs/src/extend/custom-formatters.md index b95db0f8bd62..6f2c40ebcab4 100644 --- a/docs/src/extend/custom-formatters.md +++ b/docs/src/extend/custom-formatters.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: extend eslint title: Custom Formatters order: 4 - --- Custom formatters let you display linting results in a format that best fits your needs, whether that's in a specific file format, a certain display style, or a format optimized for a particular tool. @@ -20,8 +19,8 @@ Each formatter is a function that receives a `results` object and a `context` as ```js //my-awesome-formatter.js -module.exports = function(results, context) { - return JSON.stringify(results, null, 2); +module.exports = function (results, context) { + return JSON.stringify(results, null, 2); }; ``` @@ -29,9 +28,9 @@ A formatter can also be an async function (from ESLint v8.4.0), the following sh ```js //my-awesome-formatter.js -module.exports = async function(results) { - const formatted = await asyncTask(); - return formatted; +module.exports = async function (results) { + const formatted = await asyncTask(); + return formatted; }; ``` @@ -49,42 +48,41 @@ The `results` object passed into a formatter is an array of [`result`](#the-resu ```js [ - { - filePath: "/path/to/a/file.js", - messages: [ - { - ruleId: "curly", - severity: 2, - message: "Expected { after 'if' condition.", - line: 2, - column: 1, - nodeType: "IfStatement" - }, - { - ruleId: "no-process-exit", - severity: 2, - message: "Don't use process.exit(); throw an error instead.", - line: 3, - column: 1, - nodeType: "CallExpression" - } - ], - errorCount: 2, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - source: - "var err = doStuff();\nif (err) console.log('failed tests: ' + err);\nprocess.exit(1);\n" - }, - { - filePath: "/path/to/Gruntfile.js", - messages: [], - errorCount: 0, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0 - } -] + { + filePath: "/path/to/a/file.js", + messages: [ + { + ruleId: "curly", + severity: 2, + message: "Expected { after 'if' condition.", + line: 2, + column: 1, + nodeType: "IfStatement", + }, + { + ruleId: "no-process-exit", + severity: 2, + message: "Don't use process.exit(); throw an error instead.", + line: 3, + column: 1, + nodeType: "CallExpression", + }, + ], + errorCount: 2, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "var err = doStuff();\nif (err) console.log('failed tests: ' + err);\nprocess.exit(1);\n", + }, + { + filePath: "/path/to/Gruntfile.js", + messages: [], + errorCount: 0, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, +]; ``` #### The `result` Object @@ -94,33 +92,34 @@ also be manually applied to that page. --> Each object in the `results` array is a `result` object. Each `result` object contains the path of the file that was linted and information about linting issues that were encountered. Here are the properties available on each `result` object: -* **filePath**: The absolute path to the file that was linted. -* **messages**: An array of [`message`](#the-message-object) objects. See below for more info about messages. -* **errorCount**: The number of errors for the given file. -* **warningCount**: The number of warnings for the given file. -* **source**: The source code for the given file. This property is omitted if this file has no errors/warnings or if the `output` property is present. -* **output**: The source code for the given file with as many fixes applied as possible. This property is omitted if no fix is available. +- **filePath**: The absolute path to the file that was linted. +- **messages**: An array of [`message`](#the-message-object) objects. See below for more info about messages. +- **errorCount**: The number of errors for the given file. +- **warningCount**: The number of warnings for the given file. +- **stats**: The optional [`stats`](./stats#-stats-type) object that only exists when the `stats` option is used. +- **source**: The source code for the given file. This property is omitted if this file has no errors/warnings or if the `output` property is present. +- **output**: The source code for the given file with as many fixes applied as possible. This property is omitted if no fix is available. ##### The `message` Object Each `message` object contains information about the ESLint rule that was triggered by some source code. The properties available on each `message` object are: -* **ruleId**: the ID of the rule that produced the error or warning. -* **severity**: the severity of the failure, `1` for warnings and `2` for errors. -* **message**: the human readable description of the error. -* **line**: the line where the issue is located. -* **column**: the column where the issue is located. -* **nodeType**: the type of the node in the [AST](https://github.com/estree/estree/blob/master/es5.md#node-objects) +- **ruleId**: the ID of the rule that produced the error or warning. If the error or warning was not produced by a rule (for example, if it's a parsing error), this is `null`. +- **severity**: the severity of the failure, `1` for warnings and `2` for errors. +- **message**: the human readable description of the error. +- **line**: the line where the issue is located. +- **column**: the column where the issue is located. +- **nodeType**: (**Deprecated:** This property will be removed in a future version of ESLint.) the type of the node in the [AST](https://github.com/estree/estree/blob/master/es5.md#node-objects) or `null` if the issue isn't related to a particular AST node. ### The `context` Argument The formatter function receives a `context` object as its second argument. The object has the following properties: -* `cwd`: The current working directory. This value comes from the `cwd` constructor option of the [ESLint](../integrate/nodejs-api#-new-eslintoptions) class. -* `maxWarningsExceeded` (optional): If `--max-warnings` was set and the number of warnings exceeded the limit, this property's value is an object containing two properties: - * `maxWarnings`: the value of the `--max-warnings` option - * `foundWarnings`: the number of lint warnings -* `rulesMeta`: The `meta` property values of rules. See the [Custom Rules](custom-rules) page for more information about rules. +- `cwd`: The current working directory. This value comes from the `cwd` constructor option of the [ESLint](../integrate/nodejs-api#-new-eslintoptions) class. +- `maxWarningsExceeded` (optional): If `--max-warnings` was set and the number of warnings exceeded the limit, this property's value is an object containing two properties: + - `maxWarnings`: the value of the `--max-warnings` option + - `foundWarnings`: the number of lint warnings +- `rulesMeta`: The `meta` property values of rules. See the [Custom Rules](custom-rules) page for more information about rules. For example, here's what the object would look like if the rule `no-extra-semi` had been run: @@ -162,61 +161,61 @@ Custom formatters have access to environment variables and so can change their b Here's an example that uses a `FORMATTER_SKIP_WARNINGS` environment variable to determine whether to show warnings in the results: ```js -module.exports = function(results) { - var skipWarnings = process.env.FORMATTER_SKIP_WARNINGS === "true"; - - var results = results || []; - var summary = results.reduce( - function(seq, current) { - current.messages.forEach(function(msg) { - var logMessage = { - filePath: current.filePath, - ruleId: msg.ruleId, - message: msg.message, - line: msg.line, - column: msg.column - }; - - if (msg.severity === 1) { - logMessage.type = "warning"; - seq.warnings.push(logMessage); - } - if (msg.severity === 2) { - logMessage.type = "error"; - seq.errors.push(logMessage); - } - }); - return seq; - }, - { - errors: [], - warnings: [] - } - ); - - if (summary.errors.length > 0 || summary.warnings.length > 0) { - var warnings = !skipWarnings ? summary.warnings : []; // skip the warnings in that case - - var lines = summary.errors - .concat(warnings) - .map(function(msg) { - return ( - "\n" + - msg.type + - " " + - msg.ruleId + - "\n " + - msg.filePath + - ":" + - msg.line + - ":" + - msg.column - ); - }) - .join("\n"); - - return lines + "\n"; - } +module.exports = function (results) { + var skipWarnings = process.env.FORMATTER_SKIP_WARNINGS === "true"; + + var results = results || []; + var summary = results.reduce( + function (seq, current) { + current.messages.forEach(function (msg) { + var logMessage = { + filePath: current.filePath, + ruleId: msg.ruleId, + message: msg.message, + line: msg.line, + column: msg.column, + }; + + if (msg.severity === 1) { + logMessage.type = "warning"; + seq.warnings.push(logMessage); + } + if (msg.severity === 2) { + logMessage.type = "error"; + seq.errors.push(logMessage); + } + }); + return seq; + }, + { + errors: [], + warnings: [], + }, + ); + + if (summary.errors.length > 0 || summary.warnings.length > 0) { + var warnings = !skipWarnings ? summary.warnings : []; // skip the warnings in that case + + var lines = summary.errors + .concat(warnings) + .map(function (msg) { + return ( + "\n" + + msg.type + + " " + + msg.ruleId + + "\n " + + msg.filePath + + ":" + + msg.line + + ":" + + msg.column + ); + }) + .join("\n"); + + return lines + "\n"; + } }; ``` @@ -266,11 +265,11 @@ Because ESLint knows to look for packages beginning with `eslint-formatter-` whe Tips for the `package.json` of a custom formatter: -* The [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) entry point must be the JavaScript file implementing your custom formatter. -* Add these [`keywords`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) to help users find your formatter: - * `"eslint"` - * `"eslint-formatter"` - * `"eslintformatter"` +- The [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) entry point must be the JavaScript file implementing your custom formatter. +- Add these [`keywords`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) to help users find your formatter: + - `"eslint"` + - `"eslint-formatter"` + - `"eslintformatter"` See all [custom formatters on npm](https://www.npmjs.com/search?q=eslint-formatter). @@ -281,28 +280,28 @@ See all [custom formatters on npm](https://www.npmjs.com/search?q=eslint-formatt A formatter that only reports on the total count of errors and warnings will look like this: ```javascript -module.exports = function(results, context) { - // accumulate the errors and warnings - var summary = results.reduce( - function(seq, current) { - seq.errors += current.errorCount; - seq.warnings += current.warningCount; - return seq; - }, - { errors: 0, warnings: 0 } - ); - - if (summary.errors > 0 || summary.warnings > 0) { - return ( - "Errors: " + - summary.errors + - ", Warnings: " + - summary.warnings + - "\n" - ); - } - - return ""; +module.exports = function (results, context) { + // accumulate the errors and warnings + var summary = results.reduce( + function (seq, current) { + seq.errors += current.errorCount; + seq.warnings += current.warningCount; + return seq; + }, + { errors: 0, warnings: 0 }, + ); + + if (summary.errors > 0 || summary.warnings > 0) { + return ( + "Errors: " + + summary.errors + + ", Warnings: " + + summary.warnings + + "\n" + ); + } + + return ""; }; ``` @@ -323,59 +322,60 @@ Errors: 2, Warnings: 4 A more complex report could look like this: ```javascript -module.exports = function(results, context) { - var results = results || []; - - var summary = results.reduce( - function(seq, current) { - current.messages.forEach(function(msg) { - var logMessage = { - filePath: current.filePath, - ruleId: msg.ruleId, - ruleUrl: context.rulesMeta[msg.ruleId].docs.url, - message: msg.message, - line: msg.line, - column: msg.column - }; - - if (msg.severity === 1) { - logMessage.type = "warning"; - seq.warnings.push(logMessage); - } - if (msg.severity === 2) { - logMessage.type = "error"; - seq.errors.push(logMessage); - } - }); - return seq; - }, - { - errors: [], - warnings: [] - } - ); - - if (summary.errors.length > 0 || summary.warnings.length > 0) { - var lines = summary.errors - .concat(summary.warnings) - .map(function(msg) { - return ( - "\n" + - msg.type + - " " + - msg.ruleId + (msg.ruleUrl ? " (" + msg.ruleUrl + ")" : "") + - "\n " + - msg.filePath + - ":" + - msg.line + - ":" + - msg.column - ); - }) - .join("\n"); - - return lines + "\n"; - } +module.exports = function (results, context) { + var results = results || []; + + var summary = results.reduce( + function (seq, current) { + current.messages.forEach(function (msg) { + var logMessage = { + filePath: current.filePath, + ruleId: msg.ruleId, + ruleUrl: context.rulesMeta[msg.ruleId].docs.url, + message: msg.message, + line: msg.line, + column: msg.column, + }; + + if (msg.severity === 1) { + logMessage.type = "warning"; + seq.warnings.push(logMessage); + } + if (msg.severity === 2) { + logMessage.type = "error"; + seq.errors.push(logMessage); + } + }); + return seq; + }, + { + errors: [], + warnings: [], + }, + ); + + if (summary.errors.length > 0 || summary.warnings.length > 0) { + var lines = summary.errors + .concat(summary.warnings) + .map(function (msg) { + return ( + "\n" + + msg.type + + " " + + msg.ruleId + + (msg.ruleUrl ? " (" + msg.ruleUrl + ")" : "") + + "\n " + + msg.filePath + + ":" + + msg.line + + ":" + + msg.column + ); + }) + .join("\n"); + + return lines + "\n"; + } }; ``` diff --git a/docs/src/extend/custom-parsers.md b/docs/src/extend/custom-parsers.md index 55ed726f046c..8a84dda68199 100644 --- a/docs/src/extend/custom-parsers.md +++ b/docs/src/extend/custom-parsers.md @@ -5,18 +5,19 @@ eleventyNavigation: parent: extend eslint title: Custom Parsers order: 5 - --- +{%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} + ESLint custom parsers let you extend ESLint to support linting new non-standard JavaScript language features or custom syntax in your code. A parser is responsible for taking your code and transforming it into an abstract syntax tree (AST) that ESLint can then analyze and lint. ## Creating a Custom Parser ### Methods in Custom Parsers -A custom parser is a JavaScript object with either a `parse` or `parseForESLint` method. The `parse` method only returns the AST, whereas `parseForESLint` also returns additional values that let the parser customize the behavior of ESLint even more. +A custom parser is a JavaScript object with either a `parse()` or `parseForESLint()` method. The `parse` method only returns the AST, whereas `parseForESLint()` also returns additional values that let the parser customize the behavior of ESLint even more. -Both methods should take in the source code as the first argument, and an optional configuration object as the second argument, which is provided as [`parserOptions`](../use/configure/language-options#specifying-parser-options) in a configuration file. +Both methods should be instance (own) properties and take in the source code as the first argument, and an optional configuration object as the second argument, which is provided as [`parserOptions`](../use/configure/language-options#specifying-parser-options) in a configuration file. ```javascript // customParser.js @@ -25,12 +26,12 @@ const espree = require("espree"); // Logs the duration it takes to parse each file. function parse(code, options) { - const label = `Parsing file "${options.filePath}"`; - console.time(label); - const ast = espree.parse(code, options); - console.timeEnd(label); - return ast; // Only the AST is returned. -}; + const label = `Parsing file "${options.filePath}"`; + console.time(label); + const ast = espree.parse(code, options); + console.timeEnd(label); + return ast; // Only the AST is returned. +} module.exports = { parse }; ``` @@ -43,12 +44,12 @@ The `parse` method should simply return the [AST](#ast-specification) object. The `parseForESLint` method should return an object that contains the required property `ast` and optional properties `services`, `scopeManager`, and `visitorKeys`. -* `ast` should contain the [AST](#ast-specification) object. -* `services` can contain any parser-dependent services (such as type checkers for nodes). The value of the `services` property is available to rules as `context.sourceCode.parserServices`. Default is an empty object. -* `scopeManager` can be a [ScopeManager](./scope-manager-interface) object. Custom parsers can use customized scope analysis for experimental/enhancement syntaxes. The default is the `ScopeManager` object which is created by [eslint-scope](https://github.com/eslint/eslint-scope). - * Support for `scopeManager` was added in ESLint v4.14.0. ESLint versions that support `scopeManager` will provide an `eslintScopeManager: true` property in `parserOptions`, which can be used for feature detection. -* `visitorKeys` can be an object to customize AST traversal. The keys of the object are the type of AST nodes. Each value is an array of the property names which should be traversed. The default is [KEYS of `eslint-visitor-keys`](https://github.com/eslint/eslint-visitor-keys#evkkeys). - * Support for `visitorKeys` was added in ESLint v4.14.0. ESLint versions that support `visitorKeys` will provide an `eslintVisitorKeys: true` property in `parserOptions`, which can be used for feature detection. +- `ast` should contain the [AST](#ast-specification) object. +- `services` can contain any parser-dependent services (such as type checkers for nodes). The value of the `services` property is available to rules as `context.sourceCode.parserServices`. Default is an empty object. +- `scopeManager` can be a [ScopeManager](./scope-manager-interface) object. Custom parsers can use customized scope analysis for experimental/enhancement syntaxes. The default is the `ScopeManager` object which is created by [eslint-scope](https://github.com/eslint/js/tree/main/packages/eslint-scope). + - Support for `scopeManager` was added in ESLint v4.14.0. ESLint versions that support `scopeManager` will provide an `eslintScopeManager: true` property in `parserOptions`, which can be used for feature detection. +- `visitorKeys` can be an object to customize AST traversal. The keys of the object are the type of AST nodes. Each value is an array of the property names which should be traversed. The default is [KEYS of `eslint-visitor-keys`](https://github.com/eslint/js/tree/main/packages/eslint-visitor-keys#evkkeys). + - Support for `visitorKeys` was added in ESLint v4.14.0. ESLint versions that support `visitorKeys` will provide an `eslintVisitorKeys: true` property in `parserOptions`, which can be used for feature detection. ### Meta Data in Custom Parsers @@ -57,10 +58,10 @@ For easier debugging and more effective caching of custom parsers, it's recommen ```js // preferred location of name and version module.exports = { - meta: { - name: "eslint-parser-custom", - version: "1.2.3" - } + meta: { + name: "eslint-parser-custom", + version: "1.2.3", + }, }; ``` @@ -74,8 +75,8 @@ The AST that custom parsers should create is based on [ESTree](https://github.co All nodes must have `range` property. -* `range` (`number[]`) is an array of two numbers. Both numbers are a 0-based index which is the position in the array of source code characters. The first is the start position of the node, the second is the end position of the node. `code.slice(node.range[0], node.range[1])` must be the text of the node. This range does not include spaces/parentheses which are around the node. -* `loc` (`SourceLocation`) must not be `null`. [The `loc` property is defined as nullable by ESTree](https://github.com/estree/estree/blob/25834f7247d44d3156030f8e8a2d07644d771fdb/es5.md#node-objects), but ESLint requires this property. The `SourceLocation#source` property can be `undefined`. ESLint does not use the `SourceLocation#source` property. +- `range` (`number[]`) is an array of two numbers. Both numbers are a 0-based index which is the position in the array of source code characters. The first is the start position of the node, the second is the end position of the node. `code.slice(node.range[0], node.range[1])` must be the text of the node. This range does not include spaces/parentheses which are around the node. +- `loc` (`SourceLocation`) must not be `null`. [The `loc` property is defined as nullable by ESTree](https://github.com/estree/estree/blob/25834f7247d44d3156030f8e8a2d07644d771fdb/es5.md#node-objects), but ESLint requires this property. The `SourceLocation#source` property can be `undefined`. ESLint does not use the `SourceLocation#source` property. The `parent` property of all nodes must be rewritable. Before any rules have access to the AST, ESLint sets each node's `parent` property to its parent node while traversing. @@ -85,16 +86,16 @@ The `Program` node must have `tokens` and `comments` properties. Both properties ```ts interface Token { - type: string; - loc: SourceLocation; - // See the "All Nodes" section for details of the `range` property. - range: [number, number]; - value: string; + type: string; + loc: SourceLocation; + // See the "All Nodes" section for details of the `range` property. + range: [number, number]; + value: string; } ``` -* `tokens` (`Token[]`) is the array of tokens which affect the behavior of programs. Arbitrary spaces can exist between tokens, so rules check the `Token#range` to detect spaces between tokens. This must be sorted by `Token#range[0]`. -* `comments` (`Token[]`) is the array of comment tokens. This must be sorted by `Token#range[0]`. +- `tokens` (`Token[]`) is the array of tokens which affect the behavior of programs. Arbitrary spaces can exist between tokens, so rules check the `Token#range` to detect spaces between tokens. This must be sorted by `Token#range[0]`. +- `comments` (`Token[]`) is the array of comment tokens. This must be sorted by `Token#range[0]`. The range indexes of all tokens and comments must not overlap with the range of other tokens and comments. @@ -102,7 +103,7 @@ The range indexes of all tokens and comments must not overlap with the range of The `Literal` node must have `raw` property. -* `raw` (`string`) is the source code of this literal. This is the same as `code.slice(node.range[0], node.range[1])`. +- `raw` (`string`) is the source code of this literal. This is the same as `code.slice(node.range[0], node.range[1])`. ## Packaging a Custom Parser @@ -117,18 +118,37 @@ For more information on publishing an npm package, refer to the [npm documentati Once you've published the npm package, you can use it by adding the package to your project. For example: -```shell -npm install eslint-parser-myparser --save-dev +{{ npm_tabs({ + command: "install", + packages: ["eslint-parser-myparser"], + args: ["--save-dev"] +}) }} + +Then add the custom parser to your ESLint configuration file with the `languageOptions.parser` property. For example: + +```js +// eslint.config.js + +const myparser = require("eslint-parser-myparser"); + +module.exports = [ + { + languageOptions: { + parser: myparser, + }, + // ... rest of configuration + }, +]; ``` -Then add the custom parser to your ESLint configuration file with the `parser` property. For example: +When using legacy configuration, specify the `parser` property as a string: ```js // .eslintrc.js module.exports = { - parser: 'eslint-parser-myparser', - // ... rest of configuration + parser: "eslint-parser-myparser", + // ... rest of configuration }; ``` @@ -144,23 +164,36 @@ A simple custom parser that provides a `context.sourceCode.parserServices.foo()` // awesome-custom-parser.js var espree = require("espree"); function parseForESLint(code, options) { - return { - ast: espree.parse(code, options), - services: { - foo: function() { - console.log("foo"); - } - }, - scopeManager: null, - visitorKeys: null - }; -}; + return { + ast: espree.parse(code, options), + services: { + foo: function () { + console.log("foo"); + }, + }, + scopeManager: null, + visitorKeys: null, + }; +} module.exports = { parseForESLint }; ``` Include the custom parser in an ESLint configuration file: +```js +// eslint.config.js +module.exports = [ + { + languageOptions: { + parser: require("./path/to/awesome-custom-parser"), + }, + }, +]; +``` + +Or if using legacy configuration: + ```js // .eslintrc.json { diff --git a/docs/src/extend/custom-processors-deprecated.md b/docs/src/extend/custom-processors-deprecated.md new file mode 100644 index 000000000000..1de004e55bd8 --- /dev/null +++ b/docs/src/extend/custom-processors-deprecated.md @@ -0,0 +1,188 @@ +--- +title: Custom Processors (Deprecated) +--- + +::: warning +This documentation is for custom processors using the deprecated eslintrc configuration format. [View the updated documentation](custom-processors). +::: + +You can also create custom processors that tell ESLint how to process files other than standard JavaScript. For example, you could write a custom processor to extract and process JavaScript from Markdown files ([@eslint/markdown](https://www.npmjs.com/package/@eslint/markdown) includes a custom processor for this). + +## Custom Processor Specification + +In order to create a custom processor, the object exported from your module has to conform to the following interface: + +```js +module.exports = { + processors: { + "processor-name": { + meta: { + name: "eslint-processor-name", + version: "1.2.3", + }, + // takes text of the file and filename + preprocess: function (text, filename) { + // here, you can strip out any non-JS content + // and split into multiple strings to lint + + return [ + // return an array of code blocks to lint + { text: code1, filename: "0.js" }, + { text: code2, filename: "1.js" }, + ]; + }, + + // takes a Message[][] and filename + postprocess: function (messages, filename) { + // `messages` argument contains two-dimensional array of Message objects + // where each top-level array item contains array of lint messages related + // to the text that was returned in array from preprocess() method + + // you need to return a one-dimensional array of the messages you want to keep + return [].concat(...messages); + }, + + supportsAutofix: true, // (optional, defaults to false) + }, + }, +}; +``` + +**The `preprocess` method** takes the file contents and filename as arguments, and returns an array of code blocks to lint. The code blocks will be linted separately but still be registered to the filename. + +A code block has two properties `text` and `filename`. The `text` property is the content of the block and the `filename` property is the name of the block. The name of the block can be anything, but should include the file extension, which tells the linter how to process the current block. The linter checks the [`--ext` CLI option](../use/command-line-interface#--ext) to see if the current block should be linted and resolves `overrides` configs to check how to process the current block. + +It's up to the plugin to decide if it needs to return just one part of the non-JavaScript file or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts. However, for `.md` files, you can return multiple items because each JavaScript block might be independent. + +**The `postprocess` method** takes a two-dimensional array of arrays of lint messages and the filename. Each item in the input array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the locations of all errors to correspond to locations in the original, unprocessed code, and aggregate them into a single flat array and return it. + +Reported problems have the following location information in each lint message: + +```typescript +type LintMessage = { + /// The 1-based line number where the message occurs. + line?: number; + + /// The 1-based column number where the message occurs. + column?: number; + + /// The 1-based line number of the end location. + endLine?: number; + + /// The 1-based column number of the end location. + endColumn?: number; + + /// If `true`, this is a fatal error. + fatal?: boolean; + + /// Information for an autofix. + fix: Fix; + + /// The error message. + message: string; + + /// The ID of the rule which generated the message, or `null` if not applicable. + ruleId: string | null; + + /// The severity of the message. + severity: 0 | 1 | 2; + + /// Information for suggestions. + suggestions?: Suggestion[]; +}; + +type Fix = { + range: [number, number]; + text: string; +}; + +type Suggestion = { + desc?: string; + messageId?: string; + fix: Fix; +}; +``` + +By default, ESLint does not perform autofixes when a custom processor is used, even when the `--fix` flag is enabled on the command line. To allow ESLint to autofix code when using your processor, you should take the following additional steps: + +1. Update the `postprocess` method to additionally transform the `fix` property of reported problems. All autofixable problems have a `fix` property, which is an object with the following schema: + + ```typescript + { + range: [number, number], + text: string + } + ``` + + The `range` property contains two indexes in the code, referring to the start and end location of a contiguous section of text that will be replaced. The `text` property refers to the text that will replace the given range. + + In the initial list of problems, the `fix` property will refer to a fix in the processed JavaScript. The `postprocess` method should transform the object to refer to a fix in the original, unprocessed file. + +2. Add a `supportsAutofix: true` property to the processor. + +You can have both rules and custom processors in a single plugin. You can also have multiple processors in one plugin. To support multiple extensions, add each one to the `processors` element and point them to the same object. + +**The `meta` object** helps ESLint cache the processor and provide more friendly debug message. The `meta.name` property should match the processor name and the `meta.version` property should match the npm package version for your processors. The easiest way to accomplish this is by reading this information from your `package.json`. + +## Specifying Processor in Config Files + +To use a processor, add its ID to a `processor` section in the config file. Processor ID is a concatenated string of plugin name and processor name with a slash as a separator. This can also be added to a `overrides` section of the config, to specify which processors should handle which files. + +For example: + +```yml +plugins: + - a-plugin +overrides: + - files: "*.md" + processor: a-plugin/markdown +``` + +See [Specify a Processor](../use/configure/plugins#specify-a-processor) in the Plugin Configuration documentation for more details. + +## File Extension-named Processor + +::: warning +This feature is deprecated and only works in eslintrc-style configuration files. Flat config files do not automatically apply processors; you need to explicitly set the `processor` property. +::: + +If a custom processor name starts with `.`, ESLint handles the processor as a **file extension-named processor**. ESLint applies the processor to files with that filename extension automatically. Users don't need to specify the file extension-named processors in their config files. + +For example: + +```js +module.exports = { + processors: { + // This processor will be applied to `*.md` files automatically. + // Also, you can use this processor as "plugin-id/.md" explicitly. + ".md": { + preprocess(text, filename) { /* ... */ }, + postprocess(messageLists, filename) { /* ... */ } + } + // This processor will not be applied to any files automatically. + // To use this processor, you must explicitly specify it + // in your configuration as "plugin-id/markdown". + "markdown": { + preprocess(text, filename) { /* ... */ }, + postprocess(messageLists, filename) { /* ... */ } + } + } +} +``` + +You can also use the same custom processor with multiple filename extensions. The following example shows using the same processor for both `.md` and `.mdx` files: + +```js +const myCustomProcessor = { + /* processor methods */ +}; + +module.exports = { + // The same custom processor is applied to both + // `.md` and `.mdx` files. + processors: { + ".md": myCustomProcessor, + ".mdx": myCustomProcessor, + }, +}; +``` diff --git a/docs/src/extend/custom-processors.md b/docs/src/extend/custom-processors.md index d5a5261d9769..3c65dcfe9ea1 100644 --- a/docs/src/extend/custom-processors.md +++ b/docs/src/extend/custom-processors.md @@ -5,53 +5,67 @@ eleventyNavigation: parent: create plugins title: Custom Processors order: 3 - --- -You can also create custom processors that tell ESLint how to process files other than standard JavaScript. For example, you could write a custom processor to extract and process JavaScript from Markdown files ([eslint-plugin-markdown](https://www.npmjs.com/package/eslint-plugin-markdown) includes a custom processor for this). +You can also create custom processors that tell ESLint how to process files other than standard JavaScript. For example, you could write a custom processor to extract and process JavaScript from Markdown files ([@eslint/markdown](https://www.npmjs.com/package/@eslint/markdown) includes a custom processor for this). + +::: tip +This page explains how to create a custom processor for use with the flat config format. For the deprecated eslintrc format, [see the deprecated documentation](custom-processors-deprecated). +::: ## Custom Processor Specification In order to create a custom processor, the object exported from your module has to conform to the following interface: ```js -module.exports = { - processors: { - "processor-name": { - meta: { - name: "eslint-processor-name", - version: "1.2.3" - }, - // takes text of the file and filename - preprocess: function(text, filename) { - // here, you can strip out any non-JS content - // and split into multiple strings to lint - - return [ // return an array of code blocks to lint - { text: code1, filename: "0.js" }, - { text: code2, filename: "1.js" }, - ]; - }, - - // takes a Message[][] and filename - postprocess: function(messages, filename) { - // `messages` argument contains two-dimensional array of Message objects - // where each top-level array item contains array of lint messages related - // to the text that was returned in array from preprocess() method - - // you need to return a one-dimensional array of the messages you want to keep - return [].concat(...messages); - }, - - supportsAutofix: true // (optional, defaults to false) - } - } +const plugin = { + meta: { + name: "eslint-plugin-example", + version: "1.2.3", + }, + processors: { + "processor-name": { + meta: { + name: "eslint-processor-name", + version: "1.2.3", + }, + // takes text of the file and filename + preprocess(text, filename) { + // here, you can strip out any non-JS content + // and split into multiple strings to lint + + return [ + // return an array of code blocks to lint + { text: code1, filename: "0.js" }, + { text: code2, filename: "1.js" }, + ]; + }, + + // takes a Message[][] and filename + postprocess(messages, filename) { + // `messages` argument contains two-dimensional array of Message objects + // where each top-level array item contains array of lint messages related + // to the text that was returned in array from preprocess() method + + // you need to return a one-dimensional array of the messages you want to keep + return [].concat(...messages); + }, + + supportsAutofix: true, // (optional, defaults to false) + }, + }, }; + +// for ESM +export default plugin; + +// OR for CommonJS +module.exports = plugin; ``` **The `preprocess` method** takes the file contents and filename as arguments, and returns an array of code blocks to lint. The code blocks will be linted separately but still be registered to the filename. -A code block has two properties `text` and `filename`. The `text` property is the content of the block and the `filename` property is the name of the block. The name of the block can be anything, but should include the file extension, which tells the linter how to process the current block. The linter checks the [`--ext` CLI option](../use/command-line-interface#--ext) to see if the current block should be linted and resolves `overrides` configs to check how to process the current block. +A code block has two properties `text` and `filename`. The `text` property is the content of the block and the `filename` property is the name of the block. The name of the block can be anything, but should include the file extension, which tells ESLint how to process the current block. ESLint checks matching `files` entries in the project's config to determine if the code blocks should be linted. It's up to the plugin to decide if it needs to return just one part of the non-JavaScript file or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts. However, for `.md` files, you can return multiple items because each JavaScript block might be independent. @@ -61,49 +75,47 @@ Reported problems have the following location information in each lint message: ```typescript type LintMessage = { + /// The 1-based line number where the message occurs. + line?: number; - /// The 1-based line number where the message occurs. - line?: number; - - /// The 1-based column number where the message occurs. - column?: number; + /// The 1-based column number where the message occurs. + column?: number; - /// The 1-based line number of the end location. - endLine?: number; + /// The 1-based line number of the end location. + endLine?: number; - /// The 1-based column number of the end location. - endColumn?: number; + /// The 1-based column number of the end location. + endColumn?: number; - /// If `true`, this is a fatal error. - fatal?: boolean; + /// If `true`, this is a fatal error. + fatal?: boolean; - /// Information for an autofix. - fix: Fix; + /// Information for an autofix. + fix: Fix; - /// The error message. - message: string; + /// The error message. + message: string; - /// The ID of the rule which generated the message, or `null` if not applicable. - ruleId: string | null; + /// The ID of the rule which generated the message, or `null` if not applicable. + ruleId: string | null; - /// The severity of the message. - severity: 0 | 1 | 2; + /// The severity of the message. + severity: 0 | 1 | 2; - /// Information for suggestions. - suggestions?: Suggestion[]; + /// Information for suggestions. + suggestions?: Suggestion[]; }; type Fix = { - range: [number, number]; - text: string; -} + range: [number, number]; + text: string; +}; type Suggestion = { - desc?: string; - messageId?: string; - fix: Fix; -} - + desc?: string; + messageId?: string; + fix: Fix; +}; ``` By default, ESLint does not perform autofixes when a custom processor is used, even when the `--fix` flag is enabled on the command line. To allow ESLint to autofix code when using your processor, you should take the following additional steps: @@ -125,65 +137,79 @@ By default, ESLint does not perform autofixes when a custom processor is used, e You can have both rules and custom processors in a single plugin. You can also have multiple processors in one plugin. To support multiple extensions, add each one to the `processors` element and point them to the same object. -**The `meta` object** helps ESLint cache the processor and provide more friendly debug message. The `meta.name` property should match the processor name and the `meta.version` property should match the npm package version for your processors. The easiest way to accomplish this is by reading this information from your `package.json`. +### How `meta` Objects are Used -## Specifying Processor in Config Files +The `meta` object helps ESLint cache configurations that use a processor and to provide more friendly debug messages. -To use a processor, add its ID to a `processor` section in the config file. Processor ID is a concatenated string of plugin name and processor name with a slash as a separator. This can also be added to a `overrides` section of the config, to specify which processors should handle which files. +#### Plugin `meta` Object -For example: +The [plugin `meta` object](plugins#meta-data-in-plugins) provides information about the plugin itself. When a processor is specified using the string format `plugin-name/processor-name`, ESLint automatically uses the plugin `meta` to generate a name for the processor. This is the most common case for processors. -```yml -plugins: - - a-plugin -overrides: - - files: "*.md" - processor: a-plugin/markdown -``` +Example: -See [Specify a Processor](../use/configure/plugins#specify-a-processor) in the Plugin Configuration documentation for more details. +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; +import example from "eslint-plugin-example"; + +export default defineConfig([ + { + files: ["**/*.txt"], // apply processor to text files + plugins: { + example, + }, + processor: "example/processor-name", + }, + // ... other configs +]); +``` -## File Extension-named Processor +In this example, the processor name is `"example/processor-name"`, and that's the value that will be used for serializing configurations. -::: warning -This feature is deprecated and only works in eslintrc-style configuration files. Flat config files do not automatically apply processors; you need to explicitly set the `processor` property. -::: +#### Processor `meta` Object -If a custom processor name starts with `.`, ESLint handles the processor as a **file extension-named processor**. ESLint applies the processor to files with that filename extension automatically. Users don't need to specify the file extension-named processors in their config files. +Each processor can also specify its own `meta` object. This information is used when the processor object is passed directly to `processor` in a configuration. In that case, ESLint doesn't know which plugin the processor belongs to. The `meta.name` property should match the processor name and the `meta.version` property should match the npm package version for your processors. The easiest way to accomplish this is by reading this information from your `package.json`. -For example: +Example: ```js -module.exports = { - processors: { - // This processor will be applied to `*.md` files automatically. - // Also, you can use this processor as "plugin-id/.md" explicitly. - ".md": { - preprocess(text, filename) { /* ... */ }, - postprocess(messageLists, filename) { /* ... */ } - } - // This processor will not be applied to any files automatically. - // To use this processor, you must explicitly specify it - // in your configuration as "plugin-id/markdown". - "markdown": { - preprocess(text, filename) { /* ... */ }, - postprocess(messageLists, filename) { /* ... */ } - } - } -} +// eslint.config.js +import { defineConfig } from "eslint/config"; +import example from "eslint-plugin-example"; + +export default defineConfig([ + { + files: ["**/*.txt"], + processor: example.processors["processor-name"], + }, + // ... other configs +]); ``` -You can also use the same custom processor with multiple filename extensions. The following example shows using the same processor for both `.md` and `.mdx` files: +In this example, specifying `example.processors["processor-name"]` directly uses the processor's own `meta` object, which must be defined to ensure proper handling when the processor is not referenced through the plugin name. + +#### Why Both Meta Objects are Needed + +It is recommended that both the plugin and each processor provide their respective meta objects. This ensures that features relying on meta objects, such as `--print-config` and `--cache`, work correctly regardless of how the processor is specified in the configuration. + +## Specifying Processor in Config Files + +In order to use a processor from a plugin in a configuration file, import the plugin and include it in the `plugins` key, specifying a namespace. Then, use that namespace to reference the processor in the `processor` configuration, like this: ```js -const myCustomProcessor = { /* processor methods */ }; - -module.exports = { - // The same custom processor is applied to both - // `.md` and `.mdx` files. - processors: { - ".md": myCustomProcessor, - ".mdx": myCustomProcessor - } -} +// eslint.config.js +import { defineConfig } from "eslint/config"; +import example from "eslint-plugin-example"; + +export default defineConfig([ + { + files: ["**/*.txt"], + plugins: { + example, + }, + processor: "example/processor-name", + }, +]); ``` + +See [Specify a Processor](../use/configure/plugins#specify-a-processor) in the Plugin Configuration documentation for more details. diff --git a/docs/src/extend/custom-rule-tutorial.md b/docs/src/extend/custom-rule-tutorial.md index 8426961acae5..0556cc3c0ef8 100644 --- a/docs/src/extend/custom-rule-tutorial.md +++ b/docs/src/extend/custom-rule-tutorial.md @@ -6,14 +6,18 @@ eleventyNavigation: title: Custom Rule Tutorial order: 1 --- + +{%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} +{%- from 'components/npx_tabs.macro.html' import npx_tabs %} + This tutorial covers how to create a custom rule for ESLint and distribute it with a plugin. You can create custom rules to validate if your code meets a certain expectation, and determine what to do if it does not meet that expectation. Plugins package custom rules and other configuration, allowing you to easily share and reuse them in different projects. To learn more about custom rules and plugins refer to the following documentation: -* [Custom Rules](custom-rules) -* [Plugins](plugins) +- [Custom Rules](custom-rules) +- [Plugins](plugins) ## Why Create a Custom Rule? @@ -25,8 +29,8 @@ Before creating a custom rule that isn't specific to your company or project, it Before you begin, make sure you have the following installed in your development environment: -* [Node.js](https://nodejs.org/en/download/) -* [npm](https://www.npmjs.com/) +- [Node.js](https://nodejs.org/en/download/) +- [npm](https://www.npmjs.com/) This tutorial also assumes that you have a basic understanding of ESLint and ESLint rules. @@ -69,14 +73,14 @@ In the `enforce-foo-bar.js` file, add some scaffolding for the `enforce-foo-bar` // enforce-foo-bar.js module.exports = { - meta: { - // TODO: add metadata - }, - create(context) { - return { - // TODO: add callback function(s) - }; - } + meta: { + // TODO: add metadata + }, + create(context) { + return { + // TODO: add callback function(s) + }; + }, }; ``` @@ -90,19 +94,20 @@ Start by exporting an object with a `meta` property containing the rule's metada // enforce-foo-bar.js module.exports = { - meta: { - type: "problem", - docs: { - description: "Enforce that a variable named `foo` can only be assigned a value of 'bar'.", - }, - fixable: "code", - schema: [] - }, - create(context) { - return { - // TODO: add callback function(s) - }; - } + meta: { + type: "problem", + docs: { + description: + "Enforce that a variable named `foo` can only be assigned a value of 'bar'.", + }, + fixable: "code", + schema: [], + }, + create(context) { + return { + // TODO: add callback function(s) + }; + }, }; ``` @@ -113,6 +118,10 @@ To learn more about rule metadata, refer to [Rule Structure](custom-rules#rule-s Define the rule's `create` function, which accepts a `context` object and returns an object with a property for each syntax node type you want to handle. In this case, you want to handle `VariableDeclarator` nodes. You can choose any [ESTree node type](https://github.com/estree/estree) or [selector](selectors). +::: tip +You can view the AST for any JavaScript code using [Code Explorer](http://explorer.eslint.org). This is helpful in determining the type of nodes you'd like to target. +::: + Inside the `VariableDeclarator` visitor method, check if the node represents a `const` variable declaration, if its name is `foo`, and if it's not assigned to the string `"bar"`. You do this by evaluating the `node` passed to the `VariableDeclaration` method. If the `const foo` declaration is assigned a value of `"bar"`, then the rule does nothing. If `const foo` **is not** assigned a value of `"bar"`, then `context.report()` reports an error to ESLint. The error report includes information about the error and how to fix it. @@ -185,9 +194,11 @@ touch enforce-foo-bar.test.js You will use the `eslint` package in the test file. Install it as a development dependency: -```shell -npm install eslint --save-dev -``` +{{ npm_tabs({ + command: "install", + packages: ["eslint"], + args: ["--save-dev"] +}) }} And add a test script to your `package.json` file to run the tests: @@ -211,31 +222,36 @@ The `RuleTester#run()` method tests the rule against valid and invalid test case ```javascript // enforce-foo-bar.test.js -const {RuleTester} = require("eslint"); +const { RuleTester } = require("eslint"); const fooBarRule = require("./enforce-foo-bar"); const ruleTester = new RuleTester({ - // Must use at least ecmaVersion 2015 because - // that's when `const` variables were introduced. - parserOptions: { ecmaVersion: 2015 } + // Must use at least ecmaVersion 2015 because + // that's when `const` variables were introduced. + languageOptions: { ecmaVersion: 2015 }, }); // Throws error if the tests in ruleTester.run() do not pass ruleTester.run( - "enforce-foo-bar", // rule name - fooBarRule, // rule code - { // checks - // 'valid' checks cases that should pass - valid: [{ - code: "const foo = 'bar';", - }], - // 'invalid' checks cases that should not pass - invalid: [{ - code: "const foo = 'baz';", - output: 'const foo = "bar";', - errors: 1, - }], - } + "enforce-foo-bar", // rule name + fooBarRule, // rule code + { + // checks + // 'valid' checks cases that should pass + valid: [ + { + code: "const foo = 'bar';", + }, + ], + // 'invalid' checks cases that should not pass + invalid: [ + { + code: "const foo = 'baz';", + output: 'const foo = "bar";', + errors: 1, + }, + ], + }, ); console.log("All tests passed!"); @@ -281,10 +297,10 @@ You can use a locally defined plugin to execute the custom rule in your project. You might want to use a locally defined plugin in one of the following scenarios: -* You want to test the plugin before publishing it to npm. -* You want to use a plugin, but do not want to publish it to npm. +- You want to test the plugin before publishing it to npm. +- You want to use a plugin, but do not want to publish it to npm. -Before you can add the plugin to the project, create an ESLint configuration for your project using a [flat configuration file](../use/configure/configuration-files-new), `eslint.config.js`: +Before you can add the plugin to the project, create an ESLint configuration for your project using a [flat configuration file](../use/configure/configuration-files), `eslint.config.js`: ```shell touch eslint.config.js @@ -300,19 +316,19 @@ Then, add the following code to `eslint.config.js`: const eslintPluginExample = require("./eslint-plugin-example"); module.exports = [ - { - files: ["**/*.js"], - languageOptions: { - sourceType: "commonjs", - ecmaVersion: "latest", - }, - // Using the eslint-plugin-example plugin defined locally - plugins: {"example": eslintPluginExample}, - rules: { - "example/enforce-foo-bar": "error", - }, - } -] + { + files: ["**/*.js"], + languageOptions: { + sourceType: "commonjs", + ecmaVersion: "latest", + }, + // Using the eslint-plugin-example plugin defined locally + plugins: { example: eslintPluginExample }, + rules: { + "example/enforce-foo-bar": "error", + }, + }, +]; ``` Before you can test the rule, you must create a file to test the rule on. @@ -329,11 +345,11 @@ Add the following code to `example.js`: // example.js function correctFooBar() { - const foo = "bar"; + const foo = "bar"; } -function incorrectFoo(){ - const foo = "baz"; // Problem! +function incorrectFoo() { + const foo = "baz"; // Problem! } ``` @@ -341,9 +357,10 @@ Now you're ready to test the custom rule with the locally defined plugin. Run ESLint on `example.js`: -```shell -npx eslint example.js -``` +{{ npx_tabs({ + package: "eslint", + args: ["example.js"] +}) }} This produces the following output in the terminal: @@ -362,7 +379,7 @@ To publish a plugin containing a rule to npm, you need to configure the `package 1. `"name"`: A unique name for the package. No other package on npm can have the same name. 1. `"main"`: The relative path to the plugin file. Following this example, the path is `"eslint-plugin-example.js"`. 1. `"description"`: A description of the package that's viewable on npm. -1. `"peerDependencies"`: Add `"eslint": ">=8.0.0"` as a peer dependency. Any version greater than or equal to that is necessary to use the plugin. Declaring `eslint` as a peer dependency requires that users add the package to the project separately from the plugin. +1. `"peerDependencies"`: Add `"eslint": ">=9.0.0"` as a peer dependency. Any version greater than or equal to that is necessary to use the plugin. Declaring `eslint` as a peer dependency requires that users add the package to the project separately from the plugin. 1. `"keywords"`: Include the standard keywords `["eslint", "eslintplugin", "eslint-plugin"]` to make the package easy to find. You can add any other keywords that might be relevant to your plugin as well. A complete annotated example of what a plugin's `package.json` file should look like: @@ -379,9 +396,9 @@ A complete annotated example of what a plugin's `package.json` file should look "scripts": { "test": "node enforce-foo-bar.test.js" }, - // Add eslint>=8.0.0 as a peer dependency. + // Add eslint>=9.0.0 as a peer dependency. "peerDependencies": { - "eslint": ">=8.0.0" + "eslint": ">=9.0.0" }, // Add these standard keywords to make plugin easy to find! "keywords": [ @@ -392,7 +409,7 @@ A complete annotated example of what a plugin's `package.json` file should look "author": "", "license": "ISC", "devDependencies": { - "eslint": "^8.36.0" + "eslint": "^9.0.0" } } ``` @@ -407,9 +424,12 @@ Next, you can use the published plugin. Run the following command in your project to download the package: -```shell -npm install --save-dev eslint-plugin-example # Add your package name here -``` +{{ npm_tabs({ + command: "install", + packages: ["eslint-plugin-example"], + args: ["--save-dev"], + comment: "Add your package name here" +}) }} Update the `eslint.config.js` to use the packaged version of the plugin: @@ -427,9 +447,10 @@ Now you're ready to test the custom rule. Run ESLint on the `example.js` file you created in step 8, now with the downloaded plugin: -```shell -npx eslint example.js -``` +{{ npx_tabs({ + package: "eslint", + args: ["example.js"] +}) }} This produces the following output in the terminal: @@ -445,9 +466,10 @@ As you can see in the above message, you can actually fix the issue with the `-- Run ESLint again with the `--fix` flag: -```shell -npx eslint example.js --fix -``` +{{ npx_tabs({ + package: "eslint", + args: ["example.js", "--fix"] +}) }} There is no error output in the terminal when you run this, but you can see the fix applied in `example.js`. You should see the following: @@ -456,8 +478,8 @@ There is no error output in the terminal when you run this, but you can see the // ... rest of file -function incorrectFoo(){ - const foo = "bar"; // Fixed! +function incorrectFoo() { + const foo = "bar"; // Fixed! } ``` diff --git a/docs/src/extend/custom-rules-deprecated.md b/docs/src/extend/custom-rules-deprecated.md index 49faa4f13c7e..33770091b86b 100644 --- a/docs/src/extend/custom-rules-deprecated.md +++ b/docs/src/extend/custom-rules-deprecated.md @@ -1,580 +1,7 @@ --- title: Working with Rules (Deprecated) - --- -**Note:** This page covers the deprecated rule format for ESLint <= 2.13.1. [This is the most recent rule format](./custom-rules). - -Each rule in ESLint has two files named with its identifier (for example, `no-extra-semi`). - -* in the `lib/rules` directory: a source file (for example, `no-extra-semi.js`) -* in the `tests/lib/rules` directory: a test file (for example, `no-extra-semi.js`) - -**Important:** If you submit a **core** rule to the ESLint repository, you **must** follow some conventions explained below. - -Here is the basic format of the source file for a rule: - -```js -/** - * @fileoverview Rule to disallow unnecessary semicolons - * @author Nicholas C. Zakas - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Rule Definition -//------------------------------------------------------------------------------ - -module.exports = function(context) { - return { - // callback functions - }; -}; - -module.exports.schema = []; // no options -``` - -## Rule Basics - -`schema` (array) specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../use/configure/rules) - -`create` (function) returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code: - -* if a key is a node type, ESLint calls that **visitor** function while going **down** the tree -* if a key is a node type plus `:exit`, ESLint calls that **visitor** function while going **up** the tree -* if a key is an event name, ESLint calls that **handler** function for [code path analysis](code-path-analysis) - -A rule can use the current node and its surrounding tree to report or fix problems. - -Here are methods for the [array-callback-return](../rules/array-callback-return) rule: - -```js -function checkLastSegment (node) { - // report problem for function if last code path segment is reachable -} - -module.exports = function(context) { - // declare the state of the rule - return { - ReturnStatement: function(node) { - // at a ReturnStatement node while going down - }, - // at a function expression node while going up: - "FunctionExpression:exit": checkLastSegment, - "ArrowFunctionExpression:exit": checkLastSegment, - onCodePathStart: function (codePath, node) { - // at the start of analyzing a code path - }, - onCodePathEnd: function(codePath, node) { - // at the end of analyzing a code path - } - }; -}; -``` - -## The Context Object - -The `context` object contains additional functionality that is helpful for rules to do their jobs. As the name implies, the `context` object contains information that is relevant to the context of the rule. The `context` object has the following properties: - -* `parserOptions` - the parser options configured for this run (more details [here](../use/configure/language-options#specifying-parser-options)). -* `id` - the rule ID. -* `options` - an array of rule options. -* `settings` - the `settings` from configuration. -* `parserPath` - the full path to the `parser` from configuration. - -Additionally, the `context` object has the following methods: - -* `getAncestors()` - returns an array of ancestor nodes based on the current traversal. -* `getDeclaredVariables(node)` - returns the declared variables on the given node. -* `getFilename()` - returns the filename associated with the source. -* `getScope()` - returns the current scope. -* `getSourceCode()` - returns a `SourceCode` object that you can use to work with the source that was passed to ESLint -* `markVariableAsUsed(name)` - marks the named variable in scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. -* `report(descriptor)` - reports a problem in the code. - -**Deprecated:** The following methods on the `context` object are deprecated. Please use the corresponding methods on `SourceCode` instead: - -* `getAllComments()` - returns an array of all comments in the source. Use `sourceCode.getAllComments()` instead. -* `getComments(node)` - returns the leading and trailing comments arrays for the given node. Use `sourceCode.getComments(node)` instead. -* `getFirstToken(node)` - returns the first token representing the given node. Use `sourceCode.getFirstToken(node)` instead. -* `getFirstTokens(node, count)` - returns the first `count` tokens representing the given node. Use `sourceCode.getFirstTokens(node, count)` instead. -* `getJSDocComment(node)` - returns the JSDoc comment for a given node or `null` if there is none. Use `sourceCode.getJSDocComment(node)` instead. -* `getLastToken(node)` - returns the last token representing the given node. Use `sourceCode.getLastToken(node)` instead. -* `getLastTokens(node, count)` - returns the last `count` tokens representing the given node. Use `sourceCode.getLastTokens(node, count)` instead. -* `getNodeByRangeIndex(index)` - returns the deepest node in the AST containing the given source index. Use `sourceCode.getNodeByRangeIndex(index)` instead. -* `getSource(node)` - returns the source code for the given node. Omit `node` to get the whole source. Use `sourceCode.getText(node)` instead. -* `getSourceLines()` - returns the entire source code split into an array of string lines. Use `sourceCode.lines` instead. -* `getTokenAfter(nodeOrToken)` - returns the first token after the given node or token. Use `sourceCode.getTokenAfter(nodeOrToken)` instead. -* `getTokenBefore(nodeOrToken)` - returns the first token before the given node or token. Use `sourceCode.getTokenBefore(nodeOrToken)` instead. -* `getTokenByRangeStart(index)` - returns the token whose range starts at the given index in the source. Use `sourceCode.getTokenByRangeStart(index)` instead. -* `getTokens(node)` - returns all tokens for the given node. Use `sourceCode.getTokens(node)` instead. -* `getTokensAfter(nodeOrToken, count)` - returns `count` tokens after the given node or token. Use `sourceCode.getTokensAfter(nodeOrToken, count)` instead. -* `getTokensBefore(nodeOrToken, count)` - returns `count` tokens before the given node or token. Use `sourceCode.getTokensBefore(nodeOrToken, count)` instead. -* `getTokensBetween(node1, node2)` - returns the tokens between two nodes. Use `sourceCode.getTokensBetween(node1, node2)` instead. -* `report(node, [location], message)` - reports a problem in the code. - -### context.report() - -The main method you'll use is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties: - -* `message` - the problem message. -* `node` - (optional) the AST node related to the problem. If present and `loc` is not specified, then the starting location of the node is used as the location of the problem. -* `loc` - (optional) an object specifying the location of the problem. If both `loc` and `node` are specified, then the location is used from `loc` instead of `node`. - * `line` - the 1-based line number at which the problem occurred. - * `column` - the 0-based column number at which the problem occurred. -* `data` - (optional) placeholder data for `message`. -* `fix` - (optional) a function that applies a fix to resolve the problem. - -Note that at least one of `node` or `loc` is required. - -The simplest example is to use just `node` and `message`: - -```js -context.report({ - node: node, - message: "Unexpected identifier" -}); -``` - -The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node. - -You can also use placeholders in the message and provide `data`: - -```js -{% raw %} -context.report({ - node: node, - message: "Unexpected identifier: {{ identifier }}", - data: { - identifier: node.name - } -}); -{% endraw %} -``` - -Note that leading and trailing whitespace is optional in message parameters. - -The node contains all of the information necessary to figure out the line and column number of the offending text as well the source text representing the node. - -### Applying Fixes - -If you'd like ESLint to attempt to fix the problem you're reporting, you can do so by specifying the `fix` function when using `context.report()`. The `fix` function receives a single argument, a `fixer` object, that you can use to apply a fix. For example: - -```js -context.report({ - node: node, - message: "Missing semicolon". - fix: function(fixer) { - return fixer.insertTextAfter(node, ";"); - } -}); -``` - -Here, the `fix()` function is used to insert a semicolon after the node. Note that the fix is not immediately applied and may not be applied at all if there are conflicts with other fixes. If the fix cannot be applied, then the problem message is reported as usual; if the fix can be applied, then the problem message is not reported. - -The `fixer` object has the following methods: - -* `insertTextAfter(nodeOrToken, text)` - inserts text after the given node or token -* `insertTextAfterRange(range, text)` - inserts text after the given range -* `insertTextBefore(nodeOrToken, text)` - inserts text before the given node or token -* `insertTextBeforeRange(range, text)` - inserts text before the given range -* `remove(nodeOrToken)` - removes the given node or token -* `removeRange(range)` - removes text in the given range -* `replaceText(nodeOrToken, text)` - replaces the text in the given node or token -* `replaceTextRange(range, text)` - replaces the text in the given range - -Best practices for fixes: - -1. Make fixes that are as small as possible. Anything more than a single character is risky and could prevent other, simpler fixes from being made. -1. Only make one fix per message. This is enforced because you must return the result of the fixer operation from `fix()`. -1. Fixes should not introduce clashes with other rules. You can accidentally introduce a new problem that won't be reported until ESLint is run again. Another good reason to make as small a fix as possible. - -### context.options - -Some rules require options in order to function correctly. These options appear in configuration (`.eslintrc`, command line, or in comments). For example: - -```json -{ - "quotes": [2, "double"] -} -``` - -The `quotes` rule in this example has one option, `"double"` (the `2` is the error level). You can retrieve the options for a rule by using `context.options`, which is an array containing every configured option for the rule. In this case, `context.options[0]` would contain `"double"`: - -```js -module.exports = function(context) { - - var isDouble = (context.options[0] === "double"); - - // ... -} -``` - -Since `context.options` is just an array, you can use it to determine how many options have been passed as well as retrieving the actual options themselves. Keep in mind that the error level is not part of `context.options`, as the error level cannot be known or modified from inside a rule. - -When using options, make sure that your rule has some logic defaults in case the options are not provided. - -### context.getSourceCode() - -The `SourceCode` object is the main object for getting more information about the source code being linted. You can retrieve the `SourceCode` object at any time by using the `getSourceCode()` method: - -```js -module.exports = function(context) { - - var sourceCode = context.getSourceCode(); - - // ... -} -``` - -Once you have an instance of `SourceCode`, you can use the methods on it to work with the code: - -* `getAllComments()` - returns an array of all comments in the source. -* `getComments(node)` - returns the leading and trailing comments arrays for the given node. -* `getFirstToken(node)` - returns the first token representing the given node. -* `getFirstTokens(node, count)` - returns the first `count` tokens representing the given node. -* `getJSDocComment(node)` - returns the JSDoc comment for a given node or `null` if there is none. -* `getLastToken(node)` - returns the last token representing the given node. -* `getLastTokens(node, count)` - returns the last `count` tokens representing the given node. -* `getNodeByRangeIndex(index)` - returns the deepest node in the AST containing the given source index. -* `isSpaceBetweenTokens(first, second)` - returns true if there is a whitespace character between the two tokens. -* `getText(node)` - returns the source code for the given node. Omit `node` to get the whole source. -* `getTokenAfter(nodeOrToken)` - returns the first token after the given node or token. -* `getTokenBefore(nodeOrToken)` - returns the first token before the given node or token. -* `getTokenByRangeStart(index)` - returns the token whose range starts at the given index in the source. -* `getTokens(node)` - returns all tokens for the given node. -* `getTokensAfter(nodeOrToken, count)` - returns `count` tokens after the given node or token. -* `getTokensBefore(nodeOrToken, count)` - returns `count` tokens before the given node or token. -* `getTokensBetween(node1, node2)` - returns the tokens between two nodes. - -There are also some properties you can access: - -* `hasBOM` - the flag to indicate whether or not the source code has Unicode BOM. -* `text` - the full text of the code being linted. Unicode BOM has been stripped from this text. -* `ast` - the `Program` node of the AST for the code being linted. -* `lines` - an array of lines, split according to the specification's definition of line breaks. - -You should use a `SourceCode` object whenever you need to get more information about the code being linted. - -### Options Schemas - -Rules may export a `schema` property, which is a [JSON schema](http://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. - -There are two formats for a rule's exported `schema`. The first is a full JSON Schema object describing all possible options the rule accepts, including the rule's error level as the first argument and any optional arguments thereafter. - -However, to simplify schema creation, rules may also export an array of schemas for each optional positional argument, and ESLint will automatically validate the required error level first. For example, the `yoda` rule accepts a primary mode argument, as well as an extra options object with named properties. - -```js -// "yoda": [2, "never", { "exceptRange": true }] -module.exports.schema = [ - { - "enum": ["always", "never"] - }, - { - "type": "object", - "properties": { - "exceptRange": { - "type": "boolean" - } - }, - "additionalProperties": false - } -]; -``` - -In the preceding example, the error level is assumed to be the first argument. It is followed by the first optional argument, a string which may be either `"always"` or `"never"`. The final optional argument is an object, which may have a Boolean property named `exceptRange`. - -To learn more about JSON Schema, we recommend looking at some [examples](http://json-schema.org/examples.html) to start, and also reading [Understanding JSON Schema](http://spacetelescope.github.io/understanding-json-schema/) (a free ebook). - -### Getting the Source - -If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows: - -```js - -// get all source -var source = sourceCode.getText(); - -// get source for just this AST node -var nodeSource = sourceCode.getText(node); - -// get source for AST node plus previous two characters -var nodeSourceWithPrev = sourceCode.getText(node, 2); - -// get source for AST node plus following two characters -var nodeSourceWithFollowing = sourceCode.getText(node, 0, 2); -``` - -In this way, you can look for patterns in the JavaScript text itself when the AST isn't providing the appropriate data (such as location of commas, semicolons, parentheses, etc.). - -### Accessing comments - -If you need to access comments for a specific node you can use `sourceCode.getComments(node)`: - -```js -// the "comments" variable has a "leading" and "trailing" property containing -// its leading and trailing comments, respectively -var comments = sourceCode.getComments(node); -``` - -Keep in mind that comments are technically not a part of the AST and are only attached to it on demand, i.e. when you call `getComments()`. - -**Note:** One of the libraries adds AST node properties for comments - do not use these properties. Always use `sourceCode.getComments()` as this is the only guaranteed API for accessing comments (we will likely change how comments are handled later). - -### Accessing Code Paths - -ESLint analyzes code paths while traversing AST. -You can access that code path objects with five events related to code paths. - -[details here](code-path-analysis) - -## Rule Unit Tests - -Each rule must have a set of unit tests submitted with it to be accepted. The test file is named the same as the source file but lives in `tests/lib/`. For example, if your rule source file is `lib/rules/foo.js` then your test file should be `tests/lib/rules/foo.js`. - -For your rule, be sure to test: - -1. All instances that should be flagged as warnings. -1. At least one pattern that should **not** be flagged as a warning. - -The basic pattern for a rule unit test file is: - -```js -/** - * @fileoverview Tests for no-with rule. - * @author Nicholas C. Zakas - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -var rule = require("../../../lib/rules/no-with"), - RuleTester = require("../../../lib/testers/rule-tester"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -var ruleTester = new RuleTester(); -ruleTester.run("no-with", rule, { - valid: [ - "foo.bar()" - ], - invalid: [ - { - code: "with(foo) { bar() }", - errors: [{ message: "Unexpected use of 'with' statement.", type: "WithStatement"}] - } - ] -}); -``` - -Be sure to replace the value of `"no-with"` with your rule's ID. There are plenty of examples in the `tests/lib/rules/` directory. - -### Valid Code - -Each valid case can be either a string or an object. The object form is used when you need to specify additional global variables or arguments for the rule. For example, the following defines `window` as a global variable for code that should not trigger the rule being tested: - -```js -valid: [ - { - code: "window.alert()", - globals: [ "window" ] - } -] -``` - -You can also pass options to the rule (if it accepts them). These arguments are equivalent to how people can configure rules in their `.eslintrc` file. For example: - -```js -valid: [ - { - code: "var msg = 'Hello';", - options: [ "single" ] - } -] -``` - -The `options` property must be an array of options. This gets passed through to `context.options` in the rule. - -### Invalid Code - -Each invalid case must be an object containing the code to test and at least one message that is produced by the rule. The `errors` key specifies an array of objects, each containing a message (your rule may trigger multiple messages for the same code). You should also specify the type of AST node you expect to receive back using the `type` key. The AST node should represent the actual spot in the code where there is a problem. For example: - -```js -invalid: [ - { - code: "function doSomething() { var f; if (true) { var build = true; } f = build; }", - errors: [ - { message: "build used outside of binding context.", type: "Identifier" } - ] - } -] -``` - -In this case, the message is specific to the variable being used and the AST node type is `Identifier`. - -Similar to the valid cases, you can also specify `options` to be passed to the rule: - -```js -invalid: [ - { - code: "function doSomething() { var f; if (true) { var build = true; } f = build; }", - options: [ "double" ], - errors: [ - { message: "build used outside of binding context.", type: "Identifier" } - ] - } -] -``` - -For simpler cases where the only thing that really matters is the error message, you can also specify any `errors` as strings. You can also have some strings and some objects, if you like. - -```js -invalid: [ - { - code: "'single quotes'", - options: ["double"], - errors: ["Strings must use doublequote."] - } -] -``` - -### Specifying Parser Options - -Some tests require that a certain parser configuration must be used. This can be specified in test specifications via the `parserOptions` setting. - -For example, to set `ecmaVersion` to 6 (in order to use constructs like `for ... of`): - -```js -valid: [ - { - code: "for (x of a) doSomething();", - parserOptions: { ecmaVersion: 6 } - } -] -``` - -If you are working with ES6 modules: - -```js -valid: [ - { - code: "export default function () {};", - parserOptions: { ecmaVersion: 6, sourceType: "module" } - } -] -``` - -For non-version specific features such as JSX: - -```js -valid: [ - { - code: "var foo =
{bar}
", - parserOptions: { ecmaFeatures: { jsx: true } } - } -] -``` - -The options available and the expected syntax for `parserOptions` is the same as those used in [configuration](../use/configure/language-options#specifying-parser-options). - -### Write Several Tests - -Provide as many unit tests as possible. Your pull request will never be turned down for having too many tests submitted with it! - -## Performance Testing - -To keep the linting process efficient and unobtrusive, it is useful to verify the performance impact of new rules or modifications to existing rules. - -### Overall Performance - -The `npm run perf` command gives a high-level overview of ESLint running time with default rules (`eslint:recommended`) enabled. - -```bash -$ git checkout main -Switched to branch 'main' - -$ npm run perf -CPU Speed is 2200 with multiplier 7500000 -Performance Run #1: 1394.689313ms -Performance Run #2: 1423.295351ms -Performance Run #3: 1385.09515ms -Performance Run #4: 1382.406982ms -Performance Run #5: 1409.68566ms -Performance budget ok: 1394.689313ms (limit: 3409.090909090909ms) - -$ git checkout my-rule-branch -Switched to branch 'my-rule-branch' - -$ npm run perf -CPU Speed is 2200 with multiplier 7500000 -Performance Run #1: 1443.736547ms -Performance Run #2: 1419.193291ms -Performance Run #3: 1436.018228ms -Performance Run #4: 1473.605485ms -Performance Run #5: 1457.455283ms -Performance budget ok: 1443.736547ms (limit: 3409.090909090909ms) -``` - -### Per-rule Performance - -ESLint has a built-in method to track performance of individual rules. Setting the `TIMING` environment variable will trigger the display, upon linting completion, of the ten longest-running rules, along with their individual running time and relative performance impact as a percentage of total rule processing time. - -```bash -$ TIMING=1 eslint lib -Rule | Time (ms) | Relative -:-----------------------|----------:|--------: -no-multi-spaces | 52.472 | 6.1% -camelcase | 48.684 | 5.7% -no-irregular-whitespace | 43.847 | 5.1% -valid-jsdoc | 40.346 | 4.7% -handle-callback-err | 39.153 | 4.6% -space-infix-ops | 35.444 | 4.1% -no-undefined | 25.693 | 3.0% -no-shadow | 22.759 | 2.7% -no-empty-class | 21.976 | 2.6% -semi | 19.359 | 2.3% -``` - -To test one rule explicitly, combine the `--no-eslintrc`, and `--rule` options: - -```bash -$ TIMING=1 eslint --no-eslintrc --rule "quotes: [2, 'double']" lib -Rule | Time (ms) | Relative -:------|----------:|--------: -quotes | 18.066 | 100.0% -``` - -## Rule Naming Conventions - -The rule naming conventions for ESLint are fairly simple: - -* If your rule is disallowing something, prefix it with `no-` such as `no-eval` for disallowing `eval()` and `no-debugger` for disallowing `debugger`. -* If your rule is enforcing the inclusion of something, use a short name without a special prefix. -* Keep your rule names as short as possible, use abbreviations where appropriate, and no more than four words. -* Use dashes between words. - -## Rule Acceptance Criteria - -Because rules are highly personal (and therefore very contentious), accepted rules should: - -* Not be library-specific. -* Demonstrate a possible issue that can be resolved by rewriting the code. -* Be general enough so as to apply for a large number of developers. -* Not be the opposite of an existing rule. -* Not overlap with an existing rule. - -## Runtime Rules - -The thing that makes ESLint different from other linters is the ability to define custom rules at runtime. This is perfect for rules that are specific to your project or company and wouldn't make sense for ESLint to ship with. With runtime rules, you don't have to wait for the next version of ESLint or be disappointed that your rule isn't general enough to apply to the larger JavaScript community, just write your rules and include them at runtime. - -Runtime rules are written in the same format as all other rules. Create your rule as you would any other and then follow these steps: +As of ESLint v9.0.0, the function-style rule format that was current in ESLint <= 2.13.1 is no longer supported. -1. Place all of your runtime rules in the same directory (i.e., `eslint_rules`). -2. Create a [configuration file](../use/configure/) and specify your rule ID error level under the `rules` key. Your rule will not run unless it has a value of `1` or `2` in the configuration file. -3. Run the [command line interface](../use/command-line-interface) using the `--rulesdir` option to specify the location of your runtime rules. +[This is the most recent rule format](./custom-rules). diff --git a/docs/src/extend/custom-rules.md b/docs/src/extend/custom-rules.md index 35df20541b6d..06db29a3a7a9 100644 --- a/docs/src/extend/custom-rules.md +++ b/docs/src/extend/custom-rules.md @@ -5,72 +5,76 @@ eleventyNavigation: parent: create plugins title: Custom Rules order: 2 - --- You can create custom rules to use with ESLint. You might want to create a custom rule if the [core rules](../rules/) do not cover your use case. -**Note:** This page covers the most recent rule format for ESLint >= 3.0.0. There is also a [deprecated rule format](./custom-rules-deprecated). - Here's the basic format of a custom rule: ```js // customRule.js module.exports = { - meta: { - type: "suggestion", - docs: { - description: "Description of the rule", - }, - fixable: "code", - schema: [] // no options - }, - create: function(context) { - return { - // callback functions - }; - } + meta: { + type: "suggestion", + docs: { + description: "Description of the rule", + }, + fixable: "code", + schema: [], // no options + }, + create: function (context) { + return { + // callback functions + }; + }, }; ``` +::: warning +The core rules shipped in the `eslint` package are not considered part of the public API and are not designed to be extended from. Building on top of these rules is fragile and will most likely result in your rules breaking completely at some point in the future. If you're interested in creating a rule that is similar to a core rule, you should first copy the rule file into your project and proceed from there. +::: + ## Rule Structure The source file for a rule exports an object with the following properties. Both custom rules and core rules follow this format. `meta`: (`object`) Contains metadata for the rule: -* `type`: (`string`) Indicates the type of rule, which is one of `"problem"`, `"suggestion"`, or `"layout"`: +- `type`: (`string`) Indicates the type of rule, which is one of `"problem"`, `"suggestion"`, or `"layout"`: + + - `"problem"`: The rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. + - `"suggestion"`: The rule is identifying something that could be done in a better way but no errors will occur if the code isn't changed. + - `"layout"`: The rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren't specified in the AST. - * `"problem"`: The rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. - * `"suggestion"`: The rule is identifying something that could be done in a better way but no errors will occur if the code isn't changed. - * `"layout"`: The rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren't specified in the AST. +- `docs`: (`object`) Properties often used for documentation generation and tooling. Required for core rules and optional for custom rules. Custom rules can include additional properties here as needed. -* `docs`: (`object`) Required for core rules and optional for custom rules. Core rules have specific entries inside of `docs` while custom rules can include any properties that you need. The following properties are only relevant when working on core rules. + - `description`: (`string`) Provides a short description of the rule. For core rules, this is used in [rules index](../rules/). + - `recommended`: (`boolean`) For core rules, this specifies whether the rule is enabled by the `recommended` config from `@eslint/js`. + - `url`: (`string`) Specifies the URL at which the full documentation can be accessed. Code editors often use this to provide a helpful link on highlighted rule violations. - * `description`: (`string`) Provides the short description of the rule in the [rules index](../rules/). - * `recommended`: (`boolean`) Specifies whether the `"extends": "eslint:recommended"` property in a [configuration file](../use/configure/configuration-files#extending-configuration-files) enables the rule. - * `url`: (`string`) Specifies the URL at which the full documentation can be accessed (enabling code editors to provide a helpful link on highlighted rule violations). +- `fixable`: (`string`) Either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../use/command-line-interface#--fix) automatically fixes problems reported by the rule. -* `fixable`: (`string`) Either `"code"` or `"whitespace"` if the `--fix` option on the [command line](../use/command-line-interface#--fix) automatically fixes problems reported by the rule. + **Important:** the `fixable` property is mandatory for fixable rules. If this property isn't specified, ESLint will throw an error whenever the rule attempts to produce a fix. Omit the `fixable` property if the rule is not fixable. - **Important:** the `fixable` property is mandatory for fixable rules. If this property isn't specified, ESLint will throw an error whenever the rule attempts to produce a fix. Omit the `fixable` property if the rule is not fixable. +- `hasSuggestions`: (`boolean`) Specifies whether rules can return suggestions (defaults to `false` if omitted). -* `hasSuggestions`: (`boolean`) Specifies whether rules can return suggestions (defaults to `false` if omitted). + **Important:** the `hasSuggestions` property is mandatory for rules that provide suggestions. If this property isn't set to `true`, ESLint will throw an error whenever the rule attempts to produce a suggestion. Omit the `hasSuggestions` property if the rule does not provide suggestions. - **Important:** the `hasSuggestions` property is mandatory for rules that provide suggestions. If this property isn't set to `true`, ESLint will throw an error whenever the rule attempts to produce a suggestion. Omit the `hasSuggestions` property if the rule does not provide suggestions. +- `schema`: (`object | array | false`) Specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../use/configure/rules). Mandatory when the rule has options. -* `schema`: (`object | array`) Specifies the [options](#options-schemas) so ESLint can prevent invalid [rule configurations](../use/configure/rules). +- `defaultOptions`: (`array`) Specifies [default options](#option-defaults) for the rule. If present, any user-provided options in their config will be merged on top of them recursively. -* `deprecated`: (`boolean`) Indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated. +- `deprecated`: (`boolean | DeprecatedInfo`) Indicates whether the rule has been deprecated. You may omit the `deprecated` property if the rule has not been deprecated. + There is a dedicated page for the [DeprecatedInfo](./rule-deprecation) -* `replacedBy`: (`array`) In the case of a deprecated rule, specify replacement rule(s). +- `replacedBy`: (`array`, **Deprecated** Use `meta.deprecated.replacedBy` instead.) In the case of a deprecated rule, specify replacement rule(s). `create()`: Returns an object with methods that ESLint calls to "visit" nodes while traversing the abstract syntax tree (AST as defined by [ESTree](https://github.com/estree/estree)) of JavaScript code: -* If a key is a node type or a [selector](./selectors), ESLint calls that **visitor** function while going **down** the tree. -* If a key is a node type or a [selector](./selectors) plus `:exit`, ESLint calls that **visitor** function while going **up** the tree. -* If a key is an event name, ESLint calls that **handler** function for [code path analysis](code-path-analysis). +- If a key is a node type or a [selector](./selectors), ESLint calls that **visitor** function while going **down** the tree. +- If a key is a node type or a [selector](./selectors) plus `:exit`, ESLint calls that **visitor** function while going **up** the tree. +- If a key is an event name, ESLint calls that **handler** function for [code path analysis](code-path-analysis). A rule can use the current node and its surrounding tree to report or fix problems. @@ -103,6 +107,10 @@ module.exports = { }; ``` +::: tip +You can view the complete AST for any JavaScript code using [Code Explorer](http://explorer.eslint.org). +::: + ## The Context Object The `context` object is the only argument of the `create` method in a rule. For example: @@ -123,37 +131,29 @@ As the name implies, the `context` object contains information that is relevant The `context` object has the following properties: -* `id`: (`string`) The rule ID. -* `filename`: (`string`) The filename associated with the source. -* `physicalFilename`: (`string`) When linting a file, it provides the full path of the file on disk without any code block information. When linting text, it provides the value passed to `—stdin-filename` or `` if not specified. -* `cwd`: (`string`) The `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. -* `options`: (`array`) An array of the [configured options](../use/configure/rules) for this rule. This array does not include the rule severity (see the [dedicated section](#accessing-options-passed-to-a-rule)). -* `sourceCode`: (`object`) A `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). -* `settings`: (`object`) The [shared settings](../use/configure/configuration-files#adding-shared-settings) from the configuration. -* `parserPath`: (`string`) The name of the `parser` from the configuration. -* `parserServices`: (**Deprecated:** Use `SourceCode#parserServices` instead.) Contains parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) -* `parserOptions`: The parser options configured for this run (more details [here](../use/configure/language-options#specifying-parser-options)). +- `id`: (`string`) The rule ID. +- `filename`: (`string`) The filename associated with the source. +- `physicalFilename`: (`string`) When linting a file, it provides the full path of the file on disk without any code block information. When linting text, it provides the value passed to `—stdin-filename` or `` if not specified. +- `cwd`: (`string`) The `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. +- `options`: (`array`) An array of the [configured options](../use/configure/rules) for this rule. This array does not include the rule severity (see the [dedicated section](#accessing-options-passed-to-a-rule)). +- `sourceCode`: (`object`) A `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). +- `settings`: (`object`) The [shared settings](../use/configure/configuration-files#configuring-shared-settings) from the configuration. +- `languageOptions`: (`object`) more details for each property [here](../use/configure/language-options) + - `sourceType`: (`'script' | 'module' | 'commonjs'`) The mode for the current file. + - `ecmaVersion`: (`number`) The ECMA version used to parse the current file. + - `parser`: (`object`): The parser used to parse the current file. + - `parserOptions`: (`object`) The parser options configured for this file. + - `globals`: (`object`) The specified globals. +- `parserPath`: (`string`, **Removed** Use `context.languageOptions.parser` instead.) The name of the `parser` from the configuration. +- `parserOptions`: (**Deprecated** Use `context.languageOptions.parserOptions` instead.) The parser options configured for this run (more details [here](../use/configure/language-options#specifying-parser-options)). Additionally, the `context` object has the following methods: -* `getAncestors()`: (**Deprecated:** Use `SourceCode#getAncestors(node)` instead.) Returns an array of the ancestors of the currently-traversed node, starting at the root of the AST and continuing through the direct parent of the current node. This array does not include the currently-traversed node itself. -* `getCwd()`: (**Deprecated:** Use `context.cwd` instead.) Returns the `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. -* `getDeclaredVariables(node)`: (**Deprecated:** Use `SourceCode#getDeclaredVariables(node)` instead.) Returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. - * If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. - * If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. - * If the node is a `FunctionDeclaration` or `FunctionExpression`, the variable for the function name is returned, in addition to variables for the function parameters. - * If the node is an `ArrowFunctionExpression`, variables for the parameters are returned. - * If the node is a `ClassDeclaration` or a `ClassExpression`, the variable for the class name is returned. - * If the node is a `CatchClause`, the variable for the exception is returned. - * If the node is an `ImportDeclaration`, variables for all of its specifiers are returned. - * If the node is an `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`, the declared variable is returned. - * Otherwise, if the node does not declare any variables, an empty array is returned. -* `getFilename()`: (**Deprecated:** Use `context.filename` instead.) Returns the filename associated with the source. -* `getPhysicalFilename()`: (**Deprecated:** Use `context.physicalFilename` instead.) When linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `—stdin-filename` or `` if not specified. -* `getScope()`: (**Deprecated:** Use `SourceCode#getScope(node)` instead.) Returns the [scope](./scope-manager-interface#scope-interface) of the currently-traversed node. This information can be used to track references to variables. -* `getSourceCode()`: (**Deprecated:** Use `context.sourceCode` instead.) Returns a `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). -* `markVariableAsUsed(name)`: (**Deprecated:** Use `SourceCode#markVariableAsUsed(name, node)` instead.) Marks a variable with the given name in the current scope as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. -* `report(descriptor)`. Reports a problem in the code (see the [dedicated section](#reporting-problems)). +- `getCwd()`: (**Deprecated:** Use `context.cwd` instead.) Returns the `cwd` option passed to the [Linter](../integrate/nodejs-api#linter). It is a path to a directory that should be considered the current working directory. +- `getFilename()`: (**Deprecated:** Use `context.filename` instead.) Returns the filename associated with the source. +- `getPhysicalFilename()`: (**Deprecated:** Use `context.physicalFilename` instead.) When linting a file, it returns the full path of the file on disk without any code block information. When linting text, it returns the value passed to `—stdin-filename` or `` if not specified. +- `getSourceCode()`: (**Deprecated:** Use `context.sourceCode` instead.) Returns a `SourceCode` object that you can use to work with the source that was passed to ESLint (see [Accessing the Source Code](#accessing-the-source-code)). +- `report(descriptor)`. Reports a problem in the code (see the [dedicated section](#reporting-problems)). **Note:** Earlier versions of ESLint supported additional methods on the `context` object. Those methods were removed in the new format and should not be relied upon. @@ -161,17 +161,18 @@ Additionally, the `context` object has the following methods: The main method you'll use when writing custom rules is `context.report()`, which publishes a warning or error (depending on the configuration being used). This method accepts a single argument, which is an object containing the following properties: -* `message`: (`string`) The problem message. -* `node`: (optional `object`) The AST node related to the problem. If present and `loc` is not specified, then the starting location of the node is used as the location of the problem. -* `loc`: (optional `object`) Specifies the location of the problem. If both `loc` and `node` are specified, then the location is used from `loc` instead of `node`. - * `start`: An object of the start location. - * `line`: (`number`) The 1-based line number at which the problem occurred. - * `column`: (`number`) The 0-based column number at which the problem occurred. - * `end`: An object of the end location. - * `line`: (`number`) The 1-based line number at which the problem occurred. - * `column`: (`number`) The 0-based column number at which the problem occurred. -* `data`: (optional `object`) [Placeholder](#using-message-placeholders) data for `message`. -* `fix(fixer)`: (optional `function`) Applies a [fix](#applying-fixes) to resolve the problem. +- `messageId`: (`string`) The ID of the message (see [messageIds](#messageids)) (recommended over `message`). +- `message`: (`string`) The problem message (alternative to `messageId`). +- `node`: (optional `object`) This can be an AST node, a token, or a comment related to the problem. If present and `loc` is not specified, then the starting location of the node is used as the location of the problem. +- `loc`: (optional `object`) Specifies the location of the problem. If both `loc` and `node` are specified, then the location is used from `loc` instead of `node`. + - `start`: An object of the start location. + - `line`: (`number`) The 1-based line number at which the problem occurred. + - `column`: (`number`) The 0-based column number at which the problem occurred. + - `end`: An object of the end location. + - `line`: (`number`) The 1-based line number at which the problem occurred. + - `column`: (`number`) The 0-based column number at which the problem occurred. +- `data`: (optional `object`) [Placeholder](#using-message-placeholders) data for `message`. +- `fix(fixer)`: (optional `function`) Applies a [fix](#applying-fixes) to resolve the problem. Note that at least one of `node` or `loc` is required. @@ -179,8 +180,8 @@ The simplest example is to use just `node` and `message`: ```js context.report({ - node: node, - message: "Unexpected identifier" + node: node, + message: "Unexpected identifier", }); ``` @@ -208,9 +209,11 @@ The node contains all the information necessary to figure out the line and colum #### `messageId`s -Instead of typing out messages in both the `context.report()` call and your tests, you can use `messageId`s instead. +`messageId`s are the recommended approach to reporting messages in `context.report()` calls because of the following benefits: -This allows you to avoid retyping error messages. It also prevents errors reported in different sections of your rule from having out-of-date messages. +- Rule violation messages can be stored in a central `meta.messages` object for convenient management. +- Rule violation messages do not need to be repeated in both the rule file and rule test file. +- As a result, the barrier for changing rule violation messages is lower, encouraging more frequent contributions to improve and optimize them for the greatest clarity and usefulness. Rule file: @@ -262,17 +265,17 @@ var RuleTester = require("eslint").RuleTester; var ruleTester = new RuleTester(); ruleTester.run("avoid-name", rule, { - valid: ["bar", "baz"], - invalid: [ - { - code: "foo", - errors: [ - { - messageId: "avoidName" - } - ] - } - ] + valid: ["bar", "baz"], + invalid: [ + { + code: "foo", + errors: [ + { + messageId: "avoidName", + }, + ], + }, + ], }); ``` @@ -282,11 +285,11 @@ If you'd like ESLint to attempt to fix the problem you're reporting, you can do ```js context.report({ - node: node, - message: "Missing semicolon", - fix(fixer) { - return fixer.insertTextAfter(node, ";"); - } + node: node, + message: "Missing semicolon", + fix(fixer) { + return fixer.insertTextAfter(node, ";"); + }, }); ``` @@ -296,23 +299,23 @@ Here, the `fix()` function is used to insert a semicolon after the node. Note th The `fixer` object has the following methods: -* `insertTextAfter(nodeOrToken, text)`: Insert text after the given node or token. -* `insertTextAfterRange(range, text)`: Insert text after the given range. -* `insertTextBefore(nodeOrToken, text)`: Insert text before the given node or token. -* `insertTextBeforeRange(range, text)`: Insert text before the given range. -* `remove(nodeOrToken)`: Remove the given node or token. -* `removeRange(range)`: Remove text in the given range. -* `replaceText(nodeOrToken, text)`: Replace the text in the given node or token. -* `replaceTextRange(range, text)`: Replace the text in the given range. +- `insertTextAfter(nodeOrToken, text)`: Insert text after the given node or token. +- `insertTextAfterRange(range, text)`: Insert text after the given range. +- `insertTextBefore(nodeOrToken, text)`: Insert text before the given node or token. +- `insertTextBeforeRange(range, text)`: Insert text before the given range. +- `remove(nodeOrToken)`: Remove the given node or token. +- `removeRange(range)`: Remove text in the given range. +- `replaceText(nodeOrToken, text)`: Replace the text in the given node or token. +- `replaceTextRange(range, text)`: Replace the text in the given range. A `range` is a two-item array containing character indices inside the source code. The first item is the start of the range (inclusive) and the second item is the end of the range (exclusive). Every node and token has a `range` property to identify the source code range they represent. The above methods return a `fixing` object. The `fix()` function can return the following values: -* A `fixing` object. -* An array which includes `fixing` objects. -* An iterable object which enumerates `fixing` objects. Especially, the `fix()` function can be a generator. +- A `fixing` object. +- An array which includes `fixing` objects. +- An iterable object which enumerates `fixing` objects. Especially, the `fix()` function can be a generator. If you make a `fix()` function which returns multiple `fixing` objects, those `fixing` objects must not overlap. @@ -322,21 +325,20 @@ Best practices for fixes: 1. Make fixes as small as possible. Fixes that are unnecessarily large could conflict with other fixes, and prevent them from being applied. 1. Only make one fix per message. This is enforced because you must return the result of the fixer operation from `fix()`. 1. Since all rules are run again after the initial round of fixes is applied, it's not necessary for a rule to check whether the code style of a fix will cause errors to be reported by another rule. - * For example, suppose a fixer would like to surround an object key with quotes, but it's not sure whether the user would prefer single or double quotes. + + - For example, suppose a fixer would like to surround an object key with quotes, but it's not sure whether the user would prefer single or double quotes. ```js - ({ foo : 1 }) + { foo: 1 } // should get fixed to either - - ({ 'foo': 1 }) + { 'foo': 1 } // or - - ({ "foo": 1 }) + { "foo": 1 } ``` - * This fixer can just select a quote type arbitrarily. If it guesses wrong, the resulting code will be automatically reported and fixed by the [`quotes`](../rules/quotes) rule. + - This fixer can just select a quote type arbitrarily. If it guesses wrong, the resulting code will be automatically reported and fixed by the [`quotes`](../rules/quotes) rule. Note: Making fixes as small as possible is a best practice, but in some cases it may be correct to extend the range of the fix in order to intentionally prevent other rules from making fixes in a surrounding range in the same pass. For instance, if replacement text declares a new variable, it can be useful to prevent other changes in the scope of the variable as they might cause name collisions. @@ -344,15 +346,15 @@ The following example replaces `node` and also ensures that no other fixes will ```js context.report({ - node, - message, - *fix(fixer) { - yield fixer.replaceText(node, replacementText); - - // extend range of the fix to the range of `node.parent` - yield fixer.insertTextBefore(node.parent, ""); - yield fixer.insertTextAfter(node.parent, ""); - } + node, + message, + *fix(fixer) { + yield fixer.replaceText(node, replacementText); + + // extend range of the fix to the range of `node.parent` + yield fixer.insertTextBefore(node.parent, ""); + yield fixer.insertTextAfter(node.parent, ""); + }, }); ``` @@ -488,7 +490,7 @@ Some rules require options in order to function correctly. These options appear ```json { - "quotes": ["error", "double"] + "quotes": ["error", "double"] } ``` @@ -496,11 +498,18 @@ The `quotes` rule in this example has one option, `"double"` (the `error` is the ```js module.exports = { - create: function(context) { - var isDouble = (context.options[0] === "double"); - - // ... - } + meta: { + schema: [ + { + enum: ["single", "double", "backtick"], + }, + ], + }, + create: function (context) { + var isDouble = context.options[0] === "double"; + + // ... + }, }; ``` @@ -508,17 +517,19 @@ Since `context.options` is just an array, you can use it to determine how many o When using options, make sure that your rule has some logical defaults in case the options are not provided. +Rules with options must specify a [schema](#options-schemas). + ### Accessing the Source Code The `SourceCode` object is the main object for getting more information about the source code being linted. You can retrieve the `SourceCode` object at any time by using the `context.sourceCode` property: ```js module.exports = { - create: function(context) { - var sourceCode = context.sourceCode; + create: function (context) { + var sourceCode = context.sourceCode; - // ... - } + // ... + }, }; ``` @@ -526,57 +537,71 @@ module.exports = { Once you have an instance of `SourceCode`, you can use the following methods on it to work with the code: -* `getText(node)`: Returns the source code for the given node. Omit `node` to get the whole source (see the [dedicated section](#accessing-the-source-text)). -* `getAllComments()`: Returns an array of all comments in the source (see the [dedicated section](#accessing-comments)). -* `getCommentsBefore(nodeOrToken)`: Returns an array of comment tokens that occur directly before the given node or token (see the [dedicated section](#accessing-comments)). -* `getCommentsAfter(nodeOrToken)`: Returns an array of comment tokens that occur directly after the given node or token (see the [dedicated section](#accessing-comments)). -* `getCommentsInside(node)`: Returns an array of all comment tokens inside a given node (see the [dedicated section](#accessing-comments)). -* `isSpaceBetween(nodeOrToken, nodeOrToken)`: Returns true if there is a whitespace character between the two tokens or, if given a node, the last token of the first node and the first token of the second node. -* `getFirstToken(node, skipOptions)`: Returns the first token representing the given node. -* `getFirstTokens(node, countOptions)`: Returns the first `count` tokens representing the given node. -* `getLastToken(node, skipOptions)`: Returns the last token representing the given node. -* `getLastTokens(node, countOptions)`: Returns the last `count` tokens representing the given node. -* `getTokenAfter(nodeOrToken, skipOptions)`: Returns the first token after the given node or token. -* `getTokensAfter(nodeOrToken, countOptions)`: Returns `count` tokens after the given node or token. -* `getTokenBefore(nodeOrToken, skipOptions)`: Returns the first token before the given node or token. -* `getTokensBefore(nodeOrToken, countOptions)`: Returns `count` tokens before the given node or token. -* `getFirstTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)`: Returns the first token between two nodes or tokens. -* `getFirstTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)`: Returns the first `count` tokens between two nodes or tokens. -* `getLastTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)`: Returns the last token between two nodes or tokens. -* `getLastTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)`: Returns the last `count` tokens between two nodes or tokens. -* `getTokens(node)`: Returns all tokens for the given node. -* `getTokensBetween(nodeOrToken1, nodeOrToken2)`: Returns all tokens between two nodes. -* `getTokenByRangeStart(index, rangeOptions)`: Returns the token whose range starts at the given index in the source. -* `getNodeByRangeIndex(index)`: Returns the deepest node in the AST containing the given source index. -* `getLocFromIndex(index)`: Returns an object with `line` and `column` properties, corresponding to the location of the given source index. `line` is 1-based and `column` is 0-based. -* `getIndexFromLoc(loc)`: Returns the index of a given location in the source code, where `loc` is an object with a 1-based `line` key and a 0-based `column` key. -* `commentsExistBetween(nodeOrToken1, nodeOrToken2)`: Returns `true` if comments exist between two nodes. +- `getText(node)`: Returns the source code for the given node. Omit `node` to get the whole source (see the [dedicated section](#accessing-the-source-text)). +- `getAllComments()`: Returns an array of all comments in the source (see the [dedicated section](#accessing-comments)). +- `getCommentsBefore(nodeOrToken)`: Returns an array of comment tokens that occur directly before the given node or token (see the [dedicated section](#accessing-comments)). +- `getCommentsAfter(nodeOrToken)`: Returns an array of comment tokens that occur directly after the given node or token (see the [dedicated section](#accessing-comments)). +- `getCommentsInside(node)`: Returns an array of all comment tokens inside a given node (see the [dedicated section](#accessing-comments)). +- `isSpaceBetween(nodeOrToken, nodeOrToken)`: Returns true if there is a whitespace character between the two tokens or, if given a node, the last token of the first node and the first token of the second node. +- `isGlobalReference(node)`: Returns true if the identifier references a global variable configured via `languageOptions.globals`, `/* global */` comments, or `ecmaVersion`, and not declared by a local binding. +- `getFirstToken(node, skipOptions)`: Returns the first token representing the given node. +- `getFirstTokens(node, countOptions)`: Returns the first `count` tokens representing the given node. +- `getLastToken(node, skipOptions)`: Returns the last token representing the given node. +- `getLastTokens(node, countOptions)`: Returns the last `count` tokens representing the given node. +- `getTokenAfter(nodeOrToken, skipOptions)`: Returns the first token after the given node or token. +- `getTokensAfter(nodeOrToken, countOptions)`: Returns `count` tokens after the given node or token. +- `getTokenBefore(nodeOrToken, skipOptions)`: Returns the first token before the given node or token. +- `getTokensBefore(nodeOrToken, countOptions)`: Returns `count` tokens before the given node or token. +- `getFirstTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)`: Returns the first token between two nodes or tokens. +- `getFirstTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)`: Returns the first `count` tokens between two nodes or tokens. +- `getLastTokenBetween(nodeOrToken1, nodeOrToken2, skipOptions)`: Returns the last token between two nodes or tokens. +- `getLastTokensBetween(nodeOrToken1, nodeOrToken2, countOptions)`: Returns the last `count` tokens between two nodes or tokens. +- `getTokens(node)`: Returns all tokens for the given node. +- `getTokensBetween(nodeOrToken1, nodeOrToken2)`: Returns all tokens between two nodes. +- `getTokenByRangeStart(index, rangeOptions)`: Returns the token whose range starts at the given index in the source. +- `getNodeByRangeIndex(index)`: Returns the deepest node in the AST containing the given source index. +- `getLocFromIndex(index)`: Returns an object with `line` and `column` properties, corresponding to the location of the given source index. `line` is 1-based and `column` is 0-based. +- `getIndexFromLoc(loc)`: Returns the index of a given location in the source code, where `loc` is an object with a 1-based `line` key and a 0-based `column` key. +- `commentsExistBetween(nodeOrToken1, nodeOrToken2)`: Returns `true` if comments exist between two nodes. +- `getAncestors(node)`: Returns an array of the ancestors of the given node, starting at the root of the AST and continuing through the direct parent of the given node. This array does not include the given node itself. +- `getDeclaredVariables(node)`: Returns a list of [variables](./scope-manager-interface#variable-interface) declared by the given node. This information can be used to track references to variables. + - If the node is a `VariableDeclaration`, all variables declared in the declaration are returned. + - If the node is a `VariableDeclarator`, all variables declared in the declarator are returned. + - If the node is a `FunctionDeclaration` or `FunctionExpression`, the variable for the function name is returned, in addition to variables for the function parameters. + - If the node is an `ArrowFunctionExpression`, variables for the parameters are returned. + - If the node is a `ClassDeclaration` or a `ClassExpression`, the variable for the class name is returned. + - If the node is a `CatchClause`, the variable for the exception is returned. + - If the node is an `ImportDeclaration`, variables for all of its specifiers are returned. + - If the node is an `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`, the declared variable is returned. + - Otherwise, if the node does not declare any variables, an empty array is returned. +- `getScope(node)`: Returns the [scope](./scope-manager-interface#scope-interface) of the given node. This information can be used to track references to variables. +- `markVariableAsUsed(name, refNode)`: Marks a variable with the given name in a scope indicated by the given reference node as used. This affects the [no-unused-vars](../rules/no-unused-vars) rule. Returns `true` if a variable with the given name was found and marked as used, otherwise `false`. `skipOptions` is an object which has 3 properties; `skip`, `includeComments`, and `filter`. Default is `{skip: 0, includeComments: false, filter: null}`. -* `skip`: (`number`) Positive integer, the number of skipping tokens. If `filter` option is given at the same time, it doesn't count filtered tokens as skipped. -* `includeComments`: (`boolean`) The flag to include comment tokens into the result. -* `filter(token)`: Function which gets a token as the first argument. If the function returns `false` then the result excludes the token. +- `skip`: (`number`) Positive integer, the number of skipping tokens. If `filter` option is given at the same time, it doesn't count filtered tokens as skipped. +- `includeComments`: (`boolean`) The flag to include comment tokens into the result. +- `filter(token)`: Function which gets a token as the first argument. If the function returns `false` then the result excludes the token. `countOptions` is an object which has 3 properties; `count`, `includeComments`, and `filter`. Default is `{count: 0, includeComments: false, filter: null}`. -* `count`: (`number`) Positive integer, the maximum number of returning tokens. -* `includeComments`: (`boolean`) The flag to include comment tokens into the result. -* `filter(token)`: Function which gets a token as the first argument, if the function returns `false` then the result excludes the token. +- `count`: (`number`) Positive integer, the maximum number of returning tokens. +- `includeComments`: (`boolean`) The flag to include comment tokens into the result. +- `filter(token)`: Function which gets a token as the first argument, if the function returns `false` then the result excludes the token. `rangeOptions` is an object that has 1 property, `includeComments`. Default is `{includeComments: false}`. -* `includeComments`: (`boolean`) The flag to include comment tokens into the result. +- `includeComments`: (`boolean`) The flag to include comment tokens into the result. There are also some properties you can access: -* `hasBOM`: (`boolean`) The flag to indicate whether the source code has Unicode BOM. -* `text`: (`string`) The full text of the code being linted. Unicode BOM has been stripped from this text. -* `ast`: (`object`) `Program` node of the AST for the code being linted. -* `scopeManager`: [ScopeManager](./scope-manager-interface#scopemanager-interface) object of the code. -* `visitorKeys`: (`object`) Visitor keys to traverse this AST. -* `parserServices`: (`object`) Contains parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) -* `lines`: (`array`) Array of lines, split according to the specification's definition of line breaks. +- `hasBOM`: (`boolean`) The flag to indicate whether the source code has Unicode BOM. +- `text`: (`string`) The full text of the code being linted. Unicode BOM has been stripped from this text. +- `ast`: (`object`) `Program` node of the AST for the code being linted. +- `scopeManager`: [ScopeManager](./scope-manager-interface#scopemanager-interface) object of the code. +- `visitorKeys`: (`object`) Visitor keys to traverse this AST. +- `parserServices`: (`object`) Contains parser-provided services for rules. The default parser does not provide any services. However, if a rule is intended to be used with a custom parser, it could use `parserServices` to access anything provided by that parser. (For example, a TypeScript parser could provide the ability to get the computed type of a given node.) +- `lines`: (`array`) Array of lines, split according to the specification's definition of line breaks. You should use a `SourceCode` object whenever you need to get more information about the code being linted. @@ -585,7 +610,6 @@ You should use a `SourceCode` object whenever you need to get more information a If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows: ```js - // get all source var source = sourceCode.getText(); @@ -613,9 +637,11 @@ You can also access comments through many of `sourceCode`'s methods using the `i ### Options Schemas -Rules may specify a `schema` property, which is a [JSON Schema](https://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. +Rules with options must specify a `meta.schema` property, which is a [JSON Schema](https://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`. -Note: Prior to ESLint v9.0.0, rules without a schema are passed their options directly from the config without any validation. In ESLint v9.0.0 and later, rules without schemas will throw errors when options are passed. See the [Require schemas and object-style rules](https://github.com/eslint/rfcs/blob/main/designs/2021-schema-object-rules/README.md) RFC for further details. +If your rule has options, it is strongly recommended that you specify a schema for options validation. However, it is possible to opt-out of options validation by setting `schema: false`, but doing so is discouraged as it increases the chance of bugs and mistakes. + +For rules that don't specify a `meta.schema` property, ESLint throws errors when any options are passed. If your rule doesn't have options, do not set `schema: false`, but simply omit the schema property or use `schema: []`, both of which prevent any options from being passed. When validating a rule's config, there are five steps: @@ -629,18 +655,18 @@ Note: this means that the rule schema cannot validate the severity. The rule sch There are two formats for a rule's `schema`: -* An array of JSON Schema objects - * Each element will be checked against the same position in the `context.options` array. - * If the `context.options` array has fewer elements than there are schemas, then the unmatched schemas are ignored - * If the `context.options` array has more elements than there are schemas, then the validation fails - * There are two important consequences to using this format: - * It is _always valid_ for a user to provide no options to your rule (beyond severity) - * If you specify an empty array, then it is _always an error_ for a user to provide any options to your rule (beyond severity) -* A full JSON Schema object that will validate the `context.options` array - * The schema should assume an array of options to validate even if your rule only accepts one option. - * The schema can be arbitrarily complex, so you can validate completely different sets of potential options via `oneOf`, `anyOf` etc. - * The supported version of JSON Schemas is [Draft-04](http://json-schema.org/draft-04/schema), so some newer features such as `if` or `$data` are unavailable. - * At present, it is explicitly planned to not update schema support beyond this level due to ecosystem compatibility concerns. See [this comment](https://github.com/eslint/eslint/issues/13888#issuecomment-872591875) for further context. +- An array of JSON Schema objects + - Each element will be checked against the same position in the `context.options` array. + - If the `context.options` array has fewer elements than there are schemas, then the unmatched schemas are ignored. + - If the `context.options` array has more elements than there are schemas, then the validation fails. + - There are two important consequences to using this format: + - It is _always valid_ for a user to provide no options to your rule (beyond severity). + - If you specify an empty array, then it is _always an error_ for a user to provide any options to your rule (beyond severity). +- A full JSON Schema object that will validate the `context.options` array + - The schema should assume an array of options to validate even if your rule only accepts one option. + - The schema can be arbitrarily complex, so you can validate completely different sets of potential options via `oneOf`, `anyOf` etc. + - The supported version of JSON Schemas is [Draft-04](http://json-schema.org/draft-04/schema), so some newer features such as `if` or `$data` are unavailable. + - At present, it is explicitly planned to not update schema support beyond this level due to ecosystem compatibility concerns. See [this comment](https://github.com/eslint/eslint/issues/13888#issuecomment-872591875) for further context. For example, the `yoda` rule accepts a primary mode argument of `"always"` or `"never"`, as well as an extra options object with an optional property `exceptRange`: @@ -654,20 +680,20 @@ For example, the `yoda` rule accepts a primary mode argument of `"always"` or `" // "yoda": ["warn", "never", { "exceptRange": true }, 5] // "yoda": ["error", { "exceptRange": true }, "never"] module.exports = { - meta: { - schema: [ - { - enum: ["always", "never"] - }, - { - type: "object", - properties: { - exceptRange: { type: "boolean" } - }, - additionalProperties: false - } - ] - } + meta: { + schema: [ + { + enum: ["always", "never"], + }, + { + type: "object", + properties: { + exceptRange: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + }, }; ``` @@ -683,25 +709,25 @@ And here is the equivalent object-based schema: // "yoda": ["warn", "never", { "exceptRange": true }, 5] // "yoda": ["error", { "exceptRange": true }, "never"] module.exports = { - meta: { - schema: { - type: "array", - minItems: 0, - maxItems: 2, - items: [ - { - enum: ["always", "never"] - }, - { - type: "object", - properties: { - exceptRange: { type: "boolean" } - }, - additionalProperties: false - } - ] - } - } + meta: { + schema: { + type: "array", + minItems: 0, + maxItems: 2, + items: [ + { + enum: ["always", "never"], + }, + { + type: "object", + properties: { + exceptRange: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + }, + }, }; ``` @@ -721,36 +747,36 @@ Object schemas can be more precise and restrictive in what is permitted. For exa // "someRule": ["warn", 7, { someOtherProperty: 5 }] // "someRule": ["warn", 7, { someNonOptionalProperty: false, someOtherProperty: 5 }] module.exports = { - meta: { - schema: { - type: "array", - minItems: 1, // Can't specify only severity! - maxItems: 2, - items: [ - { - type: "number", - minimum: 0, - maximum: 10 - }, - { - anyOf: [ - { - type: "object", - properties: { - someNonOptionalProperty: { type: "boolean" } - }, - required: ["someNonOptionalProperty"], - additionalProperties: false - }, - { - enum: ["off", "strict"] - } - ] - } - ] - } - } -} + meta: { + schema: { + type: "array", + minItems: 1, // Can't specify only severity! + maxItems: 2, + items: [ + { + type: "number", + minimum: 0, + maximum: 10, + }, + { + anyOf: [ + { + type: "object", + properties: { + someNonOptionalProperty: { type: "boolean" }, + }, + required: ["someNonOptionalProperty"], + additionalProperties: false, + }, + { + enum: ["off", "strict"], + }, + ], + }, + ], + }, + }, +}; ``` Remember, rule options are always an array, so be careful not to specify a schema for a non-array type at the top level. If your schema does not specify an array at the top-level, users can _never_ enable your rule, as their configuration will always be invalid when the rule is enabled. @@ -761,54 +787,107 @@ Here's an example schema that will always fail validation: // Possibly trying to validate ["error", { someOptionalProperty: true }] // but when the rule is enabled, config will always fail validation because the options are an array which doesn't match "object" module.exports = { - meta: { - schema: { - type: "object", - properties: { - someOptionalProperty: { - type: "boolean" - } - }, - additionalProperties: false - } - } -} + meta: { + schema: { + type: "object", + properties: { + someOptionalProperty: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + }, +}; ``` **Note:** If your rule schema uses JSON schema [`$ref`](https://json-schema.org/understanding-json-schema/structuring.html#ref) properties, you must use the full JSON Schema object rather than the array of positional property schemas. This is because ESLint transforms the array shorthand into a single schema without updating references that makes them incorrect (they are ignored). -To learn more about JSON Schema, we recommend looking at some examples on the [JSON Schema website](https://json-schema.org/learn/), or reading the free [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) ebook. +To learn more about JSON Schema, we recommend looking at some examples on the [JSON Schema website](https://json-schema.org/learn/miscellaneous-examples), or reading the free [Understanding JSON Schema](https://json-schema.org/understanding-json-schema/) ebook. + +### Option Defaults + +Rules may specify a `meta.defaultOptions` array of default values for any options. +When the rule is enabled in a user configuration, ESLint will recursively merge any user-provided option elements on top of the default elements. + +For example, given the following defaults: + +```js +export default { + meta: { + defaultOptions: [ + { + alias: "basic", + }, + ], + schema: [ + { + type: "object", + properties: { + alias: { + type: "string", + }, + }, + additionalProperties: false, + }, + ], + }, + create(context) { + const [{ alias }] = context.options; + + return { + /* ... */ + }; + }, +}; +``` + +The rule would have a runtime `alias` value of `"basic"` unless the user configuration specifies a different value, such as with `["error", { alias: "complex" }]`. + +Each element of the options array is merged according to the following rules: + +- Any missing value or explicit user-provided `undefined` will fall back to a default option +- User-provided arrays and primitive values other than `undefined` override a default option +- User-provided objects will merge into a default option object and replace a non-object default otherwise + +Option defaults will also be validated against the rule's `meta.schema`. + +**Note:** ESLint internally uses [Ajv](https://ajv.js.org) for schema validation with its [`useDefaults` option](https://ajv.js.org/guide/modifying-data.html#assigning-defaults) enabled. +Both user-provided and `meta.defaultOptions` options will override any defaults specified in a rule's schema. +ESLint may disable Ajv's `useDefaults` in a future major version. ### Accessing Shebangs -[Shebangs (#!)](https://en.wikipedia.org/wiki/Shebang_(Unix)) are represented by the unique tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined in the [Accessing Comments](#accessing-comments) section, such as `sourceCode.getAllComments()`. +[Shebangs (#!)]() are represented by the unique tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined in the [Accessing Comments](#accessing-comments) section, such as `sourceCode.getAllComments()`. ### Accessing Variable Scopes The `SourceCode#getScope(node)` method returns the scope of the given node. It is a useful method for finding information about the variables in a given scope and how they are used in other scopes. -**Deprecated:** The `context.getScope()` is deprecated; make sure to use `SourceCode#getScope(node)` instead. +::: tip +You can view scope information for any JavaScript code using [Code Explorer](http://explorer.eslint.org). +::: #### Scope types The following table contains a list of AST node types and the scope type that they correspond to. For more information about the scope types, refer to the [`Scope` object documentation](./scope-manager-interface#scope-interface). | AST Node Type | Scope Type | -|:--------------------------|:-----------| +| :------------------------ | :--------- | | `Program` | `global` | | `FunctionDeclaration` | `function` | | `FunctionExpression` | `function` | | `ArrowFunctionExpression` | `function` | | `ClassDeclaration` | `class` | | `ClassExpression` | `class` | -| `BlockStatement` â€ģ1 | `block` | -| `SwitchStatement` â€ģ1 | `switch` | -| `ForStatement` â€ģ2 | `for` | -| `ForInStatement` â€ģ2 | `for` | -| `ForOfStatement` â€ģ2 | `for` | +| `BlockStatement` â€ģ1 | `block` | +| `SwitchStatement` â€ģ1 | `switch` | +| `ForStatement` â€ģ2 | `for` | +| `ForInStatement` â€ģ2 | `for` | +| `ForOfStatement` â€ģ2 | `for` | | `WithStatement` | `with` | | `CatchClause` | `catch` | -| others | â€ģ3 | +| others | â€ģ3 | **â€ģ1** Only if the configured parser provided the block-scope feature. The default parser provides the block-scope feature if `parserOptions.ecmaVersion` is not less than `6`.
**â€ģ2** Only if the `for` statement defines the iteration variable as a block-scoped variable (E.g., `for (let i = 0;;) {}`).
@@ -824,41 +903,38 @@ Also inside of each `Variable`, the `Variable#defs` property contains an array o Global variables have the following additional properties: -* `Variable#writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only. -* `Variable#eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file. -* `Variable#eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments. -* `Variable#eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments. +- `Variable#writeable` (`boolean | undefined`) ... If `true`, this global variable can be assigned arbitrary value. If `false`, this global variable is read-only. +- `Variable#eslintExplicitGlobal` (`boolean | undefined`) ... If `true`, this global variable was defined by a `/* globals */` directive comment in the source code file. +- `Variable#eslintExplicitGlobalComments` (`Comment[] | undefined`) ... The array of `/* globals */` directive comments which defined this global variable in the source code file. This property is `undefined` if there are no `/* globals */` directive comments. +- `Variable#eslintImplicitGlobalSetting` (`"readonly" | "writable" | undefined`) ... The configured value in config files. This can be different from `variable.writeable` if there are `/* globals */` directive comments. For examples of using `SourceCode#getScope()` to track variables, refer to the source code for the following built-in rules: -* [no-shadow](https://github.com/eslint/eslint/blob/main/lib/rules/no-shadow.js): Calls `sourceCode.getScope()` at the `Program` node and inspects all child scopes to make sure a variable name is not reused at a lower scope. ([no-shadow](../rules/no-shadow) documentation) -* [no-redeclare](https://github.com/eslint/eslint/blob/main/lib/rules/no-redeclare.js): Calls `sourceCode.getScope()` at each scope to make sure that a variable is not declared twice in the same scope. ([no-redeclare](../rules/no-redeclare) documentation) +- [no-shadow](https://github.com/eslint/eslint/blob/main/lib/rules/no-shadow.js): Calls `sourceCode.getScope()` at the `Program` node and inspects all child scopes to make sure a variable name is not reused at a lower scope. ([no-shadow](../rules/no-shadow) documentation) +- [no-redeclare](https://github.com/eslint/eslint/blob/main/lib/rules/no-redeclare.js): Calls `sourceCode.getScope()` at each scope to make sure that a variable is not declared twice in the same scope. ([no-redeclare](../rules/no-redeclare) documentation) ### Marking Variables as Used -**Deprecated:** The `context.markVariableAsUsed()` method is deprecated in favor of `sourceCode.markVariableAsUsed()`. - Certain ESLint rules, such as [`no-unused-vars`](../rules/no-unused-vars), check to see if a variable has been used. ESLint itself only knows about the standard rules of variable access and so custom ways of accessing variables may not register as "used". To help with this, you can use the `sourceCode.markVariableAsUsed()` method. This method takes two arguments: the name of the variable to mark as used and an option reference node indicating the scope in which you are working. Here's an example: ```js module.exports = { - create: function(context) { - var sourceCode = context.sourceCode; - - return { - ReturnStatement(node) { - - // look in the scope of the function for myCustomVar and mark as used - sourceCode.markVariableAsUsed("myCustomVar", node); - - // or: look in the global scope for myCustomVar and mark as used - sourceCode.markVariableAsUsed("myCustomVar"); - } - } - // ... - } + create: function (context) { + var sourceCode = context.sourceCode; + + return { + ReturnStatement(node) { + // look in the scope of the function for myCustomVar and mark as used + sourceCode.markVariableAsUsed("myCustomVar", node); + + // or: look in the global scope for myCustomVar and mark as used + sourceCode.markVariableAsUsed("myCustomVar"); + }, + }; + // ... + }, }; ``` @@ -866,17 +942,16 @@ Here, the `myCustomVar` variable is marked as used relative to a `ReturnStatemen ### Accessing Code Paths -ESLint analyzes code paths while traversing AST. You can access code path objects with five events related to code paths. For more information, refer to [Code Path Analysis](code-path-analysis). +ESLint analyzes code paths while traversing AST. You can access code path objects with seven events related to code paths. For more information, refer to [Code Path Analysis](code-path-analysis). ### Deprecated `SourceCode` Methods Please note that the following `SourceCode` methods have been deprecated and will be removed in a future version of ESLint: -* `getComments()`: Replaced by `SourceCode#getCommentsBefore()`, `SourceCode#getCommentsAfter()`, and `SourceCode#getCommentsInside()`. -* `getTokenOrCommentBefore()`: Replaced by `SourceCode#getTokenBefore()` with the `{ includeComments: true }` option. -* `getTokenOrCommentAfter()`: Replaced by `SourceCode#getTokenAfter()` with the `{ includeComments: true }` option. -* `isSpaceBetweenTokens()`: Replaced by `SourceCode#isSpaceBetween()` -* `getJSDocComment()` +- `getTokenOrCommentBefore()`: Replaced by `SourceCode#getTokenBefore()` with the `{ includeComments: true }` option. +- `getTokenOrCommentAfter()`: Replaced by `SourceCode#getTokenAfter()` with the `{ includeComments: true }` option. +- `isSpaceBetweenTokens()`: Replaced by `SourceCode#isSpaceBetween()` +- `getJSDocComment()` ## Rule Unit Tests @@ -926,3 +1001,5 @@ quotes | 18.066 | 100.0% ``` To see a longer list of results (more than 10), set the environment variable to another value such as `TIMING=50` or `TIMING=all`. + +For more granular timing information (per file per rule), use the [`stats`](./stats) option instead. diff --git a/docs/src/extend/index.md b/docs/src/extend/index.md index 815a42b457bd..85d5967f32ef 100644 --- a/docs/src/extend/index.md +++ b/docs/src/extend/index.md @@ -4,16 +4,15 @@ eleventyNavigation: key: extend eslint title: Extend ESLint order: 2 - --- This guide is intended for those who wish to extend the functionality of ESLint. In order to extend ESLint, it's recommended that: -* You know JavaScript, since ESLint is written in JavaScript. -* You have some familiarity with Node.js, since ESLint runs on it. -* You're comfortable with command-line programs. +- You know JavaScript, since ESLint is written in JavaScript. +- You have some familiarity with Node.js, since ESLint runs on it. +- You're comfortable with command-line programs. If that sounds like you, then continue reading to get started. diff --git a/docs/src/extend/languages.md b/docs/src/extend/languages.md new file mode 100644 index 000000000000..4588d8380711 --- /dev/null +++ b/docs/src/extend/languages.md @@ -0,0 +1,141 @@ +--- +title: Languages +eleventyNavigation: + key: languages + parent: create plugins + title: Languages + order: 4 +--- + +Starting with ESLint v9.7.0, you can extend ESLint with additional languages through plugins. While ESLint began as a linter strictly for JavaScript, the ESLint core is generic and can be used to lint any programming language. Each language is defined as an object that contains all of the parsing, evaluating, and traversal functionality required to lint a file. These languages are then distributed in plugins for use in user configurations. + +## Language Requirements + +In order to create a language, you need: + +1. **A parser.** The parser is the piece that converts plain text into a data structure. There is no specific format that ESLint requires the data structure to be in, so you can use any already-existing parser, or write your own. +1. **A `SourceCode` object.** The way ESLint works with an AST is through a `SourceCode` object. There are some required methods on each `SourceCode`, and you can also add more methods or properties that you'd like to expose to rules. +1. **A `Language` object.** The `Language` object contains information about the language itself along with methods for parsing and creating the `SourceCode` object. + +### Parser Requirements for Languages + +To get started, make sure you have a parser that can be called from JavaScript. The parser must return a data structure representing the code that was parsed. Most parsers return an [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) (AST) to represent the code, but they can also return a [concrete syntax tree](https://en.wikipedia.org/wiki/Parse_tree) (CST). Whether an AST or CST is returned doesn't matter to ESLint, it only matters that there is a data structure to traverse. + +While there is no specific structure an AST or CST must follow, it's easier to integrate with ESLint when each node in the tree contains the following information: + +1. **Type** - A property on each node representing the node type is required. For example, in JavaScript, the `type` property contains this information for each node. ESLint rules use node types to define the visitor methods, so it's important that each node can be identified by a string. The name of the property doesn't matter (discussed further below) so long as one exists. This property is typically named `type` or `kind` by most parsers. +1. **Location** - A property on each node representing the location of the node in the original source code is required. The location must contain: + + - The line on which the node starts + - The column on which the node starts + - The line on which the node ends + - The column on which the node ends + + As with the node type, the property name doesn't matter. Two common property names are `loc` (as in [ESTree](https://github.com/estree/estree/blob/3851d4a6eae5e5473371893959b88b62007469e8/es5.md#node-objects)) and `position` (as in [Unist](https://github.com/syntax-tree/unist?tab=readme-ov-file#node)). This information is used by ESLint to report errors and rule violations. + +1. **Range** - A property on each node representing the location of the node's source inside the source code is required. The range indicates the index at which the first character is found and the index after the last character, such that calling `code.slice(start, end)` returns the text that the node represents. Once again, no specific property name is required, and this information may even be merged with location information. ESTree uses the `range` property while Unist includes this information on `position` along with the location information. This information is used by ESLint to apply autofixes. + +### The `SourceCode` Object + +ESLint holds information about source code in a `SourceCode` object. This object is the API used both by ESLint internally and by rules written to work on the code (via `context.sourceCode`). The `SourceCode` object must implement the `TextSourceCode` interface as defined in the [`@eslint/core`](https://npmjs.com/package/@eslint/core) package. + +A basic `SourceCode` object must implement the following: + +- `ast` - a property containing the AST or CST for the source code. +- `text` - the text of the source code. +- `getLoc(nodeOrToken)` - a method that returns the location of a given node or token. This must match the `loc` structure that ESTree uses. +- `getRange(nodeOrToken)` - a method that returns the range of a given node or token. This must return an array where the first item is the start index and the second is the end index. +- `traverse()` - a method that returns an iterable for traversing the AST or CST. The iterator must return objects that implement either `VisitTraversalStep` or `CallTraversalStep` from `@eslint/core`. + +The following optional members allow you to customize how ESLint interacts with the object: + +- `visitorKeys` - visitor keys that are specific to just this `SourceCode` object. Typically not necessary as `Language#visitorKeys` is used most of the time. +- `applyLanguageOptions(languageOptions)` - if you have specific language options that need to be applied after parsing, you can do so in this method. +- `getDisableDirectives()` - returns any disable directives in the code. ESLint uses this to apply disable directives and track unused directives. +- `getInlineConfigNodes()` - returns any inline config nodes. ESLint uses this to report errors when `noInlineConfig` is enabled. +- `applyInlineConfig()` - returns inline configuration elements to ESLint. ESLint uses this to alter the configuration of the file being linted. +- `finalize()` - this method is called just before linting begins and is your last chance to modify `SourceCode`. If you've defined `applyLanguageOptions()` or `applyInlineConfig()`, then you may have additional changes to apply before the `SourceCode` object is ready. + +Additionally, the following members are common on `SourceCode` objects and are recommended to implement: + +- `lines` - the individual lines of the source code as an array of strings. +- `getParent(node)` - returns the parent of the given node or `undefined` if the node is the root. +- `getAncestors(node)` - returns an array of the ancestry of the node with the first item as the root of the tree and each subsequent item as the descendants of the root that lead to `node`. +- `getText(node, beforeCount, afterCount)` - returns the string that represents the given node, and optionally, a specified number of characters before and after the node's range. + +See [`JSONSourceCode`](https://github.com/eslint/json/blob/main/src/languages/json-source-code.js) as an example of a basic `SourceCode` class. + +::: tip +The [`@eslint/plugin-kit`](https://npmjs.com/package/@eslint/plugin-kit) package contains multiple classes that aim to make creating a `SourceCode` object easier. The `TextSourceCodeBase` class, in particular, implements the `TextSourceCode` interface and provides some basic functionality typically found in `SourceCode` objects. +::: + +### The `Language` Object + +The `Language` object contains all of the information about the programming language as well as methods for interacting with code written in that language. ESLint uses this object to determine how to deal with a particular file. The `Language` object must implement the `Language` interface as defined in the [`@eslint/core`](https://npmjs.com/package/@eslint/core) package. + +A basic `Language` object must implement the following: + +- `fileType` - should be `"text"` (in the future, we will also support `"binary"`) +- `lineStart` - either 0 or 1 to indicate how the AST represents the first line in the file. ESLint uses this to correctly display error locations. +- `columnStart` - either 0 or 1 to indicate how the AST represents the first column in each line. ESLint uses this to correctly display error locations. +- `nodeTypeKey` - the name of the property that indicates the node type (usually `"type"` or `"kind"`). +- `validateLanguageOptions(languageOptions)` - validates language options for the language. This method is expected to throw a validation error when an expected language option doesn't have the correct type or value. Unexpected language options should be silently ignored and no error should be thrown. This method is required even if the language doesn't specify any options. +- `parse(file, context)` - parses the given file into an AST or CST, and can also include additional values meant for use in rules. Called internally by ESLint. +- `createSourceCode(file, parseResult, context)` - creates a `SourceCode` object. Call internally by ESLint after `parse()`, and the second argument is the exact return value from `parse()`. + +The following optional members allow you to customize how ESLint interacts with the object: + +- `visitorKeys` - visitor keys that are specific to the AST or CST. This is used to optimize traversal of the AST or CST inside of ESLint. While not required, it is strongly recommended, especially for AST or CST formats that deviate significantly from ESTree format. +- `defaultLanguageOptions` - default `languageOptions` when the language is used. User-specified `languageOptions` are merged with this object when calculating the config for the file being linted. +- `matchesSelectorClass(className, node, ancestry)` - allows you to specify selector classes, such as `:expression`, that match more than one node. This method is called whenever an [esquery](https://github.com/estools/esquery) selector contains a `:` followed by an identifier. +- `normalizeLanguageOptions(languageOptions)` - takes a validated language options object and normalizes its values. This is helpful for backwards compatibility when language options properties change and also to add custom serialization with a `toJSON()` method. + +See [`JSONLanguage`](https://github.com/eslint/json/blob/main/src/languages/json-language.js) as an example of a basic `Language` class. + +## Publish a Language in a Plugin + +Languages are published in plugins similar to processors and rules. Define the `languages` key in your plugin as an object whose names are the language names and the values are the language objects. Here's an example: + +```js +import { myLanguage } from "../languages/my.js"; + +const plugin = { + // preferred location of name and version + meta: { + name: "eslint-plugin-example", + version: "1.2.3", + }, + languages: { + my: myLanguage, + }, + rules: { + // add rules here + }, +}; + +// for ESM +export default plugin; + +// OR for CommonJS +module.exports = plugin; +``` + +In order to use a language from a plugin in a configuration file, import the plugin and include it in the `plugins` key, specifying a namespace. Then, use that namespace to reference the language in the `language` configuration, like this: + +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; +import example from "eslint-plugin-example"; + +export default defineConfig([ + { + files: ["**/*.my"], + plugins: { + example, + }, + language: "example/my", + }, +]); +``` + +See [Specify a Language](../use/configure/plugins#specify-a-language) in the Plugin Configuration documentation for more details. diff --git a/docs/src/extend/plugin-migration-flat-config.md b/docs/src/extend/plugin-migration-flat-config.md index 135e039c305f..bda8adf27dad 100644 --- a/docs/src/extend/plugin-migration-flat-config.md +++ b/docs/src/extend/plugin-migration-flat-config.md @@ -4,8 +4,7 @@ eleventyNavigation: key: plugin flat config parent: create plugins title: Migration to Flat Config - order: 4 - + order: 5 --- Beginning in ESLint v9.0.0, the default configuration system will be the new flat config system. In order for your plugins to work with flat config files, you'll need to make some changes to your existing plugins. @@ -16,10 +15,10 @@ To make it easier to work with your plugin in the flat config system, it's recom ```js const plugin = { - meta: {}, - configs: {}, - rules: {}, - processors: {} + meta: {}, + configs: {}, + rules: {}, + processors: {}, }; // for ESM @@ -37,13 +36,13 @@ With the old eslintrc configuration system, ESLint could pull information about ```js const plugin = { - meta: { - name: "eslint-plugin-example", - version: "1.0.0" - }, - configs: {}, - rules: {}, - processors: {} + meta: { + name: "eslint-plugin-example", + version: "1.0.0", + }, + configs: {}, + rules: {}, + processors: {}, }; // for ESM @@ -63,20 +62,21 @@ No changes are necessary for the `rules` key in your plugin. Everything works th ## Migrating Processors for Flat Config -No changes are necessary for the `processors` key in your plugin as long as you aren't using file extension-named processors. If you have any [file extension-named processors](custom-processors#file-extension-named-processor), you must update the name to a valid identifier (numbers and letters). File extension-named processors were automatically applied in the old configuration system but are not automatically applied when using flat config. Here is an example of a file extension-named processor: +Each processor should specify a `meta` object. For more information, see the [full documentation](custom-processors). + +No other changes are necessary for the `processors` key in your plugin as long as you aren't using file extension-named processors. If you have any [file extension-named processors](custom-processors-deprecated#file-extension-named-processor), you must update the name to a valid identifier (numbers and letters). File extension-named processors were automatically applied in the old configuration system but are not automatically applied when using flat config. Here is an example of a file extension-named processor: ```js const plugin = { - configs: {}, - rules: {}, - processors: { - - // no longer supported - ".md": { - preprocess() {}, - postprocess() {} - } - } + configs: {}, + rules: {}, + processors: { + // no longer supported + ".md": { + preprocess() {}, + postprocess() {}, + }, + }, }; // for ESM @@ -90,16 +90,15 @@ The name `".md"` is no longer valid for a processor, so it must be replaced with ```js const plugin = { - configs: {}, - rules: {}, - processors: { - - // works in both old and new config systems - "markdown": { - preprocess() {}, - postprocess() {} - } - } + configs: {}, + rules: {}, + processors: { + // works in both old and new config systems + markdown: { + preprocess() {}, + postprocess() {}, + }, + }, }; // for ESM @@ -112,16 +111,18 @@ module.exports = plugin; In order to use this renamed processor, you'll also need to manually specify it inside of a config, such as: ```js +import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; -export default [ - { - plugins: { - example - }, - processor: "example/markdown" - } -]; +export default defineConfig([ + { + files: ["**/*.md"], + plugins: { + example, + }, + processor: "example/markdown", + }, +]); ``` You should update your plugin's documentation to advise your users if you have renamed a file extension-named processor. @@ -155,23 +156,23 @@ To migrate to flat config format, you'll need to move the configs to after the d ```js const plugin = { - configs: {}, - rules: {}, - processors: {} + configs: {}, + rules: {}, + processors: {}, }; // assign configs here so we can reference `plugin` Object.assign(plugin.configs, { - recommended: { - plugins: { - example: plugin - }, - rules: { - "example/rule1": "error", - "example/rule2": "error" - } - } -}) + recommended: { + plugins: { + example: plugin, + }, + rules: { + "example/rule1": "error", + "example/rule2": "error", + }, + }, +}); // for ESM export default plugin; @@ -183,24 +184,48 @@ module.exports = plugin; Your users can then use this exported config like this: ```js +import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; -export default [ +export default defineConfig([ + // use recommended config and provide your own overrides + { + files: ["**/*.js"], + plugins: { + example, + }, + extends: ["example/recommended"], + rules: { + "example/rule1": "warn", + }, + }, +]); +``` - // use recommended config - example.configs.recommended, +If your config extends other configs, you can export an array: - // and provide your own overrides - { - rules: { - "example/rule1": "warn" - } - } -]; +```js +const baseConfig = require("./base-config"); + +module.exports = { + configs: { + extendedConfig: [ + baseConfig, + { + rules: { + "example/rule1": "error", + "example/rule2": "error", + }, + }, + ], + }, +}; ``` You should update your documentation so your plugin users know how to reference the exported configs. +For more information, see the [full documentation](https://eslint.org/docs/latest/extend/plugins#configs-in-plugins). + ## Migrating Environments for Flat Config Environments are no longer supported in flat config, and so we recommend transitioning your environments into exported configs. For example, suppose you export a `mocha` environment like this: @@ -229,24 +254,24 @@ To migrate this environment into a config, you need to add a new key in the `plu ```js const plugin = { - configs: {}, - rules: {}, - processors: {} + configs: {}, + rules: {}, + processors: {}, }; // assign configs here so we can reference `plugin` Object.assign(plugin.configs, { - mocha: { - languageOptions: { - globals: { - it: "writeable", - xit: "writeable", - describe: "writeable", - xdescribe: "writeable" - } - } - } -}) + mocha: { + languageOptions: { + globals: { + it: "writeable", + xit: "writeable", + describe: "writeable", + xdescribe: "writeable", + }, + }, + }, +}); // for ESM export default plugin; @@ -258,22 +283,27 @@ module.exports = plugin; Your users can then use this exported config like this: ```js +import { defineConfig } from "eslint/config"; import example from "eslint-plugin-example"; -export default [ - - // use the mocha globals - example.configs.mocha, - - // and provide your own overrides - { - languageOptions: { - globals: { - it: "readonly" - } - } - } -]; +export default defineConfig([ + { + files: ["**/tests/*.js"], + plugins: { + example, + }, + + // use the mocha globals + extends: ["example/mocha"], + + // and provide your own overrides + languageOptions: { + globals: { + it: "readonly", + }, + }, + }, +]); ``` You should update your documentation so your plugin users know how to reference the exported configs. @@ -288,6 +318,6 @@ If your plugin needs to work with both the old and new configuration systems, th ## Further Reading -* [Overview of the flat config file format blog post](https://eslint.org/blog/2022/08/new-config-system-part-2/) -* [API usage of new configuration system blog post](https://eslint.org/blog/2022/08/new-config-system-part-3/) -* [Background to new configuration system blog post](https://eslint.org/blog/2022/08/new-config-system-part-1/) +- [Overview of the flat config file format blog post](https://eslint.org/blog/2022/08/new-config-system-part-2/) +- [API usage of new configuration system blog post](https://eslint.org/blog/2022/08/new-config-system-part-3/) +- [Background to new configuration system blog post](https://eslint.org/blog/2022/08/new-config-system-part-1/) diff --git a/docs/src/extend/plugins.md b/docs/src/extend/plugins.md index 5374a0f50946..53c78b14ac7d 100644 --- a/docs/src/extend/plugins.md +++ b/docs/src/extend/plugins.md @@ -5,195 +5,388 @@ eleventyNavigation: parent: extend eslint title: Create Plugins order: 2 - --- -An ESLint plugin is an extension for ESLint that adds additional rules and configuration options. Plugins let you customize your ESLint configuration to enforce rules that are not included in the core ESLint package. Plugins can also provide additional environments, custom processors, and configurations. - -## Name a Plugin +ESLint plugins extend ESLint with additional functionality. In most cases, you'll extend ESLint by creating plugins that encapsulate the additional functionality you want to share across multiple projects. -Each plugin is an npm module with a name in the format of `eslint-plugin-`, such as `eslint-plugin-jquery`. You can also use scoped packages in the format of `@/eslint-plugin-` such as `@jquery/eslint-plugin-jquery` or even `@/eslint-plugin` such as `@jquery/eslint-plugin`. +## Creating a plugin -## Create a Plugin +A plugin is a JavaScript object that exposes certain properties to ESLint: -The easiest way to start creating a plugin is to use the [Yeoman generator](https://www.npmjs.com/package/generator-eslint). The generator will guide you through setting up the skeleton of a plugin. - -### Rules in Plugins +- `meta` - information about the plugin. +- `configs` - an object containing named configurations. +- `rules` - an object containing the definitions of custom rules. +- `processors` - an object containing named processors. -Plugins can expose custom rules for use in ESLint. To do so, the plugin must export a `rules` object containing a key-value mapping of rule ID to rule. The rule ID does not have to follow any naming convention (so it can just be `dollar-sign`, for instance). To learn more about creating custom rules in plugins, refer to [Custom Rules](custom-rules). +To get started, create a JavaScript file and export an object containing the properties you'd like ESLint to use. To make your plugin as easy to maintain as possible, we recommend that you format your plugin entrypoint file to look like this: ```js -module.exports = { - rules: { - "dollar-sign": { - create: function (context) { - // rule implementation ... - } - } - } +const plugin = { + meta: {}, + configs: {}, + rules: {}, + processors: {}, }; + +// for ESM +export default plugin; + +// OR for CommonJS +module.exports = plugin; ``` -To use the rule in ESLint, you would use the unprefixed plugin name, followed by a slash, followed by the rule name. So if this plugin were named `eslint-plugin-myplugin`, then in your configuration you'd refer to the rule by the name `myplugin/dollar-sign`. Example: `"rules": {"myplugin/dollar-sign": 2}`. +If you plan to distribute your plugin as an npm package, make sure that the module that exports the plugin object is the default export of your package. This will enable ESLint to import the plugin when it is specified in the command line in the [`--plugin` option](../use/command-line-interface#--plugin). -### Environments in Plugins +### Meta Data in Plugins -Plugins can expose additional environments for use in ESLint. To do so, the plugin must export an `environments` object. The keys of the `environments` object are the names of the different environments provided and the values are the environment settings. For example: +For easier debugging and more effective caching of plugins, it's recommended to provide a `name`, `version`, and `namespace` in a `meta` object at the root of your plugin, like this: ```js -module.exports = { - environments: { - jquery: { - globals: { - $: false - } - } - } +const plugin = { + // preferred location of name and version + meta: { + name: "eslint-plugin-example", + version: "1.2.3", + namespace: "example", + }, + rules: { + // add rules here + }, }; + +// for ESM +export default plugin; + +// OR for CommonJS +module.exports = plugin; ``` -There's a `jquery` environment defined in this plugin. To use the environment in ESLint, you would use the unprefixed plugin name, followed by a slash, followed by the environment name. So if this plugin were named `eslint-plugin-myplugin`, then you would set the environment in your configuration to be `"myplugin/jquery"`. +The `meta.name` property should match the npm package name for your plugin and the `meta.version` property should match the npm package version for your plugin. The `meta.namespace` property should match the prefix you'd like users to use for accessing the plugin's rules, processors, languages, and configs. The namespace is typically what comes after `eslint-plugin-` in your package name, which is why this example uses `"example"`. Providing a namespace allows the `defineConfig()` function to find your plugin even when a user assigns a different namespace in their config file. -Plugin environments can define the following objects: +The easiest way to add the name and version is by reading this information from your `package.json`, as in this example: -1. `globals` - acts the same `globals` in a configuration file. The keys are the names of the globals and the values are `true` to allow the global to be overwritten and `false` to disallow. -1. `parserOptions` - acts the same as `parserOptions` in a configuration file. +```js +import fs from "fs"; + +const pkg = JSON.parse( + fs.readFileSync(new URL("./package.json", import.meta.url), "utf8"), +); + +const plugin = { + // preferred location of name and version + meta: { + name: pkg.name, + version: pkg.version, + namespace: "example", + }, + rules: { + // add rules here + }, +}; -### Processors in Plugins +export default plugin; +``` + +::: tip +While there are no restrictions on plugin names, it helps others to find your plugin on [npm](https://npmjs.com) when you follow these naming conventions: + +- **Unscoped:** If your npm package name won't be scoped (doesn't begin with `@`), then the plugin name should begin with `eslint-plugin-`, such as `eslint-plugin-example`. +- **Scoped:** If your npm package name will be scoped, then the plugin name should be in the format of `@/eslint-plugin-` such as `@jquery/eslint-plugin-jquery` or even `@/eslint-plugin` such as `@jquery/eslint-plugin`. -::: warning -File extension-named processors are deprecated and only work in eslintrc-style configuration files. Flat config files do not automatically apply processors; you need to explicitly set the `processor` property. ::: -You can add processors to plugins by including the processor functions in the `processors` key. For more information on defining custom processors, refer to [Custom Processors](custom-processors). +As an alternative, you can also expose `name` and `version` properties at the root of your plugin, such as: ```js -module.exports = { - processors: { - // This processor will be applied to `*.md` files automatically. - ".md": { - preprocess(text, filename) { /* ... */ }, - postprocess(messages, filename) { /* ... */ } - } - "processor-name": { - preprocess: function(text, filename) {/* ... */}, - - postprocess: function(messages, filename) { /* ... */ }, - } - } -} +const plugin = { + // alternate location of name and version + name: "eslint-plugin-example", + version: "1.2.3", + rules: { + // add rules here + }, +}; + +// for ESM +export default plugin; + +// OR for CommonJS +module.exports = plugin; ``` -### Configs in Plugins +::: important +While the `meta` object is the preferred way to provide the plugin name and version, this format is also acceptable and is provided for backward compatibility. +::: -You can bundle configurations inside a plugin by specifying them under the `configs` key. This can be useful when you want to bundle a set of custom rules with additional configuration. Multiple configurations are supported per plugin. +### Rules in Plugins -You can include individual rules from a plugin in a config that's also included in the plugin. In the config, you must specify your plugin name in the `plugins` array as well as any rules you want to enable that are part of the plugin. Any plugin rules must be prefixed with the short or long plugin name. +Plugins can expose custom rules for use in ESLint. To do so, the plugin must export a `rules` object containing a key-value mapping of rule ID to rule. The rule ID does not have to follow any naming convention except that it should not contain a `/` character (so it can just be `dollar-sign` but not `foo/dollar-sign`, for instance). To learn more about creating custom rules in plugins, refer to [Custom Rules](custom-rules). ```js -// eslint-plugin-myPlugin - -module.exports = { - configs: { - myConfig: { - plugins: ["myPlugin"], - env: ["browser"], - rules: { - semi: "error", - "myPlugin/my-rule": "error", - "eslint-plugin-myPlugin/another-rule": "error" - } - }, - myOtherConfig: { - plugins: ["myPlugin"], - env: ["node"], - rules: { - "myPlugin/my-rule": "off", - "eslint-plugin-myPlugin/another-rule": "off", - "eslint-plugin-myPlugin/yet-another-rule": "error" - } - } - }, - rules: { - "my-rule": {/* rule definition */}, - "another-rule": {/* rule definition */}, - "yet-another-rule": {/* rule definition */} - } +const plugin = { + meta: { + name: "eslint-plugin-example", + version: "1.2.3", + }, + rules: { + "dollar-sign": { + create(context) { + // rule implementation ... + }, + }, + }, }; + +// for ESM +export default plugin; + +// OR for CommonJS +module.exports = plugin; ``` -Plugins cannot force a specific configuration to be used. Users must manually include a plugin's configurations in their configuration file. +In order to use a rule from a plugin in a configuration file, import the plugin and include it in the `plugins` key, specifying a namespace. Then, use that namespace to reference the rule in the `rules` configuration, like this: -If the example plugin above were called `eslint-plugin-myPlugin`, the `myConfig` and `myOtherConfig` configurations would then be usable in a configuration file by extending `"plugin:myPlugin/myConfig"` and `"plugin:myPlugin/myOtherConfig"`, respectively. +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; +import example from "eslint-plugin-example"; + +export default defineConfig([ + { + plugins: { + example, + }, + rules: { + "example/dollar-sign": "error", + }, + }, +]); +``` + +::: warning +Namespaces that don't begin with `@` may not contain a `/`; namespaces that begin with `@` may contain a `/`. For example, `eslint/plugin` is not a valid namespace but `@eslint/plugin` is valid. This restriction is for backwards compatibility with eslintrc plugin naming restrictions. +::: + +### Processors in Plugins + +Plugins can expose [processors](custom-processors) for use in configuration file by providing a `processors` object. Similar to rules, each key in the `processors` object is the name of a processor and each value is the processor object itself. Here's an example: -```json -// .eslintrc.json +```js +const plugin = { + meta: { + name: "eslint-plugin-example", + version: "1.2.3", + }, + processors: { + "processor-name": { + preprocess(text, filename) { + /* ... */ + }, + postprocess(messages, filename) { + /* ... */ + }, + }, + }, +}; -{ - "extends": ["plugin:myPlugin/myConfig"] -} +// for ESM +export default plugin; +// OR for CommonJS +module.exports = plugin; ``` -### Meta Data in Plugins +In order to use a processor from a plugin in a configuration file, import the plugin and include it in the `plugins` key, specifying a namespace. Then, use that namespace to reference the processor in the `processor` configuration, like this: + +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; +import example from "eslint-plugin-example"; + +export default defineConfig([ + { + files: ["**/*.txt"], + plugins: { + example, + }, + processor: "example/processor-name", + }, +]); +``` + +### Configs in Plugins -For easier debugging and more effective caching of plugins, it's recommended to provide a name and version in a `meta` object at the root of your plugin, like this: +You can bundle configurations inside a plugin by specifying them under the `configs` key. This can be useful when you want to bundle a set of custom rules with a configuration that enables the recommended options. Multiple configurations are supported per plugin. + +You can include individual rules from a plugin in a config that's also included in the plugin. In the config, you must specify your plugin name in the `plugins` object as well as any rules you want to enable that are part of the plugin. Any plugin rules must be prefixed with the plugin namespace. Here's an example: ```js -// preferred location of name and version -module.exports = { - meta: { - name: "eslint-plugin-custom", - version: "1.2.3" - } +const plugin = { + meta: { + name: "eslint-plugin-example", + version: "1.2.3", + }, + configs: {}, + rules: { + "dollar-sign": { + create(context) { + // rule implementation ... + }, + }, + }, }; + +// assign configs here so we can reference `plugin` +Object.assign(plugin.configs, { + recommended: [ + { + plugins: { + example: plugin, + }, + rules: { + "example/dollar-sign": "error", + }, + languageOptions: { + globals: { + myGlobal: "readonly", + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, + ], +}); + +// for ESM +export default plugin; + +// OR for CommonJS +module.exports = plugin; ``` -The `meta.name` property should match the npm package name for your plugin and the `meta.version` property should match the npm package version for your plugin. The easiest way to accomplish this is by reading this information from your `package.json`. +This plugin exports a `recommended` config that is an array with one config object. When there is just one config object, you can also export just the object without an enclosing array. -As an alternative, you can also expose `name` and `version` properties at the root of your plugin, such as: +In order to use a config from a plugin in a configuration file, import the plugin and use the `extends` key to reference the name of the config, like this: ```js -// alternate location of name and version -module.exports = { - name: "eslint-plugin-custom", - version: "1.2.3" -}; +// eslint.config.js +import { defineConfig } from "eslint/config"; +import example from "eslint-plugin-example"; + +export default defineConfig([ + { + files: ["**/*.js"], // any patterns you want to apply the config to + plugins: { + example, + }, + extends: ["example/recommended"], + }, +]); ``` -While the `meta` object is the preferred way to provide the plugin name and version, this format is also acceptable and is provided for backward compatibility. +::: important +Plugins cannot force a specific configuration to be used. Users must manually include a plugin's configurations in their configuration file. +::: -### Peer Dependency +#### Backwards Compatibility for Legacy Configs -To make clear that the plugin requires ESLint to work correctly, you must declare ESLint as a peer dependency by mentioning it in the `peerDependencies` field of your plugin's `package.json`. +If your plugin needs to export configs that work both with the current (flat config) system and the old (eslintrc) system, you can export both config types from the `configs` key. When exporting legacy configs, we recommend prefixing the name with `"legacy-"` (for example, `"legacy-recommended"`) to make it clear how the config should be used. -Plugin support was introduced in ESLint version `0.8.0`. Ensure the `peerDependencies` points to ESLint `0.8.0` or later. +If you're working on a plugin that has existed prior to ESLint v9.0.0, then you may already have legacy configs with names such as `"recommended"`. If you don't want to update the config name, you can also create an additional entry in the `configs` object prefixed with `"flat/"` (for example, `"flat/recommended"`). Here's an example: -```json -{ - "peerDependencies": { - "eslint": ">=0.8.0" - } -} +```js +const plugin = { + meta: { + name: "eslint-plugin-example", + version: "1.2.3", + }, + configs: {}, + rules: { + "dollar-sign": { + create(context) { + // rule implementation ... + }, + }, + }, +}; + +// assign configs here so we can reference `plugin` +Object.assign(plugin.configs, { + // flat config format + "flat/recommended": [ + { + plugins: { + example: plugin, + }, + rules: { + "example/dollar-sign": "error", + }, + languageOptions: { + globals: { + myGlobal: "readonly", + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, + ], + + // eslintrc format + recommended: { + plugins: ["example"], + rules: { + "example/dollar-sign": "error", + }, + globals: { + myGlobal: "readonly", + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, +}); + +// for ESM +export default plugin; + +// OR for CommonJS +module.exports = plugin; ``` -## Testing +With this approach, both configuration systems recognize `"recommended"`. The old config system uses the `recommended` key while the current config system uses the `flat/recommended` key. The `defineConfig()` helper first looks at the `recommended` key, and if that is not in the correct format, it looks for the `flat/recommended` key. This allows you an upgrade path if you'd later like to rename `flat/recommended` to `recommended` when you no longer need to support the old config system. + +## Testing a Plugin ESLint provides the [`RuleTester`](../integrate/nodejs-api#ruletester) utility to make it easy to test the rules of your plugin. -## Linting +## Linting a Plugin ESLint plugins should be linted too! It's suggested to lint your plugin with the `recommended` configurations of: -* [eslint](https://www.npmjs.com/package/eslint) -* [eslint-plugin-eslint-plugin](https://www.npmjs.com/package/eslint-plugin-eslint-plugin) -* [eslint-plugin-n](https://www.npmjs.com/package/eslint-plugin-n) +- [eslint](https://www.npmjs.com/package/eslint) +- [eslint-plugin-eslint-plugin](https://www.npmjs.com/package/eslint-plugin-eslint-plugin) +- [eslint-plugin-n](https://www.npmjs.com/package/eslint-plugin-n) ## Share Plugins -In order to make your plugin available to the community you have to publish it on npm. +In order to make your plugin available publicly, you have to publish it on npm. When doing so, please be sure to: + +1. **List ESLint as a peer dependency.** Because plugins are intended for use with ESLint, it's important to add the `eslint` package as a peer dependency. To do so, manually edit your `package.json` file to include a `peerDependencies` block, like this: -To make it easy for others to find your plugin, add these [keywords](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) to your `package.json` file: + ```json + { + "peerDependencies": { + "eslint": ">=9.0.0" + } + } + ``` -* `eslint` -* `eslintplugin` +2. **Specify keywords.** ESLint plugins should specify `eslint`, `eslintplugin` and `eslint-plugin` as [keywords](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) in your `package.json` file. diff --git a/docs/src/extend/rule-deprecation.md b/docs/src/extend/rule-deprecation.md new file mode 100644 index 000000000000..c6f542264a73 --- /dev/null +++ b/docs/src/extend/rule-deprecation.md @@ -0,0 +1,52 @@ +--- +title: Rule Deprecation +--- + +The rule deprecation metadata describes whether a rule is deprecated and how the rule can be replaced if there is a replacement. +The legacy format used the two top-level [rule meta](./custom-rules#rule-structure) properties `deprecated` (as a boolean only) and `replacedBy`. +In the new format, `deprecated` is a boolean or an object of type `DeprecatedInfo`, and `replacedBy` should be defined inside `deprecated` instead of at the top-level. + +## ◆ DeprecatedInfo type + +This type represents general information about a rule deprecation. +Every property is optional. + +- `message` (`string`)
+ A general message presentable to the user. May contain why this rule is deprecated or how to replace the rule. +- `url` (`string`)
+ An URL with more information about this rule deprecation. +- `replacedBy` (`ReplacedByInfo[]`)
+ Information about the available replacements for the rule. + This may be an empty array to explicitly state there is no replacement. +- `deprecatedSince` (`string`)
+ [Semver](https://semver.org/) of the version deprecating the rule. +- `availableUntil` (`string | null`)
+ [Semver](https://semver.org/) of the version likely to remove the rule, e.g. the next major version. + The special value `null` means the rule will no longer be changed but will be kept available. + +## ◆ ReplacedByInfo type + +The type describes a single possible replacement of a rule. +Every property is optional. + +- `message` (`string`)
+ A general message about this rule replacement, e.g. +- `url` (`string`)
+ An URL with more information about this rule replacement. +- `plugin` (`ExternalSpecifier`)
+ Specifies which plugin has the replacement rule. + The name should be the package name and should be "eslint" if the replacement is an ESLint core rule. + This property should be omitted if the replacement is in the same plugin. +- `rule` (`ExternalSpecifier`)
+ Specifies the replacement rule. + May be omitted if the plugin only contains a single rule or has the same name as the rule. + +### ◆ ExternalSpecifier type + +This type represents an external resource. +Every property is optional. + +- `name` (`string`)
+ The package name for `plugin` and the rule id for `rule`. +- `url` (`string`)
+ An URL pointing to documentation for the plugin / rule.. diff --git a/docs/src/extend/scope-manager-interface.md b/docs/src/extend/scope-manager-interface.md index 60e26431c0c9..612a6dc4f5bb 100644 --- a/docs/src/extend/scope-manager-interface.md +++ b/docs/src/extend/scope-manager-interface.md @@ -1,11 +1,10 @@ --- title: ScopeManager - --- -This document was written based on the implementation of [eslint-scope](https://github.com/eslint/eslint-scope), a fork of [escope](https://github.com/estools/escope), and deprecates some members ESLint is not using. +This document was written based on the implementation of [eslint-scope](https://github.com/eslint/js/tree/main/packages/eslint-scope), a fork of [escope](https://github.com/estools/escope), and deprecates some members ESLint is not using. ----- +--- ## ScopeManager interface @@ -15,30 +14,30 @@ This document was written based on the implementation of [eslint-scope](https:// #### scopes -* **Type:** `Scope[]` -* **Description:** All scopes. +- **Type:** `Scope[]` +- **Description:** All scopes. #### globalScope -* **Type:** `Scope` -* **Description:** The root scope. +- **Type:** `Scope` +- **Description:** The root scope. ### Methods #### acquire(node, inner = false) -* **Parameters:** - * `node` (`ASTNode`) ... An AST node to get their scope. - * `inner` (`boolean`) ... If the node has multiple scope, this returns the outermost scope normally. If `inner` is `true` then this returns the innermost scope. Default is `false`. -* **Return type:** `Scope | null` -* **Description:** Get the scope of a given AST node. The gotten scope's `block` property is the node. This method never returns `function-expression-name` scope. If the node does not have their scope, this returns `null`. +- **Parameters:** + - `node` (`ASTNode`) ... An AST node to get their scope. + - `inner` (`boolean`) ... If the node has multiple scope, this returns the outermost scope normally. If `inner` is `true` then this returns the innermost scope. Default is `false`. +- **Return type:** `Scope | null` +- **Description:** Get the scope of a given AST node. The gotten scope's `block` property is the node. This method never returns `function-expression-name` scope. If the node does not have their scope, this returns `null`. #### getDeclaredVariables(node) -* **Parameters:** - * `node` (`ASTNode`) ... An AST node to get their variables. -* **Return type:** `Variable[]` -* **Description:** Get the variables that a given AST node defines. The gotten variables' `def[].node`/`def[].parent` property is the node. If the node does not define any variable, this returns an empty array. +- **Parameters:** + - `node` (`ASTNode`) ... An AST node to get their variables. +- **Return type:** `Variable[]` +- **Description:** Get the variables that a given AST node defines. The gotten variables' `def[].node`/`def[].parent` property is the node. If the node does not define any variable, this returns an empty array. ### Deprecated members @@ -46,30 +45,30 @@ Those members are defined but not used in ESLint. #### isModule() -* **Parameters:** -* **Return type:** `boolean` -* **Description:** `true` if this program is module. +- **Parameters:** +- **Return type:** `boolean` +- **Description:** `true` if this program is module. #### isImpliedStrict() -* **Parameters:** -* **Return type:** `boolean` -* **Description:** `true` if this program is strict mode implicitly. I.e., `options.impliedStrict === true`. +- **Parameters:** +- **Return type:** `boolean` +- **Description:** `true` if this program is strict mode implicitly. I.e., `options.impliedStrict === true`. #### isStrictModeSupported() -* **Parameters:** -* **Return type:** `boolean` -* **Description:** `true` if this program supports strict mode. I.e., `options.ecmaVersion >= 5`. +- **Parameters:** +- **Return type:** `boolean` +- **Description:** `true` if this program supports strict mode. I.e., `options.ecmaVersion >= 5`. #### acquireAll(node) -* **Parameters:** - * `node` (`ASTNode`) ... An AST node to get their scope. -* **Return type:** `Scope[] | null` -* **Description:** Get the scopes of a given AST node. The gotten scopes' `block` property is the node. If the node does not have their scope, this returns `null`. +- **Parameters:** + - `node` (`ASTNode`) ... An AST node to get their scope. +- **Return type:** `Scope[] | null` +- **Description:** Get the scopes of a given AST node. The gotten scopes' `block` property is the node. If the node does not have their scope, this returns `null`. ----- +--- ## Scope interface @@ -79,64 +78,80 @@ Those members are defined but not used in ESLint. #### type -* **Type:** `string` -* **Description:** The type of this scope. This is one of `"block"`, `"catch"`, `"class"`, `"class-field-initializer"`, `"class-static-block"`, `"for"`, `"function"`, `"function-expression-name"`, `"global"`, `"module"`, `"switch"`, `"with"`. +- **Type:** `string` +- **Description:** The type of this scope. This is one of `"block"`, `"catch"`, `"class"`, `"class-field-initializer"`, `"class-static-block"`, `"for"`, `"function"`, `"function-expression-name"`, `"global"`, `"module"`, `"switch"`, `"with"`. #### isStrict -* **Type:** `boolean` -* **Description:** `true` if this scope is strict mode. +- **Type:** `boolean` +- **Description:** `true` if this scope is strict mode. #### upper -* **Type:** `Scope | null` -* **Description:** The parent scope. If this is the global scope then this property is `null`. +- **Type:** `Scope | null` +- **Description:** The parent scope. If this is the global scope then this property is `null`. #### childScopes -* **Type:** `Scope[]` -* **Description:** The array of child scopes. This does not include grandchild scopes. +- **Type:** `Scope[]` +- **Description:** The array of child scopes. This does not include grandchild scopes. #### variableScope -* **Type:** `Scope` -* **Description:** The nearest ancestor whose `type` is one of `"class-field-initializer"`, `"class-static-block"`, `"function"`, `"global"`, or `"module"`. For the aforementioned scopes this is a self-reference. +- **Type:** `Scope` +- **Description:** The nearest ancestor whose `type` is one of `"class-field-initializer"`, `"class-static-block"`, `"function"`, `"global"`, or `"module"`. For the aforementioned scopes this is a self-reference. > This represents the lowest enclosing function or top-level scope. Class field initializers and class static blocks are implicit functions. Historically, this was the scope which hosts variables that are defined by `var` declarations, and thus the name `variableScope`. #### block -* **Type:** `ASTNode` -* **Description:** The AST node which created this scope. +- **Type:** `ASTNode` +- **Description:** The AST node which created this scope. #### variables -* **Type:** `Variable[]` -* **Description:** The array of all variables which are defined on this scope. This does not include variables which are defined in child scopes. +- **Type:** `Variable[]` +- **Description:** The array of all variables which are defined on this scope. This does not include variables which are defined in child scopes. #### set -* **Type:** `Map` -* **Description:** The map from variable names to variable objects. - -> I hope to rename `set` field or replace by a method. +- **Type:** `Map` +- **Description:** The map from variable names to variable objects. #### references -* **Type:** `Reference[]` -* **Description:** The array of all references on this scope. This does not include references in child scopes. +- **Type:** `Reference[]` +- **Description:** The array of all references on this scope. This does not include references in child scopes. #### through -* **Type:** `Reference[]` -* **Description:** The array of references which could not be resolved in this scope. +- **Type:** `Reference[]` +- **Description:** The array of references which could not be resolved in this scope. #### functionExpressionScope -* **Type:** `boolean` -* **Description:** `true` if this scope is `"function-expression-name"` scope. +- **Type:** `boolean` +- **Description:** `true` if this scope is `"function-expression-name"` scope. + +#### implicit + +This field exists only in the root `Scope` object (the global scope). It provides information about implicit global variables. Implicit global variables are variables that are neither built-in nor explicitly declared, but created implicitly by assigning values to undeclared variables in non-strict code. `Variable` objects for these variables are not present in the root `Scope` object's fields `variables` and `set`. + +The value of the `implicit` field is an object with two properties. + +##### variables -> I hope to deprecate `functionExpressionScope` field as replacing by `scope.type === "function-expression-name"`. +- **Type:** `Variable[]` +- **Description:** The array of all implicit global variables. + +##### set + +- **Type:** `Map` +- **Description:** The map from variable names to variable objects for implicit global variables. + +::: tip +In `Variable` objects that represent implicit global variables, `references` is always an empty array. You can find references to these variables in the `through` field of the root `Scope` object (the global scope), among other unresolved references. +::: ### Deprecated members @@ -144,57 +159,57 @@ Those members are defined but not used in ESLint. #### taints -* **Type:** `Map` -* **Description:** The map from variable names to `tainted` flag. +- **Type:** `Map` +- **Description:** The map from variable names to `tainted` flag. #### dynamic -* **Type:** `boolean` -* **Description:** `true` if this scope is dynamic. I.e., the type of this scope is `"global"` or `"with"`. +- **Type:** `boolean` +- **Description:** `true` if this scope is dynamic. I.e., the type of this scope is `"global"` or `"with"`. #### directCallToEvalScope -* **Type:** `boolean` -* **Description:** `true` if this scope contains `eval()` invocations. +- **Type:** `boolean` +- **Description:** `true` if this scope contains `eval()` invocations. #### thisFound -* **Type:** `boolean` -* **Description:** `true` if this scope contains `this`. +- **Type:** `boolean` +- **Description:** `true` if this scope contains `this`. #### resolve(node) -* **Parameters:** - * `node` (`ASTNode`) ... An AST node to get their reference object. The type of the node must be `"Identifier"`. -* **Return type:** `Reference | null` -* **Description:** Returns `this.references.find(r => r.identifier === node)`. +- **Parameters:** + - `node` (`ASTNode`) ... An AST node to get their reference object. The type of the node must be `"Identifier"`. +- **Return type:** `Reference | null` +- **Description:** Returns `this.references.find(r => r.identifier === node)`. #### isStatic() -* **Parameters:** -* **Return type:** `boolean` -* **Description:** Returns `!this.dynamic`. +- **Parameters:** +- **Return type:** `boolean` +- **Description:** Returns `!this.dynamic`. #### isArgumentsMaterialized() -* **Parameters:** -* **Return type:** `boolean` -* **Description:** `true` if this is a `"function"` scope which has used `arguments` variable. +- **Parameters:** +- **Return type:** `boolean` +- **Description:** `true` if this is a `"function"` scope which has used `arguments` variable. #### isThisMaterialized() -* **Parameters:** -* **Return type:** `boolean` -* **Description:** Returns `this.thisFound`. +- **Parameters:** +- **Return type:** `boolean` +- **Description:** Returns `this.thisFound`. #### isUsedName(name) -* **Parameters:** - * `name` (`string`) ... The name to check. -* **Return type:** `boolean` -* **Description:** `true` if a given name is used in variable names or reference names. +- **Parameters:** + - `name` (`string`) ... The name to check. +- **Return type:** `boolean` +- **Description:** `true` if a given name is used in variable names or reference names. ----- +--- ## Variable interface @@ -204,30 +219,28 @@ Those members are defined but not used in ESLint. #### name -* **Type:** `string` -* **Description:** The name of this variable. +- **Type:** `string` +- **Description:** The name of this variable. #### scope -* **Type:** `Scope` -* **Description:** The scope in which this variable is defined. +- **Type:** `Scope` +- **Description:** The scope in which this variable is defined. #### identifiers -* **Type:** `ASTNode[]` -* **Description:** The array of `Identifier` nodes which define this variable. If this variable is redeclared, this array includes two or more nodes. - -> I hope to deprecate `identifiers` field as replacing by `defs[].name` field. +- **Type:** `ASTNode[]` +- **Description:** The array of `Identifier` nodes which define this variable. If this variable is redeclared, this array includes two or more nodes. #### references -* **Type:** `Reference[]` -* **Description:** The array of the references of this variable. +- **Type:** `Reference[]` +- **Description:** The array of the references of this variable. #### defs -* **Type:** `Definition[]` -* **Description:** The array of the definitions of this variable. +- **Type:** `Definition[]` +- **Description:** The array of the definitions of this variable. ### Deprecated members @@ -235,15 +248,15 @@ Those members are defined but not used in ESLint. #### tainted -* **Type:** `boolean` -* **Description:** The `tainted` flag. (always `false`) +- **Type:** `boolean` +- **Description:** The `tainted` flag. (always `false`) #### stack -* **Type:** `boolean` -* **Description:** The `stack` flag. (I'm not sure what this means.) +- **Type:** `boolean` +- **Description:** The `stack` flag. (I'm not sure what this means.) ----- +--- ## Reference interface @@ -253,60 +266,60 @@ Those members are defined but not used in ESLint. #### identifier -* **Type:** `ASTNode` -* **Description:** The `Identifier` node of this reference. +- **Type:** `ASTNode` +- **Description:** The `Identifier` node of this reference. #### from -* **Type:** `Scope` -* **Description:** The `Scope` object that this reference is on. +- **Type:** `Scope` +- **Description:** The `Scope` object that this reference is on. #### resolved -* **Type:** `Variable | null` -* **Description:** The `Variable` object that this reference refers. If such variable was not defined, this is `null`. +- **Type:** `Variable | null` +- **Description:** The `Variable` object that this reference refers. If such variable was not defined, this is `null`. #### writeExpr -* **Type:** `ASTNode | null` -* **Description:** The ASTNode object which is right-hand side. +- **Type:** `ASTNode | null` +- **Description:** The ASTNode object which is right-hand side. #### init -* **Type:** `boolean` -* **Description:** `true` if this writing reference is a variable initializer or a default value. +- **Type:** `boolean` +- **Description:** `true` if this writing reference is a variable initializer or a default value. ### Methods #### isWrite() -* **Parameters:** -* **Return type:** `boolean` -* **Description:** `true` if this reference is writing. +- **Parameters:** +- **Return type:** `boolean` +- **Description:** `true` if this reference is writing. #### isRead() -* **Parameters:** -* **Return type:** `boolean` -* **Description:** `true` if this reference is reading. +- **Parameters:** +- **Return type:** `boolean` +- **Description:** `true` if this reference is reading. #### isWriteOnly() -* **Parameters:** -* **Return type:** `boolean` -* **Description:** `true` if this reference is writing but not reading. +- **Parameters:** +- **Return type:** `boolean` +- **Description:** `true` if this reference is writing but not reading. #### isReadOnly() -* **Parameters:** -* **Return type:** `boolean` -* **Description:** `true` if this reference is reading but not writing. +- **Parameters:** +- **Return type:** `boolean` +- **Description:** `true` if this reference is reading but not writing. #### isReadWrite() -* **Parameters:** -* **Return type:** `boolean` -* **Description:** `true` if this reference is reading and writing. +- **Parameters:** +- **Return type:** `boolean` +- **Description:** `true` if this reference is reading and writing. ### Deprecated members @@ -314,26 +327,26 @@ Those members are defined but not used in ESLint. #### tainted -* **Type:** `boolean` -* **Description:** The `tainted` flag. (always `false`) +- **Type:** `boolean` +- **Description:** The `tainted` flag. (always `false`) #### flag -* **Type:** `number` -* **Description:** `1` is reading, `2` is writing, `3` is reading/writing. +- **Type:** `number` +- **Description:** `1` is reading, `2` is writing, `3` is reading/writing. #### partial -* **Type:** `boolean` -* **Description:** The `partial` flag. +- **Type:** `boolean` +- **Description:** The `partial` flag. #### isStatic() -* **Parameters:** -* **Return type:** `boolean` -* **Description:** `true` if this reference is resolved statically. +- **Parameters:** +- **Return type:** `boolean` +- **Description:** `true` if this reference is resolved statically. ----- +--- ## Definition interface @@ -343,43 +356,43 @@ Those members are defined but not used in ESLint. #### type -* **Type:** `string` -* **Description:** The type of this definition. One of `"CatchClause"`, `"ClassName"`, `"FunctionName"`, `"ImplicitGlobalVariable"`, `"ImportBinding"`, `"Parameter"`, and `"Variable"`. +- **Type:** `string` +- **Description:** The type of this definition. One of `"CatchClause"`, `"ClassName"`, `"FunctionName"`, `"ImplicitGlobalVariable"`, `"ImportBinding"`, `"Parameter"`, and `"Variable"`. #### name -* **Type:** `ASTNode` -* **Description:** The `Identifier` node of this definition. +- **Type:** `ASTNode` +- **Description:** The `Identifier` node of this definition. #### node -* **Type:** `ASTNode` -* **Description:** The enclosing node of the name. +- **Type:** `ASTNode` +- **Description:** The enclosing node of the name. -| type | node | -|:---------------------------|:-----| -| `"CatchClause"` | `CatchClause` -| `"ClassName"` | `ClassDeclaration` or `ClassExpression` -| `"FunctionName"` | `FunctionDeclaration` or `FunctionExpression` -| `"ImplicitGlobalVariable"` | `Program` -| `"ImportBinding"` | `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier` -| `"Parameter"` | `FunctionDeclaration`, `FunctionExpression`, or `ArrowFunctionExpression` -| `"Variable"` | `VariableDeclarator` +| type | node | +| :------------------------- | :------------------------------------------------------------------------- | +| `"CatchClause"` | `CatchClause` | +| `"ClassName"` | `ClassDeclaration` or `ClassExpression` | +| `"FunctionName"` | `FunctionDeclaration` or `FunctionExpression` | +| `"ImplicitGlobalVariable"` | `AssignmentExpression` or `ForInStatement` or `ForOfStatement` | +| `"ImportBinding"` | `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier` | +| `"Parameter"` | `FunctionDeclaration`, `FunctionExpression`, or `ArrowFunctionExpression` | +| `"Variable"` | `VariableDeclarator` | #### parent -* **Type:** `ASTNode | undefined | null` -* **Description:** The enclosing statement node of the name. +- **Type:** `ASTNode | undefined | null` +- **Description:** The enclosing statement node of the name. -| type | parent | -|:---------------------------|:-------| -| `"CatchClause"` | `null` -| `"ClassName"` | `null` -| `"FunctionName"` | `null` -| `"ImplicitGlobalVariable"` | `null` -| `"ImportBinding"` | `ImportDeclaration` -| `"Parameter"` | `null` -| `"Variable"` | `VariableDeclaration` +| type | parent | +| :------------------------- | :-------------------- | +| `"CatchClause"` | `null` | +| `"ClassName"` | `null` | +| `"FunctionName"` | `null` | +| `"ImplicitGlobalVariable"` | `null` | +| `"ImportBinding"` | `ImportDeclaration` | +| `"Parameter"` | `null` | +| `"Variable"` | `VariableDeclaration` | ### Deprecated members @@ -387,10 +400,10 @@ Those members are defined but not used in ESLint. #### index -* **Type:** `number | undefined | null` -* **Description:** The index in the declaration statement. +- **Type:** `number | undefined | null` +- **Description:** The index in the declaration statement. #### kind -* **Type:** `string | undefined | null` -* **Description:** The kind of the declaration statement. +- **Type:** `string | undefined | null` +- **Description:** The kind of the declaration statement. diff --git a/docs/src/extend/selectors.md b/docs/src/extend/selectors.md index 5ea700dead10..f622ed80ced0 100644 --- a/docs/src/extend/selectors.md +++ b/docs/src/extend/selectors.md @@ -1,6 +1,5 @@ --- title: Selectors - --- Some rules and APIs allow the use of selectors to query an AST. This page is intended to: @@ -30,24 +29,24 @@ Selectors are not limited to matching against single node types. For example, th The following selectors are supported: -* AST node type: `ForStatement` -* wildcard (matches all nodes): `*` -* attribute existence: `[attr]` -* attribute value: `[attr="foo"]` or `[attr=123]` -* attribute regex: `[attr=/foo.*/]` (with some [known issues](#known-issues)) -* attribute conditions: `[attr!="foo"]`, `[attr>2]`, `[attr<3]`, `[attr>=2]`, or `[attr<=3]` -* nested attribute: `[attr.level2="foo"]` -* field: `FunctionDeclaration > Identifier.id` -* First or last child: `:first-child` or `:last-child` -* nth-child (no ax+b support): `:nth-child(2)` -* nth-last-child (no ax+b support): `:nth-last-child(1)` -* descendant: `FunctionExpression ReturnStatement` -* child: `UnaryExpression > Literal` -* following sibling: `VariableDeclaration ~ VariableDeclaration` -* adjacent sibling: `ArrayExpression > Literal + SpreadElement` -* negation: `:not(ForStatement)` -* matches-any: `:matches([attr] > :first-child, :last-child)` -* class of AST node: `:statement`, `:expression`, `:declaration`, `:function`, or `:pattern` +- AST node type: `ForStatement` +- wildcard (matches all nodes): `*` +- attribute existence: `[attr]` +- attribute value: `[attr="foo"]` or `[attr=123]` +- attribute regex: `[attr=/foo.*/]` (with some [known issues](#known-issues)) +- attribute conditions: `[attr!="foo"]`, `[attr>2]`, `[attr<3]`, `[attr>=2]`, or `[attr<=3]` +- nested attribute: `[attr.level2="foo"]` +- field: `FunctionDeclaration > Identifier.id` +- First or last child: `:first-child` or `:last-child` +- nth-child (no ax+b support): `:nth-child(2)` +- nth-last-child (no ax+b support): `:nth-last-child(1)` +- descendant: `FunctionExpression ReturnStatement` +- child: `UnaryExpression > Literal` +- following sibling: `VariableDeclaration ~ VariableDeclaration` +- adjacent sibling: `ArrayExpression > Literal + SpreadElement` +- negation: `:not(ForStatement)` +- matches-any: `:matches([attr] > :first-child, :last-child)` +- class of AST node: `:statement`, `:expression`, `:declaration`, `:function`, or `:pattern` This syntax is very powerful, and can be used to precisely select many syntactic patterns in your code. @@ -63,22 +62,23 @@ When writing a custom ESLint rule, you can listen for nodes that match a particu ```js module.exports = { - create(context) { - // ... - - return { - - // This listener will be called for all IfStatement nodes with blocks. - "IfStatement > BlockStatement": function(blockStatementNode) { - // ...your logic here - }, - - // This listener will be called for all function declarations with more than 3 parameters. - "FunctionDeclaration[params.length>3]": function(functionDeclarationNode) { - // ...your logic here - } - }; - } + create(context) { + // ... + + return { + // This listener will be called for all IfStatement nodes with blocks. + "IfStatement > BlockStatement": function (blockStatementNode) { + // ...your logic here + }, + + // This listener will be called for all function declarations with more than 3 parameters. + "FunctionDeclaration[params.length>3]": function ( + functionDeclarationNode, + ) { + // ...your logic here + }, + }; + }, }; ``` @@ -86,8 +86,8 @@ Adding `:exit` to the end of a selector will cause the listener to be called whe If two or more selectors match the same node, their listeners will be called in order of increasing specificity. The specificity of an AST selector is similar to the specificity of a CSS selector: -* When comparing two selectors, the selector that contains more class selectors, attribute selectors, and pseudo-class selectors (excluding `:not()`) has higher specificity. -* If the class/attribute/pseudo-class count is tied, the selector that contains more node type selectors has higher specificity. +- When comparing two selectors, the selector that contains more class selectors, attribute selectors, and pseudo-class selectors (excluding `:not()`) has higher specificity. +- If the class/attribute/pseudo-class count is tied, the selector that contains more node type selectors has higher specificity. If multiple selectors have equal specificity, their listeners will be called in alphabetical order for that node. @@ -97,9 +97,12 @@ With the [no-restricted-syntax](../rules/no-restricted-syntax) rule, you can res ```json { - "rules": { - "no-restricted-syntax": ["error", "IfStatement > :not(BlockStatement).consequent"] - } + "rules": { + "no-restricted-syntax": [ + "error", + "IfStatement > :not(BlockStatement).consequent" + ] + } } ``` @@ -107,9 +110,12 @@ With the [no-restricted-syntax](../rules/no-restricted-syntax) rule, you can res ```json { - "rules": { - "no-restricted-syntax": ["error", "IfStatement[consequent.type!='BlockStatement']"] - } + "rules": { + "no-restricted-syntax": [ + "error", + "IfStatement[consequent.type!='BlockStatement']" + ] + } } ``` @@ -117,9 +123,12 @@ As another example, you can disallow calls to `require()`: ```json { - "rules": { - "no-restricted-syntax": ["error", "CallExpression[callee.name='require']"] - } + "rules": { + "no-restricted-syntax": [ + "error", + "CallExpression[callee.name='require']" + ] + } } ``` @@ -127,9 +136,12 @@ Or you can enforce that calls to `setTimeout` always have two arguments: ```json { - "rules": { - "no-restricted-syntax": ["error", "CallExpression[callee.name='setTimeout'][arguments.length!=2]"] - } + "rules": { + "no-restricted-syntax": [ + "error", + "CallExpression[callee.name='setTimeout'][arguments.length!=2]" + ] + } } ``` @@ -143,9 +155,12 @@ For example, the following configuration disallows importing from `some/path`: ```json { - "rules": { - "no-restricted-syntax": ["error", "ImportDeclaration[source.value=/^some\\u002Fpath$/]"] - } + "rules": { + "no-restricted-syntax": [ + "error", + "ImportDeclaration[source.value=/^some\\u002Fpath$/]" + ] + } } ``` diff --git a/docs/src/extend/shareable-configs-deprecated.md b/docs/src/extend/shareable-configs-deprecated.md new file mode 100644 index 000000000000..3dc180d6a6f5 --- /dev/null +++ b/docs/src/extend/shareable-configs-deprecated.md @@ -0,0 +1,233 @@ +--- +title: Share Configurations (Deprecated) +--- + +::: warning +This documentation is for shareable configs using the deprecated eslintrc configuration format. [View the updated documentation](shareable-configs). +::: + +To share your ESLint configuration, create a **shareable config**. You can publish your shareable config on [npm](https://www.npmjs.com/) so that others can download and use it in their ESLint projects. + +This page explains how to create and publish a shareable config. + +## Creating a Shareable Config + +Shareable configs are simply npm packages that export a configuration object. To start, [create a Node.js module](https://docs.npmjs.com/getting-started/creating-node-modules) like you normally would. + +The module name must take one of the following forms: + +- Begin with `eslint-config-`, such as `eslint-config-myconfig`. +- Be an npm [scoped module](https://docs.npmjs.com/misc/scope). To create a scoped module, name or prefix the module with `@scope/eslint-config`, such as `@scope/eslint-config` or `@scope/eslint-config-myconfig`. + +In your module, export the shareable config from the module's [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) entry point file. The default main entry point is `index.js`. For example: + +```js +// index.js +module.exports = { + globals: { + MyGlobal: true, + }, + + rules: { + semi: [2, "always"], + }, +}; +``` + +Since the `index.js` file is just JavaScript, you can read these settings from a file or generate them dynamically. + +## Publishing a Shareable Config + +Once your shareable config is ready, you can [publish it to npm](https://docs.npmjs.com/getting-started/publishing-npm-packages) to share it with others. We recommend using the `eslint` and `eslintconfig` [keywords](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#keywords) in the `package.json` file so others can easily find your module. + +You should declare your dependency on ESLint in the `package.json` using the [peerDependencies](https://docs.npmjs.com/files/package.json#peerdependencies) field. The recommended way to declare a dependency for future-proof compatibility is with the ">=" range syntax, using the lowest required ESLint version. For example: + +```json +{ + "peerDependencies": { + "eslint": ">= 3" + } +} +``` + +If your shareable config depends on a plugin, you should also specify it as a `peerDependency` (plugins will be loaded relative to the end user's project, so the end user is required to install the plugins they need). However, if your shareable config depends on a [custom parser](custom-parsers) or another shareable config, you can specify these packages as `dependencies` in the `package.json`. + +You can also test your shareable config on your computer before publishing by linking your module globally. Type: + +```bash +npm link +``` + +Then, in your project that wants to use your shareable config, type: + +```bash +npm link eslint-config-myconfig +``` + +Be sure to replace `eslint-config-myconfig` with the actual name of your module. + +## Using a Shareable Config + +To use a shareable config, include the config name in the `extends` field of a configuration file. For the value, use your module name. For example: + +```json +{ + "extends": "eslint-config-myconfig" +} +``` + +You can also omit the `eslint-config-` and it is automatically assumed by ESLint: + +```json +{ + "extends": "myconfig" +} +``` + +You cannot use shareable configs with the ESLint CLI [`--config`](../use/command-line-interface#-c---config) flag. + +### npm Scoped Modules + +npm [scoped modules](https://docs.npmjs.com/misc/scope) are also supported in a number of ways. + +You can use the module name: + +```json +{ + "extends": "@scope/eslint-config" +} +``` + +You can also omit the `eslint-config` and it is automatically assumed by ESLint: + +```json +{ + "extends": "@scope" +} +``` + +The module name can also be customized. For example, if you have a package named `@scope/eslint-config-myconfig`, the configuration can be specified as: + +```json +{ + "extends": "@scope/eslint-config-myconfig" +} +``` + +You could also omit `eslint-config` to specify the configuration as: + +```json +{ + "extends": "@scope/myconfig" +} +``` + +### Overriding Settings from Shareable Configs + +You can override settings from the shareable config by adding them directly into your `.eslintrc` file. + +## Sharing Multiple Configs + +You can share multiple configs in the same npm package. Specify a default config for the package by following the directions in the [Creating a Shareable Config](#creating-a-shareable-config) section. You can specify additional shareable configs by adding a new file to your npm package and then referencing it from your ESLint config. + +As an example, you can create a file called `my-special-config.js` in the root of your npm package and export a config, such as: + +```js +// my-special-config.js +module.exports = { + rules: { + quotes: [2, "double"], + }, +}; +``` + +Then, assuming you're using the package name `eslint-config-myconfig`, you can access the additional config via: + +```json +{ + "extends": "myconfig/my-special-config" +} +``` + +When using [scoped modules](https://docs.npmjs.com/misc/scope) it is not possible to omit the `eslint-config` namespace. Doing so would result in resolution errors as explained above. Assuming the package name is `@scope/eslint-config`, the additional config can be accessed as: + +```json +{ + "extends": "@scope/eslint-config/my-special-config" +} +``` + +Note that you can leave off the `.js` from the filename. + +**Important:** We strongly recommend always including a default config for your plugin to avoid errors. + +## Local Config File Resolution + +If you need to make multiple configs that can extend each other and live in different directories, you can create a single shareable config that handles this scenario. + +As an example, let's assume you're using the package name `eslint-config-myconfig` and your package looks something like this: + +```text +myconfig +├── index.js +└─â”Ŧ lib + ├── defaults.js + ├── dev.js + ├── ci.js + └─â”Ŧ ci + ├── frontend.js + ├── backend.js + └── common.js +``` + +In the `index.js` file, you can do something like this: + +```js +module.exports = require("./lib/ci.js"); +``` + +Now inside the package you have `/lib/defaults.js`, which contains: + +```js +module.exports = { + rules: { + "no-console": 1, + }, +}; +``` + +Inside `/lib/ci.js` you have: + +```js +module.exports = require("./ci/backend"); +``` + +Inside `/lib/ci/common.js`: + +```js +module.exports = { + rules: { + "no-alert": 2, + }, + extends: "myconfig/lib/defaults", +}; +``` + +Despite being in an entirely different directory, you'll see that all `extends` must use the full package path to the config file you wish to extend. + +Now inside `/lib/ci/backend.js`: + +```js +module.exports = { + rules: { + "no-console": 1, + }, + extends: "myconfig/lib/ci/common", +}; +``` + +In the last file, once again see that to properly resolve your config, you need to include the full package path. + +## Further Reading + +- [npm Developer Guide](https://docs.npmjs.com/misc/developers) diff --git a/docs/src/extend/shareable-configs.md b/docs/src/extend/shareable-configs.md index 60b35321efd4..8ecc0477f600 100644 --- a/docs/src/extend/shareable-configs.md +++ b/docs/src/extend/shareable-configs.md @@ -5,40 +5,49 @@ eleventyNavigation: parent: extend eslint title: Share Configurations order: 3 - --- To share your ESLint configuration, create a **shareable config**. You can publish your shareable config on [npm](https://www.npmjs.com/) so that others can download and use it in their ESLint projects. This page explains how to create and publish a shareable config. +::: tip +This page explains how to create a shareable config using the flat config format. For the deprecated eslintrc format, [see the deprecated documentation](shareable-configs-deprecated). +::: + ## Creating a Shareable Config -Shareable configs are simply npm packages that export a configuration object. To start, [create a Node.js module](https://docs.npmjs.com/getting-started/creating-node-modules) like you normally would. +Shareable configs are simply npm packages that export a configuration object or array. To start, [create a Node.js module](https://docs.npmjs.com/getting-started/creating-node-modules) like you normally would. -The module name must take one of the following forms: +While you can name the package in any way that you'd like, we recommend using one of the following conventions to make your package easier to identify: -* Begin with `eslint-config-`, such as `eslint-config-myconfig`. -* Be an npm [scoped module](https://docs.npmjs.com/misc/scope). To create a scoped module, name or prefix the module with `@scope/eslint-config`, such as `@scope/eslint-config` or `@scope/eslint-config-myconfig`. +- Begin with `eslint-config-`, such as `eslint-config-myconfig`. +- For an npm [scoped module](https://docs.npmjs.com/misc/scope), name or prefix the module with `@scope/eslint-config`, such as `@scope/eslint-config` or `@scope/eslint-config-myconfig`. In your module, export the shareable config from the module's [`main`](https://docs.npmjs.com/cli/v9/configuring-npm/package-json#main) entry point file. The default main entry point is `index.js`. For example: ```js // index.js -module.exports = { - - globals: { - MyGlobal: true - }, +export default [ + { + languageOptions: { + globals: { + MyGlobal: true, + }, + }, - rules: { - semi: [2, "always"] - } - -}; + rules: { + semi: [2, "always"], + }, + }, +]; ``` -Since the `index.js` file is just JavaScript, you can read these settings from a file or generate them dynamically. +Because the `index.js` file is just JavaScript, you can read these settings from a file or generate them dynamically. + +::: tip +Most of the time, you'll want to export an array of config objects from your shareable config. However, you can also export a single config object. Make sure your documentation clearly shows an example of how to use your shareable config inside of an `eslint.config.js` file to avoid user confusion. +::: ## Publishing a Shareable Config @@ -48,190 +57,97 @@ You should declare your dependency on ESLint in the `package.json` using the [pe ```json { - "peerDependencies": { - "eslint": ">= 3" - } + "peerDependencies": { + "eslint": ">= 9" + } } ``` -If your shareable config depends on a plugin, you should also specify it as a `peerDependency` (plugins will be loaded relative to the end user's project, so the end user is required to install the plugins they need). However, if your shareable config depends on a [custom parser](custom-parsers) or another shareable config, you can specify these packages as `dependencies` in the `package.json`. - -You can also test your shareable config on your computer before publishing by linking your module globally. Type: - -```bash -npm link -``` - -Then, in your project that wants to use your shareable config, type: - -```bash -npm link eslint-config-myconfig -``` - -Be sure to replace `eslint-config-myconfig` with the actual name of your module. +If your shareable config depends on a plugin or a custom parser, you should specify these packages as `dependencies` in your `package.json`. ## Using a Shareable Config -To use a shareable config, include the config name in the `extends` field of a configuration file. For the value, use your module name. For example: - -```json -{ - "extends": "eslint-config-myconfig" -} -``` - -You can also omit the `eslint-config-` and it is automatically assumed by ESLint: - -```json -{ - "extends": "myconfig" -} -``` - -You cannot use shareable configs with the ESLint CLI [`--config`](../use/command-line-interface#-c---config) flag. - -### npm Scoped Modules - -npm [scoped modules](https://docs.npmjs.com/misc/scope) are also supported in a number of ways. +To use a shareable config, import the package inside of an `eslint.config.js` file and add it into the exported array using `extends`, like this: -You can use the module name: +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; +import myconfig from "eslint-config-myconfig"; -```json -{ - "extends": "@scope/eslint-config" -} +export default defineConfig([ + { + files: ["**/*.js"], + extends: [myconfig], + }, +]); ``` -You can also omit the `eslint-config` and it is automatically assumed by ESLint: +::: warning +It's not possible to use shareable configs with the ESLint CLI [`--config`](../use/command-line-interface#-c---config) flag. +::: -```json -{ - "extends": "@scope" -} -``` +### Overriding Settings from Shareable Configs -The module name can also be customized. For example, if you have a package named `@scope/eslint-config-myconfig`, the configuration can be specified as: +You can override settings from the shareable config by adding them directly into your `eslint.config.js` file after importing the shareable config. For example: -```json -{ - "extends": "@scope/eslint-config-myconfig" -} -``` +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; +import myconfig from "eslint-config-myconfig"; -You could also omit `eslint-config` to specify the configuration as: +export default defineConfig([ + { + files: ["**/*.js"], + extends: [myconfig], -```json -{ - "extends": "@scope/myconfig" -} + // anything from here will override myconfig + rules: { + "no-unused-vars": "warn", + }, + }, +]); ``` -### Overriding Settings from Shareable Configs - -You can override settings from the shareable config by adding them directly into your `.eslintrc` file. - ## Sharing Multiple Configs -You can share multiple configs in the same npm package. Specify a default config for the package by following the directions in the [Creating a Shareable Config](#creating-a-shareable-config) section. You can specify additional shareable configs by adding a new file to your npm package and then referencing it from your ESLint config. +Because shareable configs are just npm packages, you can export as many configs as you'd like from the same package. In addition to specifying a default config using the `main` entry in your `package.json`, you can specify additional shareable configs by adding a new file to your npm package and then referencing it from your `eslint.config.js` file. As an example, you can create a file called `my-special-config.js` in the root of your npm package and export a config, such as: ```js // my-special-config.js -module.exports = { - rules: { - quotes: [2, "double"] - } +export default { + rules: { + quotes: [2, "double"], + }, }; ``` Then, assuming you're using the package name `eslint-config-myconfig`, you can access the additional config via: -```json -{ - "extends": "myconfig/my-special-config" -} -``` - -When using [scoped modules](https://docs.npmjs.com/misc/scope) it is not possible to omit the `eslint-config` namespace. Doing so would result in resolution errors as explained above. Assuming the package name is `@scope/eslint-config`, the additional config can be accessed as: - -```json -{ - "extends": "@scope/eslint-config/my-special-config" -} -``` - -Note that you can leave off the `.js` from the filename. - -**Important:** We strongly recommend always including a default config for your plugin to avoid errors. - -## Local Config File Resolution - -If you need to make multiple configs that can extend each other and live in different directories, you can create a single shareable config that handles this scenario. - -As an example, let's assume you're using the package name `eslint-config-myconfig` and your package looks something like this: - -```text -myconfig -├── index.js -└─â”Ŧ lib - ├── defaults.js - ├── dev.js - ├── ci.js - └─â”Ŧ ci - ├── frontend.js - ├── backend.js - └── common.js -``` - -In the `index.js` file, you can do something like this: - ```js -module.exports = require('./lib/ci.js'); -``` +// eslint.config.js +import { defineConfig } from "eslint/config"; +import myconfig from "eslint-config-myconfig"; +import mySpecialConfig from "eslint-config-myconfig/my-special-config.js"; -Now inside the package you have `/lib/defaults.js`, which contains: +export default defineConfig([ + { + files: ["**/*.js"], + extends: [myconfig, mySpecialConfig], -```js -module.exports = { - rules: { - 'no-console': 1 - } -}; -``` - -Inside `/lib/ci.js` you have: - -```js -module.exports = require('./ci/backend'); -``` - -Inside `/lib/ci/common.js`: - -```js -module.exports = { - rules: { - 'no-alert': 2 - }, - extends: 'myconfig/lib/defaults' -}; -``` - -Despite being in an entirely different directory, you'll see that all `extends` must use the full package path to the config file you wish to extend. - -Now inside `/lib/ci/backend.js`: - -```js -module.exports = { - rules: { - 'no-console': 1 - }, - extends: 'myconfig/lib/ci/common' -}; + // anything from here will override myconfig + rules: { + "no-unused-vars": "warn", + }, + }, +]); ``` -In the last file, once again see that to properly resolve your config, you need to include the full package path. +::: important +We strongly recommend always including a default export for your package to avoid confusion. +::: ## Further Reading -* [npm Developer Guide](https://docs.npmjs.com/misc/developers) +- [npm Developer Guide](https://docs.npmjs.com/misc/developers) diff --git a/docs/src/extend/stats.md b/docs/src/extend/stats.md new file mode 100644 index 000000000000..e39d944ada89 --- /dev/null +++ b/docs/src/extend/stats.md @@ -0,0 +1,142 @@ +--- +title: Stats Data +eleventyNavigation: + key: stats data + parent: extend eslint + title: Stats Data + order: 6 +--- + +{%- from 'components/npx_tabs.macro.html' import npx_tabs %} + +While an analysis of the overall rule performance for an ESLint run can be carried out by setting the [TIMING](./custom-rules#profile-rule-performance) environment variable, it can sometimes be useful to acquire more _granular_ timing data (lint time per file per rule) or collect other measures of interest. In particular, when developing new [custom plugins](./plugins) and evaluating/benchmarking new languages or rule sets. For these use cases, you can optionally collect runtime statistics from ESLint. + +## Enable stats collection + +To enable collection of statistics, you can either: + +1. Use the `--stats` CLI option. This will pass the stats data into the formatter used to output results from ESLint. (Note: not all formatters output stats data.) +1. Set `stats: true` as an option on the `ESLint` constructor. + +Enabling stats data adds a new `stats` key to each [LintResult](../integrate/nodejs-api#-lintresult-type) object containing data such as parse times, fix times, lint times per rule. + +As such, it is not available via stdout but made easily ingestible via a formatter using the CLI or via the Node.js API to cater to your specific needs. + +## ◆ Stats type + +The `Stats` value is the timing information of each lint run. The `stats` property of the [LintResult](../integrate/nodejs-api#-lintresult-type) type contains it. It has the following properties: + +- `fixPasses` (`number`)
+ The number of times ESLint has applied at least one fix after linting. +- `times` (`{ passes: TimePass[] }`)
+ The times spent on (parsing, fixing, linting) a file, where the linting refers to the timing information for each rule. + - `TimePass` (`{ parse: ParseTime, rules?: Record, fix: FixTime, total: number }`)
+ An object containing the times spent on (parsing, fixing, linting) + - `ParseTime` (`{ total: number }`)
+ The total time that is spent when parsing a file. + - `RuleTime` (`{ total: number }`) + The total time that is spent on a rule. + - `FixTime` (`{ total: number }`) + The total time that is spent on applying fixes to the code. + +### CLI usage + +Let's consider the following example: + +```js [file-to-fix.js] +/*eslint no-regex-spaces: "error", wrap-regex: "error"*/ + +function a() { + return / foo/.test("bar"); +} +``` + +Run ESLint with `--stats` and output to JSON via the built-in [`json` formatter](../use/formatters/): + +{{ npx_tabs({ + package: "eslint", + args: ["file-to-fix.js", "--fix", "--stats", "-f", "json"] +}) }} + +This yields the following `stats` entry as part of the formatted lint results object: + +```json +{ + "times": { + "passes": [ + { + "parse": { + "total": 3.975959 + }, + "rules": { + "no-regex-spaces": { + "total": 0.160792 + }, + "wrap-regex": { + "total": 0.422626 + } + }, + "fix": { + "total": 0.080208 + }, + "total": 12.765959 + }, + { + "parse": { + "total": 0.623542 + }, + "rules": { + "no-regex-spaces": { + "total": 0.043084 + }, + "wrap-regex": { + "total": 0.007959 + } + }, + "fix": { + "total": 0 + }, + "total": 1.148875 + } + ] + }, + "fixPasses": 1 +} +``` + +Note, that for the simple example above, the sum of all rule times should be directly comparable to the first column of the TIMING output. Running the same command with `TIMING=all`, you can verify this: + +```bash +$ TIMING=all npx eslint file-to-fix.js --fix --stats -f json +... +Rule | Time (ms) | Relative +:---------------|----------:|--------: +wrap-regex | 0.431 | 67.9% +no-regex-spaces | 0.204 | 32.1% +``` + +### API Usage + +You can achieve the same thing using the Node.js API by passing`stats: true` as an option to the `ESLint` constructor. For example: + +```js +const { ESLint } = require("eslint"); + +(async function main() { + // 1. Create an instance. + const eslint = new ESLint({ stats: true, fix: true }); + + // 2. Lint files. + const results = await eslint.lintFiles(["file-to-fix.js"]); + + // 3. Format the results. + const formatter = await eslint.loadFormatter("json"); + const resultText = formatter.format(results); + + // 4. Output it. + console.log(resultText); +})().catch(error => { + process.exitCode = 1; + console.error(error); +}); +``` diff --git a/docs/src/extend/ways-to-extend.md b/docs/src/extend/ways-to-extend.md index 3bf8314e1afa..2cadfd50737d 100644 --- a/docs/src/extend/ways-to-extend.md +++ b/docs/src/extend/ways-to-extend.md @@ -15,7 +15,7 @@ This page explains the ways to extend ESLint, and how these extensions all fit t Plugins let you add your own ESLint custom rules and custom processors to a project. You can publish a plugin as an npm module. -Plugins are useful because your project may require some ESLint configuration that isn't included in the core `eslint` package. For example, if you're using a frontend JavaScript library like React or framework like Vue, these tools have some features that require custom rules outside the scope of the ESLint core rules. +Plugins are useful because your project may require some ESLint configuration that isn't included in the core `eslint` package. For example, if you're using a frontend JavaScript library like [React](https://react.dev/) or framework like [Vue](https://vuejs.org/), these tools have some features that require custom rules outside the scope of the ESLint core rules. Often a plugin is paired with a configuration for ESLint that applies a set of features from the plugin to a project. You can include configurations in a plugin as well. @@ -23,9 +23,9 @@ For example, [`eslint-plugin-react`](https://www.npmjs.com/package/eslint-plugin To learn more about creating the extensions you can include in a plugin, refer to the following documentation: -* [Custom Rules](custom-rules) -* [Custom Processors](custom-processors) -* [Configs in Plugins](plugins#configs-in-plugins) +- [Custom Rules](custom-rules) +- [Custom Processors](custom-processors) +- [Configs in Plugins](plugins#configs-in-plugins) To learn more about bundling these extensions into a plugin, refer to [Plugins](plugins). @@ -35,15 +35,15 @@ ESLint shareable configs are pre-defined configurations for ESLint that you can You can either publish a shareable config independently or as part of a plugin. -For example, a popular shareable config is [eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb), which contains a variety of rules in addition to some [parser options](../use/configure/language-options#specifying-parser-options). This is a set of rules for ESLint that is designed to match the style guide used by the [Airbnb JavaScript style guide](https://github.com/airbnb/javascript). By using the `eslint-config-airbnb` shareable config, you can automatically enforce the Airbnb style guide in your project without having to manually configure each rule. +For example, a popular shareable config is [`eslint-config-airbnb`](https://www.npmjs.com/package/eslint-config-airbnb), which contains a variety of rules in addition to some [parser options](../use/configure/language-options#specifying-parser-options). This is a set of rules for ESLint that is designed to match the style guide used by the [Airbnb JavaScript style guide](https://github.com/airbnb/javascript). By using the `eslint-config-airbnb` shareable config, you can automatically enforce the Airbnb style guide in your project without having to manually configure each rule. -To learn more about creating a shareable config, refer to [Share Configuration](shareable-configs). +To learn more about creating a shareable config, refer to [Share Configurations](shareable-configs). ## Custom Formatters Custom formatters take ESLint linting results and output the results in a format that you define. Custom formatters let you display linting results in a format that best fits your needs, whether that's in a specific file format, a certain display style, or a format optimized for a particular tool. You only need to create a custom formatter if the [built-in formatters](../use/formatters/) don't serve your use case. -For example, the custom formatter [eslint-formatter-gitlab](https://www.npmjs.com/package/eslint-formatter-gitlab) can be used to display ESLint results in GitLab code quality reports. +For example, the custom formatter [eslint-formatter-gitlab](https://www.npmjs.com/package/eslint-formatter-gitlab) can be used to display ESLint results in [GitLab](https://about.gitlab.com/) code quality reports. To learn more about creating a custom formatter, refer to [Custom Formatters](custom-formatters). @@ -55,6 +55,6 @@ ESLint ships with a built-in JavaScript parser (Espree), but custom parsers allo For example, the custom parser [@typescript-eslint/parser](https://typescript-eslint.io/packages/parser) extends ESLint to lint TypeScript code. -Custom parsers **cannot** be included in a plugin, unlike the other extension types. +Custom parsers can be also included in a plugin. To learn more about creating a custom parser, refer to [Custom Parsers](custom-parsers). diff --git a/docs/src/integrate/index.md b/docs/src/integrate/index.md index 19164a585308..3b241ddaff98 100644 --- a/docs/src/integrate/index.md +++ b/docs/src/integrate/index.md @@ -4,15 +4,14 @@ eleventyNavigation: key: integrate eslint title: integrate ESLint order: 3 - --- This guide is intended for those who wish to integrate the functionality of ESLint into other applications by using the ESLint API. In order to integrate ESLint, it's recommended that: -* You know JavaScript since ESLint is written in JavaScript. -* You have some familiarity with Node.js since ESLint runs on it. +- You know JavaScript since ESLint is written in JavaScript. +- You have some familiarity with Node.js since ESLint runs on it. If that sounds like you, then continue reading to get started. diff --git a/docs/src/integrate/integration-tutorial.md b/docs/src/integrate/integration-tutorial.md index 08d77c7253b8..691075574926 100644 --- a/docs/src/integrate/integration-tutorial.md +++ b/docs/src/integrate/integration-tutorial.md @@ -7,25 +7,44 @@ eleventyNavigation: order: 1 --- -This guide walks you through integrating the `ESLint` class to lint files and retrieve results, which can be useful for creating integrations with other projects. +{%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} + +This guide walks you through integrating the `ESLint` class to lint files and +retrieve results, which can be useful for creating integrations with other +projects. ## Why Create an Integration? -You might want to create an ESLint integration if you're creating developer tooling, such as the following: +You might want to create an ESLint integration if you're creating developer +tooling, such as the following: -* **Code editors and IDEs**: Integrating ESLint with code editors and IDEs can provide real-time feedback on code quality and automatically highlight potential issues as you type. Many editors already have ESLint plugins available, but you may need to create a custom integration if the existing plugins do not meet your specific requirements. +- **Code editors and IDEs**: Integrating ESLint with code editors and IDEs can + provide real-time feedback on code quality and automatically highlight + potential issues as you type. Many editors already have ESLint plugins + available, but you may need to create a custom integration if the existing + plugins do not meet your specific requirements. -* **Custom linter tools**: If you're building a custom linter tool that combines multiple linters or adds specific functionality, you may want to integrate ESLint into your tool to provide JavaScript linting capabilities. +- **Custom linter tools**: If you're building a custom linter tool that combines + multiple linters or adds specific functionality, you may want to integrate + ESLint into your tool to provide JavaScript linting capabilities. -* **Code review tools**: Integrating ESLint with code review tools can help automate the process of identifying potential issues in the codebase. +- **Code review tools**: Integrating ESLint with code review tools can help + automate the process of identifying potential issues in the codebase. -* **Learning platforms**: If you are developing a learning platform or coding tutorial, integrating ESLint can provide real-time feedback to users as they learn JavaScript, helping them improve their coding skills and learn best practices. +- **Learning platforms**: If you are developing a learning platform or coding + tutorial, integrating ESLint can provide real-time feedback to users as they + learn JavaScript, helping them improve their coding skills and learn best + practices. -* **Developer tool integration**: If you're creating or extending a developer tool, such as a bundler or testing framework, you may want to integrate ESLint to provide linting capabilities. You can integrate ESLint directly into the tool or as a plugin. +- **Developer tool integration**: If you're creating or extending a developer + tool, such as a bundler or testing framework, you may want to integrate ESLint + to provide linting capabilities. You can integrate ESLint directly into the + tool or as a plugin. ## What You'll Build -In this guide, you'll create a simple Node.js project that uses the `ESLint` class to lint files and retrieve results. +In this guide, you'll create a simple Node.js project that uses the `ESLint` +class to lint files and retrieve results. ## Requirements @@ -33,9 +52,9 @@ This tutorial assumes you are familiar with JavaScript and Node.js. To follow this tutorial, you'll need to have the following: -* Node.js (v12.22.0 or higher) -* npm -* A text editor +- Node.js (`^18.18.0`, `^20.9.0`, or `>=21.1.0`) +- npm +- A text editor ## Step 1: Setup @@ -48,15 +67,19 @@ cd eslint-integration Initialize the project with a `package.json` file: -```shell -npm init -y -``` +{{ npm_tabs({ + command: "init", + packages: [], + args: ["-y"] +}) }} Install the `eslint` package as a dependency (**not** as a dev dependency): -```shell -npm install eslint -``` +{{ npm_tabs({ + command: "install", + packages: ["eslint"], + args: [] +}) }} Create a new file called `example-eslint-integration.js` in the project root: @@ -68,7 +91,8 @@ touch example-eslint-integration.js Import the `ESLint` class from the `eslint` package and create a new instance. -You can customize the ESLint configuration by passing an options object to the `ESLint` constructor: +You can customize the ESLint configuration by passing an options object to the +`ESLint` constructor: ```javascript // example-eslint-integration.js @@ -76,16 +100,25 @@ You can customize the ESLint configuration by passing an options object to the ` const { ESLint } = require("eslint"); // Create an instance of ESLint with the configuration passed to the function -function createESLintInstance(overrideConfig){ - return new ESLint({ useEslintrc: false, overrideConfig: overrideConfig, fix: true }); +function createESLintInstance(overrideConfig) { + return new ESLint({ + overrideConfigFile: true, + overrideConfig, + fix: true, + }); } ``` ## Step 3: Lint and Fix Files -To lint a file, use the `lintFiles` method of the `ESLint` instance. The `filePaths` argument passed to `ESLint#lintFiles()` can be a string or an array of strings, representing the file path(s) you want to lint. The file paths can be globs or filenames. +To lint a file, use the `lintFiles` method of the `ESLint` instance. The +`filePaths` argument passed to `ESLint#lintFiles()` can be a string or an array +of strings, representing the file path(s) you want to lint. The file paths can +be globs or filenames. -The static method `ESLint.outputFixes()` takes the linting results from the call to `ESLint#lintFiles()`, and then writes the fixed code back to the source files. +The static method `ESLint.outputFixes()` takes the linting results from the call +to `ESLint#lintFiles()`, and then writes the fixed code back to the source +files. ```javascript // example-eslint-integration.js @@ -94,18 +127,20 @@ The static method `ESLint.outputFixes()` takes the linting results from the call // Lint the specified files and return the results async function lintAndFix(eslint, filePaths) { - const results = await eslint.lintFiles(filePaths); + const results = await eslint.lintFiles(filePaths); - // Apply automatic fixes and output fixed code - await ESLint.outputFixes(results); + // Apply automatic fixes and output fixed code + await ESLint.outputFixes(results); - return results; + return results; } ``` ## Step 4: Output Results -Define a function to output the linting results to the console. This should be specific to your integration's needs. For example, you could report the linting results to a user interface. +Define a function to output the linting results to the console. This should be +specific to your integration's needs. For example, you could report the linting +results to a user interface. In this example, we'll simply log the results to the console: @@ -117,52 +152,52 @@ In this example, we'll simply log the results to the console: // Log results to console if there are any problems function outputLintingResults(results) { - // Identify the number of problems found - const problems = results.reduce((acc, result) => acc + result.errorCount + result.warningCount, 0); - - if (problems > 0) { - console.log("Linting errors found!"); - console.log(results); - } else { - console.log("No linting errors found."); - } - return results; + // Identify the number of problems found + const problems = results.reduce( + (acc, result) => acc + result.errorCount + result.warningCount, + 0, + ); + + if (problems > 0) { + console.log("Linting errors found!"); + console.log(results); + } else { + console.log("No linting errors found."); + } + return results; } ``` ## Step 5: Put It All Together -Put the above functions together in a new function called `lintFiles`. This function will be the main entry point for your integration: +Put the above functions together in a new function called `lintFiles`. This +function will be the main entry point for your integration: ```javascript // example-eslint-integration.js // Put previous functions all together async function lintFiles(filePaths) { - - // The ESLint configuration. Alternatively, you could load the configuration - // from a .eslintrc file or just use the default config. - const overrideConfig = { - env: { - es6: true, - node: true, - }, - parserOptions: { - ecmaVersion: 2018, - }, - rules: { - "no-console": "error", - "no-unused-vars": "warn", - }, - }; - - const eslint = createESLintInstance(overrideConfig); - const results = await lintAndFix(eslint, filePaths); - return outputLintingResults(results); + // The ESLint configuration. Alternatively, you could load the configuration + // from a .eslintrc file or just use the default config. + const overrideConfig = { + languageOptions: { + ecmaVersion: 2018, + sourceType: "commonjs", + }, + rules: { + "no-console": "error", + "no-unused-vars": "warn", + }, + }; + + const eslint = createESLintInstance(overrideConfig); + const results = await lintAndFix(eslint, filePaths); + return outputLintingResults(results); } // Export integration -module.exports = { lintFiles } +module.exports = { lintFiles }; ``` Here's the complete code example for `example-eslint-integration.js`: @@ -171,66 +206,73 @@ Here's the complete code example for `example-eslint-integration.js`: const { ESLint } = require("eslint"); // Create an instance of ESLint with the configuration passed to the function -function createESLintInstance(overrideConfig){ - return new ESLint({ useEslintrc: false, overrideConfig: overrideConfig, fix: true }); +function createESLintInstance(overrideConfig) { + return new ESLint({ + overrideConfigFile: true, + overrideConfig, + fix: true, + }); } // Lint the specified files and return the results async function lintAndFix(eslint, filePaths) { - const results = await eslint.lintFiles(filePaths); + const results = await eslint.lintFiles(filePaths); - // Apply automatic fixes and output fixed code - await ESLint.outputFixes(results); + // Apply automatic fixes and output fixed code + await ESLint.outputFixes(results); - return results; + return results; } // Log results to console if there are any problems function outputLintingResults(results) { - // Identify the number of problems found - const problems = results.reduce((acc, result) => acc + result.errorCount + result.warningCount, 0); - - if (problems > 0) { - console.log("Linting errors found!"); - console.log(results); - } else { - console.log("No linting errors found."); - } - return results; + // Identify the number of problems found + const problems = results.reduce( + (acc, result) => acc + result.errorCount + result.warningCount, + 0, + ); + + if (problems > 0) { + console.log("Linting errors found!"); + console.log(results); + } else { + console.log("No linting errors found."); + } + return results; } // Put previous functions all together async function lintFiles(filePaths) { - - // The ESLint configuration. Alternatively, you could load the configuration - // from a .eslintrc file or just use the default config. - const overrideConfig = { - env: { - es6: true, - node: true, - }, - parserOptions: { - ecmaVersion: 2018, - }, - rules: { - "no-console": "error", - "no-unused-vars": "warn", - }, - }; - - const eslint = createESLintInstance(overrideConfig); - const results = await lintAndFix(eslint, filePaths); - return outputLintingResults(results); + // The ESLint configuration. Alternatively, you could load the configuration + // from an eslint.config.js file or just use the default config. + const overrideConfig = { + languageOptions: { + ecmaVersion: 2018, + sourceType: "commonjs", + }, + rules: { + "no-console": "error", + "no-unused-vars": "warn", + }, + }; + + const eslint = createESLintInstance(overrideConfig); + const results = await lintAndFix(eslint, filePaths); + return outputLintingResults(results); } // Export integration -module.exports = { lintFiles } +module.exports = { lintFiles }; ``` ## Conclusion -In this tutorial, we have covered the essentials of using the `ESLint` class to lint files and retrieve results in your projects. This knowledge can be applied to create custom integrations, such as code editor plugins, to provide real-time feedback on code quality. +In this tutorial, we have covered the essentials of using the `ESLint` class to +lint files and retrieve results in your projects. This knowledge can be applied +to create custom integrations, such as code editor plugins, to provide real-time +feedback on code quality. ## View the Tutorial Code -You can view the annotated source code for the tutorial [here](https://github.com/eslint/eslint/tree/main/docs/_examples/integration-tutorial-code). +You can view the annotated source code for the tutorial +[here](https://github.com/eslint/eslint/tree/main/docs/_examples/integration-tutorial-code). diff --git a/docs/src/integrate/nodejs-api.md b/docs/src/integrate/nodejs-api.md index 744f98295f49..2494ae8d9115 100644 --- a/docs/src/integrate/nodejs-api.md +++ b/docs/src/integrate/nodejs-api.md @@ -15,7 +15,7 @@ While ESLint is designed to be run on the command line, it's possible to use ESL The `ESLint` class is the primary class to use in Node.js applications. -This class depends on the Node.js `fs` module and the file system, so you cannot use it in browsers. If you want to lint code on browsers, use the [Linter](#linter) class instead. +This class depends on the Node.js [`fs`](https://nodejs.org/api/fs.html) module and the file system, so you cannot use it in browsers. If you want to lint code on browsers, use the [`Linter`](#linter) class instead. Here's a simple example of using the `ESLint` class: @@ -23,21 +23,21 @@ Here's a simple example of using the `ESLint` class: const { ESLint } = require("eslint"); (async function main() { - // 1. Create an instance. - const eslint = new ESLint(); + // 1. Create an instance. + const eslint = new ESLint(); - // 2. Lint files. - const results = await eslint.lintFiles(["lib/**/*.js"]); + // 2. Lint files. + const results = await eslint.lintFiles(["lib/**/*.js"]); - // 3. Format the results. - const formatter = await eslint.loadFormatter("stylish"); - const resultText = formatter.format(results); + // 3. Format the results. + const formatter = await eslint.loadFormatter("stylish"); + const resultText = formatter.format(results); - // 4. Output it. - console.log(resultText); -})().catch((error) => { - process.exitCode = 1; - console.error(error); + // 4. Output it. + console.log(resultText); +})().catch(error => { + process.exitCode = 1; + console.error(error); }); ``` @@ -47,28 +47,28 @@ Here's an example that autofixes lint problems: const { ESLint } = require("eslint"); (async function main() { - // 1. Create an instance with the `fix` option. - const eslint = new ESLint({ fix: true }); + // 1. Create an instance with the `fix` option. + const eslint = new ESLint({ fix: true }); - // 2. Lint files. This doesn't modify target files. - const results = await eslint.lintFiles(["lib/**/*.js"]); + // 2. Lint files. This doesn't modify target files. + const results = await eslint.lintFiles(["lib/**/*.js"]); - // 3. Modify the files with the fixed code. - await ESLint.outputFixes(results); + // 3. Modify the files with the fixed code. + await ESLint.outputFixes(results); - // 4. Format the results. - const formatter = await eslint.loadFormatter("stylish"); - const resultText = formatter.format(results); + // 4. Format the results. + const formatter = await eslint.loadFormatter("stylish"); + const resultText = formatter.format(results); - // 5. Output it. - console.log(resultText); -})().catch((error) => { - process.exitCode = 1; - console.error(error); + // 5. Output it. + console.log(resultText); +})().catch(error => { + process.exitCode = 1; + console.error(error); }); ``` -And here is an example of using the `ESLint` class with `lintText` API: +And here is an example of using the `ESLint` class with [`lintText`](#-eslintlinttextcode-options) API: ```js const { ESLint } = require("eslint"); @@ -81,34 +81,29 @@ const testCode = ` `; (async function main() { - // 1. Create an instance - const eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - extends: ["eslint:recommended"], - parserOptions: { - sourceType: "module", - ecmaVersion: "latest", - }, - env: { - es2022: true, - node: true, - }, - }, - }); - - // 2. Lint text. - const results = await eslint.lintText(testCode); - - // 3. Format the results. - const formatter = await eslint.loadFormatter("stylish"); - const resultText = formatter.format(results); - - // 4. Output it. - console.log(resultText); -})().catch((error) => { - process.exitCode = 1; - console.error(error); + // 1. Create an instance + const eslint = new ESLint({ + overrideConfigFile: true, + overrideConfig: { + languageOptions: { + ecmaVersion: 2018, + sourceType: "commonjs", + }, + }, + }); + + // 2. Lint text. + const results = await eslint.lintText(testCode); + + // 3. Format the results. + const formatter = await eslint.loadFormatter("stylish"); + const resultText = formatter.format(results); + + // 4. Output it. + console.log(resultText); +})().catch(error => { + process.exitCode = 1; + console.error(error); }); ``` @@ -126,56 +121,59 @@ The `ESLint` constructor takes an `options` object. If you omit the `options` ob ##### File Enumeration -* `options.cwd` (`string`)
+- `options.cwd` (`string`)
Default is `process.cwd()`. The working directory. This must be an absolute path. -* `options.errorOnUnmatchedPattern` (`boolean`)
+- `options.errorOnUnmatchedPattern` (`boolean`)
Default is `true`. Unless set to `false`, the [`eslint.lintFiles()`][eslint-lintfiles] method will throw an error when no target files are found. -* `options.extensions` (`string[] | null`)
- Default is `null`. If you pass directory paths to the [`eslint.lintFiles()`][eslint-lintfiles] method, ESLint checks the files in those directories that have the given extensions. For example, when passing the `src/` directory and `extensions` is `[".js", ".ts"]`, ESLint will lint `*.js` and `*.ts` files in `src/`. If `extensions` is `null`, ESLint checks `*.js` files and files that match `overrides[].files` patterns in your configuration.
**Note:** This option only applies when you pass directory paths to the [`eslint.lintFiles()`][eslint-lintfiles] method. If you pass glob patterns like `lib/**/*`, ESLint will lint all files matching the glob pattern regardless of extension. -* `options.globInputPaths` (`boolean`)
+- `options.globInputPaths` (`boolean`)
Default is `true`. If `false` is present, the [`eslint.lintFiles()`][eslint-lintfiles] method doesn't interpret glob patterns. -* `options.ignore` (`boolean`)
- Default is `true`. If `false` is present, the [`eslint.lintFiles()`][eslint-lintfiles] method doesn't respect `.eslintignore` files or `ignorePatterns` in your configuration. -* `options.ignorePath` (`string | null`)
- Default is `null`. The path to a file ESLint uses instead of `$CWD/.eslintignore`. If a path is present and the file doesn't exist, this constructor will throw an error. +- `options.ignore` (`boolean`)
+ Default is `true`. If `false` is present, the [`eslint.lintFiles()`][eslint-lintfiles] method doesn't respect `ignorePatterns` in your configuration. +- `options.ignorePatterns` (`string[] | null`)
+ Default is `null`. Ignore file patterns to use in addition to config ignores. These patterns are relative to `cwd`. +- `options.passOnNoPatterns` (`boolean`)
+ Default is `false`. When set to `true`, missing patterns cause the linting operation to short circuit and not report any failures. +- `options.warnIgnored` (`boolean`)
+ Default is `true`. Show warnings when the file list includes ignored files. ##### Linting -* `options.allowInlineConfig` (`boolean`)
+- `options.allowInlineConfig` (`boolean`)
Default is `true`. If `false` is present, ESLint suppresses directive comments in source code. If this option is `false`, it overrides the `noInlineConfig` setting in your configurations. -* `options.baseConfig` (`ConfigData | null`)
+- `options.baseConfig` (`Config | Config[] | null`)
Default is `null`. [Configuration object], extended by all configurations used with this instance. You can use this option to define the default settings that will be used if your configuration files don't configure it. -* `options.overrideConfig` (`ConfigData | null`)
- Default is `null`. [Configuration object], overrides all configurations used with this instance. You can use this option to define the settings that will be used even if your configuration files configure it. -* `options.overrideConfigFile` (`string | null`)
- Default is `null`. The path to a configuration file, overrides all configurations used with this instance. The `options.overrideConfig` option is applied after this option is applied. -* `options.plugins` (`Record | null`)
+- `options.overrideConfig` (`Config | Config[] | null`)
+ Default is `null`. [Configuration object], added after any existing configuration and therefore applies after what's contained in your configuration file (if used). +- `options.overrideConfigFile` (`null | true | string`)
+ Default is `null`. By default, ESLint searches for a configuration file. When this option is set to `true`, ESLint does not search for a configuration file. When this option is set to a `string` value, ESLint does not search for a configuration file, and uses the provided value as the path to the configuration file. +- `options.plugins` (`Record | null`)
Default is `null`. The plugin implementations that ESLint uses for the `plugins` setting of your configuration. This is a map-like object. Those keys are plugin IDs and each value is implementation. -* `options.reportUnusedDisableDirectives` (`"error" | "warn" | "off" | null`)
- Default is `null`. The severity to report unused eslint-disable and eslint-enable directives. If this option is a severity, it overrides the `reportUnusedDisableDirectives` setting in your configurations. -* `options.resolvePluginsRelativeTo` (`string` | `null`)
- Default is `null`. The path to a directory where plugins should be resolved from. If `null` is present, ESLint loads plugins from the location of the configuration file that contains the plugin setting. If a path is present, ESLint loads all plugins from there. -* `options.rulePaths` (`string[]`)
- Default is `[]`. An array of paths to directories to load custom rules from. -* `options.useEslintrc` (`boolean`)
- Default is `true`. If `false` is present, ESLint doesn't load configuration files (`.eslintrc.*` files). Only the configuration of the constructor options is valid. +- `options.ruleFilter` (`({ruleId: string, severity: number}) => boolean`)
+ Default is `() => true`. A predicate function that filters rules to be run. This function is called with an object containing `ruleId` and `severity`, and returns `true` if the rule should be run. +- `options.stats` (`boolean`)
+ Default is `false`. When set to `true`, additional statistics are added to the lint results (see [Stats type](../extend/stats#-stats-type)). ##### Autofix -* `options.fix` (`boolean | (message: LintMessage) => boolean`)
+- `options.fix` (`boolean | (message: LintMessage) => boolean`)
Default is `false`. If `true` is present, the [`eslint.lintFiles()`][eslint-lintfiles] and [`eslint.lintText()`][eslint-linttext] methods work in autofix mode. If a predicate function is present, the methods pass each lint message to the function, then use only the lint messages for which the function returned `true`. -* `options.fixTypes` (`("directive" | "problem" | "suggestion" | "layout")[] | null`)
+- `options.fixTypes` (`("directive" | "problem" | "suggestion" | "layout")[] | null`)
Default is `null`. The types of the rules that the [`eslint.lintFiles()`][eslint-lintfiles] and [`eslint.lintText()`][eslint-linttext] methods use for autofix. ##### Cache-related -* `options.cache` (`boolean`)
+- `options.cache` (`boolean`)
Default is `false`. If `true` is present, the [`eslint.lintFiles()`][eslint-lintfiles] method caches lint results and uses it if each target file is not changed. Please mind that ESLint doesn't clear the cache when you upgrade ESLint plugins. In that case, you have to remove the cache file manually. The [`eslint.lintText()`][eslint-linttext] method doesn't use caches even if you pass the `options.filePath` to the method. -* `options.cacheLocation` (`string`)
+- `options.cacheLocation` (`string`)
Default is `.eslintcache`. The [`eslint.lintFiles()`][eslint-lintfiles] method writes caches into this file. -* `options.cacheStrategy` (`string`)
+- `options.cacheStrategy` (`string`)
Default is `"metadata"`. Strategy for the cache to use for detecting changed files. Can be either `"metadata"` or `"content"`. +##### Other Options + +- `options.flags` (`string[]`)
+ Default is `[]`. The feature flags to enable for this instance. + ### ◆ eslint.lintFiles(patterns) ```js @@ -186,12 +184,12 @@ This method lints the files that match the glob patterns and then returns the re #### Parameters -* `patterns` (`string | string[]`)
+- `patterns` (`string | string[]`)
The lint target files. This can contain any of file paths, directory paths, and glob patterns. #### Return Value -* (`Promise`)
+- (`Promise`)
The promise that will be fulfilled with an array of [LintResult] objects. ### ◆ eslint.lintText(code, options) @@ -210,16 +208,16 @@ If the `options.filePath` value is configured to be ignored, this method returns The second parameter `options` is omittable. -* `code` (`string`)
+- `code` (`string`)
The source code text to check. -* `options.filePath` (`string`)
+- `options.filePath` (`string`)
Optional. The path to the file of the source code text. If omitted, the `result.filePath` becomes the string `""`. -* `options.warnIgnored` (`boolean`)
- Optional. If `true` is present and the `options.filePath` is a file ESLint should ignore, this method returns a lint result contains a warning message. +- `options.warnIgnored` (`boolean`)
+ Optional, defaults to `options.warnIgnored` passed to the constructor. If `true` is present and the `options.filePath` is a file ESLint should ignore, this method returns a lint result contains a warning message. #### Return Value -* (`Promise`)
+- (`Promise`)
The promise that will be fulfilled with an array of [LintResult] objects. This is an array (despite there being only one lint result) in order to keep the interfaces between this and the [`eslint.lintFiles()`][eslint-lintfiles] method similar. ### ◆ eslint.getRulesMetaForResults(results) @@ -233,12 +231,12 @@ This method returns an object containing meta information for each rule that tri #### Parameters -* `results` (`LintResult[]`)
+- `results` (`LintResult[]`)
An array of [LintResult] objects returned from a call to `ESLint#lintFiles()` or `ESLint#lintText()`. #### Return Value -* (`Object`)
+- (`Object`)
An object whose property names are the rule IDs from the `results` and whose property values are the rule's meta information (if available). ### ◆ eslint.calculateConfigForFile(filePath) @@ -249,20 +247,14 @@ const config = await eslint.calculateConfigForFile(filePath); This method calculates the configuration for a given file, which can be useful for debugging purposes. -* It resolves and merges `extends` and `overrides` settings into the top level configuration. -* It resolves the `parser` setting to absolute paths. -* It normalizes the `plugins` setting to align short names. (e.g., `eslint-plugin-foo` → `foo`) -* It adds the `processor` setting if a legacy file extension processor is matched. -* It doesn't interpret the `env` setting to the `globals` and `parserOptions` settings, so the result object contains the `env` setting as is. - #### Parameters -* `filePath` (`string`)
+- `filePath` (`string`)
The path to the file whose configuration you would like to calculate. Directory paths are forbidden because ESLint cannot handle the `overrides` setting. #### Return Value -* (`Promise`)
+- (`Promise`)
The promise that will be fulfilled with a configuration object. ### ◆ eslint.isPathIgnored(filePath) @@ -275,12 +267,12 @@ This method checks if a given file is ignored by your configuration. #### Parameters -* `filePath` (`string`)
+- `filePath` (`string`)
The path to the file you want to check. #### Return Value -* (`Promise`)
+- (`Promise`)
The promise that will be fulfilled with whether the file is ignored or not. If the file is ignored, then it will return `true`. ### ◆ eslint.loadFormatter(nameOrPath) @@ -293,21 +285,41 @@ This method loads a formatter. Formatters convert lint results to a human- or ma #### Parameters -* `nameOrPath` (`string | undefined`)
+- `nameOrPath` (`string | undefined`)
The path to the file you want to check. The following values are allowed: - * `undefined`. In this case, loads the `"stylish"` built-in formatter. - * A name of [built-in formatters][builtin-formatters]. - * A name of [third-party formatters][third-party-formatters]. For examples: - * `"foo"` will load `eslint-formatter-foo`. - * `"@foo"` will load `@foo/eslint-formatter`. - * `"@foo/bar"` will load `@foo/eslint-formatter-bar`. - * A path to the file that defines a formatter. The path must contain one or more path separators (`/`) in order to distinguish if it's a path or not. For example, start with `./`. + - `undefined`. In this case, loads the `"stylish"` built-in formatter. + - A name of [built-in formatters][builtin-formatters]. + - A name of [third-party formatters][third-party-formatters]. For examples: + - `"foo"` will load `eslint-formatter-foo`. + - `"@foo"` will load `@foo/eslint-formatter`. + - `"@foo/bar"` will load `@foo/eslint-formatter-bar`. + - A path to the file that defines a formatter. The path must contain one or more path separators (`/`) in order to distinguish if it's a path or not. For example, start with `./`. #### Return Value -* (`Promise`)
+- (`Promise`)
The promise that will be fulfilled with a [LoadedFormatter] object. +### ◆ eslint.hasFlag(flagName) + +This method is used to determine if a given feature flag is set, as in this example: + +```js +if (eslint.hasFlag("x_feature")) { + // handle flag +} +``` + +#### Parameters + +- `flagName` (`string`)
+ The flag to check. + +#### Return Value + +- (`boolean`)
+ True if the flag is enabled. + ### ◆ ESLint.version ```js @@ -318,6 +330,16 @@ The version string of ESLint. E.g. `"7.0.0"`. This is a static property. +### ◆ ESLint.defaultConfig + +```js +const defaultConfig = ESLint.defaultConfig; +``` + +The default configuration that ESLint uses internally. This is provided for tooling that wants to calculate configurations using the same defaults as ESLint. Keep in mind that the default configuration may change from version to version, so you shouldn't rely on any particular keys or values to be present. + +This is a static property. + ### ◆ ESLint.outputFixes(results) ```js @@ -330,12 +352,12 @@ This is a static method. #### Parameters -* `results` (`LintResult[]`)
+- `results` (`LintResult[]`)
The [LintResult] objects to write. #### Return Value -* (`Promise`)
+- (`Promise`)
The promise that will be fulfilled after all files are written. ### ◆ ESLint.getErrorResults(results) @@ -350,100 +372,107 @@ This is a static method. #### Parameters -* `results` (`LintResult[]`)
+- `results` (`LintResult[]`)
The [LintResult] objects to filter. #### Return Value -* (`LintResult[]`)
+- (`LintResult[]`)
The filtered [LintResult] objects. ### ◆ LintResult type The `LintResult` value is the information of the linting result of each file. The [`eslint.lintFiles()`][eslint-lintfiles] and [`eslint.lintText()`][eslint-linttext] methods return it. It has the following properties: -* `filePath` (`string`)
+- `filePath` (`string`)
The absolute path to the file of this result. This is the string `""` if the file path is unknown (when you didn't pass the `options.filePath` option to the [`eslint.lintText()`][eslint-linttext] method). -* `messages` (`LintMessage[]`)
+- `messages` (`LintMessage[]`)
The array of [LintMessage] objects. -* `suppressedMessages` (`SuppressedLintMessage[]`)
+- `suppressedMessages` (`SuppressedLintMessage[]`)
The array of [SuppressedLintMessage] objects. -* `fixableErrorCount` (`number`)
+- `fixableErrorCount` (`number`)
The number of errors that can be fixed automatically by the `fix` constructor option. -* `fixableWarningCount` (`number`)
+- `fixableWarningCount` (`number`)
The number of warnings that can be fixed automatically by the `fix` constructor option. -* `errorCount` (`number`)
+- `errorCount` (`number`)
The number of errors. This includes fixable errors and fatal errors. -* `fatalErrorCount` (`number`)
+- `fatalErrorCount` (`number`)
The number of fatal errors. -* `warningCount` (`number`)
+- `warningCount` (`number`)
The number of warnings. This includes fixable warnings. -* `output` (`string | undefined`)
+- `output` (`string | undefined`)
The modified source code text. This property is undefined if any fixable messages didn't exist. -* `source` (`string | undefined`)
+- `source` (`string | undefined`)
The original source code text. This property is undefined if any messages didn't exist or the `output` property exists. -* `usedDeprecatedRules` (`{ ruleId: string; replacedBy: string[] }[]`)
+- `stats` (`Stats | undefined`)
+ The [Stats](../extend/stats#-stats-type) object. This contains the lint performance statistics collected with the `stats` option. +- `usedDeprecatedRules` (`{ ruleId: string; replacedBy: string[]; info: DeprecatedInfo | undefined }[]`)
The information about the deprecated rules that were used to check this file. + The `info` property is set to `rule.meta.deprecated` if the rule uses the [new `deprecated` property](../extend/rule-deprecation). ### ◆ LintMessage type The `LintMessage` value is the information of each linting error. The `messages` property of the [LintResult] type contains it. It has the following properties: -* `ruleId` (`string` | `null`)
+- `ruleId` (`string` | `null`)
The rule name that generates this lint message. If this message is generated by the ESLint core rather than rules, this is `null`. -* `severity` (`1 | 2`)
+- `severity` (`1 | 2`)
The severity of this message. `1` means warning and `2` means error. -* `fatal` (`boolean | undefined`)
+- `fatal` (`boolean | undefined`)
`true` if this is a fatal error unrelated to a rule, like a parsing error. -* `message` (`string`)
+- `message` (`string`)
The error message. -* `line` (`number | undefined`)
+- `messageId` (`string | undefined`)
+ The message ID of the lint error. This property is undefined if the rule does not use message IDs. +- `line` (`number | undefined`)
The 1-based line number of the begin point of this message. -* `column` (`number | undefined`)
+- `column` (`number | undefined`)
The 1-based column number of the begin point of this message. -* `endLine` (`number | undefined`)
+- `endLine` (`number | undefined`)
The 1-based line number of the end point of this message. This property is undefined if this message is not a range. -* `endColumn` (`number | undefined`)
+- `endColumn` (`number | undefined`)
The 1-based column number of the end point of this message. This property is undefined if this message is not a range. -* `fix` (`EditInfo | undefined`)
+- `fix` (`EditInfo | undefined`)
The [EditInfo] object of autofix. This property is undefined if this message is not fixable. -* `suggestions` (`{ desc: string; fix: EditInfo }[] | undefined`)
+- `suggestions` (`{ desc: string; fix: EditInfo; messageId?: string; data?: object }[] | undefined`)
The list of suggestions. Each suggestion is the pair of a description and an [EditInfo] object to fix code. API users such as editor integrations can choose one of them to fix the problem of this message. This property is undefined if this message doesn't have any suggestions. ### ◆ SuppressedLintMessage type The `SuppressedLintMessage` value is the information of each suppressed linting error. The `suppressedMessages` property of the [LintResult] type contains it. It has the following properties: -* `ruleId` (`string` | `null`)
+- `ruleId` (`string` | `null`)
Same as `ruleId` in [LintMessage] type. -* `severity` (`1 | 2`)
+- `severity` (`1 | 2`)
Same as `severity` in [LintMessage] type. -* `fatal` (`boolean | undefined`)
+- `fatal` (`boolean | undefined`)
Same as `fatal` in [LintMessage] type. -* `message` (`string`)
+- `message` (`string`)
Same as `message` in [LintMessage] type. -* `line` (`number | undefined`)
+- `messageId` (`string | undefined`)
+ Same as `messageId` in [LintMessage] type. +- `line` (`number | undefined`)
Same as `line` in [LintMessage] type. -* `column` (`number | undefined`)
+- `column` (`number | undefined`)
Same as `column` in [LintMessage] type. -* `endLine` (`number | undefined`)
+- `endLine` (`number | undefined`)
Same as `endLine` in [LintMessage] type. -* `endColumn` (`number | undefined`)
+- `endColumn` (`number | undefined`)
Same as `endColumn` in [LintMessage] type. -* `fix` (`EditInfo | undefined`)
+- `fix` (`EditInfo | undefined`)
Same as `fix` in [LintMessage] type. -* `suggestions` (`{ desc: string; fix: EditInfo }[] | undefined`)
+- `suggestions` (`{ desc: string; fix: EditInfo; messageId?: string; data?: object }[] | undefined`)
Same as `suggestions` in [LintMessage] type. -* `suppressions` (`{ kind: string; justification: string}[]`)
+- `suppressions` (`{ kind: string; justification: string}[]`)
The list of suppressions. Each suppression is the pair of a kind and a justification. ### ◆ EditInfo type The `EditInfo` value is information to edit text. The `fix` and `suggestions` properties of [LintMessage] type contain it. It has following properties: -* `range` (`[number, number]`)
+- `range` (`[number, number]`)
The pair of 0-based indices in source code text to remove. -* `text` (`string`)
+- `text` (`string`)
The text to add. This edit information means replacing the range of the `range` property by the `text` property value. It's like `sourceCodeText.slice(0, edit.range[0]) + edit.text + sourceCodeText.slice(edit.range[1])`. Therefore, it's an add if the `range[0]` and `range[1]` property values are the same value, and it's removal if the `text` property value is empty string. @@ -452,8 +481,48 @@ This edit information means replacing the range of the `range` property by the ` The `LoadedFormatter` value is the object to convert the [LintResult] objects to text. The [eslint.loadFormatter()][eslint-loadformatter] method returns it. It has the following method: -* `format` (`(results: LintResult[], resultsMeta: ResultsMeta) => string | Promise`)
- The method to convert the [LintResult] objects to text. `resultsMeta` is an object that will contain a `maxWarningsExceeded` object if `--max-warnings` was set and the number of warnings exceeded the limit. The `maxWarningsExceeded` object will contain two properties: `maxWarnings`, the value of the `--max-warnings` option, and `foundWarnings`, the number of lint warnings. +- `format` (`(results: LintResult[], resultsMeta?: ResultsMeta) => string | Promise`)
+ The method to convert the [LintResult] objects to text. `resultsMeta` is an optional parameter that is primarily intended for use by the ESLint CLI and can contain only a `maxWarningsExceeded` property that would be passed through the [`context`](../extend/custom-formatters#the-context-argument) object when this method calls the underlying formatter function. Note that ESLint automatically generates `cwd` and `rulesMeta` properties of the `context` object, so you typically don't need to pass in the second argument when calling this method. + +--- + +## loadESLint() + +The `loadESLint()` function is used for integrations that wish to support both the current configuration system (flat config) and the old configuration system (eslintrc). This function returns the correct `ESLint` class implementation based on the arguments provided: + +```js +const { loadESLint } = require("eslint"); + +// loads the default ESLint that the CLI would use based on process.cwd() +const DefaultESLint = await loadESLint(); + +// loads the flat config version specifically +const FlatESLint = await loadESLint({ useFlatConfig: true }); + +// loads the legacy version specifically +const LegacyESLint = await loadESLint({ useFlatConfig: false }); +``` + +You can then use the returned constructor to instantiate a new `ESLint` instance, like this: + +```js +// loads the default ESLint that the CLI would use based on process.cwd() +const DefaultESLint = await loadESLint(); +const eslint = new DefaultESLint(); +``` + +If you're ever unsure which config system the returned constructor uses, check the `configType` property, which is either `"flat"` or `"eslintrc"`: + +```js +// loads the default ESLint that the CLI would use based on process.cwd() +const DefaultESLint = await loadESLint(); + +if (DefaultESLint.configType === "flat") { + // do something specific to flat config +} +``` + +If you don't need to support both the old and new configuration systems, then it's recommended to just use the `ESLint` constructor directly. --- @@ -488,7 +557,7 @@ This is a static function on `SourceCode` that is used to split the source code ```js const SourceCode = require("eslint").SourceCode; -const code = "var a = 1;\nvar b = 2;" +const code = "var a = 1;\nvar b = 2;"; // split code into an array const codeLines = SourceCode.splitLines(code); @@ -506,17 +575,17 @@ const codeLines = SourceCode.splitLines(code); ## Linter -The `Linter` object does the actual evaluation of the JavaScript code. It doesn't do any filesystem operations, it simply parses and reports on the code. In particular, the `Linter` object does not process configuration objects or files. Unless you are working in the browser, you probably want to use the [ESLint class](#eslint-class) instead. +The `Linter` object does the actual evaluation of the JavaScript code. It doesn't do any filesystem operations, it simply parses and reports on the code. In particular, the `Linter` object does not process configuration files. Unless you are working in the browser, you probably want to use the [ESLint class](#eslint-class) instead. The `Linter` is a constructor, and you can create a new instance by passing in the options you want to use. The available options are: -* `cwd` - Path to a directory that should be considered as the current working directory. It is accessible to rules from `context.cwd` or by calling `context.getCwd()` (see [The Context Object](../extend/custom-rules#the-context-object)). If `cwd` is `undefined`, it will be normalized to `process.cwd()` if the global `process` object is defined (for example, in the Node.js runtime) , or `undefined` otherwise. +- `cwd` - Path to a directory that should be considered as the current working directory. It is accessible to rules from `context.cwd` or by calling `context.getCwd()` (see [The Context Object](../extend/custom-rules#the-context-object)). If `cwd` is `undefined`, it will be normalized to `process.cwd()` if the global `process` object is defined (for example, in the Node.js runtime) , or `undefined` otherwise. For example: ```js const Linter = require("eslint").Linter; -const linter1 = new Linter({ cwd: 'path/to/project' }); +const linter1 = new Linter({ cwd: "path/to/project" }); const linter2 = new Linter(); ``` @@ -527,17 +596,18 @@ Those run on `linter2` will get `process.cwd()` if the global `process` object i The most important method on `Linter` is `verify()`, which initiates linting of the given text. This method accepts three arguments: -* `code` - the source code to lint (a string or instance of `SourceCode`). -* `config` - a configuration object that has been processed and normalized by `ESLint` using eslintrc files and/or other configuration arguments. - * **Note**: If you want to lint text and have your configuration be read and processed, use [`ESLint#lintFiles()`][eslint-lintfiles] or [`ESLint#lintText()`][eslint-linttext] instead. -* `options` - (optional) Additional options for this run. - * `filename` - (optional) the filename to associate with the source code. - * `preprocess` - (optional) A function that [Processors in Plugins](../extend/plugins#processors-in-plugins) documentation describes as the `preprocess` method. - * `postprocess` - (optional) A function that [Processors in Plugins](../extend/plugins#processors-in-plugins) documentation describes as the `postprocess` method. - * `filterCodeBlock` - (optional) A function that decides which code blocks the linter should adopt. The function receives two arguments. The first argument is the virtual filename of a code block. The second argument is the text of the code block. If the function returned `true` then the linter adopts the code block. If the function was omitted, the linter adopts only `*.js` code blocks. If you provided a `filterCodeBlock` function, it overrides this default behavior, so the linter doesn't adopt `*.js` code blocks automatically. - * `disableFixes` - (optional) when set to `true`, the linter doesn't make either the `fix` or `suggestions` property of the lint result. - * `allowInlineConfig` - (optional) set to `false` to disable inline comments from changing ESLint rules. - * `reportUnusedDisableDirectives` - (optional) when set to `true`, adds reported errors for unused `eslint-disable` and `eslint-enable` directives when no problems would be reported in the disabled area anyway. +- `code` - the source code to lint (a string or instance of `SourceCode`). +- `config` - a [Configuration object] or an array of configuration objects. + - **Note**: If you want to lint text and have your configuration be read from the file system, use [`ESLint#lintFiles()`][eslint-lintfiles] or [`ESLint#lintText()`][eslint-linttext] instead. +- `options` - (optional) Additional options for this run. + - `filename` - (optional) the filename to associate with the source code. + - `preprocess` - (optional) A function that [Processors in Plugins](../extend/plugins#processors-in-plugins) documentation describes as the `preprocess` method. + - `postprocess` - (optional) A function that [Processors in Plugins](../extend/plugins#processors-in-plugins) documentation describes as the `postprocess` method. + - `filterCodeBlock` - (optional) A function that decides which code blocks the linter should adopt. The function receives two arguments. The first argument is the virtual filename of a code block. The second argument is the text of the code block. If the function returned `true` then the linter adopts the code block. If the function was omitted, the linter adopts only `*.js` code blocks. If you provided a `filterCodeBlock` function, it overrides this default behavior, so the linter doesn't adopt `*.js` code blocks automatically. + - `disableFixes` - (optional) when set to `true`, the linter doesn't make either the `fix` or `suggestions` property of the lint result. + - `allowInlineConfig` - (optional) set to `false` to disable inline comments from changing ESLint rules. + - `reportUnusedDisableDirectives` - (optional) when set to `true`, adds reported errors for unused `eslint-disable` and `eslint-enable` directives when no problems would be reported in the disabled area anyway. + - `ruleFilter` - (optional) A function predicate that decides which rules should run. It receives an object containing `ruleId` and `severity`, and returns `true` if the rule should be run. If the third argument is a string, it is interpreted as the `filename`. @@ -547,57 +617,68 @@ You can call `verify()` like this: const Linter = require("eslint").Linter; const linter = new Linter(); -const messages = linter.verify("var foo;", { - rules: { - semi: 2 - } -}, { filename: "foo.js" }); +const messages = linter.verify( + "var foo;", + { + rules: { + semi: 2, + }, + }, + { filename: "foo.js" }, +); // or using SourceCode const Linter = require("eslint").Linter, - linter = new Linter(), - SourceCode = require("eslint").SourceCode; + linter = new Linter(), + SourceCode = require("eslint").SourceCode; const code = new SourceCode("var foo = bar;", ast); -const messages = linter.verify(code, { - rules: { - semi: 2 - } -}, { filename: "foo.js" }); +const messages = linter.verify( + code, + { + rules: { + semi: 2, + }, + }, + { filename: "foo.js" }, +); ``` The `verify()` method returns an array of objects containing information about the linting warnings and errors. Here's an example: ```js -{ - fatal: false, - ruleId: "semi", - severity: 2, - line: 1, - column: 23, - message: "Expected a semicolon.", - fix: { - range: [1, 15], - text: ";" - } -} +[ + { + fatal: false, + ruleId: "semi", + severity: 2, + line: 1, + column: 23, + message: "Expected a semicolon.", + fix: { + range: [1, 15], + text: ";", + }, + }, +]; ``` The information available for each linting message is: -* `column` - the column on which the error occurred. -* `fatal` - usually omitted, but will be set to true if there's a parsing error (not related to a rule). -* `line` - the line on which the error occurred. -* `message` - the message that should be output. -* `nodeType` - the node or token type that was reported with the problem. -* `ruleId` - the ID of the rule that triggered the messages (or null if `fatal` is true). -* `severity` - either 1 or 2, depending on your configuration. -* `endColumn` - the end column of the range on which the error occurred (this property is omitted if it's not range). -* `endLine` - the end line of the range on which the error occurred (this property is omitted if it's not range). -* `fix` - an object describing the fix for the problem (this property is omitted if no fix is available). -* `suggestions` - an array of objects describing possible lint fixes for editors to programmatically enable (see details in the [Working with Rules docs](../extend/custom-rules#providing-suggestions)). +- `column` - the column on which the error occurred. +- `fatal` - usually omitted, but will be set to true if there's a parsing error (not related to a rule). +- `line` - the line on which the error occurred. +- `message` - the message that should be output. +- `messageId` - the ID of the message used to generate the message (this property is omitted if the rule does not use message IDs). +- `nodeType` - (**Deprecated:** This property will be removed in a future version of ESLint.) the node, comment, or token type that was reported with the problem. +- `ruleId` - the ID of the rule that triggered the messages (or null if `fatal` is true). +- `severity` - either 1 or 2, depending on your configuration. +- `endColumn` - the end column of the range on which the error occurred (this property is omitted if it's not range). +- `endLine` - the end line of the range on which the error occurred (this property is omitted if it's not range). +- `fix` - an object describing the fix for the problem (this property is omitted if no fix is available). +- `suggestions` - an array of objects describing possible lint fixes for editors to programmatically enable (see details in the [Working with Rules docs](../extend/custom-rules#providing-suggestions)). You can get the suppressed messages from the previous run by `getSuppressedMessages()` method. If there is not a previous run, `getSuppressedMessage()` will return an empty list. @@ -605,33 +686,39 @@ You can get the suppressed messages from the previous run by `getSuppressedMessa const Linter = require("eslint").Linter; const linter = new Linter(); -const messages = linter.verify("var foo = bar; // eslint-disable-line -- Need to suppress", { - rules: { - semi: ["error", "never"] - } -}, { filename: "foo.js" }); +const messages = linter.verify( + "var foo = bar; // eslint-disable-line -- Need to suppress", + { + rules: { + semi: ["error", "never"], + }, + }, + { filename: "foo.js" }, +); const suppressedMessages = linter.getSuppressedMessages(); console.log(suppressedMessages[0].suppressions); // [{ "kind": "directive", "justification": "Need to suppress" }] ``` -Linting message objects have a deprecated `source` property. This property **will be removed** from linting messages in an upcoming breaking release. If you depend on this property, you should now use the `SourceCode` instance provided by the linter. - You can also get an instance of the `SourceCode` object used inside of `linter` by using the `getSourceCode()` method: ```js const Linter = require("eslint").Linter; const linter = new Linter(); -const messages = linter.verify("var foo = bar;", { - rules: { - semi: 2 - } -}, { filename: "foo.js" }); +const messages = linter.verify( + "var foo = bar;", + { + rules: { + semi: 2, + }, + }, + { filename: "foo.js" }, +); const code = linter.getSourceCode(); -console.log(code.text); // "var foo = bar;" +console.log(code.text); // "var foo = bar;" ``` In this way, you can retrieve the text and AST used for the last run of `linter.verify()`. @@ -645,9 +732,9 @@ const Linter = require("eslint").Linter; const linter = new Linter(); const messages = linter.verifyAndFix("var foo", { - rules: { - semi: 2 - } + rules: { + semi: 2, + }, }); ``` @@ -663,104 +750,46 @@ Output object from this method: The information available is: -* `fixed` - True, if the code was fixed. -* `output` - Fixed code text (might be the same as input if no fixes were applied). -* `messages` - Collection of all messages for the given code (It has the same information as explained above under `verify` block). - -### Linter#defineRule - -Each `Linter` instance holds a map of rule names to loaded rule objects. By default, all ESLint core rules are loaded. If you want to use `Linter` with custom rules, you should use the `defineRule` method to register your rules by ID. - -```js -const Linter = require("eslint").Linter; -const linter = new Linter(); +- `fixed` - True, if the code was fixed. +- `output` - Fixed code text (might be the same as input if no fixes were applied). +- `messages` - Collection of all messages for the given code (It has the same information as explained above under `verify` block). -linter.defineRule("my-custom-rule", { - // (an ESLint rule) - - create(context) { - // ... - } -}); - -const results = linter.verify("// some source text", { rules: { "my-custom-rule": "error" } }); -``` - -### Linter#defineRules +### Linter#version/Linter.version -This is a convenience method similar to `Linter#defineRule`, except that it allows you to define many rules at once using an object. +Each instance of `Linter` has a `version` property containing the semantic version number of ESLint that the `Linter` instance is from. ```js const Linter = require("eslint").Linter; const linter = new Linter(); -linter.defineRules({ - "my-custom-rule": { /* an ESLint rule */ create() {} }, - "another-custom-rule": { /* an ESLint rule */ create() {} } -}); - -const results = linter.verify("// some source text", { - rules: { - "my-custom-rule": "error", - "another-custom-rule": "warn" - } -}); +linter.version; // => '9.0.0' ``` -### Linter#getRules - -This method returns a map of all loaded rules. +There is also a `Linter.version` property that you can read without instantiating `Linter`: ```js const Linter = require("eslint").Linter; -const linter = new Linter(); -linter.getRules(); - -/* -Map { - 'accessor-pairs' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] }, - 'array-bracket-newline' => { meta: { docs: [Object], schema: [Array] }, create: [Function: create] }, - ... -} -*/ +Linter.version; // => '9.0.0' ``` -### Linter#defineParser +### Linter#getTimes() -Each instance of `Linter` holds a map of custom parsers. If you want to define a parser programmatically, you can add this function -with the name of the parser as first argument and the [parser object](../extend/custom-parsers) as second argument. The default `"espree"` parser will already be loaded for every `Linter` instance. +This method is used to get the times spent on (parsing, fixing, linting) a file. See `times` property of the [Stats](../extend/stats#-stats-type) object. -```js -const Linter = require("eslint").Linter; -const linter = new Linter(); +### Linter#getFixPassCount() -linter.defineParser("my-custom-parser", { - parse(code, options) { - // ... - } -}); +This method is used to get the number of autofix passes made. See `fixPasses` property of the [Stats](../extend/stats#-stats-type) object. -const results = linter.verify("// some source text", { parser: "my-custom-parser" }); -``` - -### Linter#version/Linter.version +### Linter#hasFlag() -Each instance of `Linter` has a `version` property containing the semantic version number of ESLint that the `Linter` instance is from. +This method is used to determine if a given feature flag is set, as in this example: ```js const Linter = require("eslint").Linter; -const linter = new Linter(); +const linter = new Linter({ flags: ["x_feature"] }); -linter.version; // => '4.5.0' -``` - -There is also a `Linter.version` property that you can read without instantiating `Linter`: - -```js -const Linter = require("eslint").Linter; - -Linter.version; // => '4.5.0' +console.log(linter.hasFlag("x_feature")); // true ``` --- @@ -775,79 +804,87 @@ Example usage: "use strict"; const rule = require("../../../lib/rules/my-rule"), - RuleTester = require("eslint").RuleTester; + RuleTester = require("eslint").RuleTester; const ruleTester = new RuleTester(); ruleTester.run("my-rule", rule, { - valid: [ - { - code: "var foo = true", - options: [{ allowFoo: true }] - } - ], - - invalid: [ - { - code: "var invalidVariable = true", - errors: [{ message: "Unexpected invalid variable." }] - }, - { - code: "var invalidVariable = true", - errors: [{ message: /^Unexpected.+variable/ }] - } - ] + valid: [ + { + code: "var foo = true", + options: [{ allowFoo: true }], + }, + ], + + invalid: [ + { + code: "var invalidVariable = true", + errors: [{ message: "Unexpected invalid variable." }], + }, + { + code: "var invalidVariable = true", + errors: [{ message: /^Unexpected.+variable/ }], + }, + ], }); ``` The `RuleTester` constructor accepts an optional object argument, which can be used to specify defaults for your test cases. For example, if all of your test cases use ES2015, you can set it as a default: ```js -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } }); +const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2015 } }); ``` +::: tip +If you don't specify any options to the `RuleTester` constructor, then it uses the ESLint defaults (`languageOptions: { ecmaVersion: "latest", sourceType: "module" }`). +::: + The `RuleTester#run()` method is used to run the tests. It should be passed the following arguments: -* The name of the rule (string) -* The rule object itself (see ["working with rules"](../extend/custom-rules)) -* An object containing `valid` and `invalid` properties, each of which is an array containing test cases. +- The name of the rule (string). +- The rule object itself (see ["working with rules"](../extend/custom-rules)). +- An object containing `valid` and `invalid` properties, each of which is an array containing test cases. A test case is an object with the following properties: -* `name` (string, optional): The name to use for the test case, to make it easier to find -* `code` (string, required): The source code that the rule should be run on -* `options` (array, optional): The options passed to the rule. The rule severity should not be included in this list. -* `filename` (string, optional): The filename for the given case (useful for rules that make assertions about filenames). -* `only` (boolean, optional): Run this case exclusively for debugging in supported test frameworks. +- `name` (string, optional): The name to use for the test case, to make it easier to find. +- `code` (string, required): The source code that the rule should be run on. +- `options` (array, optional): The options passed to the rule. The rule severity should not be included in this list. +- `before` (function, optional): Function to execute before testing the case. +- `after` (function, optional): Function to execute after testing the case regardless of its result. +- `filename` (string, optional): The filename for the given case (useful for rules that make assertions about filenames). +- `only` (boolean, optional): Run this case exclusively for debugging in supported test frameworks. In addition to the properties above, invalid test cases can also have the following properties: -* `errors` (number or array, required): Asserts some properties of the errors that the rule is expected to produce when run on this code. If this is a number, asserts the number of errors produced. Otherwise, this should be a list of objects, each containing information about a single reported error. The following properties can be used for an error (all are optional): - * `message` (string/regexp): The message for the error - * `messageId` (string): The Id for the error. See [testing errors with messageId](#testing-errors-with-messageid) for details - * `data` (object): Placeholder data which can be used in combination with `messageId` - * `type` (string): The type of the reported AST node - * `line` (number): The 1-based line number of the reported location - * `column` (number): The 1-based column number of the reported location - * `endLine` (number): The 1-based line number of the end of the reported location - * `endColumn` (number): The 1-based column number of the end of the reported location - * `suggestions` (array): An array of objects with suggestion details to check. See [Testing Suggestions](#testing-suggestions) for details +- `errors` (number or array, required): Asserts some properties of the errors that the rule is expected to produce when run on this code. If this is a number, asserts the number of errors produced. Otherwise, this should be a list of objects, each containing information about a single reported error. The following properties can be used for an error (all are optional unless otherwise noted): + + - `message` (string/regexp): The message for the error. Must provide this or `messageId`. + - `messageId` (string): The ID for the error. Must provide this or `message`. See [testing errors with messageId](#testing-errors-with-messageid) for details. + - `data` (object): Placeholder data which can be used in combination with `messageId`. + - `type` (string): (**Deprecated:** This property will be removed in a future version of ESLint.) The type of the reported AST node. + - `line` (number): The 1-based line number of the reported location. + - `column` (number): The 1-based column number of the reported location. + - `endLine` (number): The 1-based line number of the end of the reported location. + - `endColumn` (number): The 1-based column number of the end of the reported location. + - `suggestions` (array): An array of objects with suggestion details to check. Required if the rule produces suggestions. See [Testing Suggestions](#testing-suggestions) for details. If a string is provided as an error instead of an object, the string is used to assert the `message` of the error. -* `output` (string, required if the rule fixes code): Asserts the output that will be produced when using this rule for a single pass of autofixing (e.g. with the `--fix` command line flag). If this is `null`, asserts that none of the reported problems suggest autofixes. -Any additional properties of a test case will be passed directly to the linter as config options. For example, a test case can have a `parserOptions` property to configure parser behavior: +- `output` (string, required if the rule fixes code): Asserts the output that will be produced when using this rule for a single pass of autofixing (e.g. with the `--fix` command line flag). If this is `null` or omitted, asserts that none of the reported problems suggest autofixes. + +Any additional properties of a test case will be passed directly to the linter as config options. For example, a test case can have a `languageOptions` property to configure parser behavior: ```js { code: "let foo;", - parserOptions: { ecmaVersion: 2015 } + languageOptions: { ecmaVersion: 2015 } } ``` If a valid test case only uses the `code` property, it can optionally be provided as a string containing the code, rather than an object with a `code` key. -### Testing errors with `messageId` +### Testing Errors with `messageId` If the rule under test uses `messageId`s, you can use `messageId` property in a test case to assert reported error's `messageId` instead of its `message`. @@ -869,48 +906,89 @@ For messages with placeholders, a test case can also use `data` property to addi Please note that `data` in a test case does not assert `data` passed to `context.report`. Instead, it is used to form the expected message text which is then compared with the received `message`. +### Testing Fixes + +The result of applying fixes can be tested by using the `output` property of an invalid test case. The `output` property should be used only when you expect a fix to be applied to the specified `code`; you can safely omit `output` if no changes are expected to the code. Here's an example: + +```js +ruleTester.run("my-rule-for-no-foo", rule, { + valid: [], + invalid: [ + { + code: "var foo;", + output: "var bar;", + errors: [ + { + messageId: "shouldBeBar", + line: 1, + column: 5, + }, + ], + }, + ], +}); +``` + +A the end of this invalid test case, `RuleTester` expects a fix to be applied that results in the code changing from `var foo;` to `var bar;`. If the output after applying the fix doesn't match, then the test fails. + +::: important +ESLint makes its best attempt at applying all fixes, but there is no guarantee that all fixes will be applied. As such, you should aim for testing each type of fix in a separate `RuleTester` test case rather than one test case to test multiple fixes. When there is a conflict between two fixes (because they apply to the same section of code) `RuleTester` applies only the first fix. +::: + ### Testing Suggestions -Suggestions can be tested by defining a `suggestions` key on an errors object. The options to check for the suggestions are the following (all are optional): +Suggestions can be tested by defining a `suggestions` key on an errors object. If this is a number, it asserts the number of suggestions provided for the error. Otherwise, this should be an array of objects, each containing information about a single provided suggestion. The following properties can be used: -* `desc` (string): The suggestion `desc` value -* `messageId` (string): The suggestion `messageId` value for suggestions that use `messageId`s -* `data` (object): Placeholder data which can be used in combination with `messageId` -* `output` (string): A code string representing the result of applying the suggestion fix to the input code +- `desc` (string): The suggestion `desc` value. Must provide this or `messageId`. +- `messageId` (string): The suggestion `messageId` value for suggestions that use `messageId`s. Must provide this or `desc`. +- `data` (object): Placeholder data which can be used in combination with `messageId`. +- `output` (string, required): A code string representing the result of applying the suggestion fix to the input code. Example: ```js ruleTester.run("my-rule-for-no-foo", rule, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var bar;" - }] - }] - }] -}) + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + suggestions: [ + { + desc: "Rename identifier 'foo' to 'bar'", + output: "var bar;", + }, + ], + }, + ], + }, + ], +}); ``` `messageId` and `data` properties in suggestion test objects work the same way as in error test objects. See [testing errors with messageId](#testing-errors-with-messageid) for details. ```js ruleTester.run("my-rule-for-no-foo", rule, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - data: { newName: "bar" }, - output: "var bar;" - }] - }] - }] -}) + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + suggestions: [ + { + messageId: "renameFoo", + data: { newName: "bar" }, + output: "var bar;", + }, + ], + }, + ], + }, + ], +}); ``` ### Customizing RuleTester @@ -921,7 +999,7 @@ ruleTester.run("my-rule-for-no-foo", rule, { If `RuleTester.itOnly` has been set to a function value, `RuleTester` will call `RuleTester.itOnly` instead of `RuleTester.it` to run cases with `only: true`. If `RuleTester.itOnly` is not set but `RuleTester.it` has an `only` function property, `RuleTester` will fall back to `RuleTester.it.only`. -2. Otherwise, if `describe` and `it` are present as globals, `RuleTester` will use `global.describe` and `global.it` to run tests and `global.it.only` to run cases with `only: true`. This allows `RuleTester` to work when using frameworks like [Mocha](https://mochajs.org/) without any additional configuration. +2. Otherwise, if `describe` and `it` are present as globals, `RuleTester` will use `globalThis.describe` and `globalThis.it` to run tests and `globalThis.it.only` to run cases with `only: true`. This allows `RuleTester` to work when using frameworks like [Mocha](https://mochajs.org/) without any additional configuration. 3. Otherwise, `RuleTester#run` will simply execute all of the tests in sequence, and will throw an error if one of them fails. This means you can simply execute a test file that calls `RuleTester.run` using `Node.js`, without needing a testing framework. `RuleTester#run` calls the `describe` function with two arguments: a string describing the rule, and a callback function. The callback calls the `it` function with a string describing the test case, and a test function. The test function will return successfully if the test passes, and throw an error if the test fails. The signature for `only` is the same as `it`. `RuleTester` calls either `it` or `only` for every case even when some cases have `only: true`, and the test framework is responsible for implementing test case exclusivity. (Note that this is the standard behavior for test suites when using frameworks like [Mocha](https://mochajs.org/); this information is only relevant if you plan to customize `RuleTester.describe`, `RuleTester.it`, or `RuleTester.itOnly`.) @@ -932,16 +1010,16 @@ Example of customizing `RuleTester`: "use strict"; const RuleTester = require("eslint").RuleTester, - test = require("my-test-runner"), - myRule = require("../../../lib/rules/my-rule"); + test = require("my-test-runner"), + myRule = require("../../../lib/rules/my-rule"); -RuleTester.describe = function(text, method) { - RuleTester.it.title = text; - return method.call(this); +RuleTester.describe = function (text, method) { + RuleTester.it.title = text; + return method.call(this); }; -RuleTester.it = function(text, method) { - test(RuleTester.it.title + ": " + text, method); +RuleTester.it = function (text, method) { + test(RuleTester.it.title + ": " + text, method); }; // then use RuleTester as documented @@ -949,17 +1027,15 @@ RuleTester.it = function(text, method) { const ruleTester = new RuleTester(); ruleTester.run("my-rule", myRule, { - valid: [ - // valid test cases - ], - invalid: [ - // invalid test cases - ] -}) + valid: [ + // valid test cases + ], + invalid: [ + // invalid test cases + ], +}); ``` ---- - [configuration object]: ../use/configure/ [builtin-formatters]: ../use/formatters/ [third-party-formatters]: https://www.npmjs.com/search?q=eslintformatter diff --git a/docs/src/library/alert.md b/docs/src/library/alert.md index cefd830407cf..9d9969e43d90 100644 --- a/docs/src/library/alert.md +++ b/docs/src/library/alert.md @@ -1,5 +1,5 @@ --- -title: alert +title: alert --- The alert message comes in three different types: a warning, a tip, and an important note. @@ -9,11 +9,10 @@ The alert message comes in three different types: a warning, a tip, and an impor There is a shortcode for each type of alert. The shortcode expects you to provide the text and URL for the “Learn more” link. ```html -{ % warning "This rule has been removed in version x.xx", "/link/to/learn/more" % } - -{ % tip "Kind reminder to do something maybe", "/link/to/learn/more" % } - -{ % important "This rule has been deprecated in version x.xx", "/link/to/learn/more" % } +{ % warning "This rule has been removed in version x.xx", "/link/to/learn/more" +% } { % tip "Kind reminder to do something maybe", "/link/to/learn/more" % } { % +important "This rule has been deprecated in version x.xx", "/link/to/learn/more" +% } ``` ## Examples diff --git a/docs/src/library/buttons.md b/docs/src/library/buttons.md index 45bf205f546d..201bf31589b7 100644 --- a/docs/src/library/buttons.md +++ b/docs/src/library/buttons.md @@ -1,5 +1,5 @@ --- -title: Buttons +title: Buttons --- {% from 'components/button.macro.html' import button %} @@ -13,7 +13,6 @@ The button macro will default to `link`, which will render an <a> {% from 'components/button.macro.html' import button %} @@ -22,7 +21,8 @@ The button macro will default to `link`, which will render an <a> -{ { button({ type: "primary", text: "Go somewhere", url: "/url/to/somewhere/" }) } } +{ { button({ type: "primary", text: "Go somewhere", url: "/url/to/somewhere/" }) +} } ``` ## Examples diff --git a/docs/src/library/code-blocks.md b/docs/src/library/code-blocks.md index 222d3e685190..25cf7fdb1200 100644 --- a/docs/src/library/code-blocks.md +++ b/docs/src/library/code-blocks.md @@ -1,5 +1,5 @@ --- -title: Correct and incorrect code usage +title: Correct and incorrect code usage --- To indicate correct and incorrect code usage, some code blocks can have correct and incorrect icons added to them, respectively. @@ -18,6 +18,7 @@ function() { const another = []; } `` ` + ::: ::: incorrect @@ -27,6 +28,7 @@ function() { const another = []; } `` ` + ::: ``` @@ -40,24 +42,24 @@ Correct usage: const { ESLint } = require("eslint"); (async function main() { - // 1. Create an instance with the `fix` option. - const eslint = new ESLint({ fix: true }); + // 1. Create an instance with the `fix` option. + const eslint = new ESLint({ fix: true }); - // 2. Lint files. This doesn't modify target files. - const results = await eslint.lintFiles(["lib/**/*.js"]); + // 2. Lint files. This doesn't modify target files. + const results = await eslint.lintFiles(["lib/**/*.js"]); - // 3. Modify the files with the fixed code. - await ESLint.outputFixes(results); + // 3. Modify the files with the fixed code. + await ESLint.outputFixes(results); - // 4. Format the results. - const formatter = await eslint.loadFormatter("stylish"); - const resultText = formatter.format(results); + // 4. Format the results. + const formatter = await eslint.loadFormatter("stylish"); + const resultText = formatter.format(results); - // 5. Output it. - console.log(resultText); -})().catch((error) => { - process.exitCode = 1; - console.error(error); + // 5. Output it. + console.log(resultText); +})().catch(error => { + process.exitCode = 1; + console.error(error); }); ``` @@ -71,24 +73,24 @@ Incorrect usage: const { ESLint } = require("eslint"); (async function main() { - // 1. Create an instance with the `fix` option. - const eslint = new ESLint({ fix: true }); + // 1. Create an instance with the `fix` option. + const eslint = new ESLint({ fix: true }); - // 2. Lint files. This doesn't modify target files. - const results = await eslint.lintFiles(["lib/**/*.js"]); + // 2. Lint files. This doesn't modify target files. + const results = await eslint.lintFiles(["lib/**/*.js"]); - // 3. Modify the files with the fixed code. - await ESLint.outputFixes(results); + // 3. Modify the files with the fixed code. + await ESLint.outputFixes(results); - // 4. Format the results. - const formatter = await eslint.loadFormatter("stylish"); - const resultText = formatter.format(results); + // 4. Format the results. + const formatter = await eslint.loadFormatter("stylish"); + const resultText = formatter.format(results); - // 5. Output it. - console.log(resultText); -})().catch((error) => { - process.exitCode = 1; - console.error(error); + // 5. Output it. + console.log(resultText); +})().catch(error => { + process.exitCode = 1; + console.error(error); }); ``` diff --git a/docs/src/library/language-switcher.md b/docs/src/library/language-switcher.md index 745afdabc084..83fed5365326 100644 --- a/docs/src/library/language-switcher.md +++ b/docs/src/library/language-switcher.md @@ -1,5 +1,5 @@ --- -title: Language Switcher +title: Language Switcher --- {% include 'components/language-switcher.html' %} diff --git a/docs/src/library/library.json b/docs/src/library/library.json index dd622a721c6e..60d889c6e689 100644 --- a/docs/src/library/library.json +++ b/docs/src/library/library.json @@ -1,4 +1,4 @@ { - "layout": "components.html", - "permalink": "/component-library/{{ page.fileSlug }}.html" + "layout": "components.html", + "permalink": "/component-library/{{ page.fileSlug }}.html" } diff --git a/docs/src/library/link-card.md b/docs/src/library/link-card.md index d21291b1a60e..88297a048314 100644 --- a/docs/src/library/link-card.md +++ b/docs/src/library/link-card.md @@ -1,5 +1,5 @@ --- -title: Link Card +title: Link Card --- Links can be rendered as cards by using the `link` shortcode. The only required parameter is the URL you wish to scrape for metadata. @@ -10,7 +10,10 @@ Links can be rendered as cards by using the `link` shortcode. The only required ## Examples - + + {% link "https://blog.izs.me/2010/12/an-open-letter-to-javascript-leaders-regarding/" %} - + {% link "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get" %} + + diff --git a/docs/src/library/related-rules.md b/docs/src/library/related-rules.md index 21003b1d1033..76eb4496038f 100644 --- a/docs/src/library/related-rules.md +++ b/docs/src/library/related-rules.md @@ -1,5 +1,5 @@ --- -title: Related rules +title: Related rules --- The `related_rules` shortcode is used to add one or more related rules to a rule. @@ -9,7 +9,8 @@ The `related_rules` shortcode is used to add one or more related rules to a rule The shortcode expects an array of rule names. ```html -{ % related_rules ["no-extra-semi", "no-unexpected-multiline", "semi-spacing"] % } +{ % related_rules ["no-extra-semi", "no-unexpected-multiline", "semi-spacing"] % +} ``` ## Example diff --git a/docs/src/library/rule-categories.md b/docs/src/library/rule-categories.md index 8934fe7ecace..aca26fcdd6b5 100644 --- a/docs/src/library/rule-categories.md +++ b/docs/src/library/rule-categories.md @@ -7,13 +7,8 @@ title: Rule categories The rule categories—namely “recommended”, “fixable”, and “hasSuggestions”—are shown in the [rules page](../rules/). They are rendered using the `ruleCategories` macro (imported from `/components/rule-categories.macro.html`). There is also an individual macro for each category type. ```html -{ % from 'components/rule-categories.macro.html' import ruleCategories % } - -{ { ruleCategories({ - recommended: true, - fixable: true, - hasSuggestions: true -}) } } +{ % from 'components/rule-categories.macro.html' import ruleCategories % } { { +ruleCategories({ recommended: true, fixable: true, hasSuggestions: true }) } } ``` ### Example @@ -31,9 +26,7 @@ The rule categories—namely “recommended”, “fixable”, and “hasSuggest For every rule, you can render the category it belongs to using the corresponding category shortcode: ```html -{ % recommended % } -{ % fixable % } -{ % hasSuggestions % } +{ % recommended % } { % fixable % } { % hasSuggestions % } ``` ## Examples diff --git a/docs/src/library/rule-list.md b/docs/src/library/rule-list.md index 635e6b400236..e299a5fd2a15 100644 --- a/docs/src/library/rule-list.md +++ b/docs/src/library/rule-list.md @@ -1,8 +1,8 @@ --- -title: Rule list +title: Replacement Rule list --- -The rule list is a macro defined in `components/rule-list.macro.html`. The macro accepts a list of rule names and renders comma-separated links. +The rule list is a macro defined in `components/rule-list.macro.html`. The macro accepts a list of `ReplacedByInfo` and renders them as or-separated links. ## Usage @@ -10,16 +10,18 @@ The rule list is a macro defined in `components/rule-list.macro.html`. The macro ```html -{% from 'components/rule-list.macro.html' import ruleList %} +{% from 'components/rule-list.macro.html' import replacementRuleList %} -{{ ruleList({ rules: ['accessor-pairs', 'no-undef'] }) }} +{{ replacementRuleList({ specifiers: [{ rule: { name: 'global-require', url: +'...' }, plugin: { name: '@eslint-comunnity/eslint-plugin-n', url: '...' } }] }) +}} ``` {% endraw %} ## Examples -{% from 'components/rule-list.macro.html' import ruleList %} +{% from 'components/rule-list.macro.html' import replacementRuleList %} -{{ ruleList({ rules: ['accessor-pairs', 'no-undef'] }) }} +{{ replacementRuleList({ specifiers: [{ rule: { name: 'global-require', url: '...' }, plugin: { name: '@eslint-comunnity/eslint-plugin-n', url: '...' } }] }) }} diff --git a/docs/src/library/rule.md b/docs/src/library/rule.md index 45bccea16a5b..87caf2492fc3 100644 --- a/docs/src/library/rule.md +++ b/docs/src/library/rule.md @@ -1,16 +1,16 @@ --- -title: Rule +title: Rule --- The rule component is a macro defined in `/components/rule.macro.html`. The macro accepts a set of parameters used to render the rule. A rule has a: -* name -* description -* a flag to indicate whether it's deprecated or removed: `deprecated` and `removed` respectively -* a replacedBy value indicating the rule it has been replaced with (if applicable) -* a categories object indicating the rule's category +- name +- description +- a flag to indicate whether it's deprecated or removed: `deprecated` and `removed` respectively +- a replacedBy value indicating the rule it has been replaced with (if applicable) +- a categories object indicating the rule's category ## Usage @@ -19,27 +19,20 @@ A rule has a: { % from 'components/rule.macro.html' import rule % } - { { rule({ - name: "rule-name", - deprecated: true, // or removed: true - replacedBy: "name-of-replacement-rule" - description: 'Example: Enforce `return` statements in getters.', - categories: { - recommended: true, - fixable: true, - hasSuggestions: false - } -}) } } +{ { rule({ name: "rule-name", deprecated: true, // or removed: true replacedBy: +"name-of-replacement-rule" description: 'Example: Enforce `return` statements in +getters.', categories: { recommended: true, fixable: true, hasSuggestions: false +} }) } } ``` ## Examples {% from 'components/rule.macro.html' import rule %} - {{ rule({ - name: "getter-return", +{{ rule({ + name: "array-bracket-newline", deprecated: true, - description: 'Enforce `return` statements in getters.', + description: 'Enforces line breaks after opening and before closing array brackets.', categories: { recommended: true, fixable: true, @@ -47,14 +40,14 @@ A rule has a: } }) }} - {{ rule({ - name: "getter-return", +{{ rule({ + name: "no-arrow-condition", removed: true, - description: 'Enforce `return` statements in getters.', - replacedBy: "other-rule-here", + description: 'Disallows arrow functions where test conditions are expected.', + replacedBy: ["no-confusing-arrow", "no-constant-condition"], categories: { - recommended: true, - fixable: true, + recommended: false, + fixable: false, hasSuggestions: false } }) }} @@ -68,5 +61,4 @@ A rule has a: fixable: false, hasSuggestions: false } - }) }} diff --git a/docs/src/library/social-icons.md b/docs/src/library/social-icons.md index b76f726bb38a..ac3088afc88a 100644 --- a/docs/src/library/social-icons.md +++ b/docs/src/library/social-icons.md @@ -1,5 +1,5 @@ --- -title: Social Icons +title: Social Icons --- {% include 'components/social-icons.html' %} diff --git a/docs/src/library/theme-switcher.md b/docs/src/library/theme-switcher.md index 12899985f757..a55ac3747f4b 100644 --- a/docs/src/library/theme-switcher.md +++ b/docs/src/library/theme-switcher.md @@ -1,5 +1,5 @@ --- -title: Theme Switcher +title: Theme Switcher --- {% include 'components/theme-switcher.html' %} diff --git a/docs/src/library/version-switcher.md b/docs/src/library/version-switcher.md index 5b72f4acc6bd..cc21b1b9d457 100644 --- a/docs/src/library/version-switcher.md +++ b/docs/src/library/version-switcher.md @@ -1,5 +1,5 @@ --- -title: Version Switcher +title: Version Switcher --- {% include 'components/version-switcher.html' %} diff --git a/docs/src/maintain/index.md b/docs/src/maintain/index.md index 1b083836f3f0..162aec127916 100644 --- a/docs/src/maintain/index.md +++ b/docs/src/maintain/index.md @@ -4,7 +4,6 @@ eleventyNavigation: key: maintain eslint title: Maintain ESLint order: 5 - --- This guide is intended for those who work as part of the ESLint project team. diff --git a/docs/src/maintain/manage-issues.md b/docs/src/maintain/manage-issues.md index 9e243c12e720..35bcade701a3 100644 --- a/docs/src/maintain/manage-issues.md +++ b/docs/src/maintain/manage-issues.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: maintain eslint title: Manage Issues and Pull Requests order: 2 - --- New issues and pull requests are filed frequently, and how we respond to those issues directly affects the success of the project. Being part of the project team means helping to triage and address issues as they come in so the project can continue to run smoothly. @@ -33,17 +32,19 @@ The first goal when evaluating an issue or pull request is to determine which ca All of ESLint's issues and pull requests, across all GitHub repositories, are managed on our [Triage Project](https://github.com/orgs/eslint/projects/3). Please use the Triage project instead of the issues list when reviewing issues to determine what to work on. The Triage project has several columns: -* **Needs Triage**: Issues and pull requests that have not yet been reviewed by anyone -* **Triaging**: Issues and pull requests that someone has reviewed but has not been able to fully triage yet -* **Ready for Dev Team**: Issues and pull requests that have been triaged and have all the information necessary for the dev team to take a look -* **Evaluating**: The dev team is evaluating these issues and pull requests to determine whether to move forward or not -* **Feedback Needed**: A team member is requesting more input from the rest of the team before proceeding -* **Waiting for RFC**: The next step in the process is for an RFC to be written -* **RFC Opened**: An RFC is opened to address these issues -* **Blocked**: The issue can't move forward due to some dependency -* **Ready to Implement**: These issues have all the details necessary to start implementation -* **Implementing**: There is an open pull request for each of these issues or this is a pull request that has been approved -* **Completed**: The issue has been closed (either via pull request merge or by the team manually closing the issue) +- **Needs Triage**: Issues and pull requests that have not yet been reviewed by anyone. +- **Triaging**: Issues and pull requests that someone has reviewed but has not been able to fully triage yet. +- **Ready for Dev Team**: Issues and pull requests that have been triaged and have all the information necessary for the dev team to take a look. +- **Evaluating**: The dev team is evaluating these issues and pull requests to determine whether to move forward or not. +- **Feedback Needed**: A team member is requesting more input from the rest of the team before proceeding. +- **Waiting for RFC**: The next step in the process is for an RFC to be written. +- **RFC Opened**: An RFC is opened to address these issues. +- **Blocked**: The issue can't move forward due to some dependency. +- **Ready to Implement**: These issues have all the details necessary to start implementation. +- **Implementing**: There is an open pull request for each of these issues or this is a pull request that has been approved. +- **Second Review Needed**: Pull requests that already have one approval and the approver is requesting a second review before merging. +- **Merge Candidates**: Pull requests that already have at least one approval and at least one approver believes the pull request is ready to merge into the next release but would still like a TSC member to verify. +- **Complete**: The issue has been closed (either via pull request merge or by the team manually closing the issue). We make every attempt to automate movement between as many columns as we can, but sometimes moving issues needs to be done manually. @@ -57,35 +58,35 @@ The steps for triaging an issue or pull request are: 1. Move the issue or pull request from "Needs Triage" to "Triaging" in the Triage project. 1. Check: Has all the information in the issue template been provided? - * **No:** If information is missing from the issue template, or you can't tell what is being requested, please ask the author to provide the missing information: - * Add the "needs info" label to the issue so we know that this issue is stalled due to lack of information. - * Don't move on to other steps until the necessary information has been provided. - * If the issue author hasn't provided the necessary information after 7 days, please close the issue. The bot will add a comment stating that the issue was closed because there was information missing. - * **Yes:** - * If the issue is actually a question (rather than something the dev team needs to change), please [convert it to a discussion](https://docs.github.com/en/free-pro-team@latest/discussions/managing-discussions-for-your-community/moderating-discussions#converting-an-issue-to-a-discussion). You can continue the conversation as a discussion. - * If the issue is reporting a bug, or if a pull request is fixing a bug, try to reproduce the issue following the instructions in the issue. If you can reproduce the bug, please add the "repro:yes" label. (The bot will automatically remove the "repro:needed" label.) If you can't reproduce the bug, ask the author for more information about their environment or to clarify reproduction steps. - * If the issue or pull request is reporting something that works as intended, please add the "works as intended" label and close the issue. - * Please add labels describing the part of ESLint affected: - * **3rd party plugin**: Related to third-party functionality (plugins, parsers, rules, etc.) - * **build**: Related to commands run during a build (testing, linting, release scripts, etc.) - * **cli**: Related to command line input or output, or to `CLIEngine` - * **core**: Related to internal APIs - * **documentation**: Related to content on eslint.org - * **infrastructure**: Related to resources needed for builds or deployment (VMs, CI tools, bots, etc.) - * **rule**: Related to core rules - * Please assign an initial priority based on the importance of the issue or pull request. If you're not sure, use your best judgment. We can always change the priority later. - * **P1**: Urgent and important, we need to address this immediately. - * **P2**: Important but not urgent. Should be handled by a TSC member or reviewer. - * **P3**: Nice to have but not important. Can be handled by any team member. - * **P4**: A good idea that we'd like to have but may take a while for the team to get to it. - * **P5**: A good idea that the core team can't commit to. Will likely need to be done by an outside contributor. - * Please assign an initial impact assessment (make your best guess): - * **Low**: Doesn't affect many users. - * **Medium**: Affects most users or has a noticeable effect on user experience. - * **High**: Affects a lot of users, is a breaking change, or otherwise will be very noticeable to users. - * If you can't properly triage the issue or pull request, move the issue back to the "Needs Triage" column in the Triage project so someone else can triage it. - * If a pull request references an already accepted issue, move it to the "Implementing" column in the Triage project. - * If you have triaged the issue, move the issue to the "Ready for Dev Team" column in the Triage project. + - **No:** If information is missing from the issue template, or you can't tell what is being requested, please ask the author to provide the missing information: + - Add the "needs info" label to the issue so we know that this issue is stalled due to lack of information. + - Don't move on to other steps until the necessary information has been provided. + - If the issue author hasn't provided the necessary information after 7 days, please close the issue. The bot will add a comment stating that the issue was closed because there was information missing. + - **Yes:** + - If the issue is actually a question (rather than something the dev team needs to change), please [convert it to a discussion](https://docs.github.com/en/free-pro-team@latest/discussions/managing-discussions-for-your-community/moderating-discussions#converting-an-issue-to-a-discussion). You can continue the conversation as a discussion. + - If the issue is reporting a bug, or if a pull request is fixing a bug, try to reproduce the issue following the instructions in the issue. If you can reproduce the bug, please add the "repro:yes" label. (The bot will automatically remove the "repro:needed" label.) If you can't reproduce the bug, ask the author for more information about their environment or to clarify reproduction steps. + - If the issue or pull request is reporting something that works as intended, please add the "works as intended" label and close the issue. + - Please add labels describing the part of ESLint affected: + - **3rd party plugin**: Related to third-party functionality (plugins, parsers, rules, etc.). + - **build**: Related to commands run during a build (testing, linting, release scripts, etc.). + - **cli**: Related to command line input or output, or to `CLIEngine`. + - **core**: Related to internal APIs. + - **documentation**: Related to content on eslint.org. + - **infrastructure**: Related to resources needed for builds or deployment (VMs, CI tools, bots, etc.). + - **rule**: Related to core rules. + - Please assign an initial priority based on the importance of the issue or pull request. If you're not sure, use your best judgment. We can always change the priority later. + - **P1**: Urgent and important, we need to address this immediately. + - **P2**: Important but not urgent. Should be handled by a TSC member or reviewer. + - **P3**: Nice to have but not important. Can be handled by any team member. + - **P4**: A good idea that we'd like to have but may take a while for the team to get to it. + - **P5**: A good idea that the core team can't commit to. Will likely need to be done by an outside contributor. + - Please assign an initial impact assessment (make your best guess): + - **Low**: Doesn't affect many users. + - **Medium**: Affects most users or has a noticeable effect on user experience. + - **High**: Affects a lot of users, is a breaking change, or otherwise will be very noticeable to users. + - If you can't properly triage the issue or pull request, move the issue back to the "Needs Triage" column in the Triage project so someone else can triage it. + - If a pull request references an already accepted issue, move it to the "Implementing" column in the Triage project. + - If you have triaged the issue, move the issue to the "Ready for Dev Team" column in the Triage project. ## Evaluation Process @@ -93,23 +94,23 @@ When an issue has been moved to the "Ready for Dev Team" column, any dev team me 1. Move the issue into the "Evaluating" column. 1. Next steps: - * **Bugs**: If you can verify the bug, add the "accepted" label and ask if they would like to submit a pull request. - * **New Rules**: If you are willing to champion the rule (meaning you believe it should be included in ESLint core and you will take ownership of the process for including it), add a comment saying you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. - * **Rule Changes**: If you are willing to champion the change and it would not be a breaking change (requiring a major version increment), add a comment saying that you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. - * **Breaking Changes**: If you suspect or can verify that a change would be breaking, label it as "Breaking". - * **Duplicates**: If you can verify the issue is a duplicate, add a comment mentioning the duplicate issue (such as, "Duplicate of #1234") and close the issue. + - **Bugs**: If you can verify the bug, add the "accepted" label and ask if they would like to submit a pull request. + - **New Rules**: If you are willing to champion the rule (meaning you believe it should be included in ESLint core and you will take ownership of the process for including it), add a comment saying you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. + - **Rule Changes**: If you are willing to champion the change and it would not be a breaking change (requiring a major version increment), add a comment saying that you will champion the issue, assign the issue to yourself, and follow the [guidelines](#championing-issues) below. + - **Breaking Changes**: If you suspect or can verify that a change would be breaking, label it as "Breaking". + - **Duplicates**: If you can verify the issue is a duplicate, add a comment mentioning the duplicate issue (such as, "Duplicate of #1234") and close the issue. 1. Regardless of the above, always leave a comment. Don't just add labels; engage with the person who opened the issue by asking a question (request more information if necessary) or stating your opinion of the issue. If it's a verified bug, ask if the user would like to submit a pull request. 1. If the issue can't be implemented because it needs an external dependency to be updated or needs to wait for another issue to be resolved, move the issue to the "Blocked" column. 1. If the issue has been accepted and an RFC is required as the next step, move the issue to the "Waiting for RFC" column and comment on the issue that an RFC is needed. -**Note:** "Good first issue" issues are intended to help new contributors feel welcome and empowered to make a contribution to ESLint. To ensure that new contributors are given a chance to work on these issues, issues labeled "good first issue" must be open for 30 days *from the day the issue was labeled* before a team member is permitted to work on them. +**Note:** "Good first issue" issues are intended to help new contributors feel welcome and empowered to make a contribution to ESLint. To ensure that new contributors are given a chance to work on these issues, issues labeled "good first issue" must be open for 30 days _from the day the issue was labeled_ before a team member is permitted to work on them. ## Accepting Issues and Pull Requests Issues may be labeled as "accepted" when the issue is: -* A bug that you've been able to reproduce and verify (i.e. you're sure it's a bug) -* A new rule or rule change that you're championing and [consensus](#consensus) has been reached for its inclusion in the project +- A bug that you've been able to reproduce and verify (i.e. you're sure it's a bug). +- A new rule or rule change that you're championing and [consensus](#consensus) has been reached for its inclusion in the project. The "accepted" label will be added to other issues by a TSC member if it's appropriate for the roadmap. @@ -119,8 +120,8 @@ When an issue is accepted and implementation can begin, it should be moved to th New rules and rule changes require a champion. As champion, it's your job to: -* Gain [consensus](#consensus) from the ESLint team on inclusion -* Guide the rule creation process until it's complete (so only champion a rule that you have time to implement or help another contributor implement) +- Gain [consensus](#consensus) from the ESLint team on inclusion. +- Guide the rule creation process until it's complete (so only champion a rule that you have time to implement or help another contributor implement). Once consensus has been reached on inclusion, add the "accepted" label. Optionally, add "help wanted" and "good first issue" labels, as necessary. @@ -149,6 +150,13 @@ When a suggestion is too ambitious or would take too much time to complete, it's **Breaking Changes:** Be on the lookout for changes that would be breaking. Issues that represent breaking changes should be labeled as "breaking". +## Request Feedback from TSC + +To request feedback from the TSC, team members can ping `@eslint/eslint-tsc` and add the label "tsc waiting" on an issue or pull request. +Unless otherwise requested, every TSC member should provide feedback on [issues labeled "tsc waiting"](https://github.com/issues?q=org%3Aeslint+label%3A"tsc+waiting"). +If a TSC member is unable to respond in a timely manner, they should post a comment indicating when they expect to be able to leave their feedback. +The last TSC member who provides feedback on an issue labeled "tsc waiting" should remove the label. + ## When to Close an Issue All team members are allowed to close issues depending on how the issue has been resolved. @@ -160,11 +168,11 @@ Team members may close an issue **immediately** if: Team members may close an issue where the consensus is to not accept the issue after a waiting period (to ensure that other team members have a chance to review the issue before it is closed): -* Wait **2 days** if the issue was opened Monday through Friday. -* Wait **3 days** if the issue was opened on Saturday or Sunday. +- Wait **2 days** if the issue was opened Monday through Friday. +- Wait **3 days** if the issue was opened on Saturday or Sunday. In an effort to keep the issues backlog manageable, team members may also close an issue if the following conditions are met: -* **Unaccepted**: Close after it has been open for 21 days, as these issues do not have enough support to move forward. -* **Accepted**: Close after 90 days if no one from the team or the community is willing to step forward and own the work to complete to it. -* **Help wanted:** Close after 90 days if it has not been completed. +- **Unaccepted**: Close after it has been open for 21 days, as these issues do not have enough support to move forward. +- **Accepted**: Close after 90 days if no one from the team or the community is willing to step forward and own the work to complete to it. +- **Help wanted:** Close after 90 days if it has not been completed. diff --git a/docs/src/maintain/manage-releases.md b/docs/src/maintain/manage-releases.md index 7a5e763a0095..1f9f516495af 100644 --- a/docs/src/maintain/manage-releases.md +++ b/docs/src/maintain/manage-releases.md @@ -5,13 +5,12 @@ eleventyNavigation: parent: maintain eslint title: Manage Releases order: 4 - --- Releases are when a project formally publishes a new version so the community can use it. There are two types of releases: -* Regular releases that follow [semantic versioning](https://semver.org/) and are considered production-ready. -* Prereleases that are not considered production-ready and are intended to give the community a preview of upcoming changes. +- Regular releases that follow [semantic versioning](https://semver.org/) and are considered production-ready. +- Prereleases that are not considered production-ready and are intended to give the community a preview of upcoming changes. ## Release Manager @@ -19,10 +18,10 @@ One member of the Technical Steering Committee (TSC) is assigned to manage each The release manager is responsible for: -1. The scheduled release on Friday -1. Monitoring issues over the weekend -1. Determining if a patch release is necessary on Monday -1. Publishing the patch release (if necessary) +1. The scheduled release on Friday. +1. Monitoring issues over the weekend. +1. Determining if a patch release is necessary on Monday. +1. Publishing the patch release (if necessary). The release manager should seek input from the whole team on the Monday following a release to double-check if a patch release is necessary. @@ -30,33 +29,18 @@ The release manager needs to have access to ESLint's two-factor authentication f ## Release Communication -Each scheduled release should be associated with a release issue ([example](https://github.com/eslint/eslint/issues/8138)). The release issue is the source of information for the team about the status of a release. Be sure the release issue has the "release" label so that it's easy to find. +Each scheduled release is associated with an autogenerated release issue ([example](https://github.com/eslint/eslint/issues/18151)). The release issue is the source of information for the team about the status of a release and contains a checklist that the release manager should follow. ## Process -On the day of a scheduled release, the release manager should follow these steps: - -1. Review open pull requests to see if any should be merged. In general, you can merge pull requests that: - * Have been open for at least two days and approved (these are just waiting for merge). - * Important pull requests (as determined by the team). You should stop and have people review before merging if they haven't been already. - * Documentation changes. - * Small bugfixes written by a team member. -1. Log into Jenkins and schedule a build for the "ESLint Release" job. -1. Watch the console output of the build on Jenkins. At some point, the build will pause and a link will be produced with an input field for a six-digit 2FA code. -1. Enter the current six-digit 2FA code from your authenticator app. -1. Continue the build and wait for it to finish. -1. Update the release blog post with a "Highlights" section, including new rules and anything else that's important. -1. Make a release announcement in the public chatroom. -1. Make a release announcement on Twitter. -1. Make a release announcement on the release issue. Document any problems that occurred during the release, and remind the team not to merge anything other than documentation changes and bugfixes. Leave the release issue open. -1. Add the `patch release pending` label to the release issue. (When this label is present, `eslint-github-bot` will create a pending status check on non-semver-patch pull requests, to ensure that they aren't accidentally merged while a patch release is pending.) - -All release-related communications occur in the `#team` channel on Discord. +On the day of a scheduled release, the release manager should follow the steps in the release issue. + +All release-related communications occur in a thread in the `#team` channel on Discord. On the Monday following the scheduled release, the release manager needs to determine if a patch release is necessary. A patch release is considered necessary if any of the following occurred since the scheduled release: -* A regression bug is causing people's lint builds to fail when it previously passed. -* Any bug that is causing a lot of problems for users (frequently happens due to new functionality). +- A regression bug is causing people's lint builds to fail when it previously passed. +- Any bug that is causing a lot of problems for users (frequently happens due to new functionality). The patch release decision should be made as early on Monday as possible. If a patch release is necessary, then follow the same steps as the scheduled release process. @@ -64,6 +48,47 @@ In rare cases, a second patch release might be necessary if the release is known After the patch release has been published (or no patch release is necessary), close the release issue and inform the team that they can start merging in semver-minor changes again. +### Release Parameters + +The following tables show examples of the option to select as `RELEASE_TYPE` when starting `eslint-js Release` (the `@eslint/js` package release) and `eslint Release` (the `eslint` package release) jobs on Jenkins to release a new version with the latest features. In both jobs, `main` should be selected as `RELEASE_BRANCH`. + +| **HEAD Version** | **Desired Next Version** | **`eslint-js Release`
`RELEASE_TYPE`** | +| :--------------: | :----------------------: | :---------------------------------------: | +| `9.25.0` | `9.25.1` | `patch` | +| `9.25.0` | `9.26.0` | `minor` | +| `9.25.0` | `10.0.0-alpha.0` | `alpha.0` | +| `10.0.0-alpha.0` | `10.0.0-alpha.1` | `alpha` | +| `10.0.0-alpha.1` | `10.0.0-beta.0` | `beta` | +| `10.0.0-beta.0` | `10.0.0-beta.1` | `beta` | +| `10.0.0-beta.1` | `10.0.0-rc.0` | `rc` | +| `10.0.0-rc.0` | `10.0.0-rc.1` | `rc` | +| `10.0.0-rc.1` | `10.0.0` | `major` | + +| **HEAD Version** | **Desired Next Version** | **`eslint Release`
`RELEASE_TYPE`** | +| :--------------: | :----------------------: | :------------------------------------: | +| `9.25.0` | `9.25.1` or `9.26.0` | `latest` | +| `9.25.0` | `10.0.0-alpha.0` | `alpha` | +| `10.0.0-alpha.0` | `10.0.0-alpha.1` | `alpha` | +| `10.0.0-alpha.1` | `10.0.0-beta.0` | `beta` | +| `10.0.0-beta.0` | `10.0.0-beta.1` | `beta` | +| `10.0.0-beta.1` | `10.0.0-rc.0` | `rc` | +| `10.0.0-rc.0` | `10.0.0-rc.1` | `rc` | +| `10.0.0-rc.1` | `10.0.0` | `latest` | + +When releasing a new version of the previous major line, the option to select as `RELEASE_TYPE` depends on whether the HEAD version is a prerelease or not. In both jobs, the corresponding development branch (for example, `v9.x-dev`) should be selected as `RELEASE_BRANCH`. + +| **HEAD Version** | **Previous Major Line Version** | **Desired Next Version** | **`eslint-js Release`
`RELEASE_TYPE`** | +| :--------------: | :-----------------------------: | :----------------------: | :---------------------------------------: | +| `10.0.0-alpha.0` | `9.25.0` | `9.25.1` | `patch` | +| `10.0.0-alpha.0` | `9.25.0` | `9.26.0` | `minor` | +| `10.0.0` | `9.25.0` | `9.25.1` | `maintenance.patch` | +| `10.0.0` | `9.25.0` | `9.26.0` | `maintenance.minor` | + +| **HEAD Version** | **Previous Major Line Version** | **Desired Next Version** | **`eslint Release`
`RELEASE_TYPE`** | +| :--------------: | :-----------------------------: | :----------------------: | :------------------------------------: | +| `10.0.0-alpha.0` | `9.25.0` | `9.25.1` or `9.26.0` | `latest` | +| `10.0.0` | `9.25.0` | `9.25.1` or `9.26.0` | `maintenance` | + ## Emergency Releases An emergency release is unplanned and isn't the regularly scheduled release or the anticipated patch release. @@ -71,3 +96,12 @@ An emergency release is unplanned and isn't the regularly scheduled release or t In general, we try not to do emergency releases. Even if there is a regression, it's best to wait until Monday to see if any other problems arise so a patch release can fix as many issues as possible. The only real exception is if ESLint is completely unusable by most of the current users. For instance, we once pushed a release that errored for everyone because it was missing some core files. In that case, an emergency release is appropriate. + +## Troubleshooting + +### `npm publish` returns a 404 + +This typically happens due to a permission error related to the npm token. + +- `release-please` uses a granular access token that expires after a year. This token is tied to the `eslintbot` npm account and needs to be regenerated every year in March. If the access token is expired, `npm publish` returns a 404. +- Jenkins uses a classic access token without an expiration date, but it does require a 2FA code to publish. If the 2FA code is incorrect, then `npm publish` returns a 404. diff --git a/docs/src/maintain/overview.md b/docs/src/maintain/overview.md index 362637a8a3ca..d2a2e82453e8 100644 --- a/docs/src/maintain/overview.md +++ b/docs/src/maintain/overview.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: maintain eslint title: How ESLint is Maintained order: 1 - --- This page explains the different roles and structures involved in maintaining ESLint. @@ -26,18 +25,18 @@ The OpenJS Foundation does not participate in the day-to-day functioning of ESLi ESLint is funded through several sources, including: -* [**Open Collective**](https://opencollective.com/eslint): A platform for financing open source projects. -* [**GitHub Sponsors**](https://github.com/sponsors/eslint): A platform for funding open source projects associated with Github. -* [**Tidelift**](https://tidelift.com/subscription/pkg/npm-eslint): A subscription service that lets enterprises manage and fund the open source projects that their organization uses. -* [**Carbon Ads**](https://www.carbonads.net/open-source): Developer-centric advertising provider used on [eslint.org](https://eslint.org/). -* [**Stackaid.us**](https://simulation.stackaid.us/github/eslint/eslint): Tool that developers can use to allocate funding to the open source projects they use. +- [**Open Collective**](https://opencollective.com/eslint): A platform for financing open source projects. +- [**GitHub Sponsors**](https://github.com/sponsors/eslint): A platform for funding open source projects associated with GitHub. +- [**Tidelift**](https://tidelift.com/subscription/pkg/npm-eslint): A subscription service that lets enterprises manage and fund the open source projects that their organization uses. +- [**Carbon Ads**](https://www.carbonads.net/open-source): Developer-centric advertising provider used on [eslint.org](https://eslint.org/). +- [**Stackaid.us**](https://simulation.stackaid.us/github/eslint/eslint): Tool that developers can use to allocate funding to the open source projects they use. ESLint uses this funding for the following purposes: -* Pay team members and contractors -* Fund projects -* Pay for services that keep ESLint running (web hosting, software subscriptions, etc.) -* Provide financial support to our dependencies and ecosystem +- Pay team members and contractors. +- Fund projects. +- Pay for services that keep ESLint running (web hosting, software subscriptions, etc.). +- Provide financial support to our dependencies and ecosystem. ## Joining the Maintainer Team diff --git a/docs/src/maintain/review-pull-requests.md b/docs/src/maintain/review-pull-requests.md index c1711dfd65c3..5e403ec40db3 100644 --- a/docs/src/maintain/review-pull-requests.md +++ b/docs/src/maintain/review-pull-requests.md @@ -5,7 +5,6 @@ eleventyNavigation: parent: maintain eslint title: Review Pull Requests order: 3 - --- Pull requests are submitted frequently and represent our best opportunity to interact with the community. As such, it's important that pull requests are well-reviewed before being merged and that interactions on pull requests are positive. @@ -26,66 +25,91 @@ The bot will add a comment specifying the problems that it finds. You do not nee Once the bot checks have been satisfied, you check the following: -1. Double-check that the commit message tag ("Fix:", "New:", etc.) is correct based on the issue (or, if no issue is referenced, based on the stated problem). +1. Double-check that the pull request title is correct based on the issue (or, if no issue is referenced, based on the stated problem). 1. If the pull request makes a change to core, ensure that an issue exists and the pull request references the issue in the commit message. 1. Does the code follow our conventions (including header comments, JSDoc comments, etc.)? If not, please leave that feedback and reference the [Code Conventions](../contribute/code-conventions) documentation. 1. For code changes: - * Are there tests that verify the change? If not, please ask for them. - * Is documentation needed for the change? If yes, please ask the submitter to add the necessary documentation. + - Are there tests that verify the change? If not, please ask for them. + - Is documentation needed for the change? If yes, please ask the submitter to add the necessary documentation. 1. Are there any automated testing errors? If yes, please ask the submitter to check on them. 1. If you've reviewed the pull request and there are no outstanding issues, leave a comment "LGTM" to indicate your approval. If you would like someone else to verify the change, comment "LGTM but would like someone else to verify." **Note:** If you are a team member and you've left a comment on the pull request, please follow up to verify that your comments have been addressed. -## Who Can Merge a Pull Request +## Required Approvals for Pull Requests -TSC members, Reviewers, Committers, and Website Team Members may merge pull requests, depending on the contents of the pull request. +Any committer, reviewer, or TSC member may approve a pull request, but the approvals required for merging differ based on the type of pull request. -Website Team Members may merge a pull request in the `eslint.org` repository if it is: +One committer approval is required to merge a non-breaking change that is: 1. A documentation change +1. A bug fix (for either rules or core) 1. A dependency upgrade +1. Related to the build 1. A chore -Committers may merge a pull request if it is a non-breaking change and is: +For a non-breaking feature, pull requests require approval from one reviewer or TSC member, plus one additional approval from any other team member. + +For a breaking change, pull requests require an approval from two TSC members. + +::: important +If you approve a pull request and don't merge it, please leave a comment explaining why you didn't merge it. You might say something like, "LGTM. Waiting three days before merging." or "LGTM. Requires TSC member approval before merging." or "LGTM. Would like another review before merging.". +::: + +## Moving a Pull Request Through the Triage Board + +When a pull request is created, whether by a team member or an outside contributor, it is placed in the "Needs Triage" column of the Triage board automatically. The pull request should remain in that column until a team member begins reviewing it. + +If the pull request does not have a related issue, then it should be moved through the normal [triage process for issues](./manage-issues) to be marked as accepted. Once accepted, move the pull request to the "Implementing" column. + +If the pull request does have a related issue, then: + +- If the issue is accepted, move the pull request to the "Implementing" column. +- If the issue is not accepted, move the pull request to the "Evaluating" column until the issue is marked as accepted, at which point move the pull request to "Implementing". + +Once the pull request has one approval, one of three things can happen: + +1. The pull request has the required approvals and the waiting period (see below) has passed so it can be merged. +1. The pull request has the required approvals and the waiting period has not passed, so it should be moved to the "Merge Candidates" column. +1. The pull request requires another approval before it can be merged, so it should be moved to the "Second Review Needed" column. + +When the pull request has a second approval, it should either be merged (if 100% ready) or moved to the "Merge Candidates" column if there are any outstanding concerns that should be reviewed before the next release. + +## Who Can Merge a Pull Request + +TSC members, reviewers, committers, and website team members may merge pull requests, depending on the contents of the pull request, once it has received the required approvals. + +Website Team Members may merge a pull request in the `eslint.org` repository if it is: 1. A documentation change -1. A bug fix (for either rules or core) 1. A dependency upgrade -1. Related to the build tool 1. A chore -In addition, committers may merge any non-breaking pull request if it has been approved by at least one TSC member. - -TSC members may merge all pull requests, including those that committers may merge. - ## When to Merge a Pull Request We use the "Merge" button to merge requests into the repository. Before merging a pull request, verify that: -1. All comments have been addressed -1. Any team members who made comments have verified that their concerns were addressed -1. All automated tests are passing (never merge a pull request with failing tests) +1. All comments have been addressed. +1. Any team members who made comments have verified that their concerns were addressed. +1. All automated tests are passing (never merge a pull request with failing tests). Be sure to say thank you to the submitter before merging, especially if they put a lot of work into the pull request. Team members may merge a pull request immediately if it: -1. Makes a small documentation change -1. Is a chore -1. Fixes a block of other work on the repository (build-related, test-related, dependency-related, etc.) -1. Is an important fix to get into a patch release +1. Makes a small documentation change. +1. Is a chore. +1. Fixes a block of other work on the repository (build-related, test-related, dependency-related, etc.). +1. Is an important fix to get into a patch release. Otherwise, team members should observe a waiting period before merging a pull request: -* Wait **2 days** if the pull request was opened Monday through Friday. -* Wait **3 days** if the pull request was opened on Saturday or Sunday. +- Wait **2 days** if the pull request was opened Monday through Friday. +- Wait **3 days** if the pull request was opened on Saturday or Sunday. The waiting period ensures that other team members have a chance to review the pull request before it is merged. -If the pull request was created from a branch on the `eslint/eslint` repository (as opposed to a fork), delete the branch after merging the pull request. (GitHub will display a "Delete branch" button after the pull request is merged.) - -**Note:** You should not merge your own pull request unless you're received feedback from at least one other team member. +**Note:** You should not merge your pull request unless you receive the required approvals. ## When to Close a Pull Request diff --git a/docs/src/pages/404.html b/docs/src/pages/404.html index ee6413c11437..73eb3039a193 100644 --- a/docs/src/pages/404.html +++ b/docs/src/pages/404.html @@ -6,24 +6,34 @@ ---
-
-
-

- {{ site.404_page.title }} - {{ site.404_page.subtitle }} -

-

- {{ site.404_page.description }} -

- -
+
+
+

+ {{ site.404_page.title }} + {{ site.404_page.subtitle }} +

+

+ {{ site.404_page.description }} +

+ +
-
- -
-
+
+ +
+
diff --git a/docs/src/pages/component-library.html b/docs/src/pages/component-library.html index 555698e8736b..2d15bf474fe4 100644 --- a/docs/src/pages/component-library.html +++ b/docs/src/pages/component-library.html @@ -4,6 +4,6 @@ hook: "component-library" --- -The list of components on the left includes shortcodes, macros and partials used across the Docs. - -Most of the components are shortcodes used in individual doc pages. Usage notes are included for each component. +The list of components on the left includes shortcodes, macros and partials used +across the Docs. Most of the components are shortcodes used in individual doc +pages. Usage notes are included for each component. diff --git a/docs/src/pages/flags.md b/docs/src/pages/flags.md new file mode 100644 index 000000000000..65afe37584e5 --- /dev/null +++ b/docs/src/pages/flags.md @@ -0,0 +1,133 @@ +--- +title: Feature Flags +permalink: /flags/index.html +eleventyNavigation: + key: feature flags + parent: use eslint + title: Feature Flags + order: 6 +--- + +{%- from 'components/npx_tabs.macro.html' import npx_tabs %} + +ESLint ships experimental and future breaking changes behind feature flags to let users opt-in to behavior they want. Flags are used in these situations: + +1. When a feature is experimental and not ready to be enabled for everyone. +1. When a feature is a breaking change that will be formally merged in the next major release, but users may opt-in to that behavior prior to the next major release. + +## Flag Prefixes + +The prefix of a flag indicates its status: + +- `unstable_` indicates that the feature is experimental and the implementation may change before the feature is stabilized. This is a "use at your own risk" feature. +- `v##_` indicates that the feature is stabilized and will be available in the next major release. For example, `v10_some_feature` indicates that this is a breaking change that will be formally released in ESLint v10.0.0. These flags are removed each major release, and further use of them throws an error. + +A feature may move from unstable to being enabled by default without a major release if it is a non-breaking change. + +The following policies apply to `unstable_` flags. + +- When the feature is stabilized + - If enabling the feature by default would be a breaking change, a new `v##_` flag is added as active, and the `unstable_` flag becomes inactive. Further use of the `unstable_` flag automatically enables the `v##_` flag but emits a warning. + - Otherwise, the feature is enabled by default, and the `unstable_` flag becomes inactive. Further use of the `unstable_` flag emits a warning. +- If the feature is abandoned, the `unstable_` flag becomes inactive. Further use of it throws an error. +- All inactive `unstable_` flags are removed each major release, and further use of them throws an error. + +## Active Flags + +The following flags are currently available for use in ESLint. + + + + + + + + + +{%- for name, desc in flags.active -%} + +{%- endfor -%} + +
FlagDescription
{{name}}{{desc}}
+ +## Inactive Flags + +The following flags were once used but are no longer active. + + + + + + + + + + +{%- for name, data in flags.inactive -%} + +{%- endfor -%} + +
FlagDescriptionInactivity Reason
{{name}}{{data.description}}{{data.inactivityReason}}
+ +## How to Use Feature Flags + +Because feature flags are strictly opt-in, you need to manually enable the flags that you want. + +### Enable Feature Flags with the CLI + +On the command line, you can specify feature flags using the `--flag` option. You can specify as many flags as you'd like: + +{{ npx_tabs({ + package: "eslint", + args: ["--flag", "flag_one", "--flag", "flag_two", "file.js"] +}) }} + +### Enable Feature Flags with Environment Variables + +You can also set feature flags using the `ESLINT_FLAGS` environment variable. Multiple flags can be specified as a comma-separated list and are merged with any flags passed on the CLI or in the API. For example, here's how you can add feature flags to your `.bashrc` or `.bash_profile` files: + +```bash +export ESLINT_FLAGS="flag_one,flag_two" +``` + +This approach is especially useful in CI/CD pipelines or when you want to enable the same flags across multiple ESLint commands. + +### Enable Feature Flags with the API + +When using the API, you can pass a `flags` array to both the `ESLint` and `Linter` classes: + +```js +const { ESLint, Linter } = require("eslint"); + +const eslint = new ESLint({ + flags: ["flag_one", "flag_two"], +}); + +const linter = new Linter({ + flags: ["flag_one", "flag_two"], +}); +``` + +::: tip +The `ESLint` class also reads the `ESLINT_FLAGS` environment variable to set flags. +::: + +### Enable Feature Flags in VS Code + +To enable flags in the VS Code ESLint Extension for the editor, specify the flags you'd like in the `eslint.options` setting in your [`settings.json`](https://code.visualstudio.com/docs/configure/settings#_settings-json-file) file: + +```json +{ + "eslint.options": { "flags": ["flag_one", "flag_two"] } +} +``` + +To enable flags in the VS Code ESLint Extension for a lint task, specify the `eslint.lintTask.options` settings: + +```json +{ + "eslint.lintTask.options": "--flag flag_one --flag flag_two ." +} +``` + +{# #} diff --git a/docs/src/pages/pages.11tydata.json b/docs/src/pages/pages.11tydata.json index 228d99ba551c..20344d4b5158 100644 --- a/docs/src/pages/pages.11tydata.json +++ b/docs/src/pages/pages.11tydata.json @@ -1,3 +1,3 @@ { - "hook": "page" + "hook": "page" } diff --git a/docs/src/pages/rules.md b/docs/src/pages/rules.md index 0b475b868fdd..ac5c545c74f6 100644 --- a/docs/src/pages/rules.md +++ b/docs/src/pages/rules.md @@ -17,6 +17,7 @@ Rules in ESLint are grouped by type to help you understand their purpose. Each r index: true, recommended: true, fixable: true, + frozen: true, hasSuggestions: true }) }} @@ -35,6 +36,7 @@ Rules in ESLint are grouped by type to help you understand their purpose. Each r {%- set name_value = the_rule.name -%} {%- set description_value = the_rule.description -%} {%- set isRecommended = the_rule.recommended -%} + {%- set isFrozen = the_rule.frozen -%} {%- set isFixable = the_rule.fixable -%} {%- set isHasSuggestions = the_rule.hasSuggestions -%} @@ -45,10 +47,12 @@ Rules in ESLint are grouped by type to help you understand their purpose. Each r categories: { recommended: isRecommended, fixable: isFixable, + frozen: isFrozen, hasSuggestions: isHasSuggestions } }) }} {%- endfor -%} + {%- endfor -%} {%- if rules.deprecated -%} @@ -58,11 +62,11 @@ Rules in ESLint are grouped by type to help you understand their purpose. Each r {{ rules_categories.deprecated.description | safe }} {%- for the_rule in rules.deprecated -%} - {%- set name_value = the_rule.name -%} - {%- set isReplacedBy = the_rule.replacedBy -%} - {%- set isRecommended = the_rule.recommended -%} - {%- set isFixable = the_rule.fixable -%} - {%- set isHasSuggestions = the_rule.hasSuggestions -%} +{%- set name_value = the_rule.name -%} +{%- set isReplacedBy = the_rule.replacedBy -%} +{%- set isRecommended = the_rule.recommended -%} +{%- set isFixable = the_rule.fixable -%} +{%- set isHasSuggestions = the_rule.hasSuggestions -%} {{ rule({ name: name_value, @@ -74,6 +78,7 @@ Rules in ESLint are grouped by type to help you understand their purpose. Each r hasSuggestions: isHasSuggestions } }) }} + {%- endfor -%} {%- endif -%} @@ -84,14 +89,15 @@ Rules in ESLint are grouped by type to help you understand their purpose. Each r {{ rules_categories.removed.description | safe }} {%- for the_rule in rules.removed -%} - {%- set name_value = the_rule.removed -%} - {%- set isReplacedBy = the_rule.replacedBy -%} +{%- set name_value = the_rule.removed -%} +{%- set isReplacedBy = the_rule.replacedBy -%} {{ rule({ name: name_value, removed: true, replacedBy: isReplacedBy }) }} + {%- endfor -%} {%- endif -%} diff --git a/docs/src/rules/accessor-pairs.md b/docs/src/rules/accessor-pairs.md index b84205d34935..776b116e6d28 100644 --- a/docs/src/rules/accessor-pairs.md +++ b/docs/src/rules/accessor-pairs.md @@ -17,7 +17,7 @@ Here are some examples: ```js // Bad -var o = { +const o = { set a(value) { this.val = value; } @@ -25,7 +25,7 @@ var o = { // Good -var o = { +const p = { set a(value) { this.val = value; }, @@ -61,15 +61,15 @@ Examples of **incorrect** code for the default `{ "setWithoutGet": true }` optio ```js /*eslint accessor-pairs: "error"*/ -var o = { +const q = { set a(value) { this.val = value; } }; -var o = {d: 1}; -Object.defineProperty(o, 'c', { +const r = {d: 1}; +Object.defineProperty(r, 'c', { set: function(value) { this.val = value; } @@ -85,7 +85,7 @@ Examples of **correct** code for the default `{ "setWithoutGet": true }` option: ```js /*eslint accessor-pairs: "error"*/ -var o = { +const s = { set a(value) { this.val = value; }, @@ -94,8 +94,8 @@ var o = { } }; -var o = {d: 1}; -Object.defineProperty(o, 'c', { +const t = {d: 1}; +Object.defineProperty(t, 'c', { set: function(value) { this.val = value; }, @@ -117,27 +117,27 @@ Examples of **incorrect** code for the `{ "getWithoutSet": true }` option: ```js /*eslint accessor-pairs: ["error", { "getWithoutSet": true }]*/ -var o = { +const u = { set a(value) { this.val = value; } }; -var o = { +const v = { get a() { return this.val; } }; -var o = {d: 1}; -Object.defineProperty(o, 'c', { +const w = {d: 1}; +Object.defineProperty(w, 'c', { set: function(value) { this.val = value; } }); -var o = {d: 1}; -Object.defineProperty(o, 'c', { +const x = {d: 1}; +Object.defineProperty(x, 'c', { get: function() { return this.val; } @@ -152,7 +152,7 @@ Examples of **correct** code for the `{ "getWithoutSet": true }` option: ```js /*eslint accessor-pairs: ["error", { "getWithoutSet": true }]*/ -var o = { +const y = { set a(value) { this.val = value; }, @@ -161,8 +161,8 @@ var o = { } }; -var o = {d: 1}; -Object.defineProperty(o, 'c', { +const z = {d: 1}; +Object.defineProperty(z, 'c', { set: function(value) { this.val = value; }, @@ -281,14 +281,14 @@ might not report a missing pair for a getter/setter that has a computed key, lik ```js /*eslint accessor-pairs: "error"*/ -var a = 1; +const z = 1; // no warnings -var o = { - get [a++]() { +const a = { + get [z++]() { return this.val; }, - set [a++](value) { + set [z++](value) { this.val = value; } }; @@ -301,7 +301,7 @@ might not report a missing pair for a getter/setter, like in the following examp /*eslint accessor-pairs: "error"*/ // no warnings -var o = { +const b = { get a() { return this.val; }, diff --git a/docs/src/rules/array-bracket-newline.md b/docs/src/rules/array-bracket-newline.md index 900fea16fb09..ffb9c0d5f7c4 100644 --- a/docs/src/rules/array-bracket-newline.md +++ b/docs/src/rules/array-bracket-newline.md @@ -4,9 +4,6 @@ rule_type: layout related_rules: - array-bracket-spacing --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/array-bracket-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - A number of style guides require or disallow line breaks inside of array brackets. ## Rule Details @@ -35,12 +32,12 @@ Examples of **incorrect** code for this rule with the `"always"` option: ```js /*eslint array-bracket-newline: ["error", "always"]*/ -var a = []; -var b = [1]; -var c = [1, 2]; -var d = [1, +const a = []; +const b = [1]; +const c = [1, 2]; +const d = [1, 2]; -var e = [function foo() { +const e = [function foo() { dosomething(); }]; ``` @@ -54,19 +51,19 @@ Examples of **correct** code for this rule with the `"always"` option: ```js /*eslint array-bracket-newline: ["error", "always"]*/ -var a = [ +const a = [ ]; -var b = [ +const b = [ 1 ]; -var c = [ +const c = [ 1, 2 ]; -var d = [ +const d = [ 1, 2 ]; -var e = [ +const e = [ function foo() { dosomething(); } @@ -84,19 +81,19 @@ Examples of **incorrect** code for this rule with the `"never"` option: ```js /*eslint array-bracket-newline: ["error", "never"]*/ -var a = [ +const a = [ ]; -var b = [ +const b = [ 1 ]; -var c = [ +const c = [ 1, 2 ]; -var d = [ +const d = [ 1, 2 ]; -var e = [ +const e = [ function foo() { dosomething(); } @@ -112,12 +109,12 @@ Examples of **correct** code for this rule with the `"never"` option: ```js /*eslint array-bracket-newline: ["error", "never"]*/ -var a = []; -var b = [1]; -var c = [1, 2]; -var d = [1, +const a = []; +const b = [1]; +const c = [1, 2]; +const d = [1, 2]; -var e = [function foo() { +const e = [function foo() { dosomething(); }]; ``` @@ -133,15 +130,15 @@ Examples of **incorrect** code for this rule with the `"consistent"` option: ```js /*eslint array-bracket-newline: ["error", "consistent"]*/ -var a = [1 +const a = [1 ]; -var b = [ +const b = [ 1]; -var c = [function foo() { +const c = [function foo() { dosomething(); } ] -var d = [ +const d = [ function foo() { dosomething(); }] @@ -156,17 +153,17 @@ Examples of **correct** code for this rule with the `"consistent"` option: ```js /*eslint array-bracket-newline: ["error", "consistent"]*/ -var a = []; -var b = [ +const a = []; +const b = [ ]; -var c = [1]; -var d = [ +const c = [1]; +const d = [ 1 ]; -var e = [function foo() { +const e = [function foo() { dosomething(); }]; -var f = [ +const f = [ function foo() { dosomething(); } @@ -184,17 +181,17 @@ Examples of **incorrect** code for this rule with the default `{ "multiline": tr ```js /*eslint array-bracket-newline: ["error", { "multiline": true }]*/ -var a = [ +const a = [ ]; -var b = [ +const b = [ 1 ]; -var c = [ +const c = [ 1, 2 ]; -var d = [1, +const d = [1, 2]; -var e = [function foo() { +const e = [function foo() { dosomething(); }]; ``` @@ -208,14 +205,14 @@ Examples of **correct** code for this rule with the default `{ "multiline": true ```js /*eslint array-bracket-newline: ["error", { "multiline": true }]*/ -var a = []; -var b = [1]; -var c = [1, 2]; -var d = [ +const a = []; +const b = [1]; +const c = [1, 2]; +const d = [ 1, 2 ]; -var e = [ +const e = [ function foo() { dosomething(); } @@ -233,15 +230,15 @@ Examples of **incorrect** code for this rule with the `{ "minItems": 2 }` option ```js /*eslint array-bracket-newline: ["error", { "minItems": 2 }]*/ -var a = [ +const a = [ ]; -var b = [ +const b = [ 1 ]; -var c = [1, 2]; -var d = [1, +const c = [1, 2]; +const d = [1, 2]; -var e = [ +const e = [ function foo() { dosomething(); } @@ -257,16 +254,16 @@ Examples of **correct** code for this rule with the `{ "minItems": 2 }` option: ```js /*eslint array-bracket-newline: ["error", { "minItems": 2 }]*/ -var a = []; -var b = [1]; -var c = [ +const a = []; +const b = [1]; +const c = [ 1, 2 ]; -var d = [ +const d = [ 1, 2 ]; -var e = [function foo() { +const e = [function foo() { dosomething(); }]; ``` @@ -282,15 +279,15 @@ Examples of **incorrect** code for this rule with the `{ "multiline": true, "min ```js /*eslint array-bracket-newline: ["error", { "multiline": true, "minItems": 2 }]*/ -var a = [ +const a = [ ]; -var b = [ +const b = [ 1 ]; -var c = [1, 2]; -var d = [1, +const c = [1, 2]; +const d = [1, 2]; -var e = [function foo() { +const e = [function foo() { dosomething(); }]; ``` @@ -304,16 +301,16 @@ Examples of **correct** code for this rule with the `{ "multiline": true, "minIt ```js /*eslint array-bracket-newline: ["error", { "multiline": true, "minItems": 2 }]*/ -var a = []; -var b = [1]; -var c = [ +const a = []; +const b = [1]; +const c = [ 1, 2 ]; -var d = [ +const d = [ 1, 2 ]; -var e = [ +const e = [ function foo() { dosomething(); } diff --git a/docs/src/rules/array-bracket-spacing.md b/docs/src/rules/array-bracket-spacing.md index 53fa82cf562b..0b677f4b3dd4 100644 --- a/docs/src/rules/array-bracket-spacing.md +++ b/docs/src/rules/array-bracket-spacing.md @@ -6,15 +6,10 @@ related_rules: - object-curly-spacing - computed-property-spacing --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/array-bracket-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - A number of style guides require or disallow spaces between array brackets and other tokens. This rule applies to both array literals and destructuring assignments (ECMAScript 6). ```js -/*eslint-env es6*/ - var arr = [ 'foo', 'bar' ]; var [ x, y ] = z; @@ -58,7 +53,6 @@ Examples of **incorrect** code for this rule with the default `"never"` option: ```js /*eslint array-bracket-spacing: ["error", "never"]*/ -/*eslint-env es6*/ var arr = [ 'foo', 'bar' ]; var arr = ['foo', 'bar' ]; @@ -81,7 +75,6 @@ Examples of **correct** code for this rule with the default `"never"` option: ```js /*eslint array-bracket-spacing: ["error", "never"]*/ -/*eslint-env es6*/ var arr = []; var arr = ['foo', 'bar', 'baz']; @@ -114,7 +107,6 @@ Examples of **incorrect** code for this rule with the `"always"` option: ```js /*eslint array-bracket-spacing: ["error", "always"]*/ -/*eslint-env es6*/ var arr = ['foo', 'bar']; var arr = ['foo', 'bar' ]; @@ -140,7 +132,6 @@ Examples of **correct** code for this rule with the `"always"` option: ```js /*eslint array-bracket-spacing: ["error", "always"]*/ -/*eslint-env es6*/ var arr = []; var arr = [ 'foo', 'bar', 'baz' ]; diff --git a/docs/src/rules/array-callback-return.md b/docs/src/rules/array-callback-return.md index 625cb14ced31..f606ec010889 100644 --- a/docs/src/rules/array-callback-return.md +++ b/docs/src/rules/array-callback-return.md @@ -9,7 +9,7 @@ If we forget to write `return` statement in a callback of those, it's probably a ```js // example: convert ['a', 'b', 'c'] --> {a: 0, b: 1, c: 2} -var indexMap = myArray.reduce(function(memo, item, index) { +const indexMap = myArray.reduce(function(memo, item, index) { memo[item] = index; }, {}); // Error: cannot set property 'b' of undefined ``` @@ -45,17 +45,17 @@ Examples of **incorrect** code for this rule: ```js /*eslint array-callback-return: "error"*/ -var indexMap = myArray.reduce(function(memo, item, index) { +const indexMap = myArray.reduce(function(memo, item, index) { memo[item] = index; }, {}); -var foo = Array.from(nodes, function(node) { +const foo = Array.from(nodes, function(node) { if (node.tagName === "DIV") { return true; } }); -var bar = foo.filter(function(x) { +const bar = foo.filter(function(x) { if (x) { return true; } else { @@ -73,19 +73,19 @@ Examples of **correct** code for this rule: ```js /*eslint array-callback-return: "error"*/ -var indexMap = myArray.reduce(function(memo, item, index) { +const indexMap = myArray.reduce(function(memo, item, index) { memo[item] = index; return memo; }, {}); -var foo = Array.from(nodes, function(node) { +const foo = Array.from(nodes, function(node) { if (node.tagName === "DIV") { return true; } return false; }); -var bar = foo.map(node => node.getAttribute("id")); +const bar = foo.map(node => node.getAttribute("id")); ``` ::: @@ -98,7 +98,7 @@ This rule accepts a configuration object with three options: * `"checkForEach": false` (default) When set to `true`, rule will also report `forEach` callbacks that return a value. * `"allowVoid": false` (default) When set to `true`, allows `void` in `forEach` callbacks, so rule will not report the return value with a `void` operator. -**Note:** `{ "allowVoid": true }` works only if `checkForEach` option is set to `true`. +**Note:** `{ "allowVoid": true }` works only if `checkForEach` option is set to `true`. ### allowImplicit @@ -108,7 +108,7 @@ Examples of **correct** code for the `{ "allowImplicit": true }` option: ```js /*eslint array-callback-return: ["error", { allowImplicit: true }]*/ -var undefAllTheThings = myArray.map(function(item) { +const undefAllTheThings = myArray.map(function(item) { return; }); ``` diff --git a/docs/src/rules/array-element-newline.md b/docs/src/rules/array-element-newline.md index b3aa11bd2a25..877a1f2ba206 100644 --- a/docs/src/rules/array-element-newline.md +++ b/docs/src/rules/array-element-newline.md @@ -11,9 +11,6 @@ related_rules: - block-spacing - brace-style --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/array-element-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - A number of style guides require or disallow line breaks between array elements. ## Rule Details diff --git a/docs/src/rules/arrow-body-style.md b/docs/src/rules/arrow-body-style.md index 1dc6fcc36e83..3825766325ee 100644 --- a/docs/src/rules/arrow-body-style.md +++ b/docs/src/rules/arrow-body-style.md @@ -31,9 +31,8 @@ Examples of **incorrect** code for this rule with the `"always"` option: ```js /*eslint arrow-body-style: ["error", "always"]*/ -/*eslint-env es6*/ -let foo = () => 0; +const foo = () => 0; ``` ::: @@ -44,12 +43,12 @@ Examples of **correct** code for this rule with the `"always"` option: ```js /*eslint arrow-body-style: ["error", "always"]*/ -/*eslint-env es6*/ -let foo = () => { +const foo = () => { return 0; }; -let bar = (retv, name) => { + +const bar = (retv, name) => { retv[name] = true; return retv; }; @@ -65,12 +64,12 @@ Examples of **incorrect** code for this rule with the default `"as-needed"` opti ```js /*eslint arrow-body-style: ["error", "as-needed"]*/ -/*eslint-env es6*/ -let foo = () => { +const foo = () => { return 0; }; -let bar = () => { + +const bar = () => { return { bar: { foo: 1, @@ -88,26 +87,30 @@ Examples of **correct** code for this rule with the default `"as-needed"` option ```js /*eslint arrow-body-style: ["error", "as-needed"]*/ -/*eslint-env es6*/ -let foo1 = () => 0; -let foo2 = (retv, name) => { +const foo1 = () => 0; + +const foo2 = (retv, name) => { retv[name] = true; return retv; }; -let foo3 = () => ({ + +const foo3 = () => ({ bar: { foo: 1, bar: 2, } }); -let foo4 = () => { bar(); }; -let foo5 = () => {}; -let foo6 = () => { /* do nothing */ }; -let foo7 = () => { + +const foo4 = () => { bar(); }; +const foo5 = () => {}; +const foo6 = () => { /* do nothing */ }; + +const foo7 = () => { // do nothing. }; -let foo8 = () => ({ bar: 0 }); + +const foo8 = () => ({ bar: 0 }); ``` ::: @@ -122,9 +125,10 @@ Examples of **incorrect** code for this rule with the `{ "requireReturnForObject ```js /*eslint arrow-body-style: ["error", "as-needed", { "requireReturnForObjectLiteral": true }]*/ -/*eslint-env es6*/ -let foo = () => ({}); -let bar = () => ({ bar: 0 }); + +const foo = () => ({}); + +const bar = () => ({ bar: 0 }); ``` ::: @@ -135,10 +139,10 @@ Examples of **correct** code for this rule with the `{ "requireReturnForObjectLi ```js /*eslint arrow-body-style: ["error", "as-needed", { "requireReturnForObjectLiteral": true }]*/ -/*eslint-env es6*/ -let foo = () => {}; -let bar = () => { return { bar: 0 }; }; +const foo = () => {}; + +const bar = () => { return { bar: 0 }; }; ``` ::: @@ -151,12 +155,12 @@ Examples of **incorrect** code for this rule with the `"never"` option: ```js /*eslint arrow-body-style: ["error", "never"]*/ -/*eslint-env es6*/ -let foo = () => { +const foo = () => { return 0; }; -let bar = (retv, name) => { + +const bar = (retv, name) => { retv[name] = true; return retv; }; @@ -170,10 +174,10 @@ Examples of **correct** code for this rule with the `"never"` option: ```js /*eslint arrow-body-style: ["error", "never"]*/ -/*eslint-env es6*/ -let foo = () => 0; -let bar = () => ({ foo: 0 }); +const foo = () => 0; + +const bar = () => ({ foo: 0 }); ``` ::: diff --git a/docs/src/rules/arrow-parens.md b/docs/src/rules/arrow-parens.md index d84e1999db83..f93b0dbb4e4f 100644 --- a/docs/src/rules/arrow-parens.md +++ b/docs/src/rules/arrow-parens.md @@ -4,9 +4,6 @@ rule_type: layout further_reading: - https://github.com/airbnb/javascript#arrows--one-arg-parens --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/arrow-parens) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Arrow functions can omit parentheses when they have exactly one parameter. In all other cases the parameter(s) must be wrapped in parentheses. This rule enforces the consistent use of parentheses in arrow functions. @@ -15,8 +12,6 @@ be wrapped in parentheses. This rule enforces the consistent use of parentheses This rule enforces parentheses around arrow function parameters regardless of arity. For example: ```js -/*eslint-env es6*/ - // Bad a => {} @@ -28,8 +23,6 @@ Following this style will help you find arrow functions (`=>`) which may be mist when a comparison such as `>=` was the intent. ```js -/*eslint-env es6*/ - // Bad if (a => 2) { } @@ -42,8 +35,6 @@ if (a >= 2) { The rule can also be configured to discourage the use of parens when they are not required: ```js -/*eslint-env es6*/ - // Bad (a) => {} @@ -72,7 +63,6 @@ Examples of **incorrect** code for this rule with the default `"always"` option: ```js /*eslint arrow-parens: ["error", "always"]*/ -/*eslint-env es6*/ a => {}; a => a; @@ -90,7 +80,6 @@ Examples of **correct** code for this rule with the default `"always"` option: ```js /*eslint arrow-parens: ["error", "always"]*/ -/*eslint-env es6*/ () => {}; (a) => {}; @@ -107,8 +96,6 @@ a.then((foo) => { if (true) {} }); One of the benefits of this option is that it prevents the incorrect use of arrow functions in conditionals: ```js -/*eslint-env es6*/ - var a = 1; var b = 2; // ... @@ -125,8 +112,6 @@ The contents of the `if` statement is an arrow function, not a comparison. If the arrow function is intentional, it should be wrapped in parens to remove ambiguity. ```js -/*eslint-env es6*/ - var a = 1; var b = 0; // ... @@ -141,8 +126,6 @@ if ((a) => b) { The following is another example of this behavior: ```js -/*eslint-env es6*/ - var a = 1, b = 2, c = 3, d = 4; var f = a => b ? c: d; // f = ? @@ -153,8 +136,6 @@ var f = a => b ? c: d; This should be rewritten like so: ```js -/*eslint-env es6*/ - var a = 1, b = 2, c = 3, d = 4; var f = (a) => b ? c: d; ``` @@ -167,7 +148,6 @@ Examples of **incorrect** code for this rule with the `"as-needed"` option: ```js /*eslint arrow-parens: ["error", "as-needed"]*/ -/*eslint-env es6*/ (a) => {}; (a) => a; @@ -188,7 +168,6 @@ Examples of **correct** code for this rule with the `"as-needed"` option: ```js /*eslint arrow-parens: ["error", "as-needed"]*/ -/*eslint-env es6*/ () => {}; a => {}; @@ -215,7 +194,6 @@ Examples of **incorrect** code for the `{ "requireForBlockBody": true }` option: ```js /*eslint arrow-parens: [2, "as-needed", { "requireForBlockBody": true }]*/ -/*eslint-env es6*/ (a) => a; a => {}; @@ -235,7 +213,6 @@ Examples of **correct** code for the `{ "requireForBlockBody": true }` option: ```js /*eslint arrow-parens: [2, "as-needed", { "requireForBlockBody": true }]*/ -/*eslint-env es6*/ (a) => {}; (a) => {'\n'}; diff --git a/docs/src/rules/arrow-spacing.md b/docs/src/rules/arrow-spacing.md index c0e4c3e1e133..cfbd401d09f3 100644 --- a/docs/src/rules/arrow-spacing.md +++ b/docs/src/rules/arrow-spacing.md @@ -2,14 +2,9 @@ title: arrow-spacing rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/arrow-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - This rule normalize style of spacing before/after an arrow function's arrow(`=>`). ```js -/*eslint-env es6*/ - // { "before": true, "after": true } (a) => {} @@ -31,7 +26,6 @@ Examples of **incorrect** code for this rule with the default `{ "before": true, ```js /*eslint arrow-spacing: "error"*/ -/*eslint-env es6*/ ()=> {}; () =>{}; @@ -51,7 +45,6 @@ Examples of **correct** code for this rule with the default `{ "before": true, " ```js /*eslint arrow-spacing: "error"*/ -/*eslint-env es6*/ () => {}; (a) => {}; @@ -67,7 +60,6 @@ Examples of **incorrect** code for this rule with the `{ "before": false, "after ```js /*eslint arrow-spacing: ["error", { "before": false, "after": false }]*/ -/*eslint-env es6*/ () =>{}; (a) => {}; @@ -82,7 +74,6 @@ Examples of **correct** code for this rule with the `{ "before": false, "after": ```js /*eslint arrow-spacing: ["error", { "before": false, "after": false }]*/ -/*eslint-env es6*/ ()=>{}; (a)=>{}; @@ -97,7 +88,6 @@ Examples of **incorrect** code for this rule with the `{ "before": false, "after ```js /*eslint arrow-spacing: ["error", { "before": false, "after": true }]*/ -/*eslint-env es6*/ () =>{}; (a) => {}; @@ -112,7 +102,6 @@ Examples of **correct** code for this rule with the `{ "before": false, "after": ```js /*eslint arrow-spacing: ["error", { "before": false, "after": true }]*/ -/*eslint-env es6*/ ()=> {}; (a)=> {}; diff --git a/docs/src/rules/block-spacing.md b/docs/src/rules/block-spacing.md index 02aec62a6523..37bd3b28e391 100644 --- a/docs/src/rules/block-spacing.md +++ b/docs/src/rules/block-spacing.md @@ -5,9 +5,6 @@ related_rules: - space-before-blocks - brace-style --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/block-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - ## Rule Details This rule enforces consistent spacing inside an open block token and the next token on the same line. This rule also enforces consistent spacing inside a close block token and previous token on the same line. diff --git a/docs/src/rules/brace-style.md b/docs/src/rules/brace-style.md index 4d3932e3ca1c..aa3e7ed48e8d 100644 --- a/docs/src/rules/brace-style.md +++ b/docs/src/rules/brace-style.md @@ -7,9 +7,6 @@ related_rules: further_reading: - https://en.wikipedia.org/wiki/Indent_style --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/brace-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Brace style is closely related to [indent style](https://en.wikipedia.org/wiki/Indent_style) in programming and describes the placement of braces relative to their control statement and body. There are probably a dozen, if not more, brace styles in the world. The *one true brace style* is one of the most common brace styles in JavaScript, in which the opening brace of a block is placed on the same line as its corresponding statement or declaration. For example: @@ -56,13 +53,13 @@ This rule enforces consistent brace style for blocks. This rule has a string option: -* `"1tbs"` (default) enforces one true brace style -* `"stroustrup"` enforces Stroustrup style -* `"allman"` enforces Allman style +* `"1tbs"` (default) enforces one true brace style. +* `"stroustrup"` enforces Stroustrup style. +* `"allman"` enforces Allman style. This rule has an object option for an exception: -* `"allowSingleLine": true` (default `false`) allows the opening and closing braces for a block to be on the *same* line +* `"allowSingleLine": true` (default `false`) allows the opening and closing braces for a block to be on the *same* line. ### 1tbs diff --git a/docs/src/rules/callback-return.md b/docs/src/rules/callback-return.md index 92ec02628f31..5685892aae3d 100644 --- a/docs/src/rules/callback-return.md +++ b/docs/src/rules/callback-return.md @@ -8,9 +8,6 @@ further_reading: - https://web.archive.org/web/20171224042620/https://docs.nodejitsu.com/articles/errors/what-are-the-error-conventions/ --- - -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - The callback pattern is at the heart of most I/O and event-driven programming in JavaScript. diff --git a/docs/src/rules/camelcase.md b/docs/src/rules/camelcase.md index 536fba9439b8..232684351e7e 100644 --- a/docs/src/rules/camelcase.md +++ b/docs/src/rules/camelcase.md @@ -35,7 +35,7 @@ Examples of **incorrect** code for this rule with the default `{ "properties": " import { no_camelcased } from "external-module" -var my_favorite_color = "#112C85"; +const my_favorite_color = "#112C85"; function do_something() { // ... @@ -57,15 +57,15 @@ function baz({ no_camelcased = 'default value' }) { // ... }; -var obj = { +const obj = { my_pref: 1 }; -var { category_id = 1 } = query; +const { category_id = 1 } = query; -var { foo: snake_cased } = bar; +const { foo: snake_cased } = bar; -var { foo: bar_baz = 1 } = quz; +const { foo: bar_baz = 1 } = quz; ``` ::: @@ -79,18 +79,18 @@ Examples of **correct** code for this rule with the default `{ "properties": "al import { no_camelcased as camelCased } from "external-module"; -var myFavoriteColor = "#112C85"; -var _myFavoriteColor = "#112C85"; -var myFavoriteColor_ = "#112C85"; -var MY_FAVORITE_COLOR = "#112C85"; -var foo1 = bar.baz_boom; -var foo2 = { qux: bar.baz_boom }; +const myFavoriteColor = "#112C85"; +const _myFavoriteColor = "#112C85"; +const myFavoriteColor_ = "#112C85"; +const MY_FAVORITE_COLOR = "#112C85"; +const foo1 = bar.baz_boom; +const foo2 = { qux: bar.baz_boom }; obj.do_something(); do_something(); new do_something(); -var { category_id: category } = query; +const { category_id: category } = query; function foo({ isCamelCased }) { // ... @@ -104,11 +104,11 @@ function baz({ isCamelCased = 'default value' }) { // ... }; -var { categoryId = 1 } = query; +const { categoryId = 1 } = query; -var { foo: isCamelCased } = bar; +const { foo: isCamelCased } = bar; -var { foo: isCamelCased = 1 } = quz; +const { foo: camelCasedName = 1 } = quz; ``` @@ -123,7 +123,7 @@ Examples of **correct** code for this rule with the `{ "properties": "never" }` ```js /*eslint camelcase: ["error", {properties: "never"}]*/ -var obj = { +const obj = { my_pref: 1 }; @@ -141,15 +141,15 @@ Examples of **incorrect** code for this rule with the default `{ "ignoreDestruct ```js /*eslint camelcase: "error"*/ -var { category_id } = query; +const { category_id } = query; -var { category_name = 1 } = query; +const { category_name = 1 } = query; -var { category_id: category_title } = query; +const { category_id: category_title } = query; -var { category_id: category_alias } = query; +const { category_id: category_alias } = query; -var { category_id: categoryId, ...other_props } = query; +const { category_id: categoryId, ...other_props } = query; ``` ::: @@ -163,9 +163,9 @@ Examples of **incorrect** code for this rule with the `{ "ignoreDestructuring": ```js /*eslint camelcase: ["error", {ignoreDestructuring: true}]*/ -var { category_id: category_alias } = query; +const { category_id: category_alias } = query; -var { category_id, ...other_props } = query; +const { category_id, ...other_props } = query; ``` ::: @@ -177,11 +177,11 @@ Examples of **correct** code for this rule with the `{ "ignoreDestructuring": tr ```js /*eslint camelcase: ["error", {ignoreDestructuring: true}]*/ -var { category_id } = query; +const { category_id } = query; -var { category_id = 1 } = query; +const { category_name = 1 } = query; -var { category_id: category_id } = query; +const { category_id_name: category_id_name } = query; ``` ::: @@ -195,8 +195,8 @@ Examples of additional **incorrect** code for this rule with the `{ "ignoreDestr ```js /*eslint camelcase: ["error", {ignoreDestructuring: true}]*/ -var { some_property } = obj; // allowed by {ignoreDestructuring: true} -var foo = some_property + 1; // error, ignoreDestructuring does not apply to this statement +const { some_property } = obj; // allowed by {ignoreDestructuring: true} +const foo = some_property + 1; // error, ignoreDestructuring does not apply to this statement ``` ::: @@ -210,7 +210,7 @@ Examples of additional **correct** code for this rule with the `{ "ignoreDestruc ```js /*eslint camelcase: ["error", {ignoreDestructuring: true}]*/ -var { some_property, ...rest } = obj; +const { some_property, ...rest } = obj; // do something with 'rest', nothing with 'some_property' ``` @@ -225,7 +225,7 @@ Examples of additional **correct** code for this rule with the `{ "properties": ```js /*eslint camelcase: ["error", {"properties": "never", ignoreDestructuring: true}]*/ -var { some_property } = obj; +const { some_property } = obj; doSomething({ some_property }); ``` diff --git a/docs/src/rules/capitalized-comments.md b/docs/src/rules/capitalized-comments.md index b5c48854efc3..aea265f278b0 100644 --- a/docs/src/rules/capitalized-comments.md +++ b/docs/src/rules/capitalized-comments.md @@ -41,20 +41,22 @@ Examples of **correct** code for this rule: // 丈 Non-Latin character at beginning of comment -/* eslint semi:off */ -/* eslint-env node */ -/* eslint-disable */ -/* eslint-enable */ /* istanbul ignore next */ /* jscs:enable */ /* jshint asi:true */ /* global foo */ /* globals foo */ /* exported myVar */ -// eslint-disable-line -// eslint-disable-next-line // https://github.com +/* eslint semi:2 */ +/* eslint-disable */ +foo +/* eslint-enable */ +// eslint-disable-next-line +baz +bar // eslint-disable-line + ``` ::: @@ -117,20 +119,22 @@ Examples of **correct** code for this rule: // 丈 Non-Latin character at beginning of comment -/* eslint semi:off */ -/* eslint-env node */ -/* eslint-disable */ -/* eslint-enable */ /* istanbul ignore next */ /* jscs:enable */ /* jshint asi:true */ /* global foo */ /* globals foo */ /* exported myVar */ -// eslint-disable-line -// eslint-disable-next-line // https://github.com +/* eslint semi:2 */ +/* eslint-disable */ +foo +/* eslint-enable */ +// eslint-disable-next-line +baz +bar // eslint-disable-line + ``` ::: diff --git a/docs/src/rules/class-methods-use-this.md b/docs/src/rules/class-methods-use-this.md index 5a12f9af3c93..34a0b58ea610 100644 --- a/docs/src/rules/class-methods-use-this.md +++ b/docs/src/rules/class-methods-use-this.md @@ -50,7 +50,7 @@ class A { A.sayHi(); // => "hi" ``` -Also note in the above examples that if you switch a method to a static method, *instances* of the class that call the static method (`let a = new A(); a.sayHi();`) have to be updated to being a static call (`A.sayHi();`) instead of having the instance of the *class* call the method +Also note in the above examples that if you switch a method to a static method, *instances* of the class that call the static method (`let a = new A(); a.sayHi();`) have to be updated to being a static call (`A.sayHi();`) instead of having the instance of the *class* call the method. ## Rule Details @@ -62,7 +62,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint class-methods-use-this: "error"*/ -/*eslint-env es6*/ class A { foo() { @@ -79,7 +78,7 @@ Examples of **correct** code for this rule: ```js /*eslint class-methods-use-this: "error"*/ -/*eslint-env es6*/ + class A { foo() { this.bar = "Hello World"; // OK, this is used @@ -107,14 +106,16 @@ class C { ## Options -This rule has two options: +This rule has four options: * `"exceptMethods"` allows specified method names to be ignored with this rule. -* `"enforceForClassFields"` enforces that functions used as instance field initializers utilize `this`. (default: `true`) +* `"enforceForClassFields"` enforces that arrow functions and function expressions used as instance field initializers utilize `this`. This also applies to auto-accessor fields (fields declared with the `accessor` keyword) which are part of the [stage 3 decorators proposal](https://github.com/tc39/proposal-decorators). (default: `true`) +* `"ignoreOverrideMethods"` ignores members that are marked with the `override` modifier. (TypeScript only, default: `false`) +* `"ignoreClassesWithImplements"` ignores class members that are defined within a class that `implements` an interface. (TypeScript only) ### exceptMethods -```js +```ts "class-methods-use-this": [, { "exceptMethods": [<...exceptions>] }] ``` @@ -154,11 +155,11 @@ class A { ### enforceForClassFields -```js +```ts "class-methods-use-this": [, { "enforceForClassFields": true | false }] ``` -The `enforceForClassFields` option enforces that arrow functions and function expressions used as instance field initializers utilize `this`. (default: `true`) +The `enforceForClassFields` option enforces that arrow functions and function expressions used as instance field initializers utilize `this`. This also applies to auto-accessor fields (fields declared with the `accessor` keyword) which are part of the [stage 3 decorators proposal](https://github.com/tc39/proposal-decorators). (default: `true`) Examples of **incorrect** code for this rule with the `{ "enforceForClassFields": true }` option (default): @@ -201,3 +202,209 @@ class A { ``` ::: + +Examples of **incorrect** TypeScript code for this rule with the `{ "enforceForClassFields": true }` option (default): + +::: incorrect + +```ts +/*eslint class-methods-use-this: ["error", { "enforceForClassFields": true }] */ + +class A { + foo = () => {} + accessor bar = () => {} +} +``` + +::: + +Examples of **correct** TypeScript code for this rule with the `{ "enforceForClassFields": true }` option (default): + +::: correct + +```ts +/*eslint class-methods-use-this: ["error", { "enforceForClassFields": true }] */ + +class A { + foo = () => {this;} + accessor bar = () => {this;} +} +``` + +::: + +Examples of **correct** TypeScript code for this rule with the `{ "enforceForClassFields": false }` option: + +::: correct + +```ts +/*eslint class-methods-use-this: ["error", { "enforceForClassFields": false }] */ + +class A { + foo = () => {} + accessor bar = () => {} +} +``` + +::: + +### ignoreOverrideMethods + +```ts +"class-methods-use-this": [, { "ignoreOverrideMethods": true | false }] +``` + +The `ignoreOverrideMethods` option ignores members that are marked with the `override` modifier. (default: `false`) + +Examples of **incorrect** TypeScript code for this rule with the `{ "ignoreOverrideMethods": false }` option (default): + +::: incorrect + +```ts +/*eslint class-methods-use-this: ["error", { "ignoreOverrideMethods": false }] */ + +abstract class Base { + abstract method(): void; + abstract property: () => void; +} + +class Derived extends Base { + override method() {} + override property = () => {}; +} +``` + +::: + +Examples of **correct** TypeScript code for this rule with the `{ "ignoreOverrideMethods": false }` option (default): + +::: correct + +```ts +/*eslint class-methods-use-this: ["error", { "ignoreOverrideMethods": false }] */ + +abstract class Base { + abstract method(): void; + abstract property: () => void; +} + +class Derived extends Base { + override method() { + this.foo = "Hello World"; + }; + override property = () => { + this; + }; +} +``` + +::: + +Examples of **correct** TypeScript code for this rule with the `{ "ignoreOverrideMethods": true }` option: + +::: correct + +```ts +/*eslint class-methods-use-this: ["error", { "ignoreOverrideMethods": true }] */ + +abstract class Base { + abstract method(): void; + abstract property: () => void; +} + +class Derived extends Base { + override method() {} + override property = () => {}; +} +``` + +::: + +### ignoreClassesWithImplements + +```ts +"class-methods-use-this": [, { "ignoreClassesWithImplements": "all" | "public-fields" }] +``` + +The `ignoreClassesWithImplements` ignores class members that are defined within a class that `implements` an interface. The option accepts two possible values: + +* `"all"` - Ignores all classes that implement interfaces +* `"public-fields"` - Only ignores public fields in classes that implement interfaces + +Examples of **incorrect** TypeScript code for this rule with the `{ "ignoreClassesWithImplements": "all" }`: + +::: incorrect + +```ts +/*eslint class-methods-use-this: ["error", { "ignoreClassesWithImplements": "all" }] */ + +class Standalone { + method() {} + property = () => {}; +} +``` + +::: + +Examples of **correct** TypeScript code for this rule with the `{ "ignoreClassesWithImplements": "all" }` option: + +::: correct + +```ts +/*eslint class-methods-use-this: ["error", { "ignoreClassesWithImplements": "all" }] */ + +interface Base { + method(): void; +} + +class Derived implements Base { + method() {} + property = () => {}; +} +``` + +::: + +Examples of **incorrect** TypeScript code for this rule with the `{ "ignoreClassesWithImplements": "public-fields" }` option: + +::: incorrect + +```ts +/*eslint class-methods-use-this: ["error", { "ignoreClassesWithImplements": "public-fields" }] */ + +interface Base { + method(): void; +} + +class Derived implements Base { + method() {} + property = () => {}; + + private privateMethod() {} + private privateProperty = () => {}; + + protected protectedMethod() {} + protected protectedProperty = () => {}; +} +``` + +::: + +Examples of **correct** TypeScript code for this rule with the `{ "ignoreClassesWithImplements": "public-fields" }` option: + +::: correct + +```ts +/*eslint class-methods-use-this: ["error", { "ignoreClassesWithImplements": "public-fields" }] */ + +interface Base { + method(): void; +} + +class Derived implements Base { + method() {} + property = () => {}; +} +``` + +::: diff --git a/docs/src/rules/comma-dangle.md b/docs/src/rules/comma-dangle.md index 3b4e888a095c..ed1ff64d7d30 100644 --- a/docs/src/rules/comma-dangle.md +++ b/docs/src/rules/comma-dangle.md @@ -2,9 +2,6 @@ title: comma-dangle rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/comma-dangle) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Trailing commas in object literals are valid according to the ECMAScript 5 (and ECMAScript 3!) spec. However, IE8 (when not in IE8 document mode) and below will throw an error when it encounters trailing commas in JavaScript. ```js @@ -161,7 +158,7 @@ var arr = [1,2,]; foo({ bar: "baz", qux: "quux", -}); +},); ``` ::: diff --git a/docs/src/rules/comma-spacing.md b/docs/src/rules/comma-spacing.md index ffc85f27de3f..27417c4cdf7e 100644 --- a/docs/src/rules/comma-spacing.md +++ b/docs/src/rules/comma-spacing.md @@ -15,9 +15,6 @@ further_reading: - https://www.crockford.com/code.html - https://dojotoolkit.org/reference-guide/1.9/developer/styleguide.html --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/comma-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Spacing around commas improves readability of a list of items. Although most of the style guidelines for languages prescribe adding a space after a comma and not before it, it is subjective to the preferences of a project. ```js diff --git a/docs/src/rules/comma-style.md b/docs/src/rules/comma-style.md index ee24383761a2..f96fb4d0b2d0 100644 --- a/docs/src/rules/comma-style.md +++ b/docs/src/rules/comma-style.md @@ -6,9 +6,6 @@ related_rules: further_reading: - https://gist.github.com/isaacs/357981 --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/comma-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - The Comma Style rule enforces styles for comma-separated lists. There are two comma styles primarily used in JavaScript: * The standard style, in which commas are placed at the end of the current line diff --git a/docs/src/rules/complexity.md b/docs/src/rules/complexity.md index 08dd839f5cd3..bbd69a9347ec 100644 --- a/docs/src/rules/complexity.md +++ b/docs/src/rules/complexity.md @@ -36,7 +36,7 @@ function a(x) { This rule is aimed at reducing code complexity by capping the amount of cyclomatic complexity allowed in a program. As such, it will warn when the cyclomatic complexity crosses the configured threshold (default is `20`). -Examples of **incorrect** code for a maximum of 2: +Examples of **incorrect** code for a maximum of `2`: ::: incorrect @@ -57,11 +57,19 @@ function b() { foo ||= 1; bar &&= 1; } + +function c(a = {}) { // default parameter -> 2nd path + const { b = 'default' } = a; // default value during destructuring -> 3rd path +} + +function d(a) { + return a?.b?.c; // optional chaining with two optional properties creates two additional branches +} ``` ::: -Examples of **correct** code for a maximum of 2: +Examples of **correct** code for a maximum of `2`: ::: correct @@ -85,7 +93,7 @@ function b() { Class field initializers and class static blocks are implicit functions. Therefore, their complexity is calculated separately for each initializer and each static block, and it doesn't contribute to the complexity of the enclosing code. -Examples of additional **incorrect** code for a maximum of 2: +Examples of additional **incorrect** code for a maximum of `2`: ::: incorrect @@ -107,7 +115,7 @@ class D { // this static block has complexity = 3 ::: -Examples of additional **correct** code for a maximum of 2: +Examples of additional **correct** code for a maximum of `2`: ::: correct @@ -140,13 +148,15 @@ function foo() { // this function has complexity = 1 ## Options -Optionally, you may specify a `max` object property: +This rule has a number or object option: -```json -"complexity": ["error", 2] -``` +* `"max"` (default: `20`) enforces a maximum complexity + +* `"variant": "classic" | "modified"` (default: `"classic"`) cyclomatic complexity variant to use -is equivalent to +### max + +Customize the threshold with the `max` property. ```json "complexity": ["error", { "max": 2 }] @@ -154,6 +164,51 @@ is equivalent to **Deprecated:** the object property `maximum` is deprecated. Please use the property `max` instead. +Or use the shorthand syntax: + +```json +"complexity": ["error", 2] +``` + +### variant + +Cyclomatic complexity variant to use: + +* `"classic"` (default) - Classic McCabe cyclomatic complexity +* `"modified"` - Modified cyclomatic complexity + +_Modified cyclomatic complexity_ is the same as the classic cyclomatic complexity, but each `switch` statement only increases the complexity value by `1`, regardless of how many `case` statements it contains. + +Examples of **correct** code for this rule with the `{ "max": 3, "variant": "modified" }` option: + +::: correct + +```js +/*eslint complexity: ["error", {"max": 3, "variant": "modified"}]*/ + +function a(x) { // initial modified complexity is 1 + switch (x) { // switch statement increases modified complexity by 1 + case 1: + 1; + break; + case 2: + 2; + break; + case 3: + if (x === 'foo') { // if block increases modified complexity by 1 + 3; + } + break; + default: + 4; + } +} +``` + +::: + +The classic cyclomatic complexity of the above function is `5`, but the modified cyclomatic complexity is only `3`. + ## When Not To Use It If you can't determine an appropriate complexity limit for your code, then it's best to disable this rule. diff --git a/docs/src/rules/computed-property-spacing.md b/docs/src/rules/computed-property-spacing.md index 8e8b2d5abc04..6f764b721232 100644 --- a/docs/src/rules/computed-property-spacing.md +++ b/docs/src/rules/computed-property-spacing.md @@ -6,15 +6,10 @@ related_rules: - comma-spacing - space-in-parens --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/computed-property-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - While formatting preferences are very personal, a number of style guides require or disallow spaces between computed properties in the following situations: ```js -/*eslint-env es6*/ - var obj = { prop: "value" }; var a = "prop"; var x = obj[a]; // computed property in object member expression @@ -57,7 +52,6 @@ Examples of **incorrect** code for this rule with the default `"never"` option: ```js /*eslint computed-property-spacing: ["error", "never"]*/ -/*eslint-env es6*/ obj[foo ] obj[ 'foo'] @@ -76,7 +70,6 @@ Examples of **correct** code for this rule with the default `"never"` option: ```js /*eslint computed-property-spacing: ["error", "never"]*/ -/*eslint-env es6*/ obj[foo] obj['foo'] @@ -97,7 +90,6 @@ Examples of **incorrect** code for this rule with the `"always"` option: ```js /*eslint computed-property-spacing: ["error", "always"]*/ -/*eslint-env es6*/ obj[foo] var x = {[b]: a} @@ -117,7 +109,6 @@ Examples of **correct** code for this rule with the `"always"` option: ```js /*eslint computed-property-spacing: ["error", "always"]*/ -/*eslint-env es6*/ obj[ foo ] obj[ 'foo' ] @@ -139,7 +130,6 @@ Examples of **incorrect** code for this rule with `"never"` and `{ "enforceForCl ```js /*eslint computed-property-spacing: ["error", "never", { "enforceForClassMembers": true }]*/ -/*eslint-env es6*/ class Foo { [a ]() {} @@ -163,7 +153,6 @@ Examples of **correct** code for this rule with `"never"` and `{ "enforceForClas ```js /*eslint computed-property-spacing: ["error", "never", { "enforceForClassMembers": true }]*/ -/*eslint-env es6*/ class Foo { [a]() {} @@ -187,7 +176,6 @@ Examples of **correct** code for this rule with `"never"` and `{ "enforceForClas ```js /*eslint computed-property-spacing: ["error", "never", { "enforceForClassMembers": false }]*/ -/*eslint-env es6*/ class Foo { [a ]() {} diff --git a/docs/src/rules/consistent-this.md b/docs/src/rules/consistent-this.md index 8f029adaa937..eb85dce08140 100644 --- a/docs/src/rules/consistent-this.md +++ b/docs/src/rules/consistent-this.md @@ -7,7 +7,7 @@ rule_type: suggestion It is often necessary to capture the current execution context in order to make it available subsequently. A prominent example of this are jQuery callbacks: ```js -var that = this; +const that = this; jQuery('li').click(function (event) { // here, "this" is the HTMLElement where the click event occurred that.setFoo(42); @@ -36,9 +36,9 @@ Examples of **incorrect** code for this rule with the default `"that"` option: ```js /*eslint consistent-this: ["error", "that"]*/ -var that = 42; +let that = 42; -var self = this; +let self = this; that = 42; @@ -54,11 +54,11 @@ Examples of **correct** code for this rule with the default `"that"` option: ```js /*eslint consistent-this: ["error", "that"]*/ -var that = this; +let that = this; -var self = 42; +const self = 42; -var self; +let foo; that = this; @@ -69,12 +69,12 @@ foo.bar = this; Examples of **incorrect** code for this rule with the default `"that"` option, if the variable is not initialized: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint consistent-this: ["error", "that"]*/ -var that; +let that; function f() { that = this; } @@ -84,16 +84,27 @@ function f() { Examples of **correct** code for this rule with the default `"that"` option, if the variable is not initialized: +Declaring a variable `that` and assigning `this` to it. + ::: correct ```js /*eslint consistent-this: ["error", "that"]*/ -var that; +let that; that = this; +``` + +::: + +Declaring two variables, `foo` and `that`, with `foo` initialized, and then assigning `this` to `that`. + +::: correct + +```js +/*eslint consistent-this: ["error", "that"]*/ -var foo, that; -foo = 42; +let foo = 42, that; that = this; ``` diff --git a/docs/src/rules/constructor-super.md b/docs/src/rules/constructor-super.md index c6f008f13d6d..bc48b122ede9 100644 --- a/docs/src/rules/constructor-super.md +++ b/docs/src/rules/constructor-super.md @@ -30,7 +30,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint constructor-super: "error"*/ -/*eslint-env es6*/ class A extends B { constructor() { } // Would throw a ReferenceError. @@ -56,7 +55,6 @@ Examples of **correct** code for this rule: ```js /*eslint constructor-super: "error"*/ -/*eslint-env es6*/ class A { constructor() { } diff --git a/docs/src/rules/curly.md b/docs/src/rules/curly.md index 20b2308bc4da..f9aed285b9b6 100644 --- a/docs/src/rules/curly.md +++ b/docs/src/rules/curly.md @@ -96,7 +96,7 @@ while (true) { doSomething(); } -for (var i=0; i < items.length; i++) { +for (let i=0; i < items.length; i++) { doSomething(); } ``` @@ -209,7 +209,7 @@ while (true) { doSomething(); } -for (var i = 0; foo; i++) { +for (let i = 0; foo; i++) { doSomething(); } ``` @@ -243,7 +243,7 @@ if (foo) while (true) doSomething(); -for (var i = 0; foo; i++) +for (let i = 0; foo; i++) doSomething(); ``` diff --git a/docs/src/rules/default-param-last.md b/docs/src/rules/default-param-last.md index 63672a9103ec..c0b3584d25b9 100644 --- a/docs/src/rules/default-param-last.md +++ b/docs/src/rules/default-param-last.md @@ -41,6 +41,34 @@ Examples of **correct** code for this rule: /* eslint default-param-last: ["error"] */ function f(a, b = 0) {} + +function g(a, b = 0, c = 0) {} +``` + +::: + +This rule additionally supports TypeScript type syntax. + +Examples of **incorrect** TypeScript code for this rule: + +::: incorrect + +```ts +/* eslint default-param-last: ["error"] */ + +function h(a = 0, b: number) {} +``` + +::: + +Examples of **correct** TypeScript code for this rule: + +::: correct + +```ts +/* eslint default-param-last: ["error"] */ + +function h(a = 0, b?: number) {} ``` ::: diff --git a/docs/src/rules/dot-location.md b/docs/src/rules/dot-location.md index 5abbd9cb0107..5ec01c6d91f5 100644 --- a/docs/src/rules/dot-location.md +++ b/docs/src/rules/dot-location.md @@ -5,9 +5,6 @@ related_rules: - newline-after-var - dot-notation --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/dot-location) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - JavaScript allows you to place newlines before or after a dot in a member expression. Consistency in placing a newline before or after the dot can greatly increase readability. diff --git a/docs/src/rules/dot-notation.md b/docs/src/rules/dot-notation.md index 7aa8810516bd..bcdd017dc78a 100644 --- a/docs/src/rules/dot-notation.md +++ b/docs/src/rules/dot-notation.md @@ -22,7 +22,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint dot-notation: "error"*/ -var x = foo["bar"]; +const x = foo["bar"]; ``` ::: @@ -34,9 +34,9 @@ Examples of **correct** code for this rule: ```js /*eslint dot-notation: "error"*/ -var x = foo.bar; +const x = foo.bar; -var x = foo[bar]; // Property name is a variable, square-bracket notation required +const y = foo[bar]; // Property name is a variable, square-bracket notation required ``` ::: @@ -57,8 +57,8 @@ Examples of **correct** code for the `{ "allowKeywords": false }` option: ```js /*eslint dot-notation: ["error", { "allowKeywords": false }]*/ -var foo = { "class": "CS 101" } -var x = foo["class"]; // Property name is a reserved word, square-bracket notation required +const foo = { "class": "CS 101" } +const x = foo["class"]; // Property name is a reserved word, square-bracket notation required ``` ::: @@ -91,7 +91,7 @@ Examples of **incorrect** code for the sample `{ "allowPattern": "^[a-z]+(_[a-z] ```js /*eslint dot-notation: ["error", { "allowPattern": "^[a-z]+(_[a-z]+)+$" }]*/ -var data = {}; +const data = {}; data["fooBar"] = 42; ``` @@ -103,7 +103,7 @@ Examples of **correct** code for the sample `{ "allowPattern": "^[a-z]+(_[a-z]+) ```js /*eslint dot-notation: ["error", { "allowPattern": "^[a-z]+(_[a-z]+)+$" }]*/ -var data = {}; +const data = {}; data["foo_bar"] = 42; ``` diff --git a/docs/src/rules/eol-last.md b/docs/src/rules/eol-last.md index afcd150507e7..8fb634a2877f 100644 --- a/docs/src/rules/eol-last.md +++ b/docs/src/rules/eol-last.md @@ -2,9 +2,6 @@ title: eol-last rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/eol-last) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Trailing newlines in non-empty files are a common UNIX idiom. Benefits of trailing newlines include the ability to concatenate or append to files as well as output files to the terminal without interfering with shell prompts. diff --git a/docs/src/rules/eqeqeq.md b/docs/src/rules/eqeqeq.md index 3243844f1020..0c0955dbfb7a 100644 --- a/docs/src/rules/eqeqeq.md +++ b/docs/src/rules/eqeqeq.md @@ -89,17 +89,17 @@ foo === null This rule optionally takes a second argument, which should be an object with the following supported properties: * `"null"`: Customize how this rule treats `null` literals. Possible values: - * `always` (default) - Always use === or !==. - * `never` - Never use === or !== with `null`. + * `always` (default) - Always use `===` or `!==`. + * `never` - Never use `===` or `!==` with `null`. * `ignore` - Do not apply this rule to `null`. ### smart The `"smart"` option enforces the use of `===` and `!==` except for these cases: -* Comparing two literal values -* Evaluating the value of `typeof` -* Comparing against `null` +* Comparing two literal values. +* Evaluating the value of `typeof`. +* Comparing against `null`. Examples of **incorrect** code for the `"smart"` option: diff --git a/docs/src/rules/for-direction.md b/docs/src/rules/for-direction.md index aa09cb181e36..219e97526175 100644 --- a/docs/src/rules/for-direction.md +++ b/docs/src/rules/for-direction.md @@ -15,16 +15,16 @@ Examples of **incorrect** code for this rule: ```js /*eslint for-direction: "error"*/ -for (var i = 0; i < 10; i--) { +for (let i = 0; i < 10; i--) { } -for (var i = 10; i >= 0; i++) { +for (let i = 10; i >= 0; i++) { } -for (var i = 0; i > 10; i++) { +for (let i = 0; i > 10; i++) { } -for (var i = 0; 10 > i; i--) { +for (let i = 0; 10 > i; i--) { } const n = -2; @@ -40,10 +40,10 @@ Examples of **correct** code for this rule: ```js /*eslint for-direction: "error"*/ -for (var i = 0; i < 10; i++) { +for (let i = 0; i < 10; i++) { } -for (var i = 0; 10 > i; i++) { // with counter "i" on the right +for (let i = 0; 10 > i; i++) { // with counter "i" on the right } for (let i = 10; i >= 0; i += this.step) { // direction unknown diff --git a/docs/src/rules/func-call-spacing.md b/docs/src/rules/func-call-spacing.md index 16033ef7d5cf..ef8ee64ea040 100644 --- a/docs/src/rules/func-call-spacing.md +++ b/docs/src/rules/func-call-spacing.md @@ -4,9 +4,6 @@ rule_type: layout related_rules: - no-spaced-func --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/function-call-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - When calling a function, developers may insert optional whitespace between the function's name and the parentheses that invoke it. The following pairs of function calls are equivalent: ```js diff --git a/docs/src/rules/func-name-matching.md b/docs/src/rules/func-name-matching.md index 8ef1a1c8540b..89cdcbde104b 100644 --- a/docs/src/rules/func-name-matching.md +++ b/docs/src/rules/func-name-matching.md @@ -15,11 +15,11 @@ Examples of **incorrect** code for this rule: ```js /*eslint func-name-matching: "error"*/ -var foo = function bar() {}; +let foo = function bar() {}; foo = function bar() {}; +const obj = {foo: function bar() {}}; obj.foo = function bar() {}; obj['foo'] = function bar() {}; -var obj = {foo: function bar() {}}; ({['foo']: function bar() {}}); class C { @@ -34,11 +34,11 @@ class C { ```js /*eslint func-name-matching: ["error", "never"] */ -var foo = function foo() {}; +let foo = function foo() {}; foo = function foo() {}; +const obj = {foo: function foo() {}}; obj.foo = function foo() {}; obj['foo'] = function foo() {}; -var obj = {foo: function foo() {}}; ({['foo']: function foo() {}}); class C { @@ -54,26 +54,25 @@ Examples of **correct** code for this rule: ```js /*eslint func-name-matching: "error"*/ -/*eslint func-name-matching: ["error", "always"]*/ // these are equivalent -/*eslint-env es6*/ +// equivalent to /*eslint func-name-matching: ["error", "always"]*/ -var foo = function foo() {}; -var foo = function() {}; -var foo = () => {}; +const foo = function foo() {}; +const foo1 = function() {}; +const foo2 = () => {}; foo = function foo() {}; +const obj = {foo: function foo() {}}; obj.foo = function foo() {}; obj['foo'] = function foo() {}; obj['foo//bar'] = function foo() {}; obj[foo] = function bar() {}; -var obj = {foo: function foo() {}}; -var obj = {[foo]: function bar() {}}; -var obj = {'foo//bar': function foo() {}}; -var obj = {foo: function() {}}; +const obj1 = {[foo]: function bar() {}}; +const obj2 = {'foo//bar': function foo() {}}; +const obj3 = {foo: function() {}}; obj['x' + 2] = function bar(){}; -var [ bar ] = [ function bar(){} ]; +const [ bar ] = [ function bar(){} ]; ({[foo]: function bar() {}}) class C { @@ -101,25 +100,25 @@ module['exports'] = function foo(name) {}; ```js /*eslint func-name-matching: ["error", "never"] */ -/*eslint-env es6*/ -var foo = function bar() {}; -var foo = function() {}; -var foo = () => {}; +let foo = function bar() {}; +const foo1 = function() {}; +const foo2 = () => {}; foo = function bar() {}; +const obj = {foo: function bar() {}}; obj.foo = function bar() {}; obj['foo'] = function bar() {}; obj['foo//bar'] = function foo() {}; obj[foo] = function foo() {}; -var obj = {foo: function bar() {}}; -var obj = {[foo]: function foo() {}}; -var obj = {'foo//bar': function foo() {}}; -var obj = {foo: function() {}}; +const obj1 = {foo: function bar() {}}; +const obj2 = {[foo]: function foo() {}}; +const obj3 = {'foo//bar': function foo() {}}; +const obj4 = {foo: function() {}}; obj['x' + 2] = function bar(){}; -var [ bar ] = [ function bar(){} ]; +const [ bar ] = [ function bar(){} ]; ({[foo]: function bar() {}}) class C { @@ -157,8 +156,8 @@ Examples of **correct** code for the `{ considerPropertyDescriptor: true }` opti ```js /*eslint func-name-matching: ["error", { "considerPropertyDescriptor": true }]*/ -/*eslint func-name-matching: ["error", "always", { "considerPropertyDescriptor": true }]*/ // these are equivalent -var obj = {}; +// equivalent to /*eslint func-name-matching: ["error", "always", { "considerPropertyDescriptor": true }]*/ +const obj = {}; Object.create(obj, {foo:{value: function foo() {}}}); Object.defineProperty(obj, 'bar', {value: function bar() {}}); Object.defineProperties(obj, {baz:{value: function baz() {} }}); @@ -173,8 +172,8 @@ Examples of **incorrect** code for the `{ considerPropertyDescriptor: true }` op ```js /*eslint func-name-matching: ["error", { "considerPropertyDescriptor": true }]*/ -/*eslint func-name-matching: ["error", "always", { "considerPropertyDescriptor": true }]*/ // these are equivalent -var obj = {}; +// equivalent to /*eslint func-name-matching: ["error", "always", { "considerPropertyDescriptor": true }]*/ +const obj = {}; Object.create(obj, {foo:{value: function bar() {}}}); Object.defineProperty(obj, 'bar', {value: function baz() {}}); Object.defineProperties(obj, {baz:{value: function foo() {} }}); @@ -193,7 +192,7 @@ Examples of **incorrect** code for the `{ includeCommonJSModuleExports: true }` ```js /*eslint func-name-matching: ["error", { "includeCommonJSModuleExports": true }]*/ -/*eslint func-name-matching: ["error", "always", { "includeCommonJSModuleExports": true }]*/ // these are equivalent +// equivalent to /*eslint func-name-matching: ["error", "always", { "includeCommonJSModuleExports": true }]*/ module.exports = function foo(name) {}; module['exports'] = function foo(name) {}; diff --git a/docs/src/rules/func-names.md b/docs/src/rules/func-names.md index 549806d8c26d..370f621bca36 100644 --- a/docs/src/rules/func-names.md +++ b/docs/src/rules/func-names.md @@ -113,7 +113,7 @@ Examples of **correct** code for this rule with the `"as-needed"` option: ```js /*eslint func-names: ["error", "as-needed"]*/ -var bar = function() {}; +const bar = function() {}; const cat = { meow: function() {} @@ -192,7 +192,7 @@ Examples of **correct** code for this rule with the `"always", { "generators": " ```js /*eslint func-names: ["error", "always", { "generators": "as-needed" }]*/ -var foo = function*() {}; +const foo = function*() {}; ``` ::: @@ -204,7 +204,7 @@ Examples of **incorrect** code for this rule with the `"always", { "generators": ```js /*eslint func-names: ["error", "always", { "generators": "never" }]*/ -var foo = bar(function *baz() {}); +const foo = bar(function *baz() {}); ``` ::: @@ -216,7 +216,7 @@ Examples of **correct** code for this rule with the `"always", { "generators": " ```js /*eslint func-names: ["error", "always", { "generators": "never" }]*/ -var foo = bar(function *() {}); +const foo = bar(function *() {}); ``` ::: @@ -228,7 +228,7 @@ Examples of **incorrect** code for this rule with the `"as-needed", { "generator ```js /*eslint func-names: ["error", "as-needed", { "generators": "never" }]*/ -var foo = bar(function *baz() {}); +const foo = bar(function *baz() {}); ``` ::: @@ -240,7 +240,7 @@ Examples of **correct** code for this rule with the `"as-needed", { "generators" ```js /*eslint func-names: ["error", "as-needed", { "generators": "never" }]*/ -var foo = bar(function *() {}); +const foo = bar(function *() {}); ``` ::: @@ -252,7 +252,7 @@ Examples of **incorrect** code for this rule with the `"never", { "generators": ```js /*eslint func-names: ["error", "never", { "generators": "always" }]*/ -var foo = bar(function *() {}); +const foo = bar(function *() {}); ``` ::: @@ -264,7 +264,7 @@ Examples of **correct** code for this rule with the `"never", { "generators": "a ```js /*eslint func-names: ["error", "never", { "generators": "always" }]*/ -var foo = bar(function *baz() {}); +const foo = bar(function *baz() {}); ``` ::: diff --git a/docs/src/rules/func-style.md b/docs/src/rules/func-style.md index 05b667790d43..4c27df8a4cb1 100644 --- a/docs/src/rules/func-style.md +++ b/docs/src/rules/func-style.md @@ -5,52 +5,54 @@ further_reading: - https://www.adequatelygood.com/JavaScript-Scoping-and-Hoisting.html --- - -There are two ways of defining functions in JavaScript: `function` declarations and `function` expressions. Declarations contain the `function` keyword first, followed by a name and then its arguments and the function body, for example: +There are two ways of defining functions in JavaScript: `function` declarations and function expressions assigned to variables. Function declarations are statements that begin with the `function` keyword. Function expressions can either be arrow functions or use the `function` keyword with an optional name. Here are some examples: ```js +// function declaration function doSomething() { // ... } -``` -Equivalent function expressions begin with the `var` keyword, followed by a name and then the function itself, such as: +// arrow function expression assigned to a variable +const doSomethingElse = () => { + // ... +}; -```js -var doSomething = function() { +// function expression assigned to a variable +const doSomethingAgain = function() { // ... }; ``` -The primary difference between `function` declarations and `function expressions` is that declarations are *hoisted* to the top of the scope in which they are defined, which allows you to write code that uses the function before its declaration. For example: +The primary difference between `function` declarations and function expressions is that declarations are *hoisted* to the top of the scope in which they are defined, which allows you to write code that uses the function before its declaration. For example: ```js -doSomething(); +doSomething(); // ok function doSomething() { // ... } ``` -Although this code might seem like an error, it actually works fine because JavaScript engines hoist the `function` declarations to the top of the scope. That means this code is treated as if the declaration came before the invocation. - -For `function` expressions, you must define the function before it is used, otherwise it causes an error. Example: +For function expressions, you must define the function before it is used, otherwise it causes an error. Example: ```js doSomething(); // error! -var doSomething = function() { +const doSomething = function() { // ... }; ``` -In this case, `doSomething()` is undefined at the time of invocation and so causes a runtime error. +In this case, `doSomething` is `undefined` at the time of invocation and so causes a runtime error. Due to these different behaviors, it is common to have guidelines as to which style of function should be used. There is really no correct or incorrect choice here, it is just a preference. ## Rule Details -This rule enforces a particular type of `function` style throughout a JavaScript file, either declarations or expressions. You can specify which you prefer in the configuration. +This rule enforces a particular type of function style, either `function` declarations or expressions assigned to variables. You can specify which you prefer in the configuration. + +Note: This rule does not apply to *all* functions. For example, a callback function passed as an argument to another function is not considered by this rule. ## Options @@ -59,9 +61,15 @@ This rule has a string option: * `"expression"` (default) requires the use of function expressions instead of function declarations * `"declaration"` requires the use of function declarations instead of function expressions -This rule has an object option for an exception: +This rule has an object option for two exceptions: * `"allowArrowFunctions"`: `true` (default `false`) allows the use of arrow functions. This option applies only when the string option is set to `"declaration"` (arrow functions are always allowed when the string option is set to `"expression"`, regardless of this option) +* `"allowTypeAnnotation"`: `true` (default `false`) allows the use of function expressions and arrow functions when the variable declaration has type annotation, regardless of the `allowArrowFunctions` option. This option applies only when the string option is set to `"declaration"`. (TypeScript only) +* `"overrides"`: + * `"namedExports": "expression" | "declaration" | "ignore"`: used to override function styles in named exports + * `"expression"`: like string option + * `"declaration"`: like string option + * `"ignore"`: either style is acceptable ### expression @@ -86,17 +94,35 @@ Examples of **correct** code for this rule with the default `"expression"` optio ```js /*eslint func-style: ["error", "expression"]*/ -var foo = function() { +const foo = function() { // ... }; -var foo = () => {}; +const foo1 = () => {}; // allowed as allowArrowFunctions : false is applied only for declaration ``` ::: +Overloaded function declarations are not reported as errors by this rule. These are functions that have multiple declarations with the same name but different parameter types or return types (commonly used in TypeScript to provide type information for different ways of calling the same function). + +Examples of **correct** TypeScript code for this rule with the default `"expression"` option: + +::: correct + +```ts +/*eslint func-style: ["error", "expression"]*/ + +function process(value: string): string; +function process(value: number): number; +function process(value: unknown) { + return value; +} +``` + +::: + ### declaration Examples of **incorrect** code for this rule with the `"declaration"` option: @@ -106,11 +132,11 @@ Examples of **incorrect** code for this rule with the `"declaration"` option: ```js /*eslint func-style: ["error", "declaration"]*/ -var foo = function() { +const foo = function() { // ... }; -var foo = () => {}; +const foo1 = () => {}; ``` ::: @@ -143,7 +169,139 @@ Examples of additional **correct** code for this rule with the `"declaration", { ```js /*eslint func-style: ["error", "declaration", { "allowArrowFunctions": true }]*/ -var foo = () => {}; +const foo = () => {}; +``` + +::: + +### allowTypeAnnotation + +Examples of **incorrect** TypeScript code for this rule with the `"declaration", { "allowTypeAnnotation": true }` options: + +::: incorrect + +```ts +/*eslint func-style: ["error", "declaration", { "allowTypeAnnotation": true }]*/ + +const foo = function(): void {}; +``` + +::: + +Examples of **correct** TypeScript code for this rule with the `"declaration", { "allowTypeAnnotation": true }` options: + +::: correct + +```ts +/*eslint func-style: ["error", "declaration", { "allowTypeAnnotation": true }]*/ + +type Fn = () => undefined; + +const foo: Fn = function() {}; + +const bar: Fn = () => {}; +``` + +::: + +### overrides + +#### namedExports + +##### expression + +Examples of **incorrect** code for this rule with the `"declaration"` and `{"overrides": { "namedExports": "expression" }}` option: + +::: incorrect + +```js +/*eslint func-style: ["error", "declaration", { "overrides": { "namedExports": "expression" } }]*/ + +export function foo() { + // ... +} +``` + +::: + +Examples of **correct** code for this rule with the `"declaration"` and `{"overrides": { "namedExports": "expression" }}` option: + +::: correct + +```js +/*eslint func-style: ["error", "declaration", { "overrides": { "namedExports": "expression" } }]*/ + +export const foo = function() { + // ... +}; + +export const bar = () => {}; +``` + +::: + +##### declaration + +Examples of **incorrect** code for this rule with the `"expression"` and `{"overrides": { "namedExports": "declaration" }}` option: + +::: incorrect + +```js +/*eslint func-style: ["error", "expression", { "overrides": { "namedExports": "declaration" } }]*/ + +export const foo = function() { + // ... +}; + +export const bar = () => {}; +``` + +::: + +Examples of **correct** code for this rule with the `"expression"` and `{"overrides": { "namedExports": "declaration" }}` option: + +::: correct + +```js +/*eslint func-style: ["error", "expression", { "overrides": { "namedExports": "declaration" } }]*/ + +export function foo() { + // ... +} +``` + +::: + +Examples of **correct** code for this rule with the `"expression"` and `{ "allowTypeAnnotation": true, "overrides": { "namedExports": "declaration" }}` option: + +::: correct + +```ts +/*eslint func-style: ["error", "expression", { "allowTypeAnnotation": true, "overrides": { "namedExports": "declaration" } }]*/ + +export const foo: () => void = function () {} +``` + +::: + +##### ignore + +Examples of **correct** code for this rule with the `{"overrides": { "namedExports": "ignore" }}` option: + +::: correct + +```js +/*eslint func-style: ["error", "expression", { "overrides": { "namedExports": "ignore" } }]*/ + +export const foo = function() { + // ... +}; + +export const bar = () => {}; + +export function baz() { + // ... +} ``` ::: diff --git a/docs/src/rules/function-call-argument-newline.md b/docs/src/rules/function-call-argument-newline.md index 9c20a28313ef..5baacaabd38e 100644 --- a/docs/src/rules/function-call-argument-newline.md +++ b/docs/src/rules/function-call-argument-newline.md @@ -7,9 +7,6 @@ related_rules: - object-property-newline - array-element-newline --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/function-call-argument-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - A number of style guides require or disallow line breaks between arguments of a function call. ## Rule Details diff --git a/docs/src/rules/function-paren-newline.md b/docs/src/rules/function-paren-newline.md index 09d3ca973e18..858eae9132c2 100644 --- a/docs/src/rules/function-paren-newline.md +++ b/docs/src/rules/function-paren-newline.md @@ -2,9 +2,6 @@ title: function-paren-newline rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/function-paren-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Many style guides require or disallow newlines inside of function parentheses. ## Rule Details diff --git a/docs/src/rules/generator-star-spacing.md b/docs/src/rules/generator-star-spacing.md index 2157da18fe8d..a23ce3cef3b3 100644 --- a/docs/src/rules/generator-star-spacing.md +++ b/docs/src/rules/generator-star-spacing.md @@ -4,17 +4,12 @@ rule_type: layout further_reading: - https://leanpub.com/understandinges6/read/#leanpub-auto-generators --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/generator-star-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Generators are a new type of function in ECMAScript 6 that can return multiple values over time. These special functions are indicated by placing an `*` after the `function` keyword. Here is an example of a generator function: ```js -/*eslint-env es6*/ - function* generator() { yield "44"; yield "55"; @@ -24,8 +19,6 @@ function* generator() { This is also valid: ```js -/*eslint-env es6*/ - function *generator() { yield "44"; yield "55"; @@ -35,8 +28,6 @@ function *generator() { This is valid as well: ```js -/*eslint-env es6*/ - function * generator() { yield "44"; yield "55"; @@ -113,7 +104,6 @@ Examples of **correct** code for this rule with the `"before"` option: ```js /*eslint generator-star-spacing: ["error", {"before": true, "after": false}]*/ -/*eslint-env es6*/ function *generator() {} @@ -132,7 +122,6 @@ Examples of **correct** code for this rule with the `"after"` option: ```js /*eslint generator-star-spacing: ["error", {"before": false, "after": true}]*/ -/*eslint-env es6*/ function* generator() {} @@ -151,7 +140,6 @@ Examples of **correct** code for this rule with the `"both"` option: ```js /*eslint generator-star-spacing: ["error", {"before": true, "after": true}]*/ -/*eslint-env es6*/ function * generator() {} @@ -170,7 +158,6 @@ Examples of **correct** code for this rule with the `"neither"` option: ```js /*eslint generator-star-spacing: ["error", {"before": false, "after": false}]*/ -/*eslint-env es6*/ function*generator() {} @@ -192,7 +179,6 @@ Examples of **incorrect** code for this rule with overrides present: "anonymous": "neither", "method": {"before": true, "after": true} }]*/ -/*eslint-env es6*/ function * generator() {} @@ -216,7 +202,6 @@ Examples of **correct** code for this rule with overrides present: "anonymous": "neither", "method": {"before": true, "after": true} }]*/ -/*eslint-env es6*/ function* generator() {} diff --git a/docs/src/rules/generator-star.md b/docs/src/rules/generator-star.md index 4a79edd18204..0ece6e7b0b4c 100644 --- a/docs/src/rules/generator-star.md +++ b/docs/src/rules/generator-star.md @@ -16,8 +16,6 @@ These special functions are indicated by placing an `*` after the `function` key Here is an example of a generator function: ```js -/*eslint-env es6*/ - function* generator() { yield "44"; yield "55"; @@ -27,8 +25,6 @@ function* generator() { This is also valid: ```js -/*eslint-env es6*/ - function *generator() { yield "44"; yield "55"; @@ -38,8 +34,6 @@ function *generator() { This is valid as well: ```js -/*eslint-env es6*/ - function * generator() { yield "44"; yield "55"; @@ -63,8 +57,6 @@ You can set the style in configuration like this: When using `"start"` this placement will be enforced: ```js -/*eslint-env es6*/ - function* generator() { } ``` @@ -72,8 +64,6 @@ function* generator() { When using `"middle"` this placement will be enforced: ```js -/*eslint-env es6*/ - function * generator() { } ``` @@ -81,8 +71,6 @@ function * generator() { When using `"end"` this placement will be enforced: ```js -/*eslint-env es6*/ - function *generator() { } ``` @@ -90,8 +78,6 @@ function *generator() { When using the expression syntax `"start"` will be enforced here: ```js -/*eslint-env es6*/ - var generator = function* () { } ``` @@ -99,8 +85,6 @@ var generator = function* () { When using the expression syntax `"middle"` will be enforced here: ```js -/*eslint-env es6*/ - var generator = function * () { } ``` @@ -108,8 +92,6 @@ var generator = function * () { When using the expression syntax `"end"` will be enforced here: ```js -/*eslint-env es6*/ - var generator = function *() { } ``` @@ -117,8 +99,6 @@ var generator = function *() { When using the expression syntax this is valid for both `"start"` and `"end"`: ```js -/*eslint-env es6*/ - var generator = function*() { } ``` diff --git a/docs/src/rules/getter-return.md b/docs/src/rules/getter-return.md index 9d316303d025..aaf4ea3eba2c 100644 --- a/docs/src/rules/getter-return.md +++ b/docs/src/rules/getter-return.md @@ -12,7 +12,7 @@ further_reading: The get syntax binds an object property to a function that will be called when that property is looked up. It was first introduced in ECMAScript 5: ```js -var p = { +const p = { get name(){ return "nicholas"; } @@ -38,7 +38,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint getter-return: "error"*/ -p = { +const p = { get name(){ // no returns. } @@ -66,7 +66,7 @@ Examples of **correct** code for this rule: ```js /*eslint getter-return: "error"*/ -p = { +const p = { get name(){ return "nicholas"; } @@ -99,7 +99,7 @@ Examples of **correct** code for the `{ "allowImplicit": true }` option: ```js /*eslint getter-return: ["error", { allowImplicit: true }]*/ -p = { +const p = { get name(){ return; // return undefined implicitly. } diff --git a/docs/src/rules/global-require.md b/docs/src/rules/global-require.md index 8460acb89ad1..bdf563fb020e 100644 --- a/docs/src/rules/global-require.md +++ b/docs/src/rules/global-require.md @@ -3,9 +3,6 @@ title: global-require rule_type: suggestion --- - -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - In Node.js, module dependencies are included using the `require()` function, such as: ```js @@ -36,7 +33,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint global-require: "error"*/ -/*eslint-env es6*/ // calling require() inside of a function is not allowed function readFile(filename, callback) { diff --git a/docs/src/rules/grouped-accessor-pairs.md b/docs/src/rules/grouped-accessor-pairs.md index cfb274d6c1b3..61ee8c485dd7 100644 --- a/docs/src/rules/grouped-accessor-pairs.md +++ b/docs/src/rules/grouped-accessor-pairs.md @@ -17,7 +17,7 @@ A getter and setter for the same property don't necessarily have to be defined a For example, the following statements would create the same object: ```js -var o = { +const o = { get a() { return this.val; }, @@ -27,7 +27,7 @@ var o = { b: 1 }; -var o = { +const o1 = { get a() { return this.val; }, @@ -57,7 +57,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint grouped-accessor-pairs: "error"*/ -var foo = { +const foo = { get a() { return this.val; }, @@ -67,7 +67,7 @@ var foo = { } }; -var bar = { +const bar = { set b(value) { this.val = value; }, @@ -107,7 +107,7 @@ Examples of **correct** code for this rule: ```js /*eslint grouped-accessor-pairs: "error"*/ -var foo = { +const foo = { get a() { return this.val; }, @@ -117,7 +117,7 @@ var foo = { b: 1 }; -var bar = { +const bar = { set b(value) { this.val = value; }, @@ -167,7 +167,7 @@ Examples of **incorrect** code for this rule with the `"getBeforeSet"` option: ```js /*eslint grouped-accessor-pairs: ["error", "getBeforeSet"]*/ -var foo = { +const foo = { set a(value) { this.val = value; }, @@ -204,7 +204,7 @@ Examples of **correct** code for this rule with the `"getBeforeSet"` option: ```js /*eslint grouped-accessor-pairs: ["error", "getBeforeSet"]*/ -var foo = { +const foo = { get a() { return this.val; }, @@ -243,7 +243,7 @@ Examples of **incorrect** code for this rule with the `"setBeforeGet"` option: ```js /*eslint grouped-accessor-pairs: ["error", "setBeforeGet"]*/ -var foo = { +const foo = { get a() { return this.val; }, @@ -280,7 +280,7 @@ Examples of **correct** code for this rule with the `"setBeforeGet"` option: ```js /*eslint grouped-accessor-pairs: ["error", "setBeforeGet"]*/ -var foo = { +const foo = { set a(value) { this.val = value; }, @@ -318,10 +318,10 @@ might require or miss to require grouping or order for getters/setters that have ```js /*eslint grouped-accessor-pairs: "error"*/ -var a = 1; +let a = 1; // false warning (false positive) -var foo = { +const foo = { get [a++]() { return this.val; }, @@ -332,7 +332,7 @@ var foo = { }; // missed warning (false negative) -var bar = { +const bar = { get [++a]() { return this.val; }, diff --git a/docs/src/rules/handle-callback-err.md b/docs/src/rules/handle-callback-err.md index 3f66ad2031ef..9b47b30ed482 100644 --- a/docs/src/rules/handle-callback-err.md +++ b/docs/src/rules/handle-callback-err.md @@ -6,9 +6,6 @@ further_reading: - https://web.archive.org/web/20171224042620/https://docs.nodejitsu.com/articles/errors/what-are-the-error-conventions/ --- - -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - In Node.js, a common pattern for dealing with asynchronous behavior is called the callback pattern. This pattern expects an `Error` object or `null` as the first argument of the callback. Forgetting to handle these errors can lead to some really strange behavior in your application. diff --git a/docs/src/rules/id-blacklist.md b/docs/src/rules/id-blacklist.md index 649b22e60de0..8a163095ab3b 100644 --- a/docs/src/rules/id-blacklist.md +++ b/docs/src/rules/id-blacklist.md @@ -2,6 +2,3 @@ title: id-blacklist rule_type: suggestion --- - - -This rule was **deprecated** in ESLint v7.5.0 and replaced by the [id-denylist](id-denylist) rule. diff --git a/docs/src/rules/id-denylist.md b/docs/src/rules/id-denylist.md index 82859ec8b430..f8ab9920c3e5 100644 --- a/docs/src/rules/id-denylist.md +++ b/docs/src/rules/id-denylist.md @@ -37,7 +37,7 @@ For example, to restrict the use of common generic identifiers: } ``` -**Note:** The first element of the array is for the rule severity (see [Configure Rules](../use/configure/rules). The other elements in the array are the identifiers that you want to disallow. +**Note:** The first element of the array is for the rule severity (see [Configure Rules](../use/configure/rules)). The other elements in the array are the identifiers that you want to disallow. Examples of **incorrect** code for this rule with sample `"data", "callback"` restricted identifiers: @@ -46,7 +46,7 @@ Examples of **incorrect** code for this rule with sample `"data", "callback"` re ```js /*eslint id-denylist: ["error", "data", "callback"] */ -var data = { ...values }; +const data = { ...values }; function callback() { // ... @@ -56,7 +56,7 @@ element.callback = function() { // ... }; -var itemSet = { +const itemSet = { data: [...values] }; @@ -86,7 +86,7 @@ Examples of **correct** code for this rule with sample `"data", "callback"` rest ```js /*eslint id-denylist: ["error", "data", "callback"] */ -var encodingOptions = {...values}; +const encodingOptions = {...values}; function processFileResult() { // ... @@ -96,7 +96,7 @@ element.successHandler = function() { // ... }; -var itemSet = { +const itemSet = { entities: [...values] }; diff --git a/docs/src/rules/id-length.md b/docs/src/rules/id-length.md index ea5d2fa9d1ab..8d18cacee7eb 100644 --- a/docs/src/rules/id-length.md +++ b/docs/src/rules/id-length.md @@ -12,7 +12,7 @@ related_rules: Very short identifier names like `e`, `x`, `_t` or very long ones like `hashGeneratorResultOutputContainerObject` can make code harder to read and potentially less maintainable. To prevent this, one may enforce a minimum and/or maximum identifier length. ```js -var x = 5; // too short; difficult to understand its purpose without context +const x = 5; // too short; difficult to understand its purpose without context ``` ## Rule Details @@ -29,17 +29,16 @@ Examples of **incorrect** code for this rule with the default options: ```js /*eslint id-length: "error"*/ // default is minimum 2-chars ({ "min": 2 }) -/*eslint-env es6*/ -var x = 5; +const x = 5; obj.e = document.body; -var foo = function (e) { }; +const foo = function (e) { }; try { dangerousStuff(); } catch (e) { // ignore as many do } -var myObj = { a: 1 }; +const myObj = { a: 1 }; (a) => { a * a }; class y { } class Foo { x() {} } @@ -48,11 +47,11 @@ class Baz { x = 1 } class Qux { #x = 1 } function bar(...x) { } function baz([x]) { } -var [x] = arr; -var { prop: [x]} = {}; +const [z] = arr; +const { prop: [i]} = {}; function qux({x}) { } -var { x } = {}; -var { prop: a} = {}; +const { j } = {}; +const { prop: a} = {}; ({ prop: obj.x } = {}); ``` @@ -64,19 +63,18 @@ Examples of **correct** code for this rule with the default options: ```js /*eslint id-length: "error"*/ // default is minimum 2-chars ({ "min": 2 }) -/*eslint-env es6*/ -var num = 5; +const num = 5; function _f() { return 42; } function _func() { return 42; } obj.el = document.body; -var foo = function (evt) { /* do stuff */ }; +const foo = function (evt) { /* do stuff */ }; try { dangerousStuff(); } catch (error) { // ignore as many do } -var myObj = { apple: 1 }; +const myObj = { apple: 1 }; (num) => { num * num }; function bar(num = 0) { } class MyClass { } @@ -86,15 +84,14 @@ class Baz { field = 1 } class Qux { #field = 1 } function baz(...args) { } function qux([longName]) { } -var { prop } = {}; -var { prop: [longName] } = {}; -var [longName] = arr; +const { prop } = {}; +const { prop: [name] } = {}; +const [longName] = arr; function foobar({ prop }) { } function foobaz({ a: prop }) { } -var { prop } = {}; -var { a: prop } = {}; +const { a: property } = {}; ({ prop: obj.longName } = {}); -var data = { "x": 1 }; // excused because of quotes +const data = { "x": 1 }; // excused because of quotes data["y"] = 3; // excused because of calculated property access ``` @@ -102,8 +99,8 @@ data["y"] = 3; // excused because of calculated property access This rule has an object option: -* `"min"` (default: 2) enforces a minimum identifier length -* `"max"` (default: Infinity) enforces a maximum identifier length +* `"min"` (default: `2`) enforces a minimum identifier length +* `"max"` (default: `Infinity`) enforces a maximum identifier length * `"properties": always` (default) enforces identifier length convention for property names * `"properties": never` ignores identifier length convention for property names * `"exceptions"` allows an array of specified identifier names @@ -117,9 +114,8 @@ Examples of **incorrect** code for this rule with the `{ "min": 4 }` option: ```js /*eslint id-length: ["error", { "min": 4 }]*/ -/*eslint-env es6*/ -var val = 5; +const val = 5; obj.e = document.body; function foo (e) { }; try { @@ -127,15 +123,15 @@ try { } catch (e) { // ignore as many do } -var myObj = { a: 1 }; +const myObj = { a: 1 }; (val) => { val * val }; class y { } class Foo { x() {} } function bar(...x) { } -var { x } = {}; -var { prop: a} = {}; -var [x] = arr; -var { prop: [x]} = {}; +const { x } = {}; +const { prop: a} = {}; +const [i] = arr; +const { prop: [num]} = {}; ({ prop: obj.x } = {}); ``` @@ -147,29 +143,28 @@ Examples of **correct** code for this rule with the `{ "min": 4 }` option: ```js /*eslint id-length: ["error", { "min": 4 }]*/ -/*eslint-env es6*/ -var value = 5; +const value = 5; function func() { return 42; } object.element = document.body; -var foobar = function (event) { /* do stuff */ }; +const foobar = function (event) { /* do stuff */ }; try { dangerousStuff(); } catch (error) { // ignore as many do } -var myObj = { apple: 1 }; +const myObj = { apple: 1 }; (value) => { value * value }; function foobaz(value = 0) { } class MyClass { } class Foobar { method() {} } function barbaz(...args) { } -var { prop } = {}; -var [longName] = foo; -var { a: [prop] } = {}; -var { a: longName } = {}; +const { prop } = {}; +const [longName] = foo; +const { a: [name] } = {}; +const { a: record } = {}; ({ prop: object.name } = {}); -var data = { "x": 1 }; // excused because of quotes +const data = { "x": 1 }; // excused because of quotes data["y"] = 3; // excused because of calculated property access ``` @@ -183,19 +178,18 @@ Examples of **incorrect** code for this rule with the `{ "max": 10 }` option: ```js /*eslint id-length: ["error", { "max": 10 }]*/ -/*eslint-env es6*/ -var reallyLongVarName = 5; +const reallyLongVarName = 5; function reallyLongFuncName() { return 42; } obj.reallyLongPropName = document.body; -var foo = function (reallyLongArgName) { /* do stuff */ }; +const foo = function (reallyLongArgName) { /* do stuff */ }; try { dangerousStuff(); } catch (reallyLongErrorName) { // ignore as many do } (reallyLongArgName) => { return !reallyLongArgName; }; -var [reallyLongFirstElementName] = arr; +const [reallyLongFirstElementName] = arr; ``` ::: @@ -206,19 +200,18 @@ Examples of **correct** code for this rule with the `{ "max": 10 }` option: ```js /*eslint id-length: ["error", { "max": 10 }]*/ -/*eslint-env es6*/ -var varName = 5; +const varName = 5; function funcName() { return 42; } obj.propName = document.body; -var foo = function (arg) { /* do stuff */ }; +const foo = function (arg) { /* do stuff */ }; try { dangerousStuff(); } catch (error) { // ignore as many do } (arg) => { return !arg; }; -var [first] = arr; +const [first] = arr; ``` ::: @@ -231,9 +224,8 @@ Examples of **correct** code for this rule with the `{ "properties": "never" }` ```js /*eslint id-length: ["error", { "properties": "never" }]*/ -/*eslint-env es6*/ -var myObj = { a: 1 }; +const myObj = { a: 1 }; ({ a: obj.x.y.z } = {}); ({ prop: obj.i } = {}); ``` @@ -247,20 +239,19 @@ Examples of additional **correct** code for this rule with the `{ "exceptions": ::: correct ```js -/*eslint id-length: ["error", { "exceptions": ["x", "y", "z", "Îļ"] }]*/ -/*eslint-env es6*/ +/*eslint id-length: ["error", { "exceptions": ["x", "y", "z", "Îļ", "i"] }]*/ -var x = 5; +const x = 5; function y() { return 42; } obj.x = document.body; -var foo = function (x) { /* do stuff */ }; +const foo = function (x) { /* do stuff */ }; try { dangerousStuff(); } catch (x) { // ignore as many do } (x) => { return x * x; }; -var [x] = arr; +const [i] = arr; const { z } = foo; const { a: Îļ } = foo; ``` @@ -274,20 +265,19 @@ Examples of additional **correct** code for this rule with the `{ "exceptionPatt ::: correct ```js -/*eslint id-length: ["error", { "exceptionPatterns": ["E|S", "[x-z]"] }]*/ -/*eslint-env es6*/ +/*eslint id-length: ["error", { "exceptionPatterns": ["E|S|X", "[x-z]"] }]*/ -var E = 5; +const E = 5; function S() { return 42; } obj.x = document.body; -var foo = function (x) { /* do stuff */ }; +const foo = function (x) { /* do stuff */ }; try { dangerousStuff(); } catch (x) { // ignore as many do } (y) => {return y * y}; -var [E] = arr; +const [X] = arr; const { y } = foo; const { a: z } = foo; ``` diff --git a/docs/src/rules/id-match.md b/docs/src/rules/id-match.md index 59954fc2a48a..f016b3ab43d9 100644 --- a/docs/src/rules/id-match.md +++ b/docs/src/rules/id-match.md @@ -9,7 +9,7 @@ rule_type: suggestion Naming things consistently in a project is an often underestimated aspect of code creation. When done correctly, it can save your team hours of unnecessary head scratching and misdirections. This rule allows you to precisely define and enforce the variables and function names on your team should use. -No more limiting yourself to camelCase, snake_case, PascalCase or oHungarianNotation. Id-match has all your needs covered! +No more limiting yourself to camelCase, snake_case, PascalCase, or HungarianNotation. `id-match` has all your needs covered! ## Rule Details @@ -34,10 +34,10 @@ Examples of **incorrect** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$"` ```js /*eslint id-match: ["error", "^[a-z]+([A-Z][a-z]+)*$"]*/ -var my_favorite_color = "#112C85"; -var _myFavoriteColor = "#112C85"; -var myFavoriteColor_ = "#112C85"; -var MY_FAVORITE_COLOR = "#112C85"; +const my_favorite_color = "#112C85"; +const _myFavoriteColor = "#112C85"; +const myFavoriteColor_ = "#112C85"; +const MY_FAVORITE_COLOR = "#112C85"; function do_something() { // ... } @@ -62,11 +62,11 @@ Examples of **correct** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$"` o ```js /*eslint id-match: ["error", "^[a-z]+([A-Z][a-z]+)*$"]*/ -var myFavoriteColor = "#112C85"; -var foo = bar.baz_boom; -var foo = { qux: bar.baz_boom }; +const myFavoriteColor = "#112C85"; +const foo = bar.baz_boom; +const buz = { qux: bar.baz_boom }; do_something(); -var obj = { +const obj = { my_pref: 1 }; @@ -103,7 +103,7 @@ Examples of **incorrect** code for this rule with the `"^[a-z]+([A-Z][a-z]+)*$", ```js /*eslint id-match: ["error", "^[a-z]+([A-Z][a-z]+)*$", { "properties": true }]*/ -var obj = { +const obj = { my_pref: 1 }; @@ -157,15 +157,15 @@ Examples of **incorrect** code for this rule with the default `"^[^_]+$", { "ign ```js /*eslint id-match: [2, "^[^_]+$", { "ignoreDestructuring": false }]*/ -var { category_id } = query; +const { category_id } = query; -var { category_id = 1 } = query; +const { categoryid_Default = 1 } = query; -var { category_id: category_id } = query; +const { category_ids: category_ids } = query; -var { category_id: category_alias } = query; +const { category_id: category_Alias } = query; -var { category_id: categoryId, ...other_props } = query; +const { category_id: category_IdRenamed, ...other_Props } = query; ``` ::: @@ -179,9 +179,9 @@ Examples of **incorrect** code for this rule with the `"^[^_]+$", { "ignoreDestr ```js /*eslint id-match: [2, "^[^_]+$", { "ignoreDestructuring": true }]*/ -var { category_id: category_alias } = query; +const { category_id: category_alias } = query; -var { category_id, ...other_props } = query; +const { category_id: category_Id, ...other_props } = query; ``` ::: @@ -193,11 +193,11 @@ Examples of **correct** code for this rule with the `"^[^_]+$", { "ignoreDestruc ```js /*eslint id-match: [2, "^[^_]+$", { "ignoreDestructuring": true }]*/ -var { category_id } = query; +const { category_id } = query; -var { category_id = 1 } = query; +const { category_Id = 1 } = query; -var { category_id: category_id } = query; +const { category_alias: category_alias } = query; ``` ::: diff --git a/docs/src/rules/implicit-arrow-linebreak.md b/docs/src/rules/implicit-arrow-linebreak.md index 5914b9d440d7..a60735f49469 100644 --- a/docs/src/rules/implicit-arrow-linebreak.md +++ b/docs/src/rules/implicit-arrow-linebreak.md @@ -4,9 +4,6 @@ rule_type: layout related_rules: - brace-style --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/implicit-arrow-linebreak) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - An arrow function body can contain an implicit return as an expression instead of a block body. It can be useful to enforce a consistent location for the implicitly returned expression. ## Rule Details diff --git a/docs/src/rules/indent-legacy.md b/docs/src/rules/indent-legacy.md index a8097b229324..75648b694494 100644 --- a/docs/src/rules/indent-legacy.md +++ b/docs/src/rules/indent-legacy.md @@ -3,10 +3,6 @@ title: indent-legacy rule_type: layout --- - - -This rule was **deprecated** in ESLint v4.0.0. - ESLint 4.0.0 introduced a rewrite of the [`indent`](indent) rule, which now reports more errors than it did in previous versions. To ease the process of migrating to 4.0.0, the `indent-legacy` rule was introduced as a snapshot of the `indent` rule from ESLint 3.x. If your build is failing after the upgrade to 4.0.0, you can disable `indent` and enable `indent-legacy` as a quick fix. Eventually, you should switch back to the `indent` rule to get bugfixes and improvements in future versions. --- @@ -202,7 +198,6 @@ Examples of **incorrect** code for this rule with the `2, { "VariableDeclarator" ```js /*eslint indent-legacy: ["error", 2, { "VariableDeclarator": 1 }]*/ -/*eslint-env es6*/ var a, b, @@ -223,7 +218,6 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ```js /*eslint indent-legacy: ["error", 2, { "VariableDeclarator": 1 }]*/ -/*eslint-env es6*/ var a, b, @@ -244,7 +238,6 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ```js /*eslint indent-legacy: ["error", 2, { "VariableDeclarator": 2 }]*/ -/*eslint-env es6*/ var a, b, @@ -265,7 +258,6 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ```js /*eslint indent-legacy: ["error", 2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }]*/ -/*eslint-env es6*/ var a, b, @@ -320,7 +312,7 @@ function foo(x) { })(); if(y) { - console.log('foo'); + console.log('foo'); } ``` diff --git a/docs/src/rules/indent.md b/docs/src/rules/indent.md index 768c1b18a4bd..f13a0b43a00f 100644 --- a/docs/src/rules/indent.md +++ b/docs/src/rules/indent.md @@ -2,9 +2,6 @@ title: indent rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/indent) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - There are several common guidelines which require specific indentation of nested blocks and statements, like: ```js @@ -246,7 +243,6 @@ Examples of **incorrect** code for this rule with the `2, { "VariableDeclarator" ```js /*eslint indent: ["error", 2, { "VariableDeclarator": 1 }]*/ -/*eslint-env es6*/ var a, b, @@ -267,7 +263,6 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ```js /*eslint indent: ["error", 2, { "VariableDeclarator": 1 }]*/ -/*eslint-env es6*/ var a, b, @@ -288,7 +283,6 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ```js /*eslint indent: ["error", 2, { "VariableDeclarator": 2 }]*/ -/*eslint-env es6*/ var a, b, @@ -309,7 +303,6 @@ Examples of **incorrect** code for this rule with the `2, { "VariableDeclarator" ```js /*eslint indent: ["error", 2, { "VariableDeclarator": "first" }]*/ -/*eslint-env es6*/ var a, b, @@ -330,7 +323,6 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ```js /*eslint indent: ["error", 2, { "VariableDeclarator": "first" }]*/ -/*eslint-env es6*/ var a, b, @@ -351,7 +343,6 @@ Examples of **correct** code for this rule with the `2, { "VariableDeclarator": ```js /*eslint indent: ["error", 2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }]*/ -/*eslint-env es6*/ var a, b, diff --git a/docs/src/rules/init-declarations.md b/docs/src/rules/init-declarations.md index 569e5079c72c..eb00bdda1624 100644 --- a/docs/src/rules/init-declarations.md +++ b/docs/src/rules/init-declarations.md @@ -1,14 +1,16 @@ --- title: init-declarations rule_type: suggestion +related_rules: +- no-unassigned-vars --- In JavaScript, variables can be assigned during declaration, or at any point afterwards using an assignment statement. For example, in the following code, `foo` is initialized during declaration, while `bar` is initialized later. ```js -var foo = 1; -var bar; +let foo = 1; +let bar; if (foo) { bar = 1; @@ -22,8 +24,8 @@ if (foo) { This rule is aimed at enforcing or eliminating variable initializations during declaration. For example, in the following code, `foo` is initialized during declaration, while `bar` is not. ```js -var foo = 1; -var bar; +let foo = 1; +let bar; bar = 2; ``` @@ -34,7 +36,7 @@ This rule aims to bring consistency to variable initializations and declarations The rule takes two options: -1. A string which must be either `"always"` (the default), to enforce initialization at declaration, or `"never"` to disallow initialization during declaration. This rule applies to `var`, `let`, and `const` variables, however `"never"` is ignored for `const` variables, as unassigned `const`s generate a parse error. +1. A string which must be either `"always"` (the default), to enforce initialization at declaration, or `"never"` to disallow initialization during declaration. This rule applies to `var`, `let`, `const`, `using`, and `await using` variables, however `"never"` is ignored for `const`, `using`, and `await using` variables, as not initializing these variables would generate a parse error. 2. An object that further controls the behavior of this rule. Currently, the only available parameter is `ignoreForLoopInit`, which indicates if initialization at declaration is allowed in `for` loops when `"never"` is set, since it is a very typical use case. You can configure the rule as follows: @@ -71,7 +73,6 @@ Examples of **incorrect** code for the default `"always"` option: ```js /*eslint init-declarations: ["error", "always"]*/ -/*eslint-env es6*/ function foo() { var bar; @@ -87,12 +88,16 @@ Examples of **correct** code for the default `"always"` option: ```js /*eslint init-declarations: ["error", "always"]*/ -/*eslint-env es6*/ function foo() { var bar = 1; let baz = 2; const qux = 3; + using quux = getSomething(); +} + +async function foobar() { + await using quux = getSomething(); } ``` @@ -106,13 +111,12 @@ Examples of **incorrect** code for the `"never"` option: ```js /*eslint init-declarations: ["error", "never"]*/ -/*eslint-env es6*/ function foo() { var bar = 1; let baz = 2; - for (var i = 0; i < 1; i++) {} + for (let i = 0; i < 1; i++) {} } ``` @@ -124,18 +128,22 @@ Examples of **correct** code for the `"never"` option: ```js /*eslint init-declarations: ["error", "never"]*/ -/*eslint-env es6*/ function foo() { var bar; let baz; const buzz = 1; + using quux = getSomething(); +} + +async function foobar() { + await using quux = getSomething(); } ``` ::: -The `"never"` option ignores `const` variable initializations. +The `"never"` option ignores `const`, `using`, and `await using` variable initializations. ### ignoreForLoopInit @@ -145,11 +153,103 @@ Examples of **correct** code for the `"never", { "ignoreForLoopInit": true }` op ```js /*eslint init-declarations: ["error", "never", { "ignoreForLoopInit": true }]*/ -for (var i = 0; i < 1; i++) {} +for (let i = 0; i < 1; i++) {} ``` ::: +This rule additionally supports TypeScript type syntax. + +Examples of **incorrect** TypeScript code for this rule: + +::: incorrect + +```ts +/* eslint init-declarations: ["error", "never"] */ + +let arr: string[] = ['arr', 'ar']; + +const class1 = class NAME { + constructor() { + var name1: string = 'hello'; + } +}; + +namespace myLib { + let numberOfGreetings: number = 2; +} + +``` + +::: + +::: incorrect + +```ts +/* eslint init-declarations: ["error", "always"] */ + +namespace myLib { + let numberOfGreetings: number; +} + +namespace myLib1 { + const foo: number; + namespace myLib2 { + let bar: string; + namespace myLib3 { + let baz: object; + } + } +} + +``` + +::: + +Examples of **correct** TypeScript code for this rule: + +::: correct + +```ts +/* eslint init-declarations: ["error", "never"] */ + +declare const foo: number; + +declare namespace myLib { + let numberOfGreetings: number; +} + +interface GreetingSettings { + greeting: string; + duration?: number; + color?: string; +} +``` + +::: + +::: correct + +```ts +/* eslint init-declarations: ["error", "always"] */ + +declare const foo: number; + +declare namespace myLib { + let numberOfGreetings: number; +} + +interface GreetingSettings { + greeting: string; + duration?: number; + color?: string; +} + +``` + +::: + + ## When Not To Use It When you are indifferent as to how your variables are initialized. diff --git a/docs/src/rules/jsx-quotes.md b/docs/src/rules/jsx-quotes.md index 25ea1295dc64..544109d0a725 100644 --- a/docs/src/rules/jsx-quotes.md +++ b/docs/src/rules/jsx-quotes.md @@ -4,9 +4,6 @@ rule_type: layout related_rules: - quotes --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/jsx-quotes) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - JSX attribute values can contain string literals, which are delimited with single or double quotes. ```jsx @@ -37,7 +34,7 @@ This rule has a string option: Examples of **incorrect** code for this rule with the default `"prefer-double"` option: -:::incorrect { "ecmaFeatures": { "jsx": true } } +:::incorrect { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /*eslint jsx-quotes: ["error", "prefer-double"]*/ @@ -49,7 +46,7 @@ Examples of **incorrect** code for this rule with the default `"prefer-double"` Examples of **correct** code for this rule with the default `"prefer-double"` option: -:::correct { "ecmaFeatures": { "jsx": true } } +:::correct { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /*eslint jsx-quotes: ["error", "prefer-double"]*/ @@ -64,7 +61,7 @@ Examples of **correct** code for this rule with the default `"prefer-double"` op Examples of **incorrect** code for this rule with the `"prefer-single"` option: -:::incorrect { "ecmaFeatures": { "jsx": true } } +:::incorrect { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /*eslint jsx-quotes: ["error", "prefer-single"]*/ @@ -76,7 +73,7 @@ Examples of **incorrect** code for this rule with the `"prefer-single"` option: Examples of **correct** code for this rule with the `"prefer-single"` option: -:::correct { "ecmaFeatures": { "jsx": true } } +:::correct { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /*eslint jsx-quotes: ["error", "prefer-single"]*/ diff --git a/docs/src/rules/key-spacing.md b/docs/src/rules/key-spacing.md index c572be1c53eb..f0b65f0750a7 100644 --- a/docs/src/rules/key-spacing.md +++ b/docs/src/rules/key-spacing.md @@ -2,9 +2,6 @@ title: key-spacing rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/key-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - This rule enforces spacing around the colon in object literal properties. It can verify each property individually, or it can ensure horizontal alignment of adjacent properties in an object literal. ## Rule Details diff --git a/docs/src/rules/keyword-spacing.md b/docs/src/rules/keyword-spacing.md index cd4ab2e60071..a6220be53fca 100644 --- a/docs/src/rules/keyword-spacing.md +++ b/docs/src/rules/keyword-spacing.md @@ -2,9 +2,6 @@ title: keyword-spacing rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/keyword-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Keywords are syntax elements of JavaScript, such as `try` and `if`. These keywords have special meaning to the language and so often appear in a different color in code editors. As an important part of the language, style guides often refer to the spacing that should be used around keywords. @@ -58,11 +55,10 @@ if (foo) { Examples of **correct** code for this rule with the default `{ "before": true }` option: -::: correct { "ecmaFeatures": { "jsx": true } } +::: correct { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /*eslint keyword-spacing: ["error", { "before": true }]*/ -/*eslint-env es6*/ if (foo) { //... @@ -173,7 +169,7 @@ if(foo) { Examples of **correct** code for this rule with the default `{ "after": true }` option: -::: correct { "ecmaFeatures": { "jsx": true } } +::: correct { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /*eslint keyword-spacing: ["error", { "after": true }]*/ diff --git a/docs/src/rules/line-comment-position.md b/docs/src/rules/line-comment-position.md index d032ecca8e90..b5185ac3a2ed 100644 --- a/docs/src/rules/line-comment-position.md +++ b/docs/src/rules/line-comment-position.md @@ -3,7 +3,6 @@ title: line-comment-position rule_type: layout --- - Line comments can be positioned above or beside code. This rule helps teams maintain a consistent style. ```js diff --git a/docs/src/rules/linebreak-style.md b/docs/src/rules/linebreak-style.md index d4dd0f5cf275..28f593af9082 100644 --- a/docs/src/rules/linebreak-style.md +++ b/docs/src/rules/linebreak-style.md @@ -2,9 +2,6 @@ title: linebreak-style rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/linebreak-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - When developing with a lot of people all having different editors, VCS applications and operating systems it may occur that different line endings are written by either of the mentioned (might especially happen when using the windows and mac versions of SourceTree together). @@ -52,6 +49,7 @@ var a = 'a', // \n function foo(params) { // \n // do stuff \n }// \n + ``` ::: @@ -66,6 +64,7 @@ Examples of **incorrect** code for this rule with the `"windows"` option: /*eslint linebreak-style: ["error", "windows"]*/ var a = 'a'; // \n + ``` ::: @@ -75,14 +74,15 @@ Examples of **correct** code for this rule with the `"windows"` option: ::: correct ```js -/*eslint linebreak-style: ["error", "windows"]*/ - +/*eslint linebreak-style: ["error", "windows"]*/ // \r\n +// \r\n var a = 'a', // \r\n b = 'b'; // \r\n // \r\n function foo(params) { // \r\n // do stuff \r\n } // \r\n + ``` ::: diff --git a/docs/src/rules/lines-around-comment.md b/docs/src/rules/lines-around-comment.md index 146224fa9aff..732116e79574 100644 --- a/docs/src/rules/lines-around-comment.md +++ b/docs/src/rules/lines-around-comment.md @@ -5,9 +5,6 @@ related_rules: - space-before-blocks - spaced-comment --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/lines-around-comment) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Many style guides require empty lines before or after comments. The primary goal of these rules is to make the comments easier to read and improve readability of the code. @@ -233,7 +230,7 @@ class C { switch (foo) { /* what a great and wonderful day */ - case 1: + case 1: bar(); break; } @@ -317,7 +314,7 @@ class C { } switch (foo) { - case 1: + case 1: bar(); break; @@ -653,9 +650,9 @@ const [ ### ignorePattern -By default this rule ignores comments starting with the following words: `eslint`, `jshint`, `jslint`, `istanbul`, `global`, `exported`, `jscs`. To ignore more comments in addition to the defaults, set the `ignorePattern` option to a string pattern that will be passed to the [`RegExp` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp). +By default this rule ignores comments starting with the following words: `eslint`, `jshint`, `jslint`, `istanbul`, `global`, `exported`, `jscs`. -Examples of **correct** code for the `ignorePattern` option: +Examples of **correct** code for this rule: ::: correct @@ -663,11 +660,25 @@ Examples of **correct** code for the `ignorePattern` option: /*eslint lines-around-comment: ["error"]*/ foo(); -/* eslint mentioned in this comment */ +/* jshint mentioned in this comment */ bar(); +``` +::: + +To ignore more comments in addition to the defaults, set the `ignorePattern` option to a string pattern that will be passed to the [`RegExp` constructor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/RegExp). + +Examples of **correct** code for the `ignorePattern` option: + +::: correct + +```js /*eslint lines-around-comment: ["error", { "ignorePattern": "pragma" }] */ +foo(); +/* jshint mentioned in this comment */ +bar(); + foo(); /* a valid comment using pragma in it */ ``` @@ -712,7 +723,7 @@ Examples of **incorrect** code for the `{ "applyDefaultIgnorePatterns": false }` /*eslint lines-around-comment: ["error", { "applyDefaultIgnorePatterns": false }] */ foo(); -/* eslint mentioned in comment */ +/* jshint mentioned in comment */ ``` diff --git a/docs/src/rules/lines-around-directive.md b/docs/src/rules/lines-around-directive.md index 7c18b517446d..2d9f61514a02 100644 --- a/docs/src/rules/lines-around-directive.md +++ b/docs/src/rules/lines-around-directive.md @@ -6,10 +6,6 @@ related_rules: - padded-blocks --- - - -This rule was **deprecated** in ESLint v4.0.0 and replaced by the [padding-line-between-statements](padding-line-between-statements) rule. - Directives are used in JavaScript to indicate to the execution environment that a script would like to opt into a feature such as `"strict mode"`. Directives are grouped together in a [directive prologue](https://www.ecma-international.org/ecma-262/7.0/#directive-prologue) at the top of either a file or function block and are applied to the scope in which they occur. ```js diff --git a/docs/src/rules/lines-between-class-members.md b/docs/src/rules/lines-between-class-members.md index 911344022890..7733b2bb0644 100644 --- a/docs/src/rules/lines-between-class-members.md +++ b/docs/src/rules/lines-between-class-members.md @@ -5,9 +5,6 @@ related_rules: - padded-blocks - padding-line-between-statements --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/lines-between-class-members) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - This rule improves readability by enforcing lines between class members. It will not check empty lines before the first member and after the last member, since that is already taken care of by padded-blocks. ## Rule Details diff --git a/docs/src/rules/max-len.md b/docs/src/rules/max-len.md index 1d236013104f..007f0b07ddad 100644 --- a/docs/src/rules/max-len.md +++ b/docs/src/rules/max-len.md @@ -8,9 +8,6 @@ related_rules: - max-params - max-statements --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/max-len) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Very long lines of code in any language can be difficult to read. In order to aid in readability and maintainability many coders have developed a convention to limit lines of code to X number of characters (traditionally 80 characters). ```js diff --git a/docs/src/rules/max-lines-per-function.md b/docs/src/rules/max-lines-per-function.md index 0bceb71a1980..d042826227c0 100644 --- a/docs/src/rules/max-lines-per-function.md +++ b/docs/src/rules/max-lines-per-function.md @@ -79,7 +79,7 @@ Examples of **incorrect** code for this rule with a particular max value: ```js /*eslint max-lines-per-function: ["error", 2]*/ function foo() { - var x = 0; + const x = 0; } ``` @@ -91,7 +91,7 @@ function foo() { /*eslint max-lines-per-function: ["error", 3]*/ function foo() { // a comment - var x = 0; + const x = 0; } ``` @@ -104,7 +104,7 @@ function foo() { function foo() { // a comment followed by a blank line - var x = 0; + const x = 0; } ``` @@ -117,7 +117,7 @@ Examples of **correct** code for this rule with a particular max value: ```js /*eslint max-lines-per-function: ["error", 3]*/ function foo() { - var x = 0; + const x = 0; } ``` @@ -129,7 +129,7 @@ function foo() { /*eslint max-lines-per-function: ["error", 4]*/ function foo() { // a comment - var x = 0; + const x = 0; } ``` @@ -142,7 +142,7 @@ function foo() { function foo() { // a comment followed by a blank line - var x = 0; + const x = 0; } ``` @@ -158,7 +158,7 @@ Examples of **incorrect** code for this rule with the `{ "skipBlankLines": true /*eslint max-lines-per-function: ["error", {"max": 2, "skipBlankLines": true}]*/ function foo() { - var x = 0; + const x = 0; } ``` @@ -172,7 +172,7 @@ Examples of **correct** code for this rule with the `{ "skipBlankLines": true }` /*eslint max-lines-per-function: ["error", {"max": 3, "skipBlankLines": true}]*/ function foo() { - var x = 0; + const x = 0; } ``` @@ -188,7 +188,7 @@ Examples of **incorrect** code for this rule with the `{ "skipComments": true }` /*eslint max-lines-per-function: ["error", {"max": 2, "skipComments": true}]*/ function foo() { // a comment - var x = 0; + const x = 0; } ``` @@ -202,7 +202,7 @@ Examples of **correct** code for this rule with the `{ "skipComments": true }` o /*eslint max-lines-per-function: ["error", {"max": 3, "skipComments": true}]*/ function foo() { // a comment - var x = 0; + const x = 0; } ``` @@ -217,11 +217,11 @@ Examples of **incorrect** code for this rule with the `{ "IIFEs": true }` option ```js /*eslint max-lines-per-function: ["error", {"max": 2, "IIFEs": true}]*/ (function(){ - var x = 0; + const x = 0; }()); (() => { - var x = 0; + const x = 0; })(); ``` @@ -234,11 +234,11 @@ Examples of **correct** code for this rule with the `{ "IIFEs": true }` option: ```js /*eslint max-lines-per-function: ["error", {"max": 3, "IIFEs": true}]*/ (function(){ - var x = 0; + const x = 0; }()); (() => { - var x = 0; + const x = 0; })(); ``` diff --git a/docs/src/rules/max-lines.md b/docs/src/rules/max-lines.md index 9aa4ae4d6d11..232b788e70b0 100644 --- a/docs/src/rules/max-lines.md +++ b/docs/src/rules/max-lines.md @@ -33,13 +33,13 @@ This rule has a number or object option: ### max -Examples of **incorrect** code for this rule with a max value of `2`: +Examples of **incorrect** code for this rule with a max value of `3`: ::: incorrect ```js -/*eslint max-lines: ["error", 2]*/ -var a, +/*eslint max-lines: ["error", 3]*/ +let a, b, c; ``` @@ -49,9 +49,9 @@ var a, ::: incorrect ```js -/*eslint max-lines: ["error", 2]*/ +/*eslint max-lines: ["error", 3]*/ -var a, +let a, b,c; ``` @@ -60,21 +60,21 @@ var a, ::: incorrect ```js -/*eslint max-lines: ["error", 2]*/ +/*eslint max-lines: ["error", 3]*/ // a comment -var a, +let a, b,c; ``` ::: -Examples of **correct** code for this rule with a max value of `2`: +Examples of **correct** code for this rule with a max value of `3`: ::: correct ```js -/*eslint max-lines: ["error", 2]*/ -var a, +/*eslint max-lines: ["error", 3]*/ +let a, b, c; ``` @@ -83,9 +83,9 @@ var a, ::: correct ```js -/*eslint max-lines: ["error", 2]*/ +/*eslint max-lines: ["error", 3]*/ -var a, b, c; +let a, b, c; ``` ::: @@ -93,9 +93,9 @@ var a, b, c; ::: correct ```js -/*eslint max-lines: ["error", 2]*/ +/*eslint max-lines: ["error", 3]*/ // a comment -var a, b, c; +let a, b, c; ``` ::: @@ -107,9 +107,9 @@ Examples of **incorrect** code for this rule with the `{ "skipBlankLines": true ::: incorrect ```js -/*eslint max-lines: ["error", {"max": 2, "skipBlankLines": true}]*/ +/*eslint max-lines: ["error", {"max": 3, "skipBlankLines": true}]*/ -var a, +let a, b, c; ``` @@ -121,9 +121,9 @@ Examples of **correct** code for this rule with the `{ "skipBlankLines": true }` ::: correct ```js -/*eslint max-lines: ["error", {"max": 2, "skipBlankLines": true}]*/ +/*eslint max-lines: ["error", {"max": 3, "skipBlankLines": true}]*/ -var a, +let a, b, c; ``` @@ -138,7 +138,7 @@ Examples of **incorrect** code for this rule with the `{ "skipComments": true }` ```js /*eslint max-lines: ["error", {"max": 2, "skipComments": true}]*/ // a comment -var a, +let a, b, c; ``` @@ -152,7 +152,7 @@ Examples of **correct** code for this rule with the `{ "skipComments": true }` o ```js /*eslint max-lines: ["error", {"max": 2, "skipComments": true}]*/ // a comment -var a, +let a, b, c; ``` diff --git a/docs/src/rules/max-params.md b/docs/src/rules/max-params.md index b0a55e9009fa..456729237eea 100644 --- a/docs/src/rules/max-params.md +++ b/docs/src/rules/max-params.md @@ -29,6 +29,7 @@ This rule enforces a maximum number of parameters allowed in function definition This rule has a number or object option: * `"max"` (default `3`) enforces a maximum number of parameters in function definitions +* `"countVoidThis"` (default `false`) counts a `this` declaration when the type is `void` (TypeScript only) **Deprecated:** The object property `maximum` is deprecated; please use the object property `max` instead. @@ -40,7 +41,6 @@ Examples of **incorrect** code for this rule with the default `{ "max": 3 }` opt ```js /*eslint max-params: ["error", 3]*/ -/*eslint-env es6*/ function foo1 (bar, baz, qux, qxx) { doSomething(); @@ -59,7 +59,6 @@ Examples of **correct** code for this rule with the default `{ "max": 3 }` optio ```js /*eslint max-params: ["error", 3]*/ -/*eslint-env es6*/ function foo1 (bar, baz, qux) { doSomething(); @@ -71,3 +70,63 @@ let foo2 = (bar, baz, qux) => { ``` ::: + +### countVoidThis (TypeScript only) + +This rule has a TypeScript-specific option `countVoidThis` that allows you to count a `this` declaration when the type is `void`. + +Examples of **correct** TypeScript code for this rule with the default `{ "countVoidThis": false }` option: + +:::correct + +```ts +/*eslint max-params: ["error", { "max": 2, "countVoidThis": false }]*/ + +function hasNoThis(this: void, first: string, second: string) { + // ... +} +``` + +::: + +Examples of **incorrect** TypeScript code for this rule with the default `{ "countVoidThis": false }` option: + +:::incorrect + +```ts +/*eslint max-params: ["error", { "max": 2, "countVoidThis": false }]*/ + +function hasNoThis(this: void, first: string, second: string, third: string) { + // ... +} +``` + +::: + +Examples of **correct** TypeScript code for this rule with the `{ "countVoidThis": true }` option: + +:::correct + +```ts +/*eslint max-params: ["error", { "max": 2, "countVoidThis": true }]*/ + +function hasNoThis(this: void, first: string) { + // ... +} +``` + +::: + +Examples of **incorrect** TypeScript code for this rule with the `{ "countVoidThis": true }` option: + +:::incorrect + +```ts +/*eslint max-params: ["error", { "max": 2, "countVoidThis": true }]*/ + +function hasNoThis(this: void, first: string, second: string) { + // ... +} +``` + +::: diff --git a/docs/src/rules/max-statements-per-line.md b/docs/src/rules/max-statements-per-line.md index d2c2e693ca58..c7e9584182ef 100644 --- a/docs/src/rules/max-statements-per-line.md +++ b/docs/src/rules/max-statements-per-line.md @@ -10,9 +10,6 @@ related_rules: - max-params - max-statements --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/max-statements-per-line) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - A line of code containing too many statements can be difficult to read. Code is generally read from the top down, especially when scanning, so limiting the number of statements allowed on a single line can be very beneficial for readability and maintainability. ```js diff --git a/docs/src/rules/max-statements.md b/docs/src/rules/max-statements.md index 5bdc7e8c2f49..3ec91592dc8d 100644 --- a/docs/src/rules/max-statements.md +++ b/docs/src/rules/max-statements.md @@ -16,9 +16,9 @@ The `max-statements` rule allows you to specify the maximum number of statements ```js function foo() { - var bar = 1; // one statement - var baz = 2; // two statements - var qux = 3; // three statements + const bar = 1; // one statement + const baz = 2; // two statements + const qux = 3; // three statements } ``` @@ -46,36 +46,35 @@ Examples of **incorrect** code for this rule with the default `{ "max": 10 }` op ```js /*eslint max-statements: ["error", 10]*/ -/*eslint-env es6*/ function foo() { - var foo1 = 1; - var foo2 = 2; - var foo3 = 3; - var foo4 = 4; - var foo5 = 5; - var foo6 = 6; - var foo7 = 7; - var foo8 = 8; - var foo9 = 9; - var foo10 = 10; - - var foo11 = 11; // Too many. + const foo1 = 1; + const foo2 = 2; + const foo3 = 3; + const foo4 = 4; + const foo5 = 5; + const foo6 = 6; + const foo7 = 7; + const foo8 = 8; + const foo9 = 9; + const foo10 = 10; + + const foo11 = 11; // Too many. } -let bar = () => { - var foo1 = 1; - var foo2 = 2; - var foo3 = 3; - var foo4 = 4; - var foo5 = 5; - var foo6 = 6; - var foo7 = 7; - var foo8 = 8; - var foo9 = 9; - var foo10 = 10; - - var foo11 = 11; // Too many. +const bar = () => { + const foo1 = 1; + const foo2 = 2; + const foo3 = 3; + const foo4 = 4; + const foo5 = 5; + const foo6 = 6; + const foo7 = 7; + const foo8 = 8; + const foo9 = 9; + const foo10 = 10; + + const foo11 = 11; // Too many. }; ``` @@ -87,46 +86,45 @@ Examples of **correct** code for this rule with the default `{ "max": 10 }` opti ```js /*eslint max-statements: ["error", 10]*/ -/*eslint-env es6*/ function foo() { - var foo1 = 1; - var foo2 = 2; - var foo3 = 3; - var foo4 = 4; - var foo5 = 5; - var foo6 = 6; - var foo7 = 7; - var foo8 = 8; - var foo9 = 9; + const foo1 = 1; + const foo2 = 2; + const foo3 = 3; + const foo4 = 4; + const foo5 = 5; + const foo6 = 6; + const foo7 = 7; + const foo8 = 8; + const foo9 = 9; return function () { // 10 // The number of statements in the inner function does not count toward the // statement maximum. - var bar; - var baz; + let bar; + let baz; return 42; }; } -let bar = () => { - var foo1 = 1; - var foo2 = 2; - var foo3 = 3; - var foo4 = 4; - var foo5 = 5; - var foo6 = 6; - var foo7 = 7; - var foo8 = 8; - var foo9 = 9; +const bar = () => { + const foo1 = 1; + const foo2 = 2; + const foo3 = 3; + const foo4 = 4; + const foo5 = 5; + const foo6 = 6; + const foo7 = 7; + const foo8 = 8; + const foo9 = 9; return function () { // 10 // The number of statements in the inner function does not count toward the // statement maximum. - var bar; - var baz; + let bar; + let baz; return 42; }; } @@ -172,17 +170,17 @@ Examples of additional **correct** code for this rule with the `{ "max": 10 }, { /*eslint max-statements: ["error", 10, { "ignoreTopLevelFunctions": true }]*/ function foo() { - var foo1 = 1; - var foo2 = 2; - var foo3 = 3; - var foo4 = 4; - var foo5 = 5; - var foo6 = 6; - var foo7 = 7; - var foo8 = 8; - var foo9 = 9; - var foo10 = 10; - var foo11 = 11; + const foo1 = 1; + const foo2 = 2; + const foo3 = 3; + const foo4 = 4; + const foo5 = 5; + const foo6 = 6; + const foo7 = 7; + const foo8 = 8; + const foo9 = 9; + const foo10 = 10; + const foo11 = 11; } ``` diff --git a/docs/src/rules/multiline-comment-style.md b/docs/src/rules/multiline-comment-style.md index 28e6648ad03a..919503e085a9 100644 --- a/docs/src/rules/multiline-comment-style.md +++ b/docs/src/rules/multiline-comment-style.md @@ -3,8 +3,6 @@ title: multiline-comment-style rule_type: suggestion --- - - Many style guides require a particular style for comments that span multiple lines. For example, some style guides prefer the use of a single block comment for multiline comments, whereas other style guides prefer consecutive line comments. ## Rule Details diff --git a/docs/src/rules/multiline-ternary.md b/docs/src/rules/multiline-ternary.md index 84c1a80039a1..68cb140e99b5 100644 --- a/docs/src/rules/multiline-ternary.md +++ b/docs/src/rules/multiline-ternary.md @@ -4,9 +4,6 @@ rule_type: layout related_rules: - operator-linebreak --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/multiline-ternary) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - JavaScript allows operands of ternary expressions to be separated by newlines, which can improve the readability of your program. For example: diff --git a/docs/src/rules/new-cap.md b/docs/src/rules/new-cap.md index f75c65fcb762..68ce9dce2554 100644 --- a/docs/src/rules/new-cap.md +++ b/docs/src/rules/new-cap.md @@ -7,7 +7,7 @@ rule_type: suggestion The `new` operator in JavaScript creates a new instance of a particular type of object. That type of object is represented by a constructor function. Since constructor functions are just regular functions, the only defining characteristic is that `new` is being used as part of the call. Native JavaScript functions begin with an uppercase letter to distinguish those functions that are to be used as constructors from functions that are not. Many style guides recommend following this pattern to more easily determine which functions are to be used as constructors. ```js -var friend = new Person(); +const friend = new Person(); ``` ## Rule Details @@ -64,7 +64,7 @@ Examples of **incorrect** code for this rule with the default `{ "newIsCap": tru ```js /*eslint new-cap: ["error", { "newIsCap": true }]*/ -var friend = new person(); +const friend = new person(); ``` ::: @@ -76,7 +76,7 @@ Examples of **correct** code for this rule with the default `{ "newIsCap": true ```js /*eslint new-cap: ["error", { "newIsCap": true }]*/ -var friend = new Person(); +const friend = new Person(); ``` ::: @@ -88,7 +88,7 @@ Examples of **correct** code for this rule with the `{ "newIsCap": false }` opti ```js /*eslint new-cap: ["error", { "newIsCap": false }]*/ -var friend = new person(); +const friend = new person(); ``` ::: @@ -102,7 +102,7 @@ Examples of **incorrect** code for this rule with the default `{ "capIsNew": tru ```js /*eslint new-cap: ["error", { "capIsNew": true }]*/ -var colleague = Person(); +const colleague = Person(); ``` ::: @@ -114,7 +114,7 @@ Examples of **correct** code for this rule with the default `{ "capIsNew": true ```js /*eslint new-cap: ["error", { "capIsNew": true }]*/ -var colleague = new Person(); +const colleague = new Person(); ``` ::: @@ -126,7 +126,7 @@ Examples of **correct** code for this rule with the `{ "capIsNew": false }` opti ```js /*eslint new-cap: ["error", { "capIsNew": false }]*/ -var colleague = Person(); +const colleague = Person(); ``` ::: @@ -140,9 +140,9 @@ Examples of additional **correct** code for this rule with the `{ "newIsCapExcep ```js /*eslint new-cap: ["error", { "newIsCapExceptions": ["events"] }]*/ -var events = require('events'); +const events = require('events'); -var emitter = new events(); +const emitter = new events(); ``` ::: @@ -156,9 +156,9 @@ Examples of additional **correct** code for this rule with the `{ "newIsCapExcep ```js /*eslint new-cap: ["error", { "newIsCapExceptionPattern": "^person\\.." }]*/ -var friend = new person.acquaintance(); +const friend = new person.acquaintance(); -var bestFriend = new person.friend(); +const bestFriend = new person.friend(); ``` ::: @@ -170,7 +170,7 @@ Examples of additional **correct** code for this rule with the `{ "newIsCapExcep ```js /*eslint new-cap: ["error", { "newIsCapExceptionPattern": "\\.bar$" }]*/ -var friend = new person.bar(); +const friend = new person.bar(); ``` ::: @@ -200,8 +200,8 @@ Examples of additional **correct** code for this rule with the `{ "capIsNewExcep ```js /*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^person\\.." }]*/ -var friend = person.Acquaintance(); -var bestFriend = person.Friend(); +const friend = person.Acquaintance(); +const bestFriend = person.Friend(); ``` ::: @@ -225,11 +225,11 @@ Examples of additional **correct** code for this rule with the `{ "capIsNewExcep ```js /*eslint new-cap: ["error", { "capIsNewExceptionPattern": "^Foo" }]*/ -var x = Foo(42); +const x = Foo(42); -var y = Foobar(42); +const y = Foobar(42); -var z = Foo.Bar(42); +const z = Foo.Bar(42); ``` ::: @@ -243,7 +243,7 @@ Examples of **incorrect** code for this rule with the default `{ "properties": t ```js /*eslint new-cap: ["error", { "properties": true }]*/ -var friend = new person.acquaintance(); +const friend = new person.acquaintance(); ``` ::: @@ -255,7 +255,7 @@ Examples of **correct** code for this rule with the default `{ "properties": tru ```js /*eslint new-cap: ["error", { "properties": true }]*/ -var friend = new person.Acquaintance(); +const friend = new person.Acquaintance(); ``` ::: @@ -267,7 +267,7 @@ Examples of **correct** code for this rule with the `{ "properties": false }` op ```js /*eslint new-cap: ["error", { "properties": false }]*/ -var friend = new person.acquaintance(); +const friend = new person.acquaintance(); ``` ::: diff --git a/docs/src/rules/new-parens.md b/docs/src/rules/new-parens.md index 781a430e5bfd..17b74af07304 100644 --- a/docs/src/rules/new-parens.md +++ b/docs/src/rules/new-parens.md @@ -2,9 +2,6 @@ title: new-parens rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/new-parens) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - JavaScript allows the omission of parentheses when invoking a function via the `new` keyword and the constructor has no arguments. However, some coders believe that omitting the parentheses is inconsistent with the rest of the language and thus makes code less clear. ```js diff --git a/docs/src/rules/newline-after-var.md b/docs/src/rules/newline-after-var.md index 9d6dc216ecec..6044809d4ab5 100644 --- a/docs/src/rules/newline-after-var.md +++ b/docs/src/rules/newline-after-var.md @@ -3,10 +3,6 @@ title: newline-after-var rule_type: layout --- - - -This rule was **deprecated** in ESLint v4.0.0 and replaced by the [padding-line-between-statements](padding-line-between-statements) rule. - As of today there is no consistency in separating variable declarations from the rest of the code. Some developers leave an empty line between var statements and the rest of the code like: ```js @@ -46,7 +42,6 @@ Examples of **incorrect** code for this rule with the default `"always"` option: ```js /*eslint newline-after-var: ["error", "always"]*/ -/*eslint-env es6*/ var greet = "hello,", name = "world"; @@ -74,7 +69,6 @@ Examples of **correct** code for this rule with the default `"always"` option: ```js /*eslint newline-after-var: ["error", "always"]*/ -/*eslint-env es6*/ var greet = "hello,", name = "world"; @@ -108,7 +102,6 @@ Examples of **incorrect** code for this rule with the `"never"` option: ```js /*eslint newline-after-var: ["error", "never"]*/ -/*eslint-env es6*/ var greet = "hello,", name = "world"; @@ -140,7 +133,6 @@ Examples of **correct** code for this rule with the `"never"` option: ```js /*eslint newline-after-var: ["error", "never"]*/ -/*eslint-env es6*/ var greet = "hello,", name = "world"; diff --git a/docs/src/rules/newline-before-return.md b/docs/src/rules/newline-before-return.md index 1980f5bd3463..125e785197a3 100644 --- a/docs/src/rules/newline-before-return.md +++ b/docs/src/rules/newline-before-return.md @@ -5,10 +5,6 @@ related_rules: - newline-after-var --- - - -This rule was **deprecated** in ESLint v4.0.0 and replaced by the [padding-line-between-statements](padding-line-between-statements) rule. - There is no hard and fast rule about whether empty lines should precede `return` statements in JavaScript. However, clearly delineating where a function is returning can greatly increase the readability and clarity of the code. For example: ```js diff --git a/docs/src/rules/newline-per-chained-call.md b/docs/src/rules/newline-per-chained-call.md index e74ac95e78d3..2544456728fe 100644 --- a/docs/src/rules/newline-per-chained-call.md +++ b/docs/src/rules/newline-per-chained-call.md @@ -2,9 +2,6 @@ title: newline-per-chained-call rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/newline-per-chained-call) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Chained method calls on a single line without line breaks are harder to read, so some developers place a newline character after each method call in the chain to make it more readable and easy to maintain. Let's look at the following perfectly valid (but single line) code. diff --git a/docs/src/rules/no-alert.md b/docs/src/rules/no-alert.md index fe7ffd4de71e..b52e9ec235ca 100644 --- a/docs/src/rules/no-alert.md +++ b/docs/src/rules/no-alert.md @@ -47,7 +47,7 @@ customConfirm("Are you sure?"); customPrompt("Who are you?"); function foo() { - var alert = myCustomLib.customAlert; + const alert = myCustomLib.customAlert; alert(); } ``` diff --git a/docs/src/rules/no-array-constructor.md b/docs/src/rules/no-array-constructor.md index eff97dee776b..443f3c1a23b7 100644 --- a/docs/src/rules/no-array-constructor.md +++ b/docs/src/rules/no-array-constructor.md @@ -10,7 +10,7 @@ related_rules: Use of the `Array` constructor to construct a new array is generally discouraged in favor of array literal notation because of the single-argument pitfall and because the `Array` global may be redefined. The exception is when -the Array constructor is used to intentionally create sparse arrays of a +the `Array` constructor is used to intentionally create sparse arrays of a specified size by giving the constructor a single numeric argument. ## Rule Details @@ -53,6 +53,46 @@ const createArray = Array => new Array(); ::: +This rule additionally supports TypeScript type syntax. + +Examples of **correct** code for this rule: + +:::correct + +```ts +/*eslint no-array-constructor: "error"*/ + +new Array(1, 2, 3); + +new Array(); + +Array(1, 2, 3); + +Array(); + +Array?.foo(); +``` + +::: + +Examples of **incorrect** code for this rule: + +:::incorrect + +```ts +/*eslint no-array-constructor: "error"*/ + +new Array(); + +new Array(0, 1, 2); + +Array?.(x, y); + +Array?.(0, 1, 2); +``` + +::: + ## When Not To Use It This rule enforces a nearly universal stylistic concern. That being said, this diff --git a/docs/src/rules/no-arrow-condition.md b/docs/src/rules/no-arrow-condition.md index c2a52a030fdb..0097865a2bd3 100644 --- a/docs/src/rules/no-arrow-condition.md +++ b/docs/src/rules/no-arrow-condition.md @@ -43,7 +43,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-arrow-condition: "error"*/ -/*eslint-env es6*/ if (a => 1) {} while (a => 1) {} diff --git a/docs/src/rules/no-async-promise-executor.md b/docs/src/rules/no-async-promise-executor.md index 768f3459f25a..7a4f026dfaa7 100644 --- a/docs/src/rules/no-async-promise-executor.md +++ b/docs/src/rules/no-async-promise-executor.md @@ -76,4 +76,4 @@ const result = Promise.resolve(foo); ## When Not To Use It -If your codebase doesn't support async function syntax, there's no need to enable this rule. +If your codebase doesn't support `async function` syntax, there's no need to enable this rule. diff --git a/docs/src/rules/no-await-in-loop.md b/docs/src/rules/no-await-in-loop.md index a150f5a911ff..b65a5a59e65a 100644 --- a/docs/src/rules/no-await-in-loop.md +++ b/docs/src/rules/no-await-in-loop.md @@ -5,35 +5,67 @@ rule_type: problem Performing an operation on each element of an iterable is a common task. However, performing an -`await` as part of each operation is an indication that the program is not taking full advantage of +`await` as part of each operation may indicate that the program is not taking full advantage of the parallelization benefits of `async`/`await`. -Usually, the code should be refactored to create all the promises at once, then get access to the -results using `Promise.all()`. Otherwise, each successive operation will not start until the +Often, the code can be refactored to create all the promises at once, then get access to the +results using `Promise.all()` (or one of the other [promise concurrency methods](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#promise_concurrency)). Otherwise, each successive operation will not start until the previous one has completed. -Concretely, the following function should be refactored as shown: +Concretely, the following function could be refactored as shown: ```js async function foo(things) { const results = []; for (const thing of things) { // Bad: each loop iteration is delayed until the entire asynchronous operation completes - results.push(await bar(thing)); + results.push(await doAsyncWork(thing)); } - return baz(results); + return results; } ``` ```js async function foo(things) { - const results = []; + const promises = []; for (const thing of things) { // Good: all asynchronous operations are immediately started. - results.push(bar(thing)); + promises.push(doAsyncWork(thing)); } // Now that all the asynchronous operations are running, here we wait until they all complete. - return baz(await Promise.all(results)); + const results = await Promise.all(promises); + return results; +} +``` + +This can be beneficial for subtle error-handling reasons as well. Given an array of promises that might reject, +sequential awaiting puts the program at risk of unhandled promise rejections. The exact behavior of unhandled +rejections depends on the environment running your code, but they are generally considered harmful regardless. +In Node.js, for example, [unhandled rejections cause a program to terminate](https://nodejs.org/api/cli.html#--unhandled-rejectionsmode) unless configured otherwise. + +```js +async function foo() { + const arrayOfPromises = somethingThatCreatesAnArrayOfPromises(); + for (const promise of arrayOfPromises) { + // Bad: if any of the promises reject, an exception is thrown, and + // subsequent loop iterations will not run. Therefore, rejections later + // in the array will become unhandled rejections that cannot be caught + // by a caller. + const value = await promise; + console.log(value); + } +} +``` + +```js +async function foo() { + const arrayOfPromises = somethingThatCreatesAnArrayOfPromises(); + // Good: Any rejections will cause a single exception to be thrown here, + // which may be caught and handled by the caller. + const arrayOfValues = await Promise.all(arrayOfPromises); + for (const value of arrayOfValues) { + console.log(value); + } } ``` @@ -51,13 +83,14 @@ Examples of **correct** code for this rule: /*eslint no-await-in-loop: "error"*/ async function foo(things) { - const results = []; + const promises = []; for (const thing of things) { // Good: all asynchronous operations are immediately started. - results.push(bar(thing)); + promises.push(doAsyncWork(thing)); } // Now that all the asynchronous operations are running, here we wait until they all complete. - return baz(await Promise.all(results)); + const results = await Promise.all(promises); + return results; } ``` @@ -74,9 +107,9 @@ async function foo(things) { const results = []; for (const thing of things) { // Bad: each loop iteration is delayed until the entire asynchronous operation completes - results.push(await bar(thing)); + results.push(await doAsyncWork(thing)); } - return baz(results); + return results; } ``` @@ -84,8 +117,50 @@ async function foo(things) { ## When Not To Use It -In many cases the iterations of a loop are not actually independent of each-other. For example, the -output of one iteration might be used as the input to another. Or, loops may be used to retry -asynchronous operations that were unsuccessful. Or, loops may be used to prevent your code from sending -an excessive amount of requests in parallel. In such cases it makes sense to use `await` within a +In many cases the iterations of a loop are not actually independent of each other, and awaiting in +the loop is correct. As a few examples: + +* The output of one iteration might be used as the input to another. + + ```js + async function loopIterationsDependOnEachOther() { + let previousResult = null; + for (let i = 0; i < 10; i++) { + const result = await doSomething(i, previousResult); + if (someCondition(result, previousResult)) { + break; + } else { + previousResult = result; + } + } + } + ``` + +* Loops may be used to retry asynchronous operations that were unsuccessful. + + ```js + async function retryUpTo10Times() { + for (let i = 0; i < 10; i++) { + const wasSuccessful = await tryToDoSomething(); + if (wasSuccessful) + return 'succeeded!'; + // wait to try again. + await new Promise(resolve => setTimeout(resolve, 1000)); + } + return 'failed!'; + } + ``` + +* Loops may be used to prevent your code from sending an excessive amount of requests in parallel. + + ```js + async function makeUpdatesToRateLimitedApi(thingsToUpdate) { + // we'll exceed our rate limit if we make all the network calls in parallel. + for (const thing of thingsToUpdate) { + await updateThingWithRateLimitedApi(thing); + } + } + ``` + +In such cases it makes sense to use `await` within a loop and it is recommended to disable the rule via a standard ESLint disable comment. diff --git a/docs/src/rules/no-bitwise.md b/docs/src/rules/no-bitwise.md index 3b7268fc9764..d5dde3d75b93 100644 --- a/docs/src/rules/no-bitwise.md +++ b/docs/src/rules/no-bitwise.md @@ -7,7 +7,7 @@ rule_type: suggestion The use of bitwise operators in JavaScript is very rare and often `&` or `|` is simply a mistyped `&&` or `||`, which will lead to unexpected behavior. ```js -var x = y | z; +const x = y | z; ``` ## Rule Details @@ -21,19 +21,19 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-bitwise: "error"*/ -var x = y | z; +let x = y | z; -var x = y & z; +const x1 = y & z; -var x = y ^ z; +const x2 = y ^ z; -var x = ~ z; +const x3 = ~ z; -var x = y << z; +const x4 = y << z; -var x = y >> z; +const x5 = y >> z; -var x = y >>> z; +const x6 = y >>> z; x |= y; @@ -57,13 +57,13 @@ Examples of **correct** code for this rule: ```js /*eslint no-bitwise: "error"*/ -var x = y || z; +let x = y || z; -var x = y && z; +const x1 = y && z; -var x = y > z; +const x2 = y > z; -var x = y < z; +const x3 = y < z; x += y; ``` @@ -100,7 +100,7 @@ Examples of **correct** code for this rule with the `{ "int32Hint": true }` opti ```js /*eslint no-bitwise: ["error", { "int32Hint": true }] */ -var b = a|0; +const b = a|0; ``` ::: diff --git a/docs/src/rules/no-buffer-constructor.md b/docs/src/rules/no-buffer-constructor.md index 8282da6bc508..28b2a1fa19d2 100644 --- a/docs/src/rules/no-buffer-constructor.md +++ b/docs/src/rules/no-buffer-constructor.md @@ -7,9 +7,6 @@ further_reading: - https://github.com/nodejs/node/issues/4660 --- - -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - In Node.js, the behavior of the `Buffer` constructor is different depending on the type of its argument. Passing an argument from user input to `Buffer()` without validating its type can lead to security vulnerabilities such as remote memory disclosure and denial of service. As a result, the `Buffer` constructor has been deprecated and should not be used. Use the producer methods `Buffer.from`, `Buffer.alloc`, and `Buffer.allocUnsafe` instead. ## Rule Details diff --git a/docs/src/rules/no-caller.md b/docs/src/rules/no-caller.md index 13dddd0e6633..d6c5a9813462 100644 --- a/docs/src/rules/no-caller.md +++ b/docs/src/rules/no-caller.md @@ -8,7 +8,7 @@ The use of `arguments.caller` and `arguments.callee` make several code optimizat ```js function foo() { - var callee = arguments.callee; + const callee = arguments.callee; } ``` diff --git a/docs/src/rules/no-case-declarations.md b/docs/src/rules/no-case-declarations.md index 49e6f41f08d7..dcf4d74f0e04 100644 --- a/docs/src/rules/no-case-declarations.md +++ b/docs/src/rules/no-case-declarations.md @@ -25,7 +25,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-case-declarations: "error"*/ -/*eslint-env es6*/ switch (foo) { case 1: @@ -50,7 +49,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-case-declarations: "error"*/ -/*eslint-env es6*/ // Declarations outside switch-statements are valid const a = 0; diff --git a/docs/src/rules/no-catch-shadow.md b/docs/src/rules/no-catch-shadow.md index c875f9fe4a92..ef7be48d92c5 100644 --- a/docs/src/rules/no-catch-shadow.md +++ b/docs/src/rules/no-catch-shadow.md @@ -3,9 +3,6 @@ title: no-catch-shadow rule_type: suggestion --- - -This rule was **deprecated** in ESLint v5.1.0. - In IE 8 and earlier, the catch clause parameter can overwrite the value of a variable in the outer scope, if that variable has the same name as the catch clause parameter. ```js diff --git a/docs/src/rules/no-class-assign.md b/docs/src/rules/no-class-assign.md index 360192ca2f48..1852af24d608 100644 --- a/docs/src/rules/no-class-assign.md +++ b/docs/src/rules/no-class-assign.md @@ -1,6 +1,7 @@ --- title: no-class-assign rule_type: problem +handled_by_typescript: true --- @@ -8,8 +9,6 @@ rule_type: problem `ClassDeclaration` creates a variable, and we can modify the variable. ```js -/*eslint-env es6*/ - class A { } A = 0; ``` @@ -26,7 +25,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-class-assign: "error"*/ -/*eslint-env es6*/ class A { } A = 0; @@ -38,7 +36,6 @@ A = 0; ```js /*eslint no-class-assign: "error"*/ -/*eslint-env es6*/ A = 0; class A { } @@ -50,7 +47,6 @@ class A { } ```js /*eslint no-class-assign: "error"*/ -/*eslint-env es6*/ class A { b() { @@ -65,7 +61,6 @@ class A { ```js /*eslint no-class-assign: "error"*/ -/*eslint-env es6*/ let A = class A { b() { @@ -83,7 +78,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-class-assign: "error"*/ -/*eslint-env es6*/ let A = class A { } A = 0; // A is a variable. @@ -95,7 +89,6 @@ A = 0; // A is a variable. ```js /*eslint no-class-assign: "error"*/ -/*eslint-env es6*/ let A = class { b() { @@ -110,7 +103,6 @@ let A = class { ```js /*eslint no-class-assign: 2*/ -/*eslint-env es6*/ class A { b(A) { diff --git a/docs/src/rules/no-cond-assign.md b/docs/src/rules/no-cond-assign.md index 7cb02d7c32fb..a7872a28bb1f 100644 --- a/docs/src/rules/no-cond-assign.md +++ b/docs/src/rules/no-cond-assign.md @@ -26,8 +26,8 @@ This rule disallows ambiguous assignment operators in test conditions of `if`, ` This rule has a string option: -* `"except-parens"` (default) allows assignments in test conditions *only if* they are enclosed in parentheses (for example, to allow reassigning a variable in the test of a `while` or `do...while` loop) -* `"always"` disallows all assignments in test conditions +* `"except-parens"` (default) allows assignments in test conditions *only if* they are enclosed in parentheses (for example, to allow reassigning a variable in the test of a `while` or `do...while` loop). +* `"always"` disallows all assignments in test conditions. ### except-parens @@ -39,13 +39,13 @@ Examples of **incorrect** code for this rule with the default `"except-parens"` /*eslint no-cond-assign: "error"*/ // Unintentional assignment -var x; +let x; if (x = 0) { - var b = 1; + const b = 1; } // Practical example that is similar to an error -var setHeight = function (someNode) { +const setHeight = function (someNode) { do { someNode.height = "100px"; } while (someNode = someNode.parentNode); @@ -62,20 +62,20 @@ Examples of **correct** code for this rule with the default `"except-parens"` op /*eslint no-cond-assign: "error"*/ // Assignment replaced by comparison -var x; +let x; if (x === 0) { - var b = 1; + const b = 1; } // Practical example that wraps the assignment in parentheses -var setHeight = function (someNode) { +const setHeight = function (someNode) { do { someNode.height = "100px"; } while ((someNode = someNode.parentNode)); } // Practical example that wraps the assignment and tests for 'null' -var setHeight = function (someNode) { +const set_height = function (someNode) { do { someNode.height = "100px"; } while ((someNode = someNode.parentNode) !== null); @@ -94,27 +94,27 @@ Examples of **incorrect** code for this rule with the `"always"` option: /*eslint no-cond-assign: ["error", "always"]*/ // Unintentional assignment -var x; +let x; if (x = 0) { - var b = 1; + const b = 1; } // Practical example that is similar to an error -var setHeight = function (someNode) { +const setHeight = function (someNode) { do { someNode.height = "100px"; } while (someNode = someNode.parentNode); } // Practical example that wraps the assignment in parentheses -var setHeight = function (someNode) { +const set_height = function (someNode) { do { someNode.height = "100px"; } while ((someNode = someNode.parentNode)); } // Practical example that wraps the assignment and tests for 'null' -var setHeight = function (someNode) { +const heightSetter = function (someNode) { do { someNode.height = "100px"; } while ((someNode = someNode.parentNode) !== null); @@ -131,9 +131,9 @@ Examples of **correct** code for this rule with the `"always"` option: /*eslint no-cond-assign: ["error", "always"]*/ // Assignment replaced by comparison -var x; +let x; if (x === 0) { - var b = 1; + const b = 1; } ``` diff --git a/docs/src/rules/no-confusing-arrow.md b/docs/src/rules/no-confusing-arrow.md index 349984587b82..af8c79e8ce35 100644 --- a/docs/src/rules/no-confusing-arrow.md +++ b/docs/src/rules/no-confusing-arrow.md @@ -5,9 +5,6 @@ related_rules: - no-constant-condition - arrow-parens --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/no-confusing-arrow) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Arrow functions (`=>`) are similar in syntax to some comparison operators (`>`, `<`, `<=`, and `>=`). This rule warns against using the arrow function syntax in places where it could be confused with a comparison operator. Here's an example where the usage of `=>` could be confusing: @@ -31,7 +28,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-confusing-arrow: "error"*/ -/*eslint-env es6*/ var x = a => 1 ? 2 : 3; var x = (a) => 1 ? 2 : 3; @@ -45,7 +41,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-confusing-arrow: "error"*/ -/*eslint-env es6*/ + var x = a => (1 ? 2 : 3); var x = (a) => (1 ? 2 : 3); var x = (a) => { @@ -82,7 +78,7 @@ Examples of **incorrect** code for this rule with the `{"allowParens": false}` o ```js /*eslint no-confusing-arrow: ["error", {"allowParens": false}]*/ -/*eslint-env es6*/ + var x = a => (1 ? 2 : 3); var x = (a) => (1 ? 2 : 3); ``` @@ -100,7 +96,7 @@ Examples of **correct** code for this rule with the `{"onlyOneSimpleParam": true ```js /*eslint no-confusing-arrow: ["error", {"onlyOneSimpleParam": true}]*/ -/*eslint-env es6*/ + () => 1 ? 2 : 3; (a, b) => 1 ? 2 : 3; (a = b) => 1 ? 2 : 3; diff --git a/docs/src/rules/no-console.md b/docs/src/rules/no-console.md index 9420c36ec03b..2887d31333be 100644 --- a/docs/src/rules/no-console.md +++ b/docs/src/rules/no-console.md @@ -69,7 +69,7 @@ console.error("Log an error level message."); If you're using Node.js, however, `console` is used to output information to the user and so is not strictly used for debugging purposes. If you are developing for Node.js then you most likely do not want this rule enabled. -Another case where you might not use this rule is if you want to enforce console calls and not console overwrites. For example: +Another case where you might not use this rule is if you want to enforce `console` calls and not `console` overwrites. For example: ```js /* eslint no-console: ["error", { allow: ["warn"] }] */ diff --git a/docs/src/rules/no-const-assign.md b/docs/src/rules/no-const-assign.md index ca62132a757e..61fa4f9390ab 100644 --- a/docs/src/rules/no-const-assign.md +++ b/docs/src/rules/no-const-assign.md @@ -6,14 +6,11 @@ handled_by_typescript: true -We cannot modify variables that are declared using `const` keyword. -It will raise a runtime error. - -Under non ES2015 environment, it might be ignored merely. +Constant bindings cannot be modified. An attempt to modify a constant binding will raise a runtime error. ## Rule Details -This rule is aimed to flag modifying variables that are declared using `const` keyword. +This rule is aimed to flag modifying variables that are declared using `const`, `using`, or `await using` keywords. Examples of **incorrect** code for this rule: @@ -21,7 +18,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-const-assign: "error"*/ -/*eslint-env es6*/ const a = 0; a = 1; @@ -33,7 +29,6 @@ a = 1; ```js /*eslint no-const-assign: "error"*/ -/*eslint-env es6*/ const a = 0; a += 1; @@ -45,7 +40,6 @@ a += 1; ```js /*eslint no-const-assign: "error"*/ -/*eslint-env es6*/ const a = 0; ++a; @@ -53,13 +47,30 @@ const a = 0; ::: +::: incorrect + +```js +/*eslint no-const-assign: "error"*/ + +if (foo) { + using a = getSomething(); + a = somethingElse; +} + +if (bar) { + await using a = getSomething(); + a = somethingElse; +} +``` + +::: + Examples of **correct** code for this rule: ::: correct ```js /*eslint no-const-assign: "error"*/ -/*eslint-env es6*/ const a = 0; console.log(a); @@ -71,7 +82,24 @@ console.log(a); ```js /*eslint no-const-assign: "error"*/ -/*eslint-env es6*/ + +if (foo) { + using a = getSomething(); + a.execute(); +} + +if (bar) { + await using a = getSomething(); + a.execute(); +} +``` + +::: + +::: correct + +```js +/*eslint no-const-assign: "error"*/ for (const a in [1, 2, 3]) { // `a` is re-defined (not modified) on each loop step. console.log(a); @@ -84,7 +112,6 @@ for (const a in [1, 2, 3]) { // `a` is re-defined (not modified) on each loop st ```js /*eslint no-const-assign: "error"*/ -/*eslint-env es6*/ for (const a of [1, 2, 3]) { // `a` is re-defined (not modified) on each loop step. console.log(a); @@ -95,4 +122,4 @@ for (const a of [1, 2, 3]) { // `a` is re-defined (not modified) on each loop st ## When Not To Use It -If you don't want to be notified about modifying variables that are declared using `const` keyword, you can safely disable this rule. +If you don't want to be notified about modifying variables that are declared using `const`, `using`, and `await using` keywords, you can safely disable this rule. diff --git a/docs/src/rules/no-constant-condition.md b/docs/src/rules/no-constant-condition.md index 2ac613f7abfb..27988a149c7a 100644 --- a/docs/src/rules/no-constant-condition.md +++ b/docs/src/rules/no-constant-condition.md @@ -73,7 +73,7 @@ do { doSomethingForever(); } while (x = -1); -var result = 0 ? a : b; +const result = 0 ? a : b; if(input === "hello" || "bye"){ output(input); @@ -105,7 +105,7 @@ do { doSomething(); } while (x); -var result = x !== 0 ? a : b; +const result = x !== 0 ? a : b; if(input === "hello" || input === "bye"){ output(input); @@ -118,14 +118,94 @@ if(input === "hello" || input === "bye"){ ### checkLoops -Set to `true` by default. Setting this option to `false` allows constant expressions in loops. +This is a string option having following values: -Examples of **correct** code for when `checkLoops` is `false`: +* `"all"` - Disallow constant expressions in all loops. +* `"allExceptWhileTrue"` (default) - Disallow constant expressions in all loops except `while` loops with expression `true`. +* `"none"` - Allow constant expressions in loops. + +Or instead you can set the `checkLoops` value to booleans where `true` is same as `"all"` and `false` is same as `"none"`. + +Examples of **incorrect** code for when `checkLoops` is `"all"` or `true`: + +::: incorrect + +```js +/*eslint no-constant-condition: ["error", { "checkLoops": "all" }]*/ + +while (true) { + doSomething(); +}; + +for (;true;) { + doSomething(); +}; +``` + +::: + +::: incorrect + +```js +/*eslint no-constant-condition: ["error", { "checkLoops": true }]*/ + +while (true) { + doSomething(); +}; + +do { + doSomething(); +} while (true) +``` + +::: + +Examples of **correct** code for when `checkLoops` is `"all"` or `true`: ::: correct ```js -/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ +/*eslint no-constant-condition: ["error", { "checkLoops": "all" }]*/ + +while (a === b) { + doSomething(); +}; +``` + +::: + +::: correct + +```js +/*eslint no-constant-condition: ["error", { "checkLoops": true }]*/ + +for (let x = 0; x <= 10; x++) { + doSomething(); +}; +``` + +::: + +Example of **correct** code for when `checkLoops` is `"allExceptWhileTrue"`: + +::: correct + +```js +/*eslint no-constant-condition: "error"*/ + +while (true) { + doSomething(); +}; +``` + +::: + +Examples of **correct** code for when `checkLoops` is `"none"` or `false`: + +::: correct + +```js +/*eslint no-constant-condition: ["error", { "checkLoops": "none" }]*/ while (true) { doSomething(); @@ -134,19 +214,34 @@ while (true) { } }; -for (;true;) { +do { + doSomething(); + if (condition()) { + break; + } +} while (true) +``` + +::: + +::: correct + +```js +/*eslint no-constant-condition: ["error", { "checkLoops": false }]*/ + +while (true) { doSomething(); if (condition()) { break; } }; -do { +for (;true;) { doSomething(); if (condition()) { break; } -} while (true) +}; ``` ::: diff --git a/docs/src/rules/no-constructor-return.md b/docs/src/rules/no-constructor-return.md index e429d386accb..12734d63a3cd 100644 --- a/docs/src/rules/no-constructor-return.md +++ b/docs/src/rules/no-constructor-return.md @@ -8,7 +8,7 @@ In JavaScript, returning a value in the constructor of a class may be a mistake. ## Rule Details -This rule disallows return statements in the constructor of a class. Note that returning nothing with flow control is allowed. +This rule disallows return statements in the constructor of a class. Note that returning nothing is allowed. Examples of **incorrect** code for this rule: @@ -57,6 +57,13 @@ class D { f(); } } + +class E { + constructor() { + return; + } +} + ``` ::: diff --git a/docs/src/rules/no-continue.md b/docs/src/rules/no-continue.md index fd0df533dec2..59dde307e6ab 100644 --- a/docs/src/rules/no-continue.md +++ b/docs/src/rules/no-continue.md @@ -7,7 +7,7 @@ rule_type: suggestion The `continue` statement terminates execution of the statements in the current iteration of the current or labeled loop, and continues execution of the loop with the next iteration. When used incorrectly it makes code less testable, less readable and less maintainable. Structured control flow statements such as `if` should be used instead. ```js -var sum = 0, +let sum = 0, i; for(i = 0; i < 10; i++) { @@ -30,7 +30,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-continue: "error"*/ -var sum = 0, +let sum = 0, i; for(i = 0; i < 10; i++) { @@ -49,7 +49,7 @@ for(i = 0; i < 10; i++) { ```js /*eslint no-continue: "error"*/ -var sum = 0, +let sum = 0, i; labeledLoop: for(i = 0; i < 10; i++) { @@ -70,7 +70,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-continue: "error"*/ -var sum = 0, +let sum = 0, i; for(i = 0; i < 10; i++) { diff --git a/docs/src/rules/no-control-regex.md b/docs/src/rules/no-control-regex.md index 18a5ce40148b..be30905af43f 100644 --- a/docs/src/rules/no-control-regex.md +++ b/docs/src/rules/no-control-regex.md @@ -30,13 +30,13 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-control-regex: "error"*/ -var pattern1 = /\x00/; -var pattern2 = /\x0C/; -var pattern3 = /\x1F/; -var pattern4 = /\u000C/; -var pattern5 = /\u{C}/u; -var pattern6 = new RegExp("\x0C"); // raw U+000C character in the pattern -var pattern7 = new RegExp("\\x0C"); // \x0C pattern +const pattern1 = /\x00/; +const pattern2 = /\x0C/; +const pattern3 = /\x1F/; +const pattern4 = /\u000C/; +const pattern5 = /\u{C}/u; +const pattern6 = new RegExp("\x0C"); // raw U+000C character in the pattern +const pattern7 = new RegExp("\\x0C"); // \x0C pattern ``` ::: @@ -48,14 +48,14 @@ Examples of **correct** code for this rule: ```js /*eslint no-control-regex: "error"*/ -var pattern1 = /\x20/; -var pattern2 = /\u0020/; -var pattern3 = /\u{20}/u; -var pattern4 = /\t/; -var pattern5 = /\n/; -var pattern6 = new RegExp("\x20"); -var pattern7 = new RegExp("\\t"); -var pattern8 = new RegExp("\\n"); +const pattern1 = /\x20/; +const pattern2 = /\u0020/; +const pattern3 = /\u{20}/u; +const pattern4 = /\t/; +const pattern5 = /\n/; +const pattern6 = new RegExp("\x20"); +const pattern7 = new RegExp("\\t"); +const pattern8 = new RegExp("\\n"); ``` ::: diff --git a/docs/src/rules/no-delete-var.md b/docs/src/rules/no-delete-var.md index f66aec5879fb..507c25c63d2f 100644 --- a/docs/src/rules/no-delete-var.md +++ b/docs/src/rules/no-delete-var.md @@ -20,7 +20,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-delete-var: "error"*/ -var x; +let x; delete x; ``` diff --git a/docs/src/rules/no-dupe-args.md b/docs/src/rules/no-dupe-args.md index 748d52c2e21f..faedda898b15 100644 --- a/docs/src/rules/no-dupe-args.md +++ b/docs/src/rules/no-dupe-args.md @@ -25,7 +25,7 @@ function foo(a, b, a) { console.log("value of the second a:", a); } -var bar = function (a, b, a) { +const bar = function (a, b, a) { console.log("value of the second a:", a); }; ``` @@ -43,7 +43,7 @@ function foo(a, b, c) { console.log(a, b, c); } -var bar = function (a, b, c) { +const bar = function (a, b, c) { console.log(a, b, c); }; ``` diff --git a/docs/src/rules/no-dupe-class-members.md b/docs/src/rules/no-dupe-class-members.md index 6565c9f15ee7..ad3272a5c4af 100644 --- a/docs/src/rules/no-dupe-class-members.md +++ b/docs/src/rules/no-dupe-class-members.md @@ -10,14 +10,12 @@ If there are declarations of the same name in class members, the last declaratio It can cause unexpected behaviors. ```js -/*eslint-env es6*/ - class Foo { bar() { console.log("hello"); } bar() { console.log("goodbye"); } } -var foo = new Foo(); +const foo = new Foo(); foo.bar(); // goodbye ``` @@ -97,6 +95,24 @@ class E { ::: +This rule additionally supports TypeScript type syntax. It has support for TypeScript's method overload definitions. + +Examples of **correct** TypeScript code for this rule: + +::: correct + +```ts +/* eslint no-dupe-class-members: "error" */ + +class A { + foo(value: string): void; + foo(value: number): void; + foo(value: string | number) {} // ✅ This is the actual implementation. +} +``` + +::: + ## When Not To Use It This rule should not be used in ES3/5 environments. diff --git a/docs/src/rules/no-dupe-keys.md b/docs/src/rules/no-dupe-keys.md index 1527bf8f1ec7..10fdaebb998a 100644 --- a/docs/src/rules/no-dupe-keys.md +++ b/docs/src/rules/no-dupe-keys.md @@ -9,7 +9,7 @@ handled_by_typescript: true Multiple properties with the same key in object literals can cause unexpected behavior in your application. ```js -var foo = { +const foo = { bar: "baz", bar: "qux" }; @@ -26,17 +26,17 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-dupe-keys: "error"*/ -var foo = { +const foo = { bar: "baz", bar: "qux" }; -var foo = { +const bar = { "bar": "baz", bar: "qux" }; -var foo = { +const baz = { 0x1: "baz", 1: "qux" }; @@ -51,10 +51,15 @@ Examples of **correct** code for this rule: ```js /*eslint no-dupe-keys: "error"*/ -var foo = { +const foo = { bar: "baz", quxx: "qux" }; + +const obj = { + "__proto__": baz, // defines object's prototype + ["__proto__"]: qux // defines a property named "__proto__" +}; ``` ::: diff --git a/docs/src/rules/no-duplicate-case.md b/docs/src/rules/no-duplicate-case.md index 59a2a9874953..c65a58f7d059 100644 --- a/docs/src/rules/no-duplicate-case.md +++ b/docs/src/rules/no-duplicate-case.md @@ -18,7 +18,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-duplicate-case: "error"*/ -var a = 1, +const a = 1, one = 1; switch (a) { @@ -64,7 +64,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-duplicate-case: "error"*/ -var a = 1, +const a = 1, one = 1; switch (a) { diff --git a/docs/src/rules/no-duplicate-imports.md b/docs/src/rules/no-duplicate-imports.md index 2d7a5538194d..8eaebb889896 100644 --- a/docs/src/rules/no-duplicate-imports.md +++ b/docs/src/rules/no-duplicate-imports.md @@ -61,7 +61,12 @@ import * as something from 'module'; ## Options -This rule takes one optional argument, an object with a single key, `includeExports` which is a `boolean`. It defaults to `false`. +This rule has an object option: + +* `"includeExports"`: `true` (default `false`) checks for exports in addition to imports. +* `"allowSeparateTypeImports"`: `true` (default `false`) allows a type import alongside a value import from the same module in TypeScript files. + +### includeExports If re-exporting from an imported module, you should add the imports to the `import`-statement, and export that directly, not use `export ... from`. @@ -110,3 +115,59 @@ export * from 'module'; ``` ::: + +### allowSeparateTypeImports + +TypeScript allows importing types using `import type`. By default, this rule flags instances of `import type` that have the same specifier as `import`. The `allowSeparateTypeImports` option allows you to override this behavior. + +Example of **incorrect** TypeScript code for this rule with the default `{ "allowSeparateTypeImports": false }` option: + +::: incorrect + +```ts +/*eslint no-duplicate-imports: ["error", { "allowSeparateTypeImports": false }]*/ + +import { someValue } from 'module'; +import type { SomeType } from 'module'; +``` + +::: + +Example of **correct** TypeScript code for this rule with the default `{ "allowSeparateTypeImports": false }` option: + +::: correct + +```ts +/*eslint no-duplicate-imports: ["error", { "allowSeparateTypeImports": false }]*/ + +import { someValue, type SomeType } from 'module'; +``` + +::: + +Example of **incorrect** TypeScript code for this rule with the `{ "allowSeparateTypeImports": true }` option: + +::: incorrect + +```ts +/*eslint no-duplicate-imports: ["error", { "allowSeparateTypeImports": true }]*/ + +import { someValue } from 'module'; +import type { SomeType } from 'module'; +import type { AnotherType } from 'module'; +``` + +::: + +Example of **correct** TypeScript code for this rule with the `{ "allowSeparateTypeImports": true }` option: + +::: correct + +```ts +/*eslint no-duplicate-imports: ["error", { "allowSeparateTypeImports": true }]*/ + +import { someValue } from 'module'; +import type { SomeType, AnotherType } from 'module'; +``` + +::: diff --git a/docs/src/rules/no-else-return.md b/docs/src/rules/no-else-return.md index 379de922b321..c9052d3fc7a2 100644 --- a/docs/src/rules/no-else-return.md +++ b/docs/src/rules/no-else-return.md @@ -19,14 +19,14 @@ function foo() { ## Rule Details -This rule is aimed at highlighting an unnecessary block of code following an `if` containing a return statement. As such, it will warn when it encounters an `else` following a chain of `if`s, all of them containing a `return` statement. +This rule is aimed at highlighting an unnecessary block of code following an `if` containing a `return` statement. As such, it will warn when it encounters an `else` following a chain of `if`s, all of them containing a `return` statement. ## Options This rule has an object option: -* `allowElseIf: true` (default) allows `else if` blocks after a return -* `allowElseIf: false` disallows `else if` blocks after a return +* `allowElseIf: true` (default) allows `else if` blocks after a `return` +* `allowElseIf: false` disallows `else if` blocks after a `return` ### allowElseIf: true @@ -59,7 +59,7 @@ function foo3() { if (x) { return y; } else { - var t = "foo"; + const t = "foo"; } return t; @@ -110,7 +110,7 @@ function foo2() { if (x) { return y; } else if (z) { - var t = "foo"; + const t = "foo"; } else { return w; } diff --git a/docs/src/rules/no-empty-character-class.md b/docs/src/rules/no-empty-character-class.md index 2ab0679b394c..7f72ece6aec6 100644 --- a/docs/src/rules/no-empty-character-class.md +++ b/docs/src/rules/no-empty-character-class.md @@ -8,7 +8,7 @@ rule_type: problem Because empty character classes in regular expressions do not match anything, they might be typing mistakes. ```js -var foo = /^abc[]/; +const foo = /^abc[]/; ``` ## Rule Details @@ -73,5 +73,5 @@ Example of a *false negative* when this rule reports correct code: ```js /*eslint no-empty-character-class: "error"*/ -var abcNeverMatches = new RegExp("^abc[]"); +const abcNeverMatches = new RegExp("^abc[]"); ``` diff --git a/docs/src/rules/no-empty-function.md b/docs/src/rules/no-empty-function.md index 68aa2ad887b6..f4a72e44bae8 100644 --- a/docs/src/rules/no-empty-function.md +++ b/docs/src/rules/no-empty-function.md @@ -34,19 +34,18 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-empty-function: "error"*/ -/*eslint-env es6*/ function foo() {} -var bar = function() {}; +const bar = function() {}; -var bar = () => {}; +const bar1 = () => {}; function* baz() {} -var bar = function*() {}; +const bar2 = function*() {}; -var obj = { +const obj = { foo: function() {}, foo: function*() {}, @@ -89,17 +88,16 @@ Examples of **correct** code for this rule: ```js /*eslint no-empty-function: "error"*/ -/*eslint-env es6*/ function foo() { // do nothing. } -var baz = function() { +const baz = function() { // any clear comments. }; -var baz = () => { +const baz1 = () => { bar(); }; @@ -107,11 +105,11 @@ function* foobar() { // do nothing. } -var baz = function*() { +const baz2 = function*() { // do nothing. }; -var obj = { +const obj = { foo: function() { // do nothing. }, @@ -193,6 +191,10 @@ This rule has an option to allow specific kinds of functions to be empty. * `"constructors"` - Class constructors. * `"asyncFunctions"` - Async functions. * `"asyncMethods"` - Async class methods and method shorthands of object literals. + * `"privateConstructors"` - Private class constructors. (TypeScript only) + * `"protectedConstructors"` - Protected class constructors. (TypeScript only) + * `"decoratedFunctions"` - Class methods with decorators. (TypeScript only) + * `"overrideMethods"` - Methods that use the override keyword. (TypeScript only) ### allow: functions @@ -205,9 +207,9 @@ Examples of **correct** code for the `{ "allow": ["functions"] }` option: function foo() {} -var bar = function() {}; +const bar = function() {}; -var obj = { +const obj = { foo: function() {} }; ``` @@ -222,9 +224,8 @@ Examples of **correct** code for the `{ "allow": ["arrowFunctions"] }` option: ```js /*eslint no-empty-function: ["error", { "allow": ["arrowFunctions"] }]*/ -/*eslint-env es6*/ -var foo = () => {}; +const foo = () => {}; ``` ::: @@ -237,13 +238,12 @@ Examples of **correct** code for the `{ "allow": ["generatorFunctions"] }` optio ```js /*eslint no-empty-function: ["error", { "allow": ["generatorFunctions"] }]*/ -/*eslint-env es6*/ function* foo() {} -var bar = function*() {}; +const bar = function*() {}; -var obj = { +const obj = { foo: function*() {} }; ``` @@ -258,9 +258,8 @@ Examples of **correct** code for the `{ "allow": ["methods"] }` option: ```js /*eslint no-empty-function: ["error", { "allow": ["methods"] }]*/ -/*eslint-env es6*/ -var obj = { +const obj = { foo() {} }; @@ -280,9 +279,8 @@ Examples of **correct** code for the `{ "allow": ["generatorMethods"] }` option: ```js /*eslint no-empty-function: ["error", { "allow": ["generatorMethods"] }]*/ -/*eslint-env es6*/ -var obj = { +const obj = { *foo() {} }; @@ -302,9 +300,8 @@ Examples of **correct** code for the `{ "allow": ["getters"] }` option: ```js /*eslint no-empty-function: ["error", { "allow": ["getters"] }]*/ -/*eslint-env es6*/ -var obj = { +const obj = { get foo() {} }; @@ -324,9 +321,8 @@ Examples of **correct** code for the `{ "allow": ["setters"] }` option: ```js /*eslint no-empty-function: ["error", { "allow": ["setters"] }]*/ -/*eslint-env es6*/ -var obj = { +const obj = { set foo(value) {} }; @@ -346,7 +342,6 @@ Examples of **correct** code for the `{ "allow": ["constructors"] }` option: ```js /*eslint no-empty-function: ["error", { "allow": ["constructors"] }]*/ -/*eslint-env es6*/ class A { constructor() {} @@ -363,7 +358,6 @@ Examples of **correct** code for the `{ "allow": ["asyncFunctions"] }` options: ```js /*eslint no-empty-function: ["error", { "allow": ["asyncFunctions"] }]*/ -/*eslint-env es2017*/ async function a(){} ``` @@ -378,9 +372,8 @@ Examples of **correct** code for the `{ "allow": ["asyncMethods"] }` options: ```js /*eslint no-empty-function: ["error", { "allow": ["asyncMethods"] }]*/ -/*eslint-env es2017*/ -var obj = { +const obj = { async foo() {} }; @@ -392,6 +385,75 @@ class A { ::: +### allow: privateConstructors + +Examples of **correct** TypeScript code for the `{ "allow": ["privateConstructors"] }` option: + +::: correct + +```ts +/*eslint no-empty-function: ["error", { "allow": ["privateConstructors"] }]*/ + +class A { + private constructor() {} +} +``` + +::: + +### allow: protectedConstructors + +Examples of **correct** TypeScript code for the `{ "allow": ["protectedConstructors"] }` option: + +::: correct + +```ts +/*eslint no-empty-function: ["error", { "allow": ["protectedConstructors"] }]*/ + +class A { + protected constructor() {} +} +``` + +::: + +### allow: decoratedFunctions + +Examples of **correct** TypeScript code for the `{ "allow": ["decoratedFunctions"] }` option: + +::: correct + +```ts +/*eslint no-empty-function: ["error", { "allow": ["decoratedFunctions"] }]*/ + +class A { + @decorator + foo() {} +} +``` + +::: + +### allow: overrideMethods + +Examples of **correct** TypeScript code for the `{ "allow": ["overrideMethods"] }` option: + +::: correct + +```ts +/*eslint no-empty-function: ["error", { "allow": ["overrideMethods"] }]*/ + +abstract class Base { + abstract method(): void; +} + +class Derived extends Base { + override method() {} +} +``` + +::: + ## When Not To Use It If you don't want to be notified about empty functions, then it's safe to disable this rule. diff --git a/docs/src/rules/no-empty-pattern.md b/docs/src/rules/no-empty-pattern.md index 92000add1857..86aac082235a 100644 --- a/docs/src/rules/no-empty-pattern.md +++ b/docs/src/rules/no-empty-pattern.md @@ -9,21 +9,21 @@ When using destructuring, it's possible to create a pattern that has no effect. ```js // doesn't create any variables -var {a: {}} = foo; +const {a: {}} = foo; ``` In this code, no new variables are created because `a` is just a location helper while the `{}` is expected to contain the variables to create, such as: ```js // creates variable b -var {a: { b }} = foo; +const {a: { b }} = foo; ``` In many cases, the empty object pattern is a mistake where the author intended to use a default value instead, such as: ```js // creates variable a -var {a = {}} = foo; +const {a = {}} = foo; ``` The difference between these two patterns is subtle, especially because the problematic empty pattern looks just like an object literal. @@ -39,10 +39,10 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-empty-pattern: "error"*/ -var {} = foo; -var [] = foo; -var {a: {}} = foo; -var {a: []} = foo; +const {} = foo; +const [] = foo; +const {a: {}} = foo; +const {a: []} = foo; function foo({}) {} function bar([]) {} function baz({a: {}}) {} @@ -58,8 +58,8 @@ Examples of **correct** code for this rule: ```js /*eslint no-empty-pattern: "error"*/ -var {a = {}} = foo; -var {a = []} = foo; +const {a = {}} = foo; +const {b = []} = foo; function foo({a = {}}) {} function bar({a = []}) {} ``` @@ -84,10 +84,10 @@ Examples of **incorrect** code for this rule with the `{"allowObjectPatternsAsPa /*eslint no-empty-pattern: ["error", { "allowObjectPatternsAsParameters": true }]*/ function foo({a: {}}) {} -var bar = function({a: {}}) {}; -var bar = ({a: {}}) => {}; -var bar = ({} = bar) => {}; -var bar = ({} = { bar: 1 }) => {}; +const bar = function({a: {}}) {}; +const qux = ({a: {}}) => {}; +const quux = ({} = bar) => {}; +const item = ({} = { bar: 1 }) => {}; function baz([]) {} ``` @@ -102,8 +102,8 @@ Examples of **correct** code for this rule with the `{"allowObjectPatternsAsPara /*eslint no-empty-pattern: ["error", { "allowObjectPatternsAsParameters": true }]*/ function foo({}) {} -var bar = function({}) {}; -var bar = ({}) => {}; +const bar = function({}) {}; +const qux = ({}) => {}; function baz({} = {}) {} ``` diff --git a/docs/src/rules/no-eq-null.md b/docs/src/rules/no-eq-null.md index 43c95574ea33..393c28824251 100644 --- a/docs/src/rules/no-eq-null.md +++ b/docs/src/rules/no-eq-null.md @@ -1,6 +1,8 @@ --- title: no-eq-null rule_type: suggestion +related_rules: +- eqeqeq --- @@ -14,7 +16,7 @@ if (foo == null) { ## Rule Details -The `no-eq-null` rule aims reduce potential bug and unwanted behavior by ensuring that comparisons to `null` only match `null`, and not also `undefined`. As such it will flag comparisons to null when using `==` and `!=`. +The `no-eq-null` rule aims reduce potential bug and unwanted behavior by ensuring that comparisons to `null` only match `null`, and not also `undefined`. As such it will flag comparisons to `null` when using `==` and `!=`. Examples of **incorrect** code for this rule: diff --git a/docs/src/rules/no-eval.md b/docs/src/rules/no-eval.md index 64f7c78b3d66..5c22919a3a23 100644 --- a/docs/src/rules/no-eval.md +++ b/docs/src/rules/no-eval.md @@ -12,7 +12,7 @@ further_reading: JavaScript's `eval()` function is potentially dangerous and is often misused. Using `eval()` on untrusted code can open a program up to several different injection attacks. The use of `eval()` in most contexts can be substituted for a better, alternative approach to a problem. ```js -var obj = { x: "foo" }, +const obj = { x: "foo" }, key = "x", value = eval("obj." + key); ``` @@ -23,68 +23,67 @@ This rule is aimed at preventing potentially dangerous, unnecessary, and slow co Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-eval: "error"*/ -var obj = { x: "foo" }, +const obj = { x: "foo" }, key = "x", value = eval("obj." + key); -(0, eval)("var a = 0"); +(0, eval)("const a = 0"); -var foo = eval; -foo("var a = 0"); +const foo = eval; +foo("const a = 0"); // This `this` is the global object. -this.eval("var a = 0"); +this.eval("const a = 0"); ``` ::: -Example of additional **incorrect** code for this rule when `browser` environment is set to `true`: +Example of additional **incorrect** code for this rule with `window` global variable: ::: incorrect ```js /*eslint no-eval: "error"*/ -/*eslint-env browser*/ +/*global window*/ -window.eval("var a = 0"); +window.eval("const a = 0"); ``` ::: -Example of additional **incorrect** code for this rule when `node` environment is set to `true`: +Example of additional **incorrect** code for this rule with `global` global variable: ::: incorrect ```js /*eslint no-eval: "error"*/ -/*eslint-env node*/ +/*global global*/ -global.eval("var a = 0"); +global.eval("const a = 0"); ``` ::: Examples of **correct** code for this rule: -::: correct +::: correct { "sourceType": "script" } ```js /*eslint no-eval: "error"*/ -/*eslint-env es6*/ -var obj = { x: "foo" }, +const obj = { x: "foo" }, key = "x", value = obj[key]; class A { foo() { // This is a user-defined method. - this.eval("var a = 0"); + this.eval("const a = 0"); } eval() { @@ -92,7 +91,7 @@ class A { static { // This is a user-defined static method. - this.eval("var a = 0"); + this.eval("const a = 0"); } static eval() { @@ -106,7 +105,7 @@ class A { ### allowIndirect -This rule has an option to allow indirect calls to `eval`. +This rule has an option to allow ["indirect eval"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#direct_and_indirect_eval). Indirect calls to `eval` are less dangerous than direct calls to `eval` because they cannot dynamically change the scope. Because of this, they also will not negatively impact performance to the degree of direct `eval`. ```js @@ -122,7 +121,7 @@ Example of **incorrect** code for this rule with the `{"allowIndirect": true}` o ```js /*eslint no-eval: ["error", {"allowIndirect": true} ]*/ -var obj = { x: "foo" }, +const obj = { x: "foo" }, key = "x", value = eval("obj." + key); ``` @@ -136,12 +135,12 @@ Examples of **correct** code for this rule with the `{"allowIndirect": true}` op ```js /*eslint no-eval: ["error", {"allowIndirect": true} ]*/ -(0, eval)("var a = 0"); +(0, eval)("const a = 0"); -var foo = eval; -foo("var a = 0"); +const foo = eval; +foo("const a = 0"); -this.eval("var a = 0"); +this.eval("const a = 0"); ``` ::: @@ -150,9 +149,9 @@ this.eval("var a = 0"); ```js /*eslint no-eval: ["error", {"allowIndirect": true} ]*/ -/*eslint-env browser*/ +/*global window*/ -window.eval("var a = 0"); +window.eval("const a = 0"); ``` ::: @@ -161,9 +160,9 @@ window.eval("var a = 0"); ```js /*eslint no-eval: ["error", {"allowIndirect": true} ]*/ -/*eslint-env node*/ +/*global global*/ -global.eval("var a = 0"); +global.eval("const a = 0"); ``` ::: @@ -177,13 +176,13 @@ global.eval("var a = 0"); module.exports = function(eval) { // If the value of this `eval` is built-in `eval` function, this is a // call of direct `eval`. - eval("var a = 0"); + eval("const a = 0"); }; ``` * This rule cannot catch renaming the global object. Such as: ```js - var foo = window; - foo.eval("var a = 0"); + const foo = window; + foo.eval("const a = 0"); ``` diff --git a/docs/src/rules/no-ex-assign.md b/docs/src/rules/no-ex-assign.md index 395164d204bc..79263089f009 100644 --- a/docs/src/rules/no-ex-assign.md +++ b/docs/src/rules/no-ex-assign.md @@ -40,7 +40,7 @@ Examples of **correct** code for this rule: try { // code } catch (e) { - var foo = 10; + const foo = 10; } ``` diff --git a/docs/src/rules/no-extend-native.md b/docs/src/rules/no-extend-native.md index 9be6a0895869..598101aa5cf8 100644 --- a/docs/src/rules/no-extend-native.md +++ b/docs/src/rules/no-extend-native.md @@ -15,13 +15,13 @@ For example here we are overriding a builtin method that will then affect all Ob Object.prototype.extra = 55; // loop through some userIds -var users = { +const users = { "123": "Stan", "456": "David" }; // not what you'd expect -for (var id in users) { +for (const id in users) { console.log(id); // "123", "456", "extra" } ``` @@ -68,7 +68,7 @@ Object.prototype.a = "a"; This rule *does not* report any of the following less obvious approaches to modify the prototype of builtin objects: ```js -var x = Object; +const x = Object; x.prototype.thing = a; eval("Array.prototype.forEach = 'muhahaha'"); diff --git a/docs/src/rules/no-extra-bind.md b/docs/src/rules/no-extra-bind.md index 073425cf13ed..46a91eeb52d7 100644 --- a/docs/src/rules/no-extra-bind.md +++ b/docs/src/rules/no-extra-bind.md @@ -11,7 +11,7 @@ further_reading: The `bind()` method is used to create functions with specific `this` values and, optionally, binds arguments to specific values. When used to specify the value of `this`, it's important that the function actually uses `this` in its function body. For example: ```js -var boundGetName = (function getName() { +const boundGetName = (function getName() { return this.name; }).bind({ name: "ESLint" }); @@ -24,7 +24,7 @@ Sometimes during the course of code maintenance, the `this` value is removed fro ```js // useless bind -var boundGetName = (function getName() { +const boundGetName = (function getName() { return "ESLint"; }).bind({ name: "ESLint" }); @@ -45,27 +45,26 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-extra-bind: "error"*/ -/*eslint-env es6*/ -var x = function () { +const x = function () { foo(); }.bind(bar); -var x = (() => { +const y = (() => { foo(); }).bind(bar); -var x = (() => { +const z = (() => { this.foo(); }).bind(bar); -var x = function () { +const a = function () { (function () { this.foo(); }()); }.bind(bar); -var x = function () { +const b = function () { function foo() { this.bar(); } @@ -81,11 +80,11 @@ Examples of **correct** code for this rule: ```js /*eslint no-extra-bind: "error"*/ -var x = function () { +const x = function () { this.foo(); }.bind(bar); -var x = function (a) { +const y = function (a) { return a + 1; }.bind(foo, bar); ``` diff --git a/docs/src/rules/no-extra-boolean-cast.md b/docs/src/rules/no-extra-boolean-cast.md index 2408d6171746..dea7354a86e9 100644 --- a/docs/src/rules/no-extra-boolean-cast.md +++ b/docs/src/rules/no-extra-boolean-cast.md @@ -3,10 +3,6 @@ title: no-extra-boolean-cast rule_type: suggestion --- - - - - In contexts such as an `if` statement's test where the result of the expression will already be coerced to a Boolean, casting to a Boolean via double negation (`!!`) or a `Boolean` call is unnecessary. For example, these `if` statements are equivalent: ```js @@ -34,13 +30,13 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-extra-boolean-cast: "error"*/ -var foo = !!!bar; +const foo = !!!bar; -var foo = !!bar ? baz : bat; +const foo1 = !!bar ? baz : bat; -var foo = Boolean(!!bar); +const foo2 = Boolean(!!bar); -var foo = new Boolean(!!bar); +const foo3 = new Boolean(!!bar); if (!!foo) { // ... @@ -72,14 +68,14 @@ Examples of **correct** code for this rule: ```js /*eslint no-extra-boolean-cast: "error"*/ -var foo = !!bar; -var foo = Boolean(bar); +const foo = !!bar; +const foo1 = Boolean(bar); function qux() { return !!bar; } -var foo = bar ? !!baz : !!bat; +foo = bar ? !!baz : !!bat; ``` ::: @@ -88,16 +84,18 @@ var foo = bar ? !!baz : !!bat; This rule has an object option: -* `"enforceForLogicalOperands"` when set to `true`, in addition to checking default contexts, checks whether the extra boolean cast is contained within a logical expression. Default is `false`, meaning that this rule by default does not warn about extra booleans cast inside logical expression. +* `"enforceForInnerExpressions"` when set to `true`, in addition to checking default contexts, checks whether extra boolean casts are present in expressions whose result is used in a boolean context. See examples below. Default is `false`, meaning that this rule by default does not warn about extra booleans cast inside inner expressions. + +**Deprecated:** The object property `enforceForLogicalOperands` is deprecated ([eslint#18222](https://github.com/eslint/eslint/pull/18222)). Please use `enforceForInnerExpressions` instead. -### enforceForLogicalOperands +### enforceForInnerExpressions -Examples of **incorrect** code for this rule with `"enforceForLogicalOperands"` option set to `true`: +Examples of **incorrect** code for this rule with `"enforceForInnerExpressions"` option set to `true`: ::: incorrect ```js -/*eslint no-extra-boolean-cast: ["error", {"enforceForLogicalOperands": true}]*/ +/*eslint no-extra-boolean-cast: ["error", {"enforceForInnerExpressions": true}]*/ if (!!foo || bar) { //... @@ -107,23 +105,38 @@ while (!!foo && bar) { //... } -if ((!!foo || bar) && baz) { +if ((!!foo || bar) && !!baz) { //... } -foo && Boolean(bar) ? baz : bat +const foo = new Boolean(!!bar || baz); + +foo && Boolean(bar) ? baz : bat; + +const ternaryBranches = Boolean(bar ? !!baz : bat); + +const nullishCoalescingOperator = Boolean(bar ?? Boolean(baz)); + +const commaOperator = Boolean((bar, baz, !!bat)); -var foo = new Boolean(!!bar || baz) +// another comma operator example +for (let i = 0; console.log(i), Boolean(i < 10); i++) { + // ... +} ``` ::: -Examples of **correct** code for this rule with `"enforceForLogicalOperands"` option set to `true`: +Examples of **correct** code for this rule with `"enforceForInnerExpressions"` option set to `true`: ::: correct ```js -/*eslint no-extra-boolean-cast: ["error", {"enforceForLogicalOperands": true}]*/ +/*eslint no-extra-boolean-cast: ["error", {"enforceForInnerExpressions": true}]*/ + +// Note that `||` and `&&` alone aren't a boolean context for either operand +// since the resultant value need not be a boolean without casting. +const foo = !!bar || baz; if (foo || bar) { //... @@ -137,11 +150,23 @@ if ((foo || bar) && baz) { //... } -foo && bar ? baz : bat +const foo1 = new Boolean(bar || baz); + +foo && bar ? baz : bat; + +const ternaryBranches = Boolean(bar ? baz : bat); -var foo = new Boolean(bar || baz) +const nullishCoalescingOperator = Boolean(bar ?? baz); + +const commaOperator = Boolean((bar, baz, bat)); + +// another comma operator example +for (let i = 0; console.log(i), i < 10; i++) { + // ... +} -var foo = !!bar || baz; +// comma operator in non-final position +Boolean((Boolean(bar), baz, bat)); ``` ::: diff --git a/docs/src/rules/no-extra-parens.md b/docs/src/rules/no-extra-parens.md index a9b5a5ae5187..4d2010263168 100644 --- a/docs/src/rules/no-extra-parens.md +++ b/docs/src/rules/no-extra-parens.md @@ -8,9 +8,6 @@ related_rules: further_reading: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/no-extra-parens) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - This rule restricts the use of parentheses to only where they are necessary. ## Rule Details @@ -44,19 +41,19 @@ In this case, the rule will not try to remove the parentheses around `"use stric This rule has a string option: -* `"all"` (default) disallows unnecessary parentheses around *any* expression -* `"functions"` disallows unnecessary parentheses *only* around function expressions +* `"all"` (default) disallows unnecessary parentheses around *any* expression. +* `"functions"` disallows unnecessary parentheses *only* around function expressions. This rule has an object option for exceptions to the `"all"` option: -* `"conditionalAssign": false` allows extra parentheses around assignments in conditional test expressions -* `"returnAssign": false` allows extra parentheses around assignments in `return` statements -* `"nestedBinaryExpressions": false` allows extra parentheses in nested binary expressions -* `"ternaryOperandBinaryExpressions": false` allows extra parentheses around binary expressions that are operands of ternary `?:` +* `"conditionalAssign": false` allows extra parentheses around assignments in conditional test expressions. +* `"returnAssign": false` allows extra parentheses around assignments in `return` statements. +* `"nestedBinaryExpressions": false` allows extra parentheses in nested binary expressions. +* `"ternaryOperandBinaryExpressions": false` allows extra parentheses around binary expressions that are operands of ternary `?:`. * `"ignoreJSX": "none|all|multi-line|single-line"` allows extra parentheses around no/all/multi-line/single-line JSX components. Defaults to `none`. -* `"enforceForArrowConditionals": false` allows extra parentheses around ternary expressions which are the body of an arrow function -* `"enforceForSequenceExpressions": false` allows extra parentheses around sequence expressions -* `"enforceForNewInMemberExpressions": false` allows extra parentheses around `new` expressions in member expressions +* `"enforceForArrowConditionals": false` allows extra parentheses around ternary expressions which are the body of an arrow function. +* `"enforceForSequenceExpressions": false` allows extra parentheses around sequence expressions. +* `"enforceForNewInMemberExpressions": false` allows extra parentheses around `new` expressions in member expressions. * `"enforceForFunctionPrototypeMethods": false` allows extra parentheses around immediate `.call` and `.apply` method calls on function expressions and around function expressions in the same context. * `"allowParensAfterCommentPattern": "any-string-pattern"` allows extra parentheses preceded by a comment that matches a regular expression. @@ -212,7 +209,7 @@ foo ? bar : (baz || qux); Examples of **correct** code for this rule with the `all` and `{ "ignoreJSX": "all" }` options: -::: correct { "ecmaFeatures": { "jsx": true } } +::: correct { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "all" }] */ @@ -228,7 +225,7 @@ const ThatComponent = ( Examples of **incorrect** code for this rule with the `all` and `{ "ignoreJSX": "multi-line" }` options: -::: incorrect { "ecmaFeatures": { "jsx": true } } +::: incorrect { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "multi-line" }] */ @@ -240,7 +237,7 @@ const ThatComponent = (

) Examples of **correct** code for this rule with the `all` and `{ "ignoreJSX": "multi-line" }` options: -::: correct { "ecmaFeatures": { "jsx": true } } +::: correct { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "multi-line" }] */ @@ -260,7 +257,7 @@ const ThatComponent = ( Examples of **incorrect** code for this rule with the `all` and `{ "ignoreJSX": "single-line" }` options: -::: incorrect { "ecmaFeatures": { "jsx": true } } +::: incorrect { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "single-line" }] */ @@ -280,7 +277,7 @@ const ThatComponent = ( Examples of **correct** code for this rule with the `all` and `{ "ignoreJSX": "single-line" }` options: -::: correct { "ecmaFeatures": { "jsx": true } } +::: correct { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /* eslint no-extra-parens: ["error", "all", { ignoreJSX: "single-line" }] */ diff --git a/docs/src/rules/no-extra-semi.md b/docs/src/rules/no-extra-semi.md index 3f28e45e7a22..009e8d1ee47f 100644 --- a/docs/src/rules/no-extra-semi.md +++ b/docs/src/rules/no-extra-semi.md @@ -5,9 +5,6 @@ related_rules: - semi - semi-spacing --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/no-extra-semi) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Typing mistakes and misunderstandings about where semicolons are required can lead to semicolons that are unnecessary. While not technically an error, extra semicolons can cause confusion when reading code. ## Rule Details diff --git a/docs/src/rules/no-fallthrough.md b/docs/src/rules/no-fallthrough.md index c49293cad4aa..3076516aa65e 100644 --- a/docs/src/rules/no-fallthrough.md +++ b/docs/src/rules/no-fallthrough.md @@ -140,6 +140,11 @@ switch(foo) { doSomething(); } +switch(foo) { + case 1: case 2: + doSomething(); +} + switch(foo) { case 1: doSomething(); @@ -173,6 +178,8 @@ This rule has an object option: * Set the `allowEmptyCase` option to `true` to allow empty cases regardless of the layout. By default, this rule does not require a fallthrough comment after an empty `case` only if the empty `case` and the next `case` are on the same line or on consecutive lines. +* Set the `reportUnusedFallthroughComment` option to `true` to prohibit a fallthrough comment from being present if the case cannot fallthrough due to being unreachable. This is mostly intended to help avoid misleading comments occurring as a result of refactoring. + ### commentPattern Examples of **correct** code for the `{ "commentPattern": "break[\\s\\w]*omitted" }` option: @@ -230,6 +237,60 @@ switch(foo){ ::: +### reportUnusedFallthroughComment + +Examples of **incorrect** code for the `{ "reportUnusedFallthroughComment": true }` option: + +::: incorrect + +```js +/* eslint no-fallthrough: ["error", { "reportUnusedFallthroughComment": true }] */ + +switch(foo){ + case 1: + doSomething(); + break; + // falls through + case 2: doSomething(); +} + +function f() { + switch(foo){ + case 1: + if (a) { + throw new Error(); + } else if (b) { + break; + } else { + return; + } + // falls through + case 2: + break; + } +} +``` + +::: + +Examples of **correct** code for the `{ "reportUnusedFallthroughComment": true }` option: + +::: correct + +```js +/* eslint no-fallthrough: ["error", { "reportUnusedFallthroughComment": true }] */ + +switch(foo){ + case 1: + doSomething(); + break; + // just a comment + case 2: doSomething(); +} +``` + +::: + ## When Not To Use It If you don't want to enforce that each `case` statement should end with a `throw`, `return`, `break`, or comment, then you can safely turn this rule off. diff --git a/docs/src/rules/no-floating-decimal.md b/docs/src/rules/no-floating-decimal.md index e2f5e2755fc7..3f04c24aab22 100644 --- a/docs/src/rules/no-floating-decimal.md +++ b/docs/src/rules/no-floating-decimal.md @@ -2,9 +2,6 @@ title: no-floating-decimal rule_type: suggestion --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/no-floating-decimal) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Float values in JavaScript contain a decimal point, and there is no requirement that the decimal point be preceded or followed by a number. For example, the following are all valid JavaScript numbers: ```js diff --git a/docs/src/rules/no-func-assign.md b/docs/src/rules/no-func-assign.md index 6346e2b8064f..160f9380a311 100644 --- a/docs/src/rules/no-func-assign.md +++ b/docs/src/rules/no-func-assign.md @@ -6,7 +6,7 @@ handled_by_typescript: true -JavaScript functions can be written as a FunctionDeclaration `function foo() { ... }` or as a FunctionExpression `var foo = function() { ... };`. While a JavaScript interpreter might tolerate it, overwriting/reassigning a function written as a FunctionDeclaration is often indicative of a mistake or issue. +JavaScript functions can be written as a FunctionDeclaration `function foo() { ... }` or as a FunctionExpression `const foo = function() { ... };`. While a JavaScript interpreter might tolerate it, overwriting/reassigning a function written as a FunctionDeclaration is often indicative of a mistake or issue. ```js function foo() {} @@ -31,7 +31,7 @@ function baz() { baz = bar; } -var a = function hello() { +let a = function hello() { hello = 123; }; ``` @@ -58,7 +58,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-func-assign: "error"*/ -var foo = function () {} +let foo = function () {} foo = bar; function baz(baz) { // `baz` is shadowed. @@ -66,7 +66,7 @@ function baz(baz) { // `baz` is shadowed. } function qux() { - var qux = bar; // `qux` is shadowed. + const qux = bar; // `qux` is shadowed. } ``` diff --git a/docs/src/rules/no-global-assign.md b/docs/src/rules/no-global-assign.md index dcdd05d519ee..82603f9649b7 100644 --- a/docs/src/rules/no-global-assign.md +++ b/docs/src/rules/no-global-assign.md @@ -23,8 +23,7 @@ This rule disallows modifications to read-only global variables. ESLint has the capability to configure global variables as read-only. -* [Specifying Environments](../use/configure#specifying-environments) -* [Specifying Globals](../use/configure#specifying-globals) +See also: [Specifying Globals](../use/configure#specifying-globals) Examples of **incorrect** code for this rule: @@ -43,22 +42,9 @@ undefined = 1 ```js /*eslint no-global-assign: "error"*/ -/*eslint-env browser*/ +/*global window:readonly*/ window = {} -length = 1 -top = 1 -``` - -::: - -::: incorrect - -```js -/*eslint no-global-assign: "error"*/ -/*global a:readonly*/ - -a = 1 ``` ::: @@ -71,7 +57,7 @@ Examples of **correct** code for this rule: /*eslint no-global-assign: "error"*/ a = 1 -var b = 1 +let b = 1 b = 2 ``` @@ -81,24 +67,13 @@ b = 2 ```js /*eslint no-global-assign: "error"*/ -/*eslint-env browser*/ +/*global onload:writable*/ onload = function() {} ``` ::: -::: correct - -```js -/*eslint no-global-assign: "error"*/ -/*global a:writable*/ - -a = 1 -``` - -::: - ## Options This rule accepts an `exceptions` option, which can be used to specify a list of builtins for which reassignments will be allowed: diff --git a/docs/src/rules/no-implicit-coercion.md b/docs/src/rules/no-implicit-coercion.md index a7d305420593..dc7a77788164 100644 --- a/docs/src/rules/no-implicit-coercion.md +++ b/docs/src/rules/no-implicit-coercion.md @@ -11,22 +11,26 @@ Some of them might be hard to read and understand. Such as: ```js -var b = !!foo; -var b = ~foo.indexOf("."); -var n = +foo; -var n = 1 * foo; -var s = "" + foo; +const b = !!foo; +const b1 = ~foo.indexOf("."); +const n = +foo; +const n1 = -(-foo); +const n2 = foo - 0; +const n3 = 1 * foo; +const s = "" + foo; foo += ``; ``` Those can be replaced with the following code: ```js -var b = Boolean(foo); -var b = foo.indexOf(".") !== -1; -var n = Number(foo); -var n = Number(foo); -var s = String(foo); +const b = Boolean(foo); +const b1 = foo.indexOf(".") !== -1; +const n = Number(foo); +const n1 = Number(foo); +const n2 = Number(foo); +const n3 = Number(foo); +const s = String(foo); foo = String(foo); ``` @@ -42,7 +46,7 @@ This rule has three main options and one override option to allow some coercions * `"number"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `number` type. * `"string"` (`true` by default) - When this is `true`, this rule warns shorter type conversions for `string` type. * `"disallowTemplateShorthand"` (`false` by default) - When this is `true`, this rule warns `string` type conversions using `${expression}` form. -* `"allow"` (`empty` by default) - Each entry in this array can be one of `~`, `!!`, `+` or `*` that are to be allowed. +* `"allow"` (`empty` by default) - Each entry in this array can be one of `~`, `!!`, `+`, `- -`, `-`, or `*` that are to be allowed. Note that operator `+` in `allow` list would allow `+foo` (number coercion) as well as `"" + foo` (string coercion). @@ -55,8 +59,8 @@ Examples of **incorrect** code for the default `{ "boolean": true }` option: ```js /*eslint no-implicit-coercion: "error"*/ -var b = !!foo; -var b = ~foo.indexOf("."); +const b = !!foo; +const b1 = ~foo.indexOf("."); // bitwise not is incorrect only with `indexOf`/`lastIndexOf` method calling. ``` @@ -69,10 +73,10 @@ Examples of **correct** code for the default `{ "boolean": true }` option: ```js /*eslint no-implicit-coercion: "error"*/ -var b = Boolean(foo); -var b = foo.indexOf(".") !== -1; +const b = Boolean(foo); +const b1 = foo.indexOf(".") !== -1; -var n = ~foo; // This is a just bitwise not. +const n = ~foo; // This is a just bitwise not. ``` ::: @@ -86,8 +90,10 @@ Examples of **incorrect** code for the default `{ "number": true }` option: ```js /*eslint no-implicit-coercion: "error"*/ -var n = +foo; -var n = 1 * foo; +const n = +foo; +const n1 = -(-foo); +const n2 = foo - 0; +const n3 = 1 * foo; ``` ::: @@ -99,11 +105,11 @@ Examples of **correct** code for the default `{ "number": true }` option: ```js /*eslint no-implicit-coercion: "error"*/ -var n = Number(foo); -var n = parseFloat(foo); -var n = parseInt(foo, 10); +const n = Number(foo); +const n1 = parseFloat(foo); +const n2 = parseInt(foo, 10); -var n = foo * 1/4; // `* 1` is allowed when followed by the `/` operator +const n3 = foo * 1/4; // `* 1` is allowed when followed by the `/` operator ``` ::: @@ -117,8 +123,8 @@ Examples of **incorrect** code for the default `{ "string": true }` option: ```js /*eslint no-implicit-coercion: "error"*/ -var s = "" + foo; -var s = `` + foo; +const s = "" + foo; +const s1 = `` + foo; foo += ""; foo += ``; ``` @@ -132,7 +138,7 @@ Examples of **correct** code for the default `{ "string": true }` option: ```js /*eslint no-implicit-coercion: "error"*/ -var s = String(foo); +const s = String(foo); foo = String(foo); ``` @@ -149,7 +155,7 @@ Examples of **incorrect** code for the `{ "disallowTemplateShorthand": true }` o ```js /*eslint no-implicit-coercion: ["error", { "disallowTemplateShorthand": true }]*/ -var s = `${foo}`; +const s = `${foo}`; ``` ::: @@ -161,15 +167,15 @@ Examples of **correct** code for the `{ "disallowTemplateShorthand": true }` opt ```js /*eslint no-implicit-coercion: ["error", { "disallowTemplateShorthand": true }]*/ -var s = String(foo); +const s = String(foo); -var s = `a${foo}`; +const s1 = `a${foo}`; -var s = `${foo}b`; +const s2 = `${foo}b`; -var s = `${foo}${bar}`; +const s3 = `${foo}${bar}`; -var s = tag`${foo}`; +const s4 = tag`${foo}`; ``` ::: @@ -181,7 +187,7 @@ Examples of **correct** code for the default `{ "disallowTemplateShorthand": fal ```js /*eslint no-implicit-coercion: ["error", { "disallowTemplateShorthand": false }]*/ -var s = `${foo}`; +const s = `${foo}`; ``` ::: @@ -197,8 +203,8 @@ Examples of **correct** code for the sample `{ "allow": ["!!", "~"] }` option: ```js /*eslint no-implicit-coercion: [2, { "allow": ["!!", "~"] } ]*/ -var b = !!foo; -var b = ~foo.indexOf("."); +const b = !!foo; +const b1 = ~foo.indexOf("."); ``` ::: diff --git a/docs/src/rules/no-implicit-globals.md b/docs/src/rules/no-implicit-globals.md index 63a65e98a964..3cba52615f3a 100644 --- a/docs/src/rules/no-implicit-globals.md +++ b/docs/src/rules/no-implicit-globals.md @@ -118,12 +118,9 @@ Bar.prototype.baz = function () { This rule also disallows redeclarations of read-only global variables and assignments to read-only global variables. -A read-only global variable can be a built-in ES global (e.g. `Array`), an environment specific global -(e.g. `window` in the browser environment), or a global variable defined as `readonly` in the configuration file -or in a `/*global */` comment. +A read-only global variable can be a built-in ES global (e.g. `Array`), or a global variable defined as `readonly` in the configuration file or in a `/*global */` comment. -* [Specifying Environments](../use/configure#specifying-environments) -* [Specifying Globals](../use/configure#specifying-globals) +See also: [Specifying Globals](../use/configure#specifying-globals) Examples of **incorrect** code for this rule: diff --git a/docs/src/rules/no-implied-eval.md b/docs/src/rules/no-implied-eval.md index cfd067769cc0..f763059bc5b1 100644 --- a/docs/src/rules/no-implied-eval.md +++ b/docs/src/rules/no-implied-eval.md @@ -35,7 +35,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-implied-eval: "error"*/ -/*eslint-env browser*/ +/*global window*/ setTimeout("alert('Hi!');", 100); diff --git a/docs/src/rules/no-inline-comments.md b/docs/src/rules/no-inline-comments.md index d6a32db4e47b..bb32402621b8 100644 --- a/docs/src/rules/no-inline-comments.md +++ b/docs/src/rules/no-inline-comments.md @@ -18,16 +18,16 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-inline-comments: "error"*/ -var a = 1; // declaring a to 1 +const a = 1; // declaring a to 1 function getRandomNumber(){ return 4; // chosen by fair dice roll. // guaranteed to be random. } -/* A block comment before code */ var b = 2; +/* A block comment before code */ const b = 2; -var c = 3; /* A block comment after code */ +const c = 3; /* A block comment after code */ ``` ::: @@ -40,9 +40,9 @@ Examples of **correct** code for this rule: /*eslint no-inline-comments: "error"*/ // This is a comment above a line of code -var foo = 5; +const foo = 5; -var bar = 5; +const bar = 5; //This is a comment below a line of code ``` @@ -54,14 +54,14 @@ Comments inside the curly braces in JSX are allowed to be on the same line as th Examples of **incorrect** code for this rule: -::: incorrect { "ecmaFeatures": { "jsx": true } } +::: incorrect { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /*eslint no-inline-comments: "error"*/ -var foo =
{ /* On the same line with other code */ }

Some heading

; +const foo =
{ /* On the same line with other code */ }

Some heading

; -var bar = ( +const bar = (
{ // These braces are not just for the comment, so it can't be on the same line baz @@ -74,19 +74,19 @@ var bar = ( Examples of **correct** code for this rule: -::: correct { "ecmaFeatures": { "jsx": true } } +::: correct { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /*eslint no-inline-comments: "error"*/ -var foo = ( +const foo = (
{/* These braces are just for this comment and there is nothing else on this line */}

Some heading

) -var bar = ( +const bar = (
{ // There is nothing else on this line @@ -95,7 +95,7 @@ var bar = (
); -var quux = ( +const quux = (
{/* Multiline @@ -133,7 +133,7 @@ Examples of **incorrect** code for the `ignorePattern` option: ```js /*eslint no-inline-comments: ["error", { "ignorePattern": "something" }] */ -var foo = 4; // other thing +const foo = 4; // other thing ``` ::: diff --git a/docs/src/rules/no-inner-declarations.md b/docs/src/rules/no-inner-declarations.md index a81a45a61fa1..61aec8c98509 100644 --- a/docs/src/rules/no-inner-declarations.md +++ b/docs/src/rules/no-inner-declarations.md @@ -30,11 +30,23 @@ function anotherThing() { } ``` -A variable declaration is permitted anywhere a statement can go, even nested deeply inside other blocks. This is often undesirable due to variable hoisting, and moving declarations to the root of the program or function body can increase clarity. Note that [block bindings](https://leanpub.com/understandinges6/read#leanpub-auto-block-bindings) (`let`, `const`) are not hoisted and therefore they are not affected by this rule. +In ES6, [block-level functions](https://leanpub.com/understandinges6/read#leanpub-auto-block-level-functions) (functions declared inside a block) are limited to the scope of the block they are declared in and outside of the block scope they can't be accessed and called, but only when the code is in strict mode (code with `"use strict"` tag or ESM modules). In non-strict mode, they can be accessed and called outside of the block scope. ```js -/*eslint-env es6*/ +"use strict"; + +if (test) { + function doSomething () { } + + doSomething(); // no error +} + +doSomething(); // error +``` +A variable declaration is permitted anywhere a statement can go, even nested deeply inside other blocks. This is often undesirable due to variable hoisting, and moving declarations to the root of the program or function body can increase clarity. Note that [block bindings](https://leanpub.com/understandinges6/read#leanpub-auto-block-bindings) (`let`, `const`) are not hoisted and therefore they are not affected by this rule. + +```js // Good var foo = 42; @@ -65,10 +77,11 @@ This rule requires that function declarations and, optionally, variable declarat ## Options -This rule has a string option: +This rule has a string and an object option: * `"functions"` (default) disallows `function` declarations in nested blocks * `"both"` disallows `function` and `var` declarations in nested blocks +* `{ blockScopedFunctions: "allow" }` (default) this option allows `function` declarations in nested blocks when code is in strict mode (code with `"use strict"` tag or ESM modules) and `languageOptions.ecmaVersion` is set to `2015` or above. This option can be disabled by setting it to `"disallow"`. ### functions @@ -79,6 +92,8 @@ Examples of **incorrect** code for this rule with the default `"functions"` opti ```js /*eslint no-inner-declarations: "error"*/ +// script, non-strict code + if (test) { function doSomething() { } } @@ -90,14 +105,6 @@ function doSomethingElse() { } if (foo) function f(){} - -class C { - static { - if (test) { - function doSomething() { } - } - } -} ``` ::: @@ -115,6 +122,14 @@ function doSomethingElse() { function doAnotherThing() { } } +function doSomethingElse() { + "use strict"; + + if (test) { + function doAnotherThing() { } + } +} + class C { static { function doSomething() { } @@ -195,6 +210,128 @@ class C { ::: +### blockScopedFunctions + +Example of **incorrect** code for this rule with `{ blockScopedFunctions: "disallow" }` option with `ecmaVersion: 2015`: + +::: incorrect { "sourceType": "script", "ecmaVersion": 2015 } + +```js +/*eslint no-inner-declarations: ["error", "functions", { blockScopedFunctions: "disallow" }]*/ + +// non-strict code + +if (test) { + function doSomething() { } +} + +function doSomething() { + if (test) { + function doSomethingElse() { } + } +} + +// strict code + +function foo() { + "use strict"; + + if (test) { + function bar() { } + } +} +``` + +::: + +Example of **correct** code for this rule with `{ blockScopedFunctions: "disallow" }` option with `ecmaVersion: 2015`: + +::: correct { "sourceType": "script", "ecmaVersion": 2015 } + +```js +/*eslint no-inner-declarations: ["error", "functions", { blockScopedFunctions: "disallow" }]*/ + +function doSomething() { } + +function doSomething() { + function doSomethingElse() { } +} +``` + +::: + +Example of **correct** code for this rule with `{ blockScopedFunctions: "allow" }` option with `ecmaVersion: 2015`: + +::: correct { "sourceType": "script", "ecmaVersion": 2015 } + +```js +/*eslint no-inner-declarations: ["error", "functions", { blockScopedFunctions: "allow" }]*/ + +"use strict"; + +if (test) { + function doSomething() { } +} + +function doSomething() { + if (test) { + function doSomethingElse() { } + } +} + +// OR + +function foo() { + "use strict"; + + if (test) { + function bar() { } + } +} +``` + +::: + +`ESM modules` and both `class` declarations and expressions are always in strict mode. + +::: correct { "sourceType": "module" } + +```js +/*eslint no-inner-declarations: ["error", "functions", { blockScopedFunctions: "allow" }]*/ + +if (test) { + function doSomething() { } +} + +function doSomethingElse() { + if (test) { + function doAnotherThing() { } + } +} + +class Some { + static { + if (test) { + function doSomething() { } + } + } +} + +const C = class { + static { + if (test) { + function doSomething() { } + } + } +} +``` + +::: + ## When Not To Use It -The function declaration portion rule will be rendered obsolete when [block-scoped functions](https://bugzilla.mozilla.org/show_bug.cgi?id=585536) land in ES6, but until then, it should be left on to enforce valid constructions. Disable checking variable declarations when using [block-scoped-var](block-scoped-var) or if declaring variables in nested blocks is acceptable despite hoisting. +By default, this rule disallows inner function declarations only in contexts where their behavior is unspecified and thus inconsistent (pre-ES6 environments) or legacy semantics apply (non-strict mode code). If your code targets pre-ES6 environments or is not in strict mode, you should enable this rule to prevent unexpected behavior. + +In ES6+ environments, in strict mode code, the behavior of inner function declarations is well-defined and consistent - they are always block-scoped. If your code targets only ES6+ environments and is in strict mode (ES modules, or code with `"use strict"` directives) then there is no need to enable this rule unless you want to disallow inner functions as a stylistic choice, in which case you should enable this rule with the option `blockScopedFunctions: "disallow"`. + +Disable checking variable declarations when using [block-scoped-var](block-scoped-var) or if declaring variables in nested blocks is acceptable despite hoisting. diff --git a/docs/src/rules/no-invalid-regexp.md b/docs/src/rules/no-invalid-regexp.md index 565b13cdaa8b..8ad2a5fbabcf 100644 --- a/docs/src/rules/no-invalid-regexp.md +++ b/docs/src/rules/no-invalid-regexp.md @@ -53,7 +53,7 @@ If you want to allow additional constructor flags for any reason, you can specif This rule has an object option for exceptions: -* `"allowConstructorFlags"` is an array of flags +* `"allowConstructorFlags"` is a case-sensitive array of flags ### allowConstructorFlags diff --git a/docs/src/rules/no-invalid-this.md b/docs/src/rules/no-invalid-this.md index 454fdbd7185b..007692b89757 100644 --- a/docs/src/rules/no-invalid-this.md +++ b/docs/src/rules/no-invalid-this.md @@ -53,7 +53,6 @@ Examples of **incorrect** code for this rule in strict mode: ```js /*eslint no-invalid-this: "error"*/ -/*eslint-env es6*/ "use strict"; @@ -67,7 +66,7 @@ function foo() { baz(() => this); } -var foo = function() { +const bar = function() { this.a = 0; baz(() => this); }; @@ -77,7 +76,7 @@ foo(function() { baz(() => this); }); -var obj = { +const obj = { aaa: function() { return function foo() { // There is in a method `aaa`, but `foo` is not a method. @@ -101,7 +100,6 @@ Examples of **correct** code for this rule in strict mode: ```js /*eslint no-invalid-this: "error"*/ -/*eslint-env es6*/ "use strict"; @@ -122,28 +120,28 @@ class Bar { } } -var obj = { +const obj = { foo: function foo() { // OK, this is in a method (this function is on object literal). this.a = 0; } }; -var obj = { +const obj1 = { foo() { // OK, this is in a method (this function is on object literal). this.a = 0; } }; -var obj = { +const obj2 = { get foo() { // OK, this is in a method (this function is on object literal). return this.a; } }; -var obj = Object.create(null, { +const obj3 = Object.create(null, { foo: {value: function foo() { // OK, this is in a method (this function is on object literal). this.a = 0; @@ -209,7 +207,7 @@ class Baz { } } -var foo = (function foo() { +const bar = (function foo() { // OK, the `bind` method of this function is called directly. this.a = 0; }).bind(obj); @@ -254,11 +252,11 @@ function Foo() { this.a = 0; } -var bar = function Foo() { +const bar = function Foo() { this.a = 0; } -var Bar = function() { +const Bar = function() { this.a = 0; }; @@ -286,6 +284,59 @@ obj.Foo = function Foo() { ::: +This rule additionally supports TypeScript type syntax. + +Examples of **incorrect** TypeScript code for this rule: + +:::incorrect + +```ts +/*eslint no-invalid-this: "error"*/ + +function foo(bar: string) { + this.prop; + console.log(bar) +} + +/** @this Obj */ +foo(function() { + console.log(this); + z(x => console.log(x, this)); +}); + +function foo() { + class C { + accessor [this.a] = foo; + } +} +``` + +::: + +Examples of **correct** TypeScript code for this rule: + +:::correct + +```ts +/*eslint no-invalid-this: "error"*/ + +interface SomeType { + prop: string; +} + +function foo(this: SomeType) { + this.prop; +} + +class A { + a = 5; + b = this.a; + accessor c = this.a; +} +``` + +::: + ## When Not To Use It If you don't want to be notified about usage of `this` keyword outside of classes or class-like objects, you can safely disable this rule. diff --git a/docs/src/rules/no-irregular-whitespace.md b/docs/src/rules/no-irregular-whitespace.md index 4118defe6311..8b1972d1d61f 100644 --- a/docs/src/rules/no-irregular-whitespace.md +++ b/docs/src/rules/no-irregular-whitespace.md @@ -39,27 +39,27 @@ This rule disallows the following characters except where the options allow: \u000B - Line Tabulation (\v) - \u000C - Form Feed (\f) - \u00A0 - No-Break Space - -\u0085 - Next Line -\u1680 - Ogham Space Mark +\u0085 - Next Line - +\u1680 - Ogham Space Mark - \u180E - Mongolian Vowel Separator - \ufeff - Zero Width No-Break Space - -\u2000 - En Quad -\u2001 - Em Quad +\u2000 - En Quad - +\u2001 - Em Quad - \u2002 - En Space - \u2003 - Em Space - -\u2004 - Three-Per-Em -\u2005 - Four-Per-Em -\u2006 - Six-Per-Em -\u2007 - Figure Space +\u2004 - Three-Per-Em - - <3/MSP> +\u2005 - Four-Per-Em - - <4/MSP> +\u2006 - Six-Per-Em - - <6/MSP> +\u2007 - Figure Space - \u2008 - Punctuation Space - -\u2009 - Thin Space -\u200A - Hair Space +\u2009 - Thin Space - +\u200A - Hair Space - \u200B - Zero Width Space - -\u2028 - Line Separator -\u2029 - Paragraph Separator -\u202F - Narrow No-Break Space -\u205f - Medium Mathematical Space -\u3000 - Ideographic Space +\u2028 - Line Separator - - +\u2029 - Paragraph Separator - - +\u202F - Narrow No-Break Space - +\u205f - Medium Mathematical Space - +\u3000 - Ideographic Space - ``` ## Options @@ -81,31 +81,31 @@ Examples of **incorrect** code for this rule with the default `{ "skipStrings": ```js /*eslint no-irregular-whitespace: "error"*/ -var thing = function() /**/{ +const thing = function() /**/{ return 'test'; } -var thing = function( /**/){ +const foo = function( /**/){ return 'test'; } -var thing = function /**/(){ +const bar = function /**/(){ return 'test'; } -var thing = function /**/(){ +const baz = function /**/(){ return 'test'; } -var thing = function() { +const qux = function() { return 'test'; /**/ } -var thing = function() { +const quux = function() { return 'test'; /**/ } -var thing = function() { +const item = function() { // Description : some descriptive text } @@ -113,12 +113,11 @@ var thing = function() { Description : some descriptive text */ -var thing = function() { +const func = function() { return / regexp/; } -/*eslint-env es6*/ -var thing = function() { +const myFunc = function() { return `template string`; } ``` @@ -132,15 +131,15 @@ Examples of **correct** code for this rule with the default `{ "skipStrings": tr ```js /*eslint no-irregular-whitespace: "error"*/ -var thing = function() { +const thing = function() { return ' thing'; } -var thing = function() { +const foo = function() { return '​thing'; } -var thing = function() { +const bar = function() { return 'th ing'; } ``` @@ -191,7 +190,6 @@ Examples of additional **correct** code for this rule with the `{ "skipTemplates ```js /*eslint no-irregular-whitespace: ["error", { "skipTemplates": true }]*/ -/*eslint-env es6*/ function thing() { return `template string`; @@ -204,11 +202,10 @@ function thing() { Examples of additional **correct** code for this rule with the `{ "skipJSXText": true }` option: -::: correct { "ecmaFeatures": { "jsx": true } } +::: correct { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /*eslint no-irregular-whitespace: ["error", { "skipJSXText": true }]*/ -/*eslint-env es6*/ function Thing() { return
text in JSX
; // before `JSX` diff --git a/docs/src/rules/no-iterator.md b/docs/src/rules/no-iterator.md index 70186826239b..7da0a23775f4 100644 --- a/docs/src/rules/no-iterator.md +++ b/docs/src/rules/no-iterator.md @@ -48,7 +48,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-iterator: "error"*/ -var __iterator__ = foo; // Not using the `__iterator__` property. +const __iterator__ = foo; // Not using the `__iterator__` property. ``` ::: diff --git a/docs/src/rules/no-labels.md b/docs/src/rules/no-labels.md index d2437ea4dba2..aafc2d163962 100644 --- a/docs/src/rules/no-labels.md +++ b/docs/src/rules/no-labels.md @@ -76,7 +76,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-labels: "error"*/ -var f = { +const f = { label: "foo" }; diff --git a/docs/src/rules/no-lone-blocks.md b/docs/src/rules/no-lone-blocks.md index efce8248f1ce..2fda916140ac 100644 --- a/docs/src/rules/no-lone-blocks.md +++ b/docs/src/rules/no-lone-blocks.md @@ -20,7 +20,7 @@ This rule aims to eliminate unnecessary and potentially confusing blocks at the Examples of **incorrect** code for this rule: -::: incorrect +::: incorrect { "sourceType": "script" } ```js /*eslint no-lone-blocks: "error"*/ @@ -60,13 +60,12 @@ class C { ::: -Examples of **correct** code for this rule with ES6 environment: +Examples of **correct** code for this rule: ::: correct ```js /*eslint no-lone-blocks: "error"*/ -/*eslint-env es6*/ while (foo) { bar(); @@ -118,7 +117,6 @@ Examples of **correct** code for this rule with ES6 environment and strict mode ```js /*eslint no-lone-blocks: "error"*/ -/*eslint-env es6*/ "use strict"; diff --git a/docs/src/rules/no-loop-func.md b/docs/src/rules/no-loop-func.md index 8fed2d6d9095..549807b8b398 100644 --- a/docs/src/rules/no-loop-func.md +++ b/docs/src/rules/no-loop-func.md @@ -19,8 +19,6 @@ In this case, you would expect each function created within the loop to return a `let` or `const` mitigate this problem. ```js -/*eslint-env es6*/ - for (let i = 0; i < 10; i++) { funcs[i] = function() { return i; @@ -34,7 +32,7 @@ In this case, each function created within the loop returns a different number a This error is raised to highlight a piece of code that may not work as you expect it to and could also indicate a misunderstanding of how the language works. Your code may run without any problems if you do not fix this error, but in some situations it could behave unexpectedly. -This rule disallows any function within a loop that contains unsafe references (e.g. to modified variables from the outer scope). +This rule disallows any function within a loop that contains unsafe references (e.g. to modified variables from the outer scope). This rule ignores IIFEs but not async or generator functions. Examples of **incorrect** code for this rule: @@ -42,11 +40,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-loop-func: "error"*/ -/*eslint-env es6*/ - -for (var i=10; i; i--) { - (function() { return i; })(); -} var i = 0; while(i < 5) { @@ -76,6 +69,25 @@ for (let i = 0; i < 10; ++i) { setTimeout(() => console.log(foo)); } foo = 100; + +var arr = []; + +for (var i = 0; i < 5; i++) { + arr.push((f => f)(() => i)); +} + +for (var i = 0; i < 5; i++) { + arr.push((() => { + return () => i; + })()); +} + +for (var i = 0; i < 5; i++) { + (function fun () { + if (arr.includes(fun)) return i; + else arr.push(fun); + })(); +} ``` ::: @@ -86,7 +98,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-loop-func: "error"*/ -/*eslint-env es6*/ var a = function() {}; @@ -104,12 +115,91 @@ for (let i=10; i; i--) { a(); } +for (const i of foo) { + var a = function() { return i; }; // OK, all references are referring to block scoped variables in the loop. + a(); +} + +for (using i of foo) { + var a = function() { return i; }; // OK, all references are referring to block scoped variables in the loop. + a(); +} + +for (var i=10; i; i--) { + const foo = getsomething(i); + var a = function() { return foo; }; // OK, all references are referring to block scoped variables in the loop. + a(); +} + +for (var i=10; i; i--) { + using foo = getsomething(i); + var a = function() { return foo; }; // OK, all references are referring to block scoped variables in the loop. + a(); +} + +for (var i=10; i; i--) { + await using foo = getsomething(i); + var a = function() { return foo; }; // OK, all references are referring to block scoped variables in the loop. + a(); +} + var foo = 100; for (let i=10; i; i--) { var a = function() { return foo; }; // OK, all references are referring to never modified variables. a(); } //... no modifications of foo after this loop ... + +var arr = []; + +for (var i=10; i; i--) { + (function() { return i; })(); +} + +for (var i = 0; i < 5; i++) { + arr.push((f => f)((() => i)())); +} + +for (var i = 0; i < 5; i++) { + arr.push((() => { + return (() => i)(); + })()); +} ``` ::: + +This rule additionally supports TypeScript type syntax. + +Examples of **correct** TypeScript code for this rule: + +::: correct + +```ts +/*eslint no-loop-func: "error"*/ + +type MyType = 1; +let someArray: MyType[] = []; +for (let i = 0; i < 10; i += 1) { + someArray = someArray.filter((item: MyType) => !!item); +} +``` + +::: + +## Known Limitations + +The rule cannot identify whether the function instance is just immediately invoked and then discarded, or possibly stored for later use. + +```js +const foo = [1, 2, 3, 4]; +var i = 0; + +while(foo.some(e => e > i)){ + i += 1; +} +``` + +Here the `some` method immediately executes the callback function for each element in the array and then discards the function instance. The function is not stored or reused beyond the scope of the loop iteration. So, this will work as intended. + +`eslint-disable` comments can be used in such cases. diff --git a/docs/src/rules/no-magic-numbers.md b/docs/src/rules/no-magic-numbers.md index 638e0b54f4a5..3a9849e83b9d 100644 --- a/docs/src/rules/no-magic-numbers.md +++ b/docs/src/rules/no-magic-numbers.md @@ -8,7 +8,7 @@ rule_type: suggestion They should preferably be replaced by named constants. ```js -var now = Date.now(), +const now = Date.now(), inOneHour = now + (60 * 60 * 1000); ``` @@ -24,7 +24,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-magic-numbers: "error"*/ -var dutyFreePrice = 100, +const dutyFreePrice = 100, finalPrice = dutyFreePrice + (dutyFreePrice * 0.25); ``` @@ -35,9 +35,9 @@ var dutyFreePrice = 100, ```js /*eslint no-magic-numbers: "error"*/ -var data = ['foo', 'bar', 'baz']; +const data = ['foo', 'bar', 'baz']; -var dataLast = data[2]; +const dataLast = data[2]; ``` ::: @@ -47,7 +47,7 @@ var dataLast = data[2]; ```js /*eslint no-magic-numbers: "error"*/ -var SECONDS; +let SECONDS; SECONDS = 60; ``` @@ -61,9 +61,9 @@ Examples of **correct** code for this rule: ```js /*eslint no-magic-numbers: "error"*/ -var TAX = 0.25; +const TAX = 0.25; -var dutyFreePrice = 100, +const dutyFreePrice = 100, finalPrice = dutyFreePrice + (dutyFreePrice * TAX); ``` @@ -86,8 +86,8 @@ Examples of **correct** code for the sample `{ "ignore": [1] }` option: ```js /*eslint no-magic-numbers: ["error", { "ignore": [1] }]*/ -var data = ['foo', 'bar', 'baz']; -var dataLast = data.length && data[data.length - 1]; +const data = ['foo', 'bar', 'baz']; +const dataLast = data.length && data[data.length - 1]; ``` ::: @@ -121,13 +121,14 @@ Examples of **correct** code for the `{ "ignoreArrayIndexes": true }` option: ```js /*eslint no-magic-numbers: ["error", { "ignoreArrayIndexes": true }]*/ -var item = data[2]; +const item = data[2]; data[100] = a; f(data[0]); a = data[-0]; // same as data[0], -0 will be coerced to "0" +a = data[+1]; // same as data[1], +1 will be coerced to "1" a = data[0xAB]; @@ -207,6 +208,7 @@ Examples of **correct** code for the `{ "ignoreClassFieldInitialValues": true }` class C { foo = 2; bar = -3; + tux = +1; #baz = 4; static qux = 5; } @@ -243,9 +245,9 @@ Examples of **incorrect** code for the `{ "enforceConst": true }` option: ```js /*eslint no-magic-numbers: ["error", { "enforceConst": true }]*/ -var TAX = 0.25; +let TAX = 0.25; -var dutyFreePrice = 100, +let dutyFreePrice = 100, finalPrice = dutyFreePrice + (dutyFreePrice * TAX); ``` @@ -262,11 +264,11 @@ Examples of **incorrect** code for the `{ "detectObjects": true }` option: ```js /*eslint no-magic-numbers: ["error", { "detectObjects": true }]*/ -var magic = { +const magic = { tax: 0.25 }; -var dutyFreePrice = 100, +const dutyFreePrice = 100, finalPrice = dutyFreePrice + (dutyFreePrice * magic.tax); ``` @@ -279,14 +281,142 @@ Examples of **correct** code for the `{ "detectObjects": true }` option: ```js /*eslint no-magic-numbers: ["error", { "detectObjects": true }]*/ -var TAX = 0.25; +const TAX = 0.25; -var magic = { +const magic = { tax: TAX }; -var dutyFreePrice = 100, +const dutyFreePrice = 100, finalPrice = dutyFreePrice + (dutyFreePrice * magic.tax); ``` ::: + +### ignoreEnums (TypeScript only) + +Whether enums used in TypeScript are considered okay. `false` by default. + +Examples of **incorrect** code for the `{ "ignoreEnums": false }` option: + +::: incorrect + +```ts +/*eslint no-magic-numbers: ["error", { "ignoreEnums": false }]*/ + +enum foo { + SECOND = 1000, +} +``` + +::: + +Examples of **correct** code for the `{ "ignoreEnums": true }` option: + +::: correct + +```ts +/*eslint no-magic-numbers: ["error", { "ignoreEnums": true }]*/ + +enum foo { + SECOND = 1000, +} +``` + +::: + +### ignoreNumericLiteralTypes (TypeScript only) + +Whether numbers used in TypeScript numeric literal types are considered okay. `false` by default. + +Examples of **incorrect** code for the `{ "ignoreNumericLiteralTypes": false }` option: + +::: incorrect + +```ts +/*eslint no-magic-numbers: ["error", { "ignoreNumericLiteralTypes": false }]*/ + +type Foo = 1 | 2 | 3; +``` + +::: + +Examples of **correct** code for the `{ "ignoreNumericLiteralTypes": true }` option: + +::: correct + +```ts +/*eslint no-magic-numbers: ["error", { "ignoreNumericLiteralTypes": true }]*/ + +type Foo = 1 | 2 | 3; +``` + +::: + +### ignoreReadonlyClassProperties (TypeScript only) + +Whether numbers used in TypeScript readonly class properties are considered okay. `false` by default. + +Examples of **incorrect** code for the `{ "ignoreReadonlyClassProperties": false }` option: + +::: incorrect + +```ts +/*eslint no-magic-numbers: ["error", { "ignoreReadonlyClassProperties": false }]*/ + +class Foo { + readonly A = 1; + readonly B = 2; + public static readonly C = 1; + static readonly D = 1; +} +``` + +::: + +Examples of **correct** code for the `{ "ignoreReadonlyClassProperties": true }` option: + +::: correct + +```ts +/*eslint no-magic-numbers: ["error", { "ignoreReadonlyClassProperties": true }]*/ + +class Foo { + readonly A = 1; + readonly B = 2; + public static readonly C = 1; + static readonly D = 1; +} +``` + +::: + +### ignoreTypeIndexes (TypeScript only) + +Whether numbers used to index types are okay. `false` by default. + +Examples of **incorrect** code for the `{ "ignoreTypeIndexes": false }` option: + +::: incorrect + +```ts +/*eslint no-magic-numbers: ["error", { "ignoreTypeIndexes": false }]*/ + +type Foo = Bar[0]; +type Baz = Parameters[2]; +``` + +::: + +Examples of **correct** code for the `{ "ignoreTypeIndexes": true }` option: + +::: correct + +```ts +/*eslint no-magic-numbers: ["error", { "ignoreTypeIndexes": true }]*/ + +type Foo = Bar[0]; +type Baz = Parameters[2]; +``` + +::: diff --git a/docs/src/rules/no-misleading-character-class.md b/docs/src/rules/no-misleading-character-class.md index 792760f2641b..d3134a837373 100644 --- a/docs/src/rules/no-misleading-character-class.md +++ b/docs/src/rules/no-misleading-character-class.md @@ -7,10 +7,10 @@ rule_type: problem -Unicode includes the characters which are made with multiple code points. -RegExp character class syntax (`/[abc]/`) cannot handle characters which are made by multiple code points as a character; those characters will be dissolved to each code point. For example, `â‡ī¸` is made by `❇` (`U+2747`) and VARIATION SELECTOR-16 (`U+FE0F`). If this character is in RegExp character class, it will match to either `❇` (`U+2747`) or VARIATION SELECTOR-16 (`U+FE0F`) rather than `â‡ī¸`. +Unicode includes characters which are made by multiple code points. +RegExp character class syntax (`/[abc]/`) cannot handle characters which are made by multiple code points as a character; those characters will be dissolved to each code point. For example, `â‡ī¸` is made by `❇` (`U+2747`) and VARIATION SELECTOR-16 (`U+FE0F`). If this character is in a RegExp character class, it will match either `❇` (`U+2747`) or VARIATION SELECTOR-16 (`U+FE0F`) rather than `â‡ī¸`. -This rule reports the regular expressions which include multiple code point characters in character class syntax. This rule considers the following characters as multiple code point characters. +This rule reports regular expressions which include multiple code point characters in character class syntax. This rule considers the following characters as multiple code point characters. **A character with combining characters:** @@ -51,7 +51,7 @@ The combining characters are characters which belong to one of `Mc`, `Me`, and ` ## Rule Details -This rule reports the regular expressions which include multiple code point characters in character class syntax. +This rule reports regular expressions which include multiple code point characters in character class syntax. Examples of **incorrect** code for this rule: @@ -66,6 +66,7 @@ Examples of **incorrect** code for this rule: /^[đŸ‡¯đŸ‡ĩ]$/u; /^[👨‍👩‍đŸ‘Ļ]$/u; /^[👍]$/; +new RegExp("[đŸŽĩ]"); ``` ::: @@ -80,6 +81,50 @@ Examples of **correct** code for this rule: /^[abc]$/; /^[👍]$/u; /^[\q{đŸ‘ļđŸģ}]$/v; +new RegExp("^[]$"); +new RegExp(`[Aˁ-${z}]`, "u"); // variable pattern +``` + +::: + +## Options + +This rule has an object option: + +* `"allowEscape"`: When set to `true`, the rule allows any grouping of code points inside a character class as long as they are written using escape sequences. This option only has effect on regular expression literals and on regular expressions created with the `RegExp` constructor with a literal argument as a pattern. + +### allowEscape + +Examples of **incorrect** code for this rule with the `{ "allowEscape": true }` option: + +::: incorrect + +```js +/* eslint no-misleading-character-class: ["error", { "allowEscape": true }] */ + +/[\👍]/; // backslash can be omitted + +new RegExp("[\ud83d" + "\udc4d]"); + +const pattern = "[\ud83d\udc4d]"; +new RegExp(pattern); +``` + +::: + +Examples of **correct** code for this rule with the `{ "allowEscape": true }` option: + +::: correct + +```js +/* eslint no-misleading-character-class: ["error", { "allowEscape": true }] */ + +/[\ud83d\udc4d]/; +/[\u00B7\u0300-\u036F]/u; +/[👨\u200d👩]/u; +new RegExp("[\x41\u0301]"); +new RegExp(`[\u{1F1EF}\u{1F1F5}]`, "u"); +new RegExp("[\\u{1F1EF}\\u{1F1F5}]", "u"); ``` ::: diff --git a/docs/src/rules/no-mixed-operators.md b/docs/src/rules/no-mixed-operators.md index d4a8a5d77c17..0dba787b6e3b 100644 --- a/docs/src/rules/no-mixed-operators.md +++ b/docs/src/rules/no-mixed-operators.md @@ -4,9 +4,6 @@ rule_type: suggestion related_rules: - no-extra-parens --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/no-mixed-operators) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Enclosing complex expressions by parentheses clarifies the developer's intention, which makes the code more readable. This rule warns when different operators are used consecutively without parentheses in an expression. diff --git a/docs/src/rules/no-mixed-requires.md b/docs/src/rules/no-mixed-requires.md index f747ebd6362a..73049ae09335 100644 --- a/docs/src/rules/no-mixed-requires.md +++ b/docs/src/rules/no-mixed-requires.md @@ -3,9 +3,6 @@ title: no-mixed-requires rule_type: suggestion --- - -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - In the Node.js community it is often customary to separate initializations with calls to `require` modules from other variable declarations, sometimes also grouping them by the type of module. This rule helps you enforce this convention. ## Rule Details diff --git a/docs/src/rules/no-mixed-spaces-and-tabs.md b/docs/src/rules/no-mixed-spaces-and-tabs.md index ee67a1151591..acc6f34e8872 100644 --- a/docs/src/rules/no-mixed-spaces-and-tabs.md +++ b/docs/src/rules/no-mixed-spaces-and-tabs.md @@ -4,9 +4,6 @@ rule_type: layout further_reading: - https://www.emacswiki.org/emacs/SmartTabs --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/no-mixed-spaces-and-tabs) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Most code conventions require either tabs or spaces be used for indentation. As such, it's usually an error if a single line of code is indented with both tabs and spaces. ## Rule Details diff --git a/docs/src/rules/no-multi-assign.md b/docs/src/rules/no-multi-assign.md index f4f4c2fce5aa..a9d9c158eb4a 100644 --- a/docs/src/rules/no-multi-assign.md +++ b/docs/src/rules/no-multi-assign.md @@ -27,7 +27,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-multi-assign: "error"*/ -var a = b = c = 5; +let a = b = c = 5; const foo = bar = "baz"; @@ -51,9 +51,9 @@ Examples of **correct** code for this rule: ```js /*eslint no-multi-assign: "error"*/ -var a = 5; -var b = 5; -var c = 5; +let a = 5; +let b = 5; +const c = 5; const foo = "baz"; const bar = "baz"; diff --git a/docs/src/rules/no-multi-spaces.md b/docs/src/rules/no-multi-spaces.md index 1cf99efe45fd..93c357e814b9 100644 --- a/docs/src/rules/no-multi-spaces.md +++ b/docs/src/rules/no-multi-spaces.md @@ -10,9 +10,6 @@ related_rules: - space-unary-ops - space-return-throw-case --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/no-multi-spaces) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Multiple spaces in a row that are not used for indentation are typically mistakes. For example: ```js diff --git a/docs/src/rules/no-multi-str.md b/docs/src/rules/no-multi-str.md index 67444b42736f..f25b5baed575 100644 --- a/docs/src/rules/no-multi-str.md +++ b/docs/src/rules/no-multi-str.md @@ -7,7 +7,7 @@ rule_type: suggestion It's possible to create multiline strings in JavaScript by using a slash before a newline, such as: ```js -var x = "Line 1 \ +const x = "Line 1 \ Line 2"; ``` @@ -24,7 +24,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-multi-str: "error"*/ -var x = "some very \ +const x = "some very \ long text"; ``` @@ -37,9 +37,9 @@ Examples of **correct** code for this rule: ```js /*eslint no-multi-str: "error"*/ -var x = "some very long text"; +const x = "some very long text"; -var x = "some very " + +const y = "some very " + "long text"; ``` diff --git a/docs/src/rules/no-multiple-empty-lines.md b/docs/src/rules/no-multiple-empty-lines.md index 51724e60f51b..e79ab589eff6 100644 --- a/docs/src/rules/no-multiple-empty-lines.md +++ b/docs/src/rules/no-multiple-empty-lines.md @@ -2,9 +2,6 @@ title: no-multiple-empty-lines rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/no-multiple-empty-lines) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Some developers prefer to have multiple blank lines removed, while others feel that it helps improve readability. Whitespace is useful for separating logical sections of code, but excess whitespace takes up more of the screen. ## Rule Details diff --git a/docs/src/rules/no-native-reassign.md b/docs/src/rules/no-native-reassign.md index c2c658f77054..42cfd2bbc015 100644 --- a/docs/src/rules/no-native-reassign.md +++ b/docs/src/rules/no-native-reassign.md @@ -7,9 +7,6 @@ related_rules: - no-shadow --- - -This rule was **deprecated** in ESLint v3.3.0 and replaced by the [no-global-assign](no-global-assign) rule. - JavaScript environments contain a number of built-in global variables, such as `window` in browsers and `process` in Node.js. In almost all cases, you don't want to assign a value to these global variables as doing so could result in losing access to important functionality. For example, you probably don't want to do this in browser code: ```js @@ -24,8 +21,7 @@ This rule disallows modifications to read-only global variables. ESLint has the capability to configure global variables as read-only. -* [Specifying Environments](../use/configure#specifying-environments) -* [Specifying Globals](../use/configure#specifying-globals) +See also: [Specifying Globals](../use/configure#specifying-globals) Examples of **incorrect** code for this rule: @@ -44,22 +40,9 @@ undefined = 1 ```js /*eslint no-native-reassign: "error"*/ -/*eslint-env browser*/ +/*global window:readonly*/ window = {} -length = 1 -top = 1 -``` - -::: - -::: incorrect - -```js -/*eslint no-native-reassign: "error"*/ -/*global a:readonly*/ - -a = 1 ``` ::: @@ -82,24 +65,13 @@ b = 2 ```js /*eslint no-native-reassign: "error"*/ -/*eslint-env browser*/ +/*global onload:writable*/ onload = function() {} ``` ::: -::: correct - -```js -/*eslint no-native-reassign: "error"*/ -/*global a:writable*/ - -a = 1 -``` - -::: - ## Options This rule accepts an `exceptions` option, which can be used to specify a list of builtins for which reassignments will be allowed: diff --git a/docs/src/rules/no-negated-in-lhs.md b/docs/src/rules/no-negated-in-lhs.md index 2a27116f6e7c..be33dbbbc3af 100644 --- a/docs/src/rules/no-negated-in-lhs.md +++ b/docs/src/rules/no-negated-in-lhs.md @@ -3,9 +3,6 @@ title: no-negated-in-lhs rule_type: problem --- - -This rule was **deprecated** in ESLint v3.3.0 and replaced by the [no-unsafe-negation](no-unsafe-negation) rule. - Just as developers might type `-a + b` when they mean `-(a + b)` for the negative of a sum, they might type `!key in object` by mistake when they almost certainly mean `!(key in object)` to test that a key is not in an object. ## Rule Details diff --git a/docs/src/rules/no-nested-ternary.md b/docs/src/rules/no-nested-ternary.md index 58bcad30808d..3b3bae87cb67 100644 --- a/docs/src/rules/no-nested-ternary.md +++ b/docs/src/rules/no-nested-ternary.md @@ -10,7 +10,7 @@ related_rules: Nesting ternary expressions can make code more difficult to understand. ```js -var foo = bar ? baz : qux === quxx ? bing : bam; +const foo = bar ? baz : qux === quxx ? bing : bam; ``` ## Rule Details @@ -24,7 +24,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-nested-ternary: "error"*/ -var thing = foo ? bar : baz === qux ? quxx : foobar; +const thing = foo ? bar : baz === qux ? quxx : foobar; foo ? baz === qux ? quxx() : foobar() : bar(); ``` @@ -38,16 +38,16 @@ Examples of **correct** code for this rule: ```js /*eslint no-nested-ternary: "error"*/ -var thing = foo ? bar : foobar; +const thing = foo ? bar : foobar; -var thing; +let otherThing; if (foo) { - thing = bar; + otherThing = bar; } else if (baz === qux) { - thing = quxx; + otherThing = quxx; } else { - thing = foobar; + otherThing = foobar; } ``` diff --git a/docs/src/rules/no-new-func.md b/docs/src/rules/no-new-func.md index 8289e3ed9e77..92768385a424 100644 --- a/docs/src/rules/no-new-func.md +++ b/docs/src/rules/no-new-func.md @@ -7,18 +7,18 @@ rule_type: suggestion It's possible to create functions in JavaScript from strings at runtime using the `Function` constructor, such as: ```js -var x = new Function("a", "b", "return a + b"); -var x = Function("a", "b", "return a + b"); -var x = Function.call(null, "a", "b", "return a + b"); -var x = Function.apply(null, ["a", "b", "return a + b"]); -var x = Function.bind(null, "a", "b", "return a + b")(); +const a = new Function("a", "b", "return a + b"); +const b = Function("a", "b", "return a + b"); +const c = Function.call(null, "a", "b", "return a + b"); +const d = Function.apply(null, ["a", "b", "return a + b"]); +const x = Function.bind(null, "a", "b", "return a + b")(); ``` -This is considered by many to be a bad practice due to the difficulty in debugging and reading these types of functions. In addition, Content-Security-Policy (CSP) directives may disallow the use of eval() and similar methods for creating code from strings. +This is considered by many to be a bad practice due to the difficulty in debugging and reading these types of functions. In addition, Content-Security-Policy (CSP) directives may disallow the use of `eval()` and similar methods for creating code from strings. ## Rule Details -This error is raised to highlight the use of a bad practice. By passing a string to the Function constructor, you are requiring the engine to parse that string much in the way it has to when you call the `eval` function. +This error is raised to highlight the use of a bad practice. By passing a string to the `Function` constructor, you are requiring the engine to parse that string much in the way it has to when you call the `eval` function. Examples of **incorrect** code for this rule: @@ -27,12 +27,12 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-new-func: "error"*/ -var x = new Function("a", "b", "return a + b"); -var x = Function("a", "b", "return a + b"); -var x = Function.call(null, "a", "b", "return a + b"); -var x = Function.apply(null, ["a", "b", "return a + b"]); -var x = Function.bind(null, "a", "b", "return a + b")(); -var f = Function.bind(null, "a", "b", "return a + b"); // assuming that the result of Function.bind(...) will be eventually called. +const a = new Function("a", "b", "return a + b"); +const b = Function("a", "b", "return a + b"); +const c = Function.call(null, "a", "b", "return a + b"); +const d = Function.apply(null, ["a", "b", "return a + b"]); +const x = Function.bind(null, "a", "b", "return a + b")(); +const y = Function.bind(null, "a", "b", "return a + b"); // assuming that the result of Function.bind(...) will be eventually called. ``` ::: @@ -44,7 +44,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-new-func: "error"*/ -var x = function (a, b) { +const x = function (a, b) { return a + b; }; ``` diff --git a/docs/src/rules/no-new-native-nonconstructor.md b/docs/src/rules/no-new-native-nonconstructor.md index c9b75679a1ef..a8d35a108001 100644 --- a/docs/src/rules/no-new-native-nonconstructor.md +++ b/docs/src/rules/no-new-native-nonconstructor.md @@ -2,6 +2,7 @@ title: no-new-native-nonconstructor layout: doc rule_type: problem +handled_by_typescript: true related_rules: - no-obj-calls further_reading: @@ -15,10 +16,10 @@ It is a convention in JavaScript that global variables beginning with an upperca ```js // throws a TypeError -let foo = new Symbol("foo"); +const foo = new Symbol("foo"); // throws a TypeError -let result = new BigInt(9007199254740991); +const result = new BigInt(9007199254740991); ``` Both `new Symbol` and `new BigInt` throw a type error because they are functions and not classes. It is easy to make this mistake by assuming the uppercase letters indicate classes. @@ -38,10 +39,9 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-new-native-nonconstructor: "error"*/ -/*eslint-env es2022*/ -var foo = new Symbol('foo'); -var bar = new BigInt(9007199254740991); +const foo = new Symbol('foo'); +const bar = new BigInt(9007199254740991); ``` ::: @@ -52,10 +52,9 @@ Examples of **correct** code for this rule: ```js /*eslint no-new-native-nonconstructor: "error"*/ -/*eslint-env es2022*/ -var foo = Symbol('foo'); -var bar = BigInt(9007199254740991); +const foo = Symbol('foo'); +const bar = BigInt(9007199254740991); // Ignores shadowed Symbol. function baz(Symbol) { diff --git a/docs/src/rules/no-new-object.md b/docs/src/rules/no-new-object.md index bdd577411984..1c7d33e04376 100644 --- a/docs/src/rules/no-new-object.md +++ b/docs/src/rules/no-new-object.md @@ -6,8 +6,6 @@ related_rules: - no-new-wrappers --- -This rule was **deprecated** in ESLint v8.50.0 and replaced by the [no-object-constructor](no-object-constructor) rule. The new rule flags more situations where object literal syntax can be used, and it does not report a problem when the `Object` constructor is invoked with an argument. - The `Object` constructor is used to create new generic objects in JavaScript, such as: ```js diff --git a/docs/src/rules/no-new-require.md b/docs/src/rules/no-new-require.md index 494d4881da2f..cdf36c76a1eb 100644 --- a/docs/src/rules/no-new-require.md +++ b/docs/src/rules/no-new-require.md @@ -3,9 +3,6 @@ title: no-new-require rule_type: suggestion --- - -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - The `require` function is used to include modules that exist in separate files, such as: ```js diff --git a/docs/src/rules/no-new-symbol.md b/docs/src/rules/no-new-symbol.md index d557811f30c4..feebea6ae279 100644 --- a/docs/src/rules/no-new-symbol.md +++ b/docs/src/rules/no-new-symbol.md @@ -6,8 +6,6 @@ further_reading: - https://www.ecma-international.org/ecma-262/6.0/#sec-symbol-objects --- - - `Symbol` is not intended to be used with the `new` operator, but to be called as a function. ```js @@ -28,7 +26,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-new-symbol: "error"*/ -/*eslint-env es6*/ var foo = new Symbol('foo'); ``` @@ -41,7 +38,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-new-symbol: "error"*/ -/*eslint-env es6*/ var foo = Symbol('foo'); diff --git a/docs/src/rules/no-new-wrappers.md b/docs/src/rules/no-new-wrappers.md index fe11e3af7ee0..6e51e4956e9c 100644 --- a/docs/src/rules/no-new-wrappers.md +++ b/docs/src/rules/no-new-wrappers.md @@ -12,7 +12,7 @@ further_reading: There are three primitive types in JavaScript that have wrapper objects: string, number, and boolean. These are represented by the constructors `String`, `Number`, and `Boolean`, respectively. The primitive wrapper types are used whenever one of these primitive values is read, providing them with object-like capabilities such as methods. Behind the scenes, an object of the associated wrapper type is created and then destroyed, which is why you can call methods on primitive values, such as: ```js -var text = "Hello world".substring(2); +const text = "Hello world".substring(2); ``` Behind the scenes in this example, a `String` object is constructed. The `substring()` method exists on `String.prototype` and so is accessible to the string instance. @@ -20,21 +20,21 @@ Behind the scenes in this example, a `String` object is constructed. The `substr It's also possible to manually create a new wrapper instance: ```js -var stringObject = new String("Hello world"); -var numberObject = new Number(33); -var booleanObject = new Boolean(false); +const stringObject = new String("Hello world"); +const numberObject = new Number(33); +const booleanObject = new Boolean(false); ``` Although possible, there aren't any good reasons to use these primitive wrappers as constructors. They tend to confuse other developers more than anything else because they seem like they should act as primitives, but they do not. For example: ```js -var stringObject = new String("Hello world"); +const stringObject = new String("Hello world"); console.log(typeof stringObject); // "object" -var text = "Hello world"; +const text = "Hello world"; console.log(typeof text); // "string" -var booleanObject = new Boolean(false); +const booleanObject = new Boolean(false); if (booleanObject) { // all objects are truthy! console.log("This executes"); } @@ -55,13 +55,13 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-new-wrappers: "error"*/ -var stringObject = new String("Hello world"); -var numberObject = new Number(33); -var booleanObject = new Boolean(false); +const stringObject = new String("Hello world"); +const numberObject = new Number(33); +const booleanObject = new Boolean(false); -var stringObject = new String; -var numberObject = new Number; -var booleanObject = new Boolean; +const stringObject2 = new String; +const numberObject2 = new Number; +const booleanObject2 = new Boolean; ``` ::: @@ -73,10 +73,10 @@ Examples of **correct** code for this rule: ```js /*eslint no-new-wrappers: "error"*/ -var text = String(someValue); -var num = Number(someValue); +const text = String(someValue); +const num = Number(someValue); -var object = new MyString(); +const object = new MyString(); ``` ::: diff --git a/docs/src/rules/no-new.md b/docs/src/rules/no-new.md index c8cea29cb0ff..24362791ff99 100644 --- a/docs/src/rules/no-new.md +++ b/docs/src/rules/no-new.md @@ -7,7 +7,7 @@ rule_type: suggestion The goal of using `new` with a constructor is typically to create an object of a particular type and store that object in a variable, such as: ```js -var person = new Person(); +const person = new Person(); ``` It's less common to use `new` and not store the result, such as: @@ -41,7 +41,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-new: "error"*/ -var thing = new Thing(); +const thing = new Thing(); Foo(); ``` diff --git a/docs/src/rules/no-nonoctal-decimal-escape.md b/docs/src/rules/no-nonoctal-decimal-escape.md index 43d0282635af..78cef1b0e77c 100644 --- a/docs/src/rules/no-nonoctal-decimal-escape.md +++ b/docs/src/rules/no-nonoctal-decimal-escape.md @@ -39,13 +39,13 @@ Examples of **incorrect** code for this rule: "\9"; -var foo = "w\8less"; +const foo = "w\8less"; -var bar = "December 1\9"; +const bar = "December 1\9"; -var baz = "Don't use \8 and \9 escapes."; +const baz = "Don't use \8 and \9 escapes."; -var quux = "\0\8"; +const quux = "\0\8"; ``` ::: @@ -61,13 +61,13 @@ Examples of **correct** code for this rule: "9"; -var foo = "w8less"; +const foo = "w8less"; -var bar = "December 19"; +const bar = "December 19"; -var baz = "Don't use \\8 and \\9 escapes."; +const baz = "Don't use \\8 and \\9 escapes."; -var quux = "\0\u0038"; +const quux = "\0\u0038"; ``` ::: diff --git a/docs/src/rules/no-obj-calls.md b/docs/src/rules/no-obj-calls.md index 2fde92e5cbf5..e7cadbd16778 100644 --- a/docs/src/rules/no-obj-calls.md +++ b/docs/src/rules/no-obj-calls.md @@ -38,27 +38,26 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-obj-calls: "error"*/ -/*eslint-env es2017, browser */ -var math = Math(); +const math = Math(); -var newMath = new Math(); +const newMath = new Math(); -var json = JSON(); +const json = JSON(); -var newJSON = new JSON(); +const newJSON = new JSON(); -var reflect = Reflect(); +const reflect = Reflect(); -var newReflect = new Reflect(); +const newReflect = new Reflect(); -var atomics = Atomics(); +const atomics = Atomics(); -var newAtomics = new Atomics(); +const newAtomics = new Atomics(); -var intl = Intl(); +const intl = Intl(); -var newIntl = new Intl(); +const newIntl = new Intl(); ``` ::: @@ -69,19 +68,18 @@ Examples of **correct** code for this rule: ```js /*eslint no-obj-calls: "error"*/ -/*eslint-env es2017, browser*/ function area(r) { return Math.PI * r * r; } -var object = JSON.parse("{}"); +const object = JSON.parse("{}"); -var value = Reflect.get({ x: 1, y: 2 }, "x"); +const value = Reflect.get({ x: 1, y: 2 }, "x"); -var first = Atomics.load(foo, 0); +const first = Atomics.load(foo, 0); -var segmenterFr = new Intl.Segmenter("fr", { granularity: "word" }); +const segmenterFr = new Intl.Segmenter("fr", { granularity: "word" }); ``` ::: diff --git a/docs/src/rules/no-octal-escape.md b/docs/src/rules/no-octal-escape.md index 755bc56bc84f..d470007536a2 100644 --- a/docs/src/rules/no-octal-escape.md +++ b/docs/src/rules/no-octal-escape.md @@ -7,7 +7,7 @@ rule_type: suggestion As of the ECMAScript 5 specification, octal escape sequences in string literals are deprecated and should not be used. Unicode escape sequences should be used instead. ```js -var foo = "Copyright \251"; +const foo = "Copyright \251"; ``` ## Rule Details @@ -23,7 +23,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-octal-escape: "error"*/ -var foo = "Copyright \251"; +const foo = "Copyright \251"; ``` ::: @@ -35,9 +35,9 @@ Examples of **correct** code for this rule: ```js /*eslint no-octal-escape: "error"*/ -var foo = "Copyright \u00A9"; // unicode +const foo = "Copyright \u00A9"; // unicode -var foo = "Copyright \xA9"; // hexadecimal +const buz = "Copyright \xA9"; // hexadecimal ``` ::: diff --git a/docs/src/rules/no-octal.md b/docs/src/rules/no-octal.md index c40d9a977b23..5a006c6ef186 100644 --- a/docs/src/rules/no-octal.md +++ b/docs/src/rules/no-octal.md @@ -8,7 +8,7 @@ rule_type: suggestion Octal literals are numerals that begin with a leading zero, such as: ```js -var num = 071; // 57 +const num = 071; // 57 ``` Because the leading zero which identifies an octal literal has been a source of confusion and error in JavaScript code, ECMAScript 5 deprecates the use of octal numeric literals. @@ -26,8 +26,8 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-octal: "error"*/ -var num = 071; -var result = 5 + 07; +const num = 071; +const result = 5 + 07; ``` ::: @@ -39,7 +39,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-octal: "error"*/ -var num = "071"; +const num = "071"; ``` ::: diff --git a/docs/src/rules/no-param-reassign.md b/docs/src/rules/no-param-reassign.md index a3cc558cf451..d2547ed951c5 100644 --- a/docs/src/rules/no-param-reassign.md +++ b/docs/src/rules/no-param-reassign.md @@ -6,7 +6,7 @@ further_reading: --- -Assignment to variables declared as function parameters can be misleading and lead to confusing behavior, as modifying function parameters will also mutate the `arguments` object when not in strict mode (see [When Not To Use It](#when-not-to-use-it) below). Often, assignment to function parameters is unintended and indicative of a mistake or programmer error. +Assignment to variables declared as function parameters can be misleading and lead to confusing behavior, as modifying function parameters will also mutate the `arguments` object when not in `strict` mode (see [When Not To Use It](#when-not-to-use-it) below). Often, assignment to function parameters is unintended and indicative of a mistake or programmer error. This rule can be also configured to fail when function parameters are modified. Side effects on parameters can cause counter-intuitive execution flow and make errors difficult to track down. @@ -21,19 +21,19 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-param-reassign: "error"*/ -var foo = function(bar) { +const foo = function(bar) { bar = 13; } -var foo = function(bar) { +const foo1 = function(bar) { bar++; } -var foo = function(bar) { +const foo2 = function(bar) { for (bar in baz) {} } -var foo = function(bar) { +const foo3 = function(bar) { for (bar of baz) {} } ``` @@ -47,8 +47,8 @@ Examples of **correct** code for this rule: ```js /*eslint no-param-reassign: "error"*/ -var foo = function(bar) { - var baz = bar; +const foo = function(bar) { + const baz = bar; } ``` @@ -67,23 +67,23 @@ Examples of **correct** code for the default `{ "props": false }` option: ```js /*eslint no-param-reassign: ["error", { "props": false }]*/ -var foo = function(bar) { +const foo = function(bar) { bar.prop = "value"; } -var foo = function(bar) { +const foo1 = function(bar) { delete bar.aaa; } -var foo = function(bar) { +const foo2 = function(bar) { bar.aaa++; } -var foo = function(bar) { +const foo3 = function(bar) { for (bar.aaa in baz) {} } -var foo = function(bar) { +const foo4 = function(bar) { for (bar.aaa of baz) {} } ``` @@ -97,23 +97,23 @@ Examples of **incorrect** code for the `{ "props": true }` option: ```js /*eslint no-param-reassign: ["error", { "props": true }]*/ -var foo = function(bar) { +const foo = function(bar) { bar.prop = "value"; } -var foo = function(bar) { +const foo1 = function(bar) { delete bar.aaa; } -var foo = function(bar) { +const foo2 = function(bar) { bar.aaa++; } -var foo = function(bar) { +const foo3 = function(bar) { for (bar.aaa in baz) {} } -var foo = function(bar) { +const foo4 = function(bar) { for (bar.aaa of baz) {} } ``` @@ -127,23 +127,23 @@ Examples of **correct** code for the `{ "props": true }` option with `"ignorePro ```js /*eslint no-param-reassign: ["error", { "props": true, "ignorePropertyModificationsFor": ["bar"] }]*/ -var foo = function(bar) { +const foo = function(bar) { bar.prop = "value"; } -var foo = function(bar) { +const foo1 = function(bar) { delete bar.aaa; } -var foo = function(bar) { +const foo2 = function(bar) { bar.aaa++; } -var foo = function(bar) { +const foo3 = function(bar) { for (bar.aaa in baz) {} } -var foo = function(bar) { +const foo4 = function(bar) { for (bar.aaa of baz) {} } ``` @@ -157,23 +157,23 @@ Examples of **correct** code for the `{ "props": true }` option with `"ignorePro ```js /*eslint no-param-reassign: ["error", { "props": true, "ignorePropertyModificationsForRegex": ["^bar"] }]*/ -var foo = function(barVar) { +const foo = function(barVar) { barVar.prop = "value"; } -var foo = function(barrito) { +const foo1 = function(barrito) { delete barrito.aaa; } -var foo = function(bar_) { +const foo2 = function(bar_) { bar_.aaa++; } -var foo = function(barBaz) { +const foo3 = function(barBaz) { for (barBaz.aaa in baz) {} } -var foo = function(barBaz) { +const foo4 = function(barBaz) { for (barBaz.aaa of baz) {} } ``` @@ -184,4 +184,4 @@ var foo = function(barBaz) { If you want to allow assignment to function parameters, then you can safely disable this rule. -Strict mode code doesn't sync indices of the arguments object with each parameter binding. Therefore, this rule is not necessary to protect against arguments object mutation in ESM modules or other strict mode functions. +`strict` mode code doesn't sync indices of the arguments object with each parameter binding. Therefore, this rule is not necessary to protect against arguments object mutation in ESM modules or other `strict` mode functions. diff --git a/docs/src/rules/no-path-concat.md b/docs/src/rules/no-path-concat.md index 1042f3179208..2783ebf517b9 100644 --- a/docs/src/rules/no-path-concat.md +++ b/docs/src/rules/no-path-concat.md @@ -3,9 +3,6 @@ title: no-path-concat rule_type: suggestion --- - -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - In Node.js, the `__dirname` and `__filename` global variables contain the directory path and the file path of the currently executing script file, respectively. Sometimes, developers try to use these variables to create paths to other files, such as: ```js diff --git a/docs/src/rules/no-plusplus.md b/docs/src/rules/no-plusplus.md index 4f564b51cb90..456cbc3a1f68 100644 --- a/docs/src/rules/no-plusplus.md +++ b/docs/src/rules/no-plusplus.md @@ -7,8 +7,8 @@ rule_type: suggestion Because the unary `++` and `--` operators are subject to automatic semicolon insertion, differences in whitespace can change semantics of source code. ```js -var i = 10; -var j = 20; +let i = 10; +let j = 20; i ++ j @@ -16,8 +16,8 @@ j ``` ```js -var i = 10; -var j = 20; +let i = 10; +let j = 20; i ++ @@ -36,10 +36,10 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-plusplus: "error"*/ -var foo = 0; +let foo = 0; foo++; -var bar = 42; +let bar = 42; bar--; for (i = 0; i < l; i++) { @@ -56,10 +56,10 @@ Examples of **correct** code for this rule: ```js /*eslint no-plusplus: "error"*/ -var foo = 0; +let foo = 0; foo += 1; -var bar = 42; +let bar = 42; bar -= 1; for (i = 0; i < l; i += 1) { diff --git a/docs/src/rules/no-process-env.md b/docs/src/rules/no-process-env.md index 99cd1f41a0e9..2768365db82e 100644 --- a/docs/src/rules/no-process-env.md +++ b/docs/src/rules/no-process-env.md @@ -6,9 +6,6 @@ further_reading: - https://blog.benhall.me.uk/2012/02/storing-application-config-data-in/ --- - -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - The `process.env` object in Node.js is used to store deployment/configuration parameters. Littering it through out a project could lead to maintenance issues as it's another kind of global dependency. As such, it could lead to merge conflicts in a multi-user setup and deployment issues in a multi-server setup. Instead, one of the best practices is to define all those parameters in a single configuration/settings file which could be accessed throughout the project. ## Rule Details diff --git a/docs/src/rules/no-process-exit.md b/docs/src/rules/no-process-exit.md index 90e312b2d452..630567c79c24 100644 --- a/docs/src/rules/no-process-exit.md +++ b/docs/src/rules/no-process-exit.md @@ -3,9 +3,6 @@ title: no-process-exit rule_type: suggestion --- - -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - The `process.exit()` method in Node.js is used to immediately stop the Node.js process and exit. This is a dangerous operation because it can occur in any method at any point in time, potentially stopping a Node.js application completely when an error occurs. For example: ```js diff --git a/docs/src/rules/no-promise-executor-return.md b/docs/src/rules/no-promise-executor-return.md index f163d9ebf0b4..d82e44734771 100644 --- a/docs/src/rules/no-promise-executor-return.md +++ b/docs/src/rules/no-promise-executor-return.md @@ -38,7 +38,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-promise-executor-return: "error"*/ -/*eslint-env es6*/ new Promise((resolve, reject) => { if (someCondition) { @@ -76,7 +75,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-promise-executor-return: "error"*/ -/*eslint-env es6*/ // Turn return inline into two lines new Promise((resolve, reject) => { @@ -125,7 +123,6 @@ Examples of **correct** code for this rule with the `{ "allowVoid": true }` opti ```js /*eslint no-promise-executor-return: ["error", { allowVoid: true }]*/ -/*eslint-env es6*/ new Promise((resolve, reject) => { if (someCondition) { diff --git a/docs/src/rules/no-proto.md b/docs/src/rules/no-proto.md index 629cbbf9a9b3..9bf608a6d69f 100644 --- a/docs/src/rules/no-proto.md +++ b/docs/src/rules/no-proto.md @@ -19,9 +19,9 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-proto: "error"*/ -var a = obj.__proto__; +const a = obj.__proto__; -var a = obj["__proto__"]; +const a1 = obj["__proto__"]; obj.__proto__ = b; @@ -37,11 +37,11 @@ Examples of **correct** code for this rule: ```js /*eslint no-proto: "error"*/ -var a = Object.getPrototypeOf(obj); +const a = Object.getPrototypeOf(obj); Object.setPrototypeOf(obj, b); -var c = { __proto__: a }; +const c = { __proto__: a }; ``` ::: diff --git a/docs/src/rules/no-prototype-builtins.md b/docs/src/rules/no-prototype-builtins.md index e3da23e9cdc6..81b01999af0d 100644 --- a/docs/src/rules/no-prototype-builtins.md +++ b/docs/src/rules/no-prototype-builtins.md @@ -22,11 +22,11 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-prototype-builtins: "error"*/ -var hasBarProperty = foo.hasOwnProperty("bar"); +const hasBarProperty = foo.hasOwnProperty("bar"); -var isPrototypeOfBar = foo.isPrototypeOf(bar); +const isPrototypeOfBar = foo.isPrototypeOf(bar); -var barIsEnumerable = foo.propertyIsEnumerable("bar"); +const barIsEnumerable = foo.propertyIsEnumerable("bar"); ``` ::: @@ -38,11 +38,11 @@ Examples of **correct** code for this rule: ```js /*eslint no-prototype-builtins: "error"*/ -var hasBarProperty = Object.prototype.hasOwnProperty.call(foo, "bar"); +const hasBarProperty = Object.prototype.hasOwnProperty.call(foo, "bar"); -var isPrototypeOfBar = Object.prototype.isPrototypeOf.call(foo, bar); +const isPrototypeOfBar = Object.prototype.isPrototypeOf.call(foo, bar); -var barIsEnumerable = {}.propertyIsEnumerable.call(foo, "bar"); +const barIsEnumerable = {}.propertyIsEnumerable.call(foo, "bar"); ``` ::: diff --git a/docs/src/rules/no-redeclare.md b/docs/src/rules/no-redeclare.md index e3ce497753ee..28104809a480 100644 --- a/docs/src/rules/no-redeclare.md +++ b/docs/src/rules/no-redeclare.md @@ -87,19 +87,4 @@ var Object = 0; ::: -Examples of **incorrect** code for the `{ "builtinGlobals": true }` option and the `browser` environment: - -::: incorrect { "sourceType": "script" } - -```js -/*eslint no-redeclare: ["error", { "builtinGlobals": true }]*/ -/*eslint-env browser*/ - -var top = 0; -``` - -::: - -The `browser` environment has many built-in global variables (for example, `top`). Some of built-in global variables cannot be redeclared. - -Note that when using the `node` or `commonjs` environments (or `ecmaFeatures.globalReturn`, if using the default parser), the top scope of a program is not actually the global scope, but rather a "module" scope. When this is the case, declaring a variable named after a builtin global is not a redeclaration, but rather a shadowing of the global variable. In that case, the [`no-shadow`](no-shadow) rule with the `"builtinGlobals"` option should be used. +Note that when using `sourceType: "commonjs"` (or `ecmaFeatures.globalReturn`, if using the default parser), the top scope of a program is not actually the global scope, but rather a "module" scope. When this is the case, declaring a variable named after a builtin global is not a redeclaration, but rather a shadowing of the global variable. In that case, the [`no-shadow`](no-shadow) rule with the `"builtinGlobals"` option should be used. diff --git a/docs/src/rules/no-regex-spaces.md b/docs/src/rules/no-regex-spaces.md index f6ef344bd175..7f9dc5fabd6b 100644 --- a/docs/src/rules/no-regex-spaces.md +++ b/docs/src/rules/no-regex-spaces.md @@ -13,13 +13,13 @@ related_rules: Regular expressions can be very complex and difficult to understand, which is why it's important to keep them as simple as possible in order to avoid mistakes. One of the more error-prone things you can do with a regular expression is to use more than one space, such as: ```js -var re = /foo bar/; +const re = /foo bar/; ``` In this regular expression, it's very hard to tell how many spaces are intended to be matched. It's better to use only one space and then specify how many spaces are expected, such as: ```js -var re = /foo {3}bar/; +const re = /foo {3}bar/; ``` Now it is very clear that three spaces are expected to be matched. @@ -35,8 +35,8 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-regex-spaces: "error"*/ -var re = /foo bar/; -var re = new RegExp("foo bar"); +const re = /foo bar/; +const re1 = new RegExp("foo bar"); ``` ::: @@ -48,8 +48,8 @@ Examples of **correct** code for this rule: ```js /*eslint no-regex-spaces: "error"*/ -var re = /foo {3}bar/; -var re = new RegExp("foo {3}bar"); +const re = /foo {3}bar/; +const re1 = new RegExp("foo {3}bar"); ``` ::: diff --git a/docs/src/rules/no-restricted-exports.md b/docs/src/rules/no-restricted-exports.md index 44672021a108..446c4fbc337a 100644 --- a/docs/src/rules/no-restricted-exports.md +++ b/docs/src/rules/no-restricted-exports.md @@ -17,6 +17,7 @@ By default, this rule doesn't disallow any names. Only the names you specify in This rule has an object option: * `"restrictedNamedExports"` is an array of strings, where each string is a name to be restricted. +* `"restrictedNamedExportsPattern"` is a string representing a regular expression pattern. Named exports matching this pattern will be restricted. This option does not apply to `default` named exports. * `"restrictDefaultExports"` is an object option with boolean properties to restrict certain default export declarations. The option works only if the `restrictedNamedExports` option does not contain the `"default"` value. The following properties are allowed: * `direct`: restricts `export default` declarations. * `named`: restricts `export { foo as default };` declarations. @@ -130,6 +131,38 @@ export default function foo() {} ::: +### restrictedNamedExportsPattern + +Example of **incorrect** code for the `"restrictedNamedExportsPattern"` option: + +::: incorrect + +```js +/*eslint no-restricted-exports: ["error", { + "restrictedNamedExportsPattern": "bar$" +}]*/ + +export const foobar = 1; +``` + +::: + +Example of **correct** code for the `"restrictedNamedExportsPattern"` option: + +::: correct + +```js +/*eslint no-restricted-exports: ["error", { + "restrictedNamedExportsPattern": "bar$" +}]*/ + +export const abc = 1; +``` + +::: + +Note that this option does not apply to `export default` or any `default` named exports. If you want to also restrict `default` exports, use the `restrictDefaultExports` option. + ### restrictDefaultExports This option allows you to restrict certain `default` declarations. The option works only if the `restrictedNamedExports` option does not contain the `"default"` value. This option accepts the following properties: diff --git a/docs/src/rules/no-restricted-globals.md b/docs/src/rules/no-restricted-globals.md index 58a4ad008171..d3105329151e 100644 --- a/docs/src/rules/no-restricted-globals.md +++ b/docs/src/rules/no-restricted-globals.md @@ -8,7 +8,7 @@ related_rules: Disallowing usage of specific global variables can be useful if you want to allow a set of global -variables by enabling an environment, but still want to disallow some of those. +variables, but still want to disallow some of those. For instance, early Internet Explorer versions exposed the current DOM event as a global variable `event`, but using this variable has been considered as a bad practice for a long time. Restricting @@ -87,7 +87,7 @@ import event from "event-module"; /*global event*/ /*eslint no-restricted-globals: ["error", "event"]*/ -var event = 1; +const event = 1; ``` ::: @@ -106,3 +106,25 @@ function onClick() { ``` ::: + +Restricted globals used in TypeScript type annotations—such as type references, interface inheritance, or class implementations—are ignored by this rule. + +Examples of **correct** TypeScript code for "Promise", "Event", and "Window" global variable names: + +::: correct + +```ts +/*eslint no-restricted-globals: ["error", "Promise", "Event", "Window"]*/ + +const fetchData: Promise = fetchString(); + +interface CustomEvent extends Event {} + +class CustomWindow implements Window {} + +function handleClick(event: Event) { + console.log(event); +} +``` + +::: diff --git a/docs/src/rules/no-restricted-imports.md b/docs/src/rules/no-restricted-imports.md index 81f7ad4a6f97..507f21c4a697 100644 --- a/docs/src/rules/no-restricted-imports.md +++ b/docs/src/rules/no-restricted-imports.md @@ -20,28 +20,62 @@ It applies to static imports only, not dynamic ones. ## Options -The syntax to specify restricted imports looks like this: +This rule has both string and object options to specify the imported modules to restrict. + +Using string option, you can specify the name of a module that you want to restrict from being imported as a value in the rule options array: ```json "no-restricted-imports": ["error", "import1", "import2"] ``` -or like this: +Examples of **incorrect** code for string option: -```json -"no-restricted-imports": ["error", { "paths": ["import1", "import2"] }] +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", "fs"]*/ + +import fs from 'fs'; ``` -When using the object form, you can also specify an array of gitignore-style patterns: +::: -```json -"no-restricted-imports": ["error", { - "paths": ["import1", "import2"], - "patterns": ["import1/private/*", "import2/*", "!import2/good"] -}] +String options also restrict the module from being exported, as in this example: + +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", "fs"]*/ + +export { fs } from 'fs'; +``` + +::: + +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", "fs"]*/ + +export * from 'fs'; ``` -You may also specify a custom message for any paths you want to restrict as follows: +::: + +Examples of **correct** code for string option: + +::: correct { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", "fs"]*/ + +import crypto from 'crypto'; +export { foo } from "bar"; +``` + +::: + +You may also specify a custom message for a particular module using the `name` and `message` properties inside an object, where the value of the `name` is the name of the module and `message` property contains the custom message. (The custom message is appended to the default error message from the rule.) ```json "no-restricted-imports": ["error", { @@ -53,7 +87,42 @@ You may also specify a custom message for any paths you want to restrict as foll }] ``` -or like this: +Examples of **incorrect** code for string option: + +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { + "name": "disallowed-import", + "message": "Please use 'allowed-import' instead" +}]*/ + +import foo from 'disallowed-import'; +``` + +::: + +### paths + +This is an object option whose value is an array containing the names of the modules you want to restrict. + +```json +"no-restricted-imports": ["error", { "paths": ["import1", "import2"] }] +``` + +Examples of **incorrect** code for `paths`: + +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { "paths": ["cluster"] }]*/ + +import cluster from 'cluster'; +``` + +::: + +Custom messages for a particular module can also be specified in `paths` array using objects with `name` and `message`. ```json "no-restricted-imports": ["error", { @@ -67,7 +136,11 @@ or like this: }] ``` -or like this if you need to restrict only certain imports from a module: +#### importNames + +This option in `paths` is an array and can be used to specify the names of certain bindings exported from a module. Import names specified inside `paths` array affect the module specified in the `name` property of corresponding object, so it is required to specify the `name` property first when you are using `importNames` or `message` option. + +Specifying `"default"` string inside the `importNames` array will restrict the default export from being imported. ```json "no-restricted-imports": ["error", { @@ -79,74 +152,183 @@ or like this if you need to restrict only certain imports from a module: }] ``` -or like this if you want to apply a custom message to pattern matches: +Examples of **incorrect** code when `importNames` in `paths` has `"default"`: -```json -"no-restricted-imports": ["error", { - "patterns": [{ - "group": ["import1/private/*"], - "message": "usage of import1 private modules not allowed." - }, { - "group": ["import2/*", "!import2/good"], - "message": "import2 is deprecated, except the modules in import2/good." - }] -}] +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + importNames: ["default"], + message: "Please use the default import from '/bar/baz/' instead." +}]}]*/ + +import DisallowedObject from "foo"; ``` -The custom message will be appended to the default error message. +::: -Pattern matches can also be configured to be case-sensitive: +Examples of **incorrect** code for `importNames` in `paths`: + +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + importNames: ["DisallowedObject"], + message: "Please import 'DisallowedObject' from '/bar/baz/' instead." +}]}]*/ + +import { DisallowedObject } from "foo"; + +import { DisallowedObject as AllowedObject } from "foo"; + +import { "DisallowedObject" as SomeObject } from "foo"; +``` + +::: + +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + importNames: ["DisallowedObject"], + message: "Please import 'DisallowedObject' from '/bar/baz/' instead." +}]}]*/ + +import * as Foo from "foo"; +``` + +::: + +Examples of **correct** code for `importNames` in `paths`: + +If the local name assigned to a default export is the same as a string in `importNames`, this will not cause an error. + +::: correct { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ name: "foo", importNames: ["DisallowedObject"] }] }]*/ + +import DisallowedObject from "foo" +``` + +::: + +::: correct { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + importNames: ["DisallowedObject"], + message: "Please import 'DisallowedObject' from '/bar/baz/' instead." +}]}]*/ + +import { AllowedObject as DisallowedObject } from "foo"; +``` + +::: + +#### allowImportNames + +This option is an array. Inverse of `importNames`, `allowImportNames` allows the imports that are specified inside this array. So it restricts all imports from a module, except specified allowed ones. + +Note: `allowImportNames` cannot be used in combination with `importNames`. ```json "no-restricted-imports": ["error", { - "patterns": [{ - "group": ["import1/private/prefix[A-Z]*"], - "caseSensitive": true - }] + "paths": [{ + "name": "import-foo", + "allowImportNames": ["Bar"], + "message": "Please use only Bar from import-foo." + }] }] ``` -Pattern matches can restrict specific import names only, similar to the `paths` option: +Examples of **incorrect** code for `allowImportNames` in `paths`: + +Disallowing all import names except 'AllowedObject'. + +::: incorrect { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + allowImportNames: ["AllowedObject"], + message: "Please use only 'AllowedObject' from 'foo'." +}]}]*/ + +import { DisallowedObject } from "foo"; +``` + +::: + +Examples of **correct** code for `allowImportNames` in `paths`: + +Disallowing all import names except 'AllowedObject'. + +::: correct { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { paths: [{ + name: "foo", + allowImportNames: ["AllowedObject"], + message: "Only use 'AllowedObject' from 'foo'." +}]}]*/ + +import { AllowedObject } from "foo"; +``` + +::: + +### patterns + +This is also an object option whose value is an array. This option allows you to specify multiple modules to restrict using `gitignore`-style patterns or regular expressions. + +Where `paths` option takes exact import paths, `patterns` option can be used to specify the import paths with more flexibility, allowing for the restriction of multiple modules within the same directory. For example: ```json "no-restricted-imports": ["error", { - "patterns": [{ - "group": ["utils/*"], - "importNames": ["isEmpty"], - "message": "Use 'isEmpty' from lodash instead." - }] + "paths": [{ + "name": "import-foo", + }] }] ``` -Regex patterns can also be used to restrict specific import Name: +This configuration restricts import of the `import-foo` module but wouldn't restrict the import of `import-foo/bar` or `import-foo/baz`. You can use `patterns` to restrict both: ```json "no-restricted-imports": ["error", { + "paths": [{ + "name": "import-foo", + }], "patterns": [{ - "group": ["import-foo/*"], - "importNamePattern": "^foo", + "group": ["import-foo/ba*"], }] }] ``` -To restrict the use of all Node.js core imports (via ): +This configuration restricts imports not just from `import-foo` using `path`, but also `import-foo/bar` and `import-foo/baz` using `patterns`. + +To re-include a module when using `gitignore-`style patterns, add a negation (`!`) mark before the pattern. (Make sure these negated patterns are placed last in the array, as order matters) ```json - "no-restricted-imports": ["error", - "assert","buffer","child_process","cluster","crypto","dgram","dns","domain","events","freelist","fs","http","https","module","net","os","path","punycode","querystring","readline","repl","smalloc","stream","string_decoder","sys","timers","tls","tracing","tty","url","util","vm","zlib" - ], +"no-restricted-imports": ["error", { + "patterns": ["import1/private/*", "import2/*", "!import2/good"] +}] ``` -## Examples +You can also use regular expressions to restrict modules (see the [`regex` option](#regex)). -Examples of **incorrect** code for this rule: +Examples of **incorrect** code for `patterns` option: ::: incorrect { "sourceType": "module" } ```js -/*eslint no-restricted-imports: ["error", "fs"]*/ +/*eslint no-restricted-imports: ["error", { "patterns": ["lodash/*"] }]*/ -import fs from 'fs'; +import pick from 'lodash/pick'; ``` ::: @@ -154,9 +336,9 @@ import fs from 'fs'; ::: incorrect { "sourceType": "module" } ```js -/*eslint no-restricted-imports: ["error", "fs"]*/ +/*eslint no-restricted-imports: ["error", { "patterns": ["lodash/*", "!lodash/pick"] }]*/ -export { fs } from 'fs'; +import pick from 'lodash/map'; ``` ::: @@ -164,92 +346,159 @@ export { fs } from 'fs'; ::: incorrect { "sourceType": "module" } ```js -/*eslint no-restricted-imports: ["error", "fs"]*/ +/*eslint no-restricted-imports: ["error", { "patterns": ["import1/*", "!import1/private/*"] }]*/ -export * from 'fs'; +import pick from 'import1/private/someModule'; ``` ::: -::: incorrect { "sourceType": "module" } +In this example, `"!import1/private/*"` is not reincluding the modules inside `private` because the negation mark (`!`) does not reinclude the files if it's parent directory is excluded by a pattern. In this case, `import1/private` directory is already excluded by the `import1/*` pattern. (The excluded directory can be reincluded using `"!import1/private"`.) + +Examples of **correct** code for `patterns` option: + +::: correct { "sourceType": "module" } ```js -/*eslint no-restricted-imports: ["error", { "paths": ["cluster"] }]*/ +/*eslint no-restricted-imports: ["error", { "patterns": ["crypto/*"] }]*/ -import cluster from 'cluster'; +import crypto from 'crypto'; ``` ::: -::: incorrect { "sourceType": "module" } +::: correct { "sourceType": "module" } ```js -/*eslint no-restricted-imports: ["error", { "patterns": ["lodash/*"] }]*/ +/*eslint no-restricted-imports: ["error", { "patterns": ["lodash/*", "!lodash/pick"] }]*/ import pick from 'lodash/pick'; ``` ::: -::: incorrect { "sourceType": "module" } +::: correct { "sourceType": "module" } ```js -/*eslint no-restricted-imports: ["error", { paths: [{ - name: "foo", - importNames: ["default"], - message: "Please use the default import from '/bar/baz/' instead." -}]}]*/ +/*eslint no-restricted-imports: ["error", { "patterns": ["import1/*", "!import1/private"] }]*/ -import DisallowedObject from "foo"; +import pick from 'import1/private/someModule'; ``` ::: +#### group + +The `patterns` array can also include objects. The `group` property is used to specify the `gitignore`-style patterns for restricting modules and the `message` property is used to specify a custom message. + +Either of the `group` or `regex` properties is required when using the `patterns` option. + +```json +"no-restricted-imports": ["error", { + "patterns": [{ + "group": ["import1/private/*"], + "message": "usage of import1 private modules not allowed." + }, { + "group": ["import2/*", "!import2/good"], + "message": "import2 is deprecated, except the modules in import2/good." + }] +}] +``` + +Examples of **incorrect** code for `group` option: + ::: incorrect { "sourceType": "module" } ```js -/*eslint no-restricted-imports: ["error", { paths: [{ - name: "foo", - importNames: ["DisallowedObject"], - message: "Please import 'DisallowedObject' from '/bar/baz/' instead." +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["lodash/*"], + message: "Please use the default import from 'lodash' instead." }]}]*/ -import { DisallowedObject } from "foo"; +import pick from 'lodash/pick'; +``` -import { DisallowedObject as AllowedObject } from "foo"; +::: -import { "DisallowedObject" as SomeObject } from "foo"; +Examples of **correct** code for this `group` option: + +::: correct { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["lodash/*"], + message: "Please use the default import from 'lodash' instead." +}]}]*/ + +import lodash from 'lodash'; ``` ::: +#### regex + +The `regex` property is used to specify the regex patterns for restricting modules. + +Note: `regex` cannot be used in combination with `group`. + +```json +"no-restricted-imports": ["error", { + "patterns": [{ + "regex": "import1/private/", + "message": "usage of import1 private modules not allowed." + }, { + "regex": "import2/(?!good)", + "message": "import2 is deprecated, except the modules in import2/good." + }] +}] +``` + +Examples of **incorrect** code for `regex` option: + ::: incorrect { "sourceType": "module" } ```js -/*eslint no-restricted-imports: ["error", { paths: [{ - name: "foo", - importNames: ["DisallowedObject"], - message: "Please import 'DisallowedObject' from '/bar/baz/' instead." +/*eslint no-restricted-imports: ["error", { patterns: [{ + regex: "@app/(?!(api/enums$)).*", }]}]*/ -import * as Foo from "foo"; +import Foo from '@app/api'; +import Bar from '@app/api/bar'; +import Baz from '@app/api/baz'; +import Bux from '@app/api/enums/foo'; ``` ::: -::: incorrect { "sourceType": "module" } +Examples of **correct** code for `regex` option: + +::: correct { "sourceType": "module" } ```js /*eslint no-restricted-imports: ["error", { patterns: [{ - group: ["lodash/*"], - message: "Please use the default import from 'lodash' instead." + regex: "@app/(?!(api/enums$)).*", }]}]*/ -import pick from 'lodash/pick'; +import Foo from '@app/api/enums'; ``` ::: +#### caseSensitive + +This is a boolean option and sets the patterns specified in the `group` or `regex` properties to be case-sensitive when `true`. Default is `false`. + +```json +"no-restricted-imports": ["error", { + "patterns": [{ + "group": ["import1/private/prefix[A-Z]*"], + "caseSensitive": true + }] +}] +``` + +Examples of **incorrect** code for `caseSensitive: true` option: + ::: incorrect { "sourceType": "module" } ```js @@ -263,6 +512,37 @@ import pick from 'fooBar'; ::: +Examples of **correct** code for `caseSensitive: true` option: + +::: correct { "sourceType": "module" } + +```js +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["foo[A-Z]*"], + caseSensitive: true +}]}]*/ + +import pick from 'food'; +``` + +::: + +#### importNames + +You can also specify `importNames` within objects inside the `patterns` array. In this case, the specified names apply only to the associated `group` or `regex` property. + +```json +"no-restricted-imports": ["error", { + "patterns": [{ + "group": ["utils/*"], + "importNames": ["isEmpty"], + "message": "Use 'isEmpty' from lodash instead." + }] +}] +``` + +Examples of **incorrect** code for `importNames` in `patterns`: + ::: incorrect { "sourceType": "module" } ```js @@ -277,147 +557,218 @@ import { isEmpty } from 'utils/collection-utils'; ::: -::: incorrect { "sourceType": "module" } +Examples of **correct** code for `importNames` in `patterns`: + +::: correct { "sourceType": "module" } ```js /*eslint no-restricted-imports: ["error", { patterns: [{ group: ["utils/*"], - importNamePattern: '^is', - message: "Use 'is*' functions from lodash instead." + importNames: ['isEmpty'], + message: "Use 'isEmpty' from lodash instead." }]}]*/ -import { isEmpty } from 'utils/collection-utils'; +import { hasValues } from 'utils/collection-utils'; ``` ::: +#### allowImportNames + +You can also specify `allowImportNames` within objects inside the `patterns` array. In this case, the specified names apply only to the associated `group` or `regex` property. + +Note: `allowImportNames` cannot be used in combination with `importNames`, `importNamePattern` or `allowImportNamePattern`. + +```json +"no-restricted-imports": ["error", { + "patterns": [{ + "group": ["utils/*"], + "allowImportNames": ["isEmpty"], + "message": "Please use only 'isEmpty' from utils." + }] +}] +``` + +Examples of **incorrect** code for `allowImportNames` in `patterns`: + ::: incorrect { "sourceType": "module" } ```js /*eslint no-restricted-imports: ["error", { patterns: [{ - group: ["foo/*"], - importNamePattern: '^(is|has)', - message: "Use 'is*' and 'has*' functions from baz/bar instead" + group: ["utils/*"], + allowImportNames: ['isEmpty'], + message: "Please use only 'isEmpty' from utils." }]}]*/ -import { isSomething, hasSomething } from 'foo/bar'; +import { hasValues } from 'utils/collection-utils'; ``` ::: -::: incorrect { "sourceType": "module" } +Examples of **correct** code for `allowImportNames` in `patterns`: + +::: correct { "sourceType": "module" } ```js /*eslint no-restricted-imports: ["error", { patterns: [{ - group: ["foo/*"], - importNames: ["bar"], - importNamePattern: '^baz', + group: ["utils/*"], + allowImportNames: ['isEmpty'], + message: "Please use only 'isEmpty' from utils." }]}]*/ -import { bar, bazQux } from 'foo/quux'; +import { isEmpty } from 'utils/collection-utils'; ``` ::: -Examples of **correct** code for this rule: +#### importNamePattern -::: correct { "sourceType": "module" } +This option allows you to use regex patterns to restrict import names: + +```json +"no-restricted-imports": ["error", { + "patterns": [{ + "group": ["import-foo/*"], + "importNamePattern": "^foo", + }] +}] +``` + +Examples of **incorrect** code for `importNamePattern` option: + +::: incorrect { "sourceType": "module" } ```js -/*eslint no-restricted-imports: ["error", "fs"]*/ +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["utils/*"], + importNamePattern: '^is', + message: "Use 'is*' functions from lodash instead." +}]}]*/ -import crypto from 'crypto'; -export { foo } from "bar"; +import { isEmpty } from 'utils/collection-utils'; ``` ::: -::: correct { "sourceType": "module" } +::: incorrect { "sourceType": "module" } ```js -/*eslint no-restricted-imports: ["error", { "paths": ["fs"], "patterns": ["eslint/*"] }]*/ +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["foo/*"], + importNamePattern: '^(is|has)', + message: "Use 'is*' and 'has*' functions from baz/bar instead" +}]}]*/ -import crypto from 'crypto'; -import eslint from 'eslint'; -export * from "path"; +import { isSomething, hasSomething } from 'foo/bar'; ``` ::: -::: correct { "sourceType": "module" } +::: incorrect { "sourceType": "module" } ```js -/*eslint no-restricted-imports: ["error", { paths: [{ name: "foo", importNames: ["DisallowedObject"] }] }]*/ +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["foo/*"], + importNames: ["bar"], + importNamePattern: '^baz', +}]}]*/ -import DisallowedObject from "foo" +import { bar, bazQux } from 'foo/quux'; ``` ::: +Examples of **correct** code for `importNamePattern` option: + ::: correct { "sourceType": "module" } ```js -/*eslint no-restricted-imports: ["error", { paths: [{ - name: "foo", - importNames: ["DisallowedObject"], - message: "Please import 'DisallowedObject' from '/bar/baz/' instead." +/*eslint no-restricted-imports: ["error", { patterns: [{ + group: ["utils/*"], + importNamePattern: '^is', + message: "Use 'is*' functions from lodash instead." }]}]*/ -import { AllowedObject as DisallowedObject } from "foo"; +import isEmpty, { hasValue } from 'utils/collection-utils'; ``` ::: -::: correct { "sourceType": "module" } +You can also use this option to allow only side-effect imports by setting it to a pattern that matches any name, such as `^`. + +Examples of **incorrect** code for `importNamePattern` option: + +::: incorrect { "sourceType": "module" } ```js /*eslint no-restricted-imports: ["error", { patterns: [{ - group: ["lodash/*"], - message: "Please use the default import from 'lodash' instead." + group: ["utils/*"], + importNamePattern: "^" }]}]*/ -import lodash from 'lodash'; +import isEmpty, { hasValue } from 'utils/collection-utils'; + +import * as file from 'utils/file-utils'; ``` ::: +Examples of **correct** code for `importNamePattern` option: + ::: correct { "sourceType": "module" } ```js /*eslint no-restricted-imports: ["error", { patterns: [{ - group: ["foo[A-Z]*"], - caseSensitive: true + group: ["utils/*"], + importNamePattern: "^" }]}]*/ -import pick from 'food'; +import 'utils/init-utils'; ``` ::: -::: correct { "sourceType": "module" } +#### allowImportNamePattern + +This is a string option. Inverse of `importNamePattern`, this option allows imports that matches the specified regex pattern. So it restricts all imports from a module, except specified allowed patterns. + +Note: `allowImportNamePattern` cannot be used in combination with `importNames`, `importNamePattern` or `allowImportNames`. + +```json +"no-restricted-imports": ["error", { + "patterns": [{ + "group": ["import-foo/*"], + "allowImportNamePattern": "^foo", + }] +}] +``` + +Examples of **incorrect** code for `allowImportNamePattern` option: + +::: incorrect { "sourceType": "module" } ```js /*eslint no-restricted-imports: ["error", { patterns: [{ group: ["utils/*"], - importNames: ['isEmpty'], - message: "Use 'isEmpty' from lodash instead." + allowImportNamePattern: '^has' }]}]*/ -import { hasValues } from 'utils/collection-utils'; +import { isEmpty } from 'utils/collection-utils'; ``` ::: +Examples of **correct** code for `allowImportNamePattern` option: + ::: correct { "sourceType": "module" } ```js /*eslint no-restricted-imports: ["error", { patterns: [{ group: ["utils/*"], - importNamePattern: '^is', - message: "Use 'is*' functions from lodash instead." + allowImportNamePattern: '^is' }]}]*/ -import isEmpty, { hasValue } from 'utils/collection-utils'; +import { isEmpty } from 'utils/collection-utils'; ``` ::: diff --git a/docs/src/rules/no-restricted-modules.md b/docs/src/rules/no-restricted-modules.md index eb2ca03ffc86..c1f58e14116d 100644 --- a/docs/src/rules/no-restricted-modules.md +++ b/docs/src/rules/no-restricted-modules.md @@ -3,9 +3,6 @@ title: no-restricted-modules rule_type: suggestion --- - -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - A module in Node.js is a simple or complex functionality organized in a JavaScript file which can be reused throughout the Node.js application. The keyword `require` is used in Node.js/CommonJS to import modules into an application. This way you can have dynamic loading where the loaded module name isn't predefined /static, or where you conditionally load a module only if it's "truly required". @@ -80,8 +77,8 @@ Examples of **incorrect** code for this rule with sample `"fs", "cluster", "lod ```js /*eslint no-restricted-modules: ["error", "fs", "cluster"]*/ -var fs = require('fs'); -var cluster = require('cluster'); +const fs = require('fs'); +const cluster = require('cluster'); ``` ::: @@ -91,7 +88,7 @@ var cluster = require('cluster'); ```js /*eslint no-restricted-modules: ["error", {"paths": ["cluster"] }]*/ -var cluster = require('cluster'); +const cluster = require('cluster'); ``` ::: @@ -101,7 +98,7 @@ var cluster = require('cluster'); ```js /*eslint no-restricted-modules: ["error", { "patterns": ["lodash/*"] }]*/ -var pick = require('lodash/pick'); +const pick = require('lodash/pick'); ``` ::: @@ -113,7 +110,7 @@ Examples of **correct** code for this rule with sample `"fs", "cluster", "lodash ```js /*eslint no-restricted-modules: ["error", "fs", "cluster"]*/ -var crypto = require('crypto'); +const crypto = require('crypto'); ``` ::: @@ -126,8 +123,8 @@ var crypto = require('crypto'); "patterns": ["lodash/*", "!lodash/pick"] }]*/ -var crypto = require('crypto'); -var pick = require('lodash/pick'); +const crypto = require('crypto'); +const pick = require('lodash/pick'); ``` ::: diff --git a/docs/src/rules/no-restricted-properties.md b/docs/src/rules/no-restricted-properties.md index 2e681733fc23..159e1345989d 100644 --- a/docs/src/rules/no-restricted-properties.md +++ b/docs/src/rules/no-restricted-properties.md @@ -71,6 +71,36 @@ If the property name is omitted, accessing any property of the given object is d } ``` +If you want to restrict a property globally but allow specific objects to use it, you can use the `allowObjects` option: + +```json +{ + "rules": { + "no-restricted-properties": [2, { + "property": "push", + "allowObjects": ["router"], + "message": "Prefer [...array, newValue] because it does not mutate the array in place." + }] + } +} +``` + +If you want to restrict all properties on an object except for specific ones, you can use the `allowProperties` option: + +```json +{ + "rules": { + "no-restricted-properties": [2, { + "object": "config", + "allowProperties": ["settings", "version"], + "message": "Accessing other properties is restricted." + }] + } +} +``` + +Note that the `allowObjects` option cannot be used together with the `object` option since they are mutually exclusive. Similarly, the `allowProperties` option cannot be used together with the `property` option since they are also mutually exclusive. + Examples of **incorrect** code for this rule: ::: incorrect @@ -81,7 +111,7 @@ Examples of **incorrect** code for this rule: "property": "disallowedPropertyName" }] */ -var example = disallowedObjectName.disallowedPropertyName; /*error Disallowed object property: disallowedObjectName.disallowedPropertyName.*/ +const example = disallowedObjectName.disallowedPropertyName; /*error Disallowed object property: disallowedObjectName.disallowedPropertyName.*/ disallowedObjectName.disallowedPropertyName(); /*error Disallowed object property: disallowedObjectName.disallowedPropertyName.*/ ``` @@ -116,6 +146,33 @@ require.resolve('foo'); ::: +::: incorrect + +```js +/* eslint no-restricted-properties: [2, { + "property": "push", + "allowObjects": ["router"], +}] */ + +myArray.push(5); +``` + +::: + +::: incorrect + +```js +/* eslint no-restricted-properties: [2, { + "object": "config", + "allowProperties": ["settings", "version"] +}] */ + +config.apiKey = "12345"; +config.timeout = 5000; +``` + +::: + Examples of **correct** code for this rule: ::: correct @@ -126,7 +183,7 @@ Examples of **correct** code for this rule: "property": "disallowedPropertyName" }] */ -var example = disallowedObjectName.somePropertyName; +const example = disallowedObjectName.somePropertyName; allowedObjectName.disallowedPropertyName(); ``` @@ -145,6 +202,34 @@ require('foo'); ::: +::: correct + +```js +/* eslint no-restricted-properties: [2, { + "property": "push", + "allowObjects": ["router", "history"], +}] */ + +router.push('/home'); +history.push('/about'); +``` + +::: + +::: correct + +```js +/* eslint no-restricted-properties: [2, { + "object": "config", + "allowProperties": ["settings", "version"] +}] */ + +config.settings = { theme: "dark" }; +config.version = "1.0.0"; +``` + +::: + ## When Not To Use It If you don't have any object/property combinations to restrict, you should not use this rule. diff --git a/docs/src/rules/no-restricted-syntax.md b/docs/src/rules/no-restricted-syntax.md index a3f51cfef809..80f6d5277b6b 100644 --- a/docs/src/rules/no-restricted-syntax.md +++ b/docs/src/rules/no-restricted-syntax.md @@ -11,10 +11,15 @@ related_rules: JavaScript has a lot of language features, and not everyone likes all of them. As a result, some projects choose to disallow the use of certain language features altogether. For instance, you might decide to disallow the use of `try-catch` or `class`, or you might decide to disallow the use of the `in` operator. -Rather than creating separate rules for every language feature you want to turn off, this rule allows you to configure the syntax elements you want to restrict use of. These elements are represented by their [ESTree](https://github.com/estree/estree) node types. For example, a function declaration is represented by `FunctionDeclaration` and the `with` statement is represented by `WithStatement`. You may find the full list of AST node names you can use [on GitHub](https://github.com/eslint/eslint-visitor-keys/blob/main/lib/visitor-keys.js) and use [AST Explorer](https://astexplorer.net/) with the espree parser to see what type of nodes your code consists of. +Rather than creating separate rules for every language feature you want to turn off, this rule allows you to configure the syntax elements you want to restrict use of. For the JavaScript language, these elements are represented by their [ESTree](https://github.com/estree/estree) node types. For example, a function declaration is represented by `FunctionDeclaration` and the `with` statement is represented by `WithStatement`. You may use [Code Explorer](https://explorer.eslint.org) to determine the nodes that represent your code. You can also specify [AST selectors](../extend/selectors) to restrict, allowing much more precise control over syntax patterns. +Note: This rule can be used with any language you lint using ESLint. To see what type of nodes your code in another language consists of, you can use: + +* [typescript-eslint Playground](https://typescript-eslint.io/play) if you're using ESLint with `typescript-eslint`. +* [ESLint Code Explorer](https://explorer.eslint.org/) if you're using ESLint to lint JavaScript, JSON, Markdown, or CSS. + ## Rule Details This rule disallows specified (that is, user-defined) syntax. @@ -66,7 +71,7 @@ with (me) { dontMess(); } -var doSomething = function () {}; +const doSomething = function () {}; foo in bar; ``` diff --git a/docs/src/rules/no-return-await.md b/docs/src/rules/no-return-await.md index 2ee5a8b3659c..bbaf7dcd6056 100644 --- a/docs/src/rules/no-return-await.md +++ b/docs/src/rules/no-return-await.md @@ -7,15 +7,16 @@ further_reading: - https://jakearchibald.com/2017/await-vs-return-vs-return-await/ --- -This rule was **deprecated** in ESLint v8.46.0 with no replacement. The original intent of this rule no longer applies due to the fact JavaScript now handles native `Promises` differently. It can now be slower to remove `await` rather than keeping it. More technical information can be found in [this V8 blog entry](https://v8.dev/blog/fast-async). +It is NOT recommended to use the `no-return-await` rule anymore because: -Using `return await` inside an `async function` keeps the current function in the call stack until the Promise that is being awaited has resolved, at the cost of an extra microtask before resolving the outer Promise. `return await` can also be used in a try/catch statement to catch errors from another function that returns a Promise. +* `return await` on a promise will not result in an extra microtask. +* `return await` yields a better stack trace for debugging. -You can avoid the extra microtask by not awaiting the return value, with the trade off of the function no longer being a part of the stack trace if an error is thrown asynchronously from the Promise being returned. This can make debugging more difficult. +Historical context: When promises were first introduced, calling `return await` introduced an additional microtask, one for the `await` and one for the return value of the async function. Each extra microtask delays the computation of a result and so this rule was added to help avoid this performance trap. Later, [ECMA-262 changed the way](https://github.com/tc39/ecma262/pull/1250) `return await` worked so it would create a single microtask, which means this rule is no longer necessary. ## Rule Details -This rule aims to prevent a likely common performance hazard due to a lack of understanding of the semantics of `async function`. +This rule warns on any usage of `return await` except in `try` blocks. Examples of **incorrect** code for this rule: @@ -65,8 +66,4 @@ async function foo4() { ## When Not To Use It -There are a few reasons you might want to turn this rule off: - -* If you want to use `await` to denote a value that is a thenable -* If you do not want the performance benefit of avoiding `return await` -* If you want the functions to show up in stack traces (useful for debugging purposes) +You should not use this rule. There is no reason to avoid `return await`. diff --git a/docs/src/rules/no-self-compare.md b/docs/src/rules/no-self-compare.md index bf0562993df1..f685f6cfd731 100644 --- a/docs/src/rules/no-self-compare.md +++ b/docs/src/rules/no-self-compare.md @@ -19,10 +19,39 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-self-compare: "error"*/ -var x = 10; +let x = 10; if (x === x) { x = 20; } ``` ::: + +## Known Limitations + +This rule works by directly comparing the tokens on both sides of the operator. It flags them as problematic if they are structurally identical. However, it doesn't consider possible side effects or that functions may return different objects even when called with the same arguments. As a result, it can produce false positives in some cases, such as: + +::: incorrect + +```js +/*eslint no-self-compare: "error"*/ + +function parseDate(dateStr) { + return new Date(dateStr); +} + +if (parseDate('December 17, 1995 03:24:00') === parseDate('December 17, 1995 03:24:00')) { + // do something +} + +let counter = 0; +function incrementUnlessReachedMaximum() { + return Math.min(counter += 1, 10); +} + +if (incrementUnlessReachedMaximum() === incrementUnlessReachedMaximum()) { + // ... +} +``` + +::: diff --git a/docs/src/rules/no-sequences.md b/docs/src/rules/no-sequences.md index cbacb46ce26f..03b0917723d5 100644 --- a/docs/src/rules/no-sequences.md +++ b/docs/src/rules/no-sequences.md @@ -7,7 +7,7 @@ rule_type: suggestion The comma operator includes multiple expressions where only one is expected. It evaluates each operand from left to right and returns the value of the last operand. However, this frequently obscures side effects, and its use is often an accident. Here are some examples of sequences: ```js -var a = (3, 5); // a = 5 +let a = (3, 5); // a = 5 a = b += 5, a + b; @@ -160,7 +160,7 @@ for (i = 0, j = 10; i < j; i++, j--); ## When Not To Use It Disable this rule if sequence expressions with the comma operator are acceptable. -Another case is where you might want to report all usages of the comma operator, even in a for loop. You can achieve this using rule `no-restricted-syntax`: +Another case is where you might want to report all usages of the comma operator, even in a `for` loop. You can achieve this using rule `no-restricted-syntax`: ```js { diff --git a/docs/src/rules/no-setter-return.md b/docs/src/rules/no-setter-return.md index 50353c754dab..bc0ae00b56e9 100644 --- a/docs/src/rules/no-setter-return.md +++ b/docs/src/rules/no-setter-return.md @@ -33,7 +33,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-setter-return: "error"*/ -var foo = { +const foo = { set a(value) { this.val = value; return value; @@ -76,7 +76,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-setter-return: "error"*/ -var foo = { +const foo = { set a(value) { this.val = value; } diff --git a/docs/src/rules/no-shadow-restricted-names.md b/docs/src/rules/no-shadow-restricted-names.md index 7a5e1b0403d9..697522e9b143 100644 --- a/docs/src/rules/no-shadow-restricted-names.md +++ b/docs/src/rules/no-shadow-restricted-names.md @@ -4,16 +4,16 @@ rule_type: suggestion related_rules: - no-shadow further_reading: -- https://es5.github.io/#x15.1.1 -- https://es5.github.io/#C +- https://262.ecma-international.org/11.0/#sec-value-properties-of-the-global-object +- https://262.ecma-international.org/11.0/#sec-strict-mode-of-ecmascript --- -ES5 §15.1.1 Value Properties of the Global Object (`NaN`, `Infinity`, `undefined`) as well as strict mode restricted identifiers `eval` and `arguments` are considered to be restricted names in JavaScript. Defining them to mean something else can have unintended consequences and confuse others reading the code. For example, there's nothing preventing you from writing: +ES2020 §18.1 Value Properties of the Global Object (`globalThis`, `NaN`, `Infinity`, `undefined`) as well as strict mode restricted identifiers `eval` and `arguments` are considered to be restricted names in JavaScript. Defining them to mean something else can have unintended consequences and confuse others reading the code. For example, there's nothing preventing you from writing: ```js -var undefined = "foo"; +const undefined = "foo"; ``` Then any code used within the same scope would not get the global `undefined`, but rather the local version with a very different meaning. @@ -31,13 +31,27 @@ function NaN(){} !function(Infinity){}; -var undefined = 5; +const undefined = 5; try {} catch(eval){} ``` ::: +::: incorrect + +```js +/*eslint no-shadow-restricted-names: "error"*/ + +import NaN from "foo"; + +import { undefined } from "bar"; + +class Infinity {} +``` + +::: + Examples of **correct** code for this rule: ::: correct { "sourceType": "script" } @@ -45,12 +59,90 @@ Examples of **correct** code for this rule: ```js /*eslint no-shadow-restricted-names: "error"*/ -var Object; +let Object; function f(a, b){} // Exception: `undefined` may be shadowed if the variable is never assigned a value. -var undefined; +let undefined; +``` + +::: + +::: correct + +```js +/*eslint no-shadow-restricted-names: "error"*/ + +import { undefined as undef } from "bar"; +``` + +::: + +## Options + +This rule has an object option: + +* `"reportGlobalThis"`: `true` (default `false`) reports declarations of `globalThis`. + +### reportGlobalThis + +Examples of **incorrect** code for the `{ "reportGlobalThis": true }` option: + +::: incorrect + +```js +/*eslint no-shadow-restricted-names: ["error", { "reportGlobalThis": true }]*/ + +const globalThis = "foo"; +``` + +::: + +::: incorrect + +```js +/*eslint no-shadow-restricted-names: ["error", { "reportGlobalThis": true }]*/ + +function globalThis() {} +``` + +::: + +::: incorrect + +```js +/*eslint no-shadow-restricted-names: ["error", { "reportGlobalThis": true }]*/ + +import { globalThis } from "bar"; +``` + +::: + +::: incorrect + +```js +/*eslint no-shadow-restricted-names: ["error", { "reportGlobalThis": true }]*/ + +class globalThis {} +``` + +::: + +Examples of **correct** code for the `{ "reportGlobalThis": true }` option: + +::: correct + +```js +/*eslint no-shadow-restricted-names: ["error", { "reportGlobalThis": true }]*/ + +const foo = globalThis; + +function bar() { + return globalThis; +} + +import { globalThis as baz } from "foo"; ``` ::: diff --git a/docs/src/rules/no-shadow.md b/docs/src/rules/no-shadow.md index 9b9a27ee7d38..f51b4f629652 100644 --- a/docs/src/rules/no-shadow.md +++ b/docs/src/rules/no-shadow.md @@ -11,9 +11,9 @@ further_reading: Shadowing is the process by which a local variable shares the same name as a variable in its containing scope. For example: ```js -var a = 3; +const a = 3; function b() { - var a = 10; + const a = 10; } ``` @@ -29,15 +29,14 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-shadow: "error"*/ -/*eslint-env es6*/ -var a = 3; +const a = 3; function b() { - var a = 10; + const a = 10; } -var c = function () { - var a = 10; +const c = function () { + const a = 10; } function d(a) { @@ -46,7 +45,7 @@ function d(a) { d(a); if (true) { - let a = 5; + const a = 5; } ``` @@ -54,7 +53,13 @@ if (true) { ## Options -This rule takes one option, an object, with properties `"builtinGlobals"`, `"hoist"`, `"allow"` and `"ignoreOnInitialization"`. +This rule takes one option, an object, with the following properties: +- `"builtinGlobals"` +- `"hoist"` +- `"allow"` +- `"ignoreOnInitialization"` +- `"ignoreTypeValueShadow"` (TypeScript only) +- `"ignoreFunctionTypeParameterNameValueShadow"` (TypeScript only) ```json { @@ -75,7 +80,7 @@ Examples of **incorrect** code for the `{ "builtinGlobals": true }` option: /*eslint no-shadow: ["error", { "builtinGlobals": true }]*/ function foo() { - var Object = 0; + const Object = 0; } ``` @@ -83,11 +88,13 @@ function foo() { ### hoist -The `hoist` option has three settings: +The `hoist` option has five settings: -* `functions` (by default) - reports shadowing before the outer functions are defined. -* `all` - reports all shadowing before the outer variables/functions are defined. -* `never` - never report shadowing before the outer variables/functions are defined. +- `functions` (by default) - reports shadowing before the outer functions are defined. +- `all` - reports all shadowing before the outer variables/functions are defined. +- `never` - never report shadowing before the outer variables/functions are defined. +- `types` (TypeScript only) - reports shadowing before the outer types are defined. +- `functions-and-types` (TypeScript only) - reports shadowing before the outer functions and types are defined. #### hoist: functions @@ -97,10 +104,9 @@ Examples of **incorrect** code for the default `{ "hoist": "functions" }` option ```js /*eslint no-shadow: ["error", { "hoist": "functions" }]*/ -/*eslint-env es6*/ if (true) { - let b = 6; + const b = 6; } function b() {} @@ -108,7 +114,7 @@ function b() {} ::: -Although `let b` in the `if` statement is before the *function* declaration in the outer scope, it is incorrect. +Although `const b` in the `if` statement is before the *function* declaration in the outer scope, it is incorrect. Examples of **correct** code for the default `{ "hoist": "functions" }` option: @@ -116,18 +122,17 @@ Examples of **correct** code for the default `{ "hoist": "functions" }` option: ```js /*eslint no-shadow: ["error", { "hoist": "functions" }]*/ -/*eslint-env es6*/ if (true) { - let a = 3; + const a = 3; } -let a = 5; +const a = 5; ``` ::: -Because `let a` in the `if` statement is before the *variable* declaration in the outer scope, it is correct. +Because `const a` in the `if` statement is before the *variable* declaration in the outer scope, it is correct. #### hoist: all @@ -137,14 +142,13 @@ Examples of **incorrect** code for the `{ "hoist": "all" }` option: ```js /*eslint no-shadow: ["error", { "hoist": "all" }]*/ -/*eslint-env es6*/ if (true) { - let a = 3; - let b = 6; + const a = 3; + const b = 6; } -let a = 5; +const a = 5; function b() {} ``` @@ -158,20 +162,57 @@ Examples of **correct** code for the `{ "hoist": "never" }` option: ```js /*eslint no-shadow: ["error", { "hoist": "never" }]*/ -/*eslint-env es6*/ if (true) { - let a = 3; - let b = 6; + const a = 3; + const b = 6; } -let a = 5; +const a = 5; function b() {} ``` ::: -Because `let a` and `let b` in the `if` statement are before the declarations in the outer scope, they are correct. +Because `const a` and `const b` in the `if` statement are before the declarations in the outer scope, they are correct. + +#### hoist: types + +Examples of **incorrect** code for the `{ "hoist": "types" }` option: + +::: incorrect + +```ts +/*eslint no-shadow: ["error", { "hoist": "types" }]*/ + +type Bar = 1; +type Foo = 1; +``` + +::: + +#### hoist: functions-and-types + +Examples of **incorrect** code for the `{ "hoist": "functions-and-types" }` option: + +::: incorrect + +```ts +/*eslint no-shadow: ["error", { "hoist": "functions-and-types" }]*/ + +// types +type Bar = 1; +type Foo = 1; + +// functions +if (true) { + const b = 6; +} + +function b() {} +``` + +::: ### allow @@ -183,7 +224,6 @@ Examples of **correct** code for the `{ "allow": ["done"] }` option: ```js /*eslint no-shadow: ["error", { "allow": ["done"] }]*/ -/*eslint-env es6*/ import async from 'async'; @@ -213,7 +253,7 @@ Examples of **incorrect** code for the `{ "ignoreOnInitialization": "true" }` op ```js /*eslint no-shadow: ["error", { "ignoreOnInitialization": true }]*/ -var x = x => x; +const x = x => x; ``` ::: @@ -227,11 +267,97 @@ Examples of **correct** code for the `{ "ignoreOnInitialization": true }` option ```js /*eslint no-shadow: ["error", { "ignoreOnInitialization": true }]*/ -var x = foo(x => x) +const x = foo(x => x) -var y = (y => y)() +const y = (y => y)() ``` ::: The rationale for callback functions is the assumption that they will be called during the initialization, so that at the time when the shadowing variable will be used, the shadowed variable has not yet been initialized. + +### ignoreTypeValueShadow + +Whether to ignore types named the same as a variable. Default: `true`. + +This is generally safe because you cannot use variables in type locations without a typeof operator, so there's little risk of confusion. + +Examples of **correct** code with `{ "ignoreTypeValueShadow": true }`: + +::: correct + +```ts +/*eslint no-shadow: ["error", { "ignoreTypeValueShadow": true }]*/ + +type Foo = number; +interface Bar { + prop: number; +} + +function f() { + const Foo = 1; + const Bar = 'test'; +} +``` + +::: + +**Note:** Shadowing specifically refers to two identical identifiers that are in different, nested scopes. This is different from redeclaration, which is when two identical identifiers are in the same scope. Redeclaration is covered by the [`no-redeclare`](/rules/no-redeclare) rule instead. + + +### ignoreFunctionTypeParameterNameValueShadow + +Whether to ignore function parameters named the same as a variable. Default: `true`. + +Each of a function type's arguments creates a value variable within the scope of the function type. This is done so that you can reference the type later using the typeof operator: + +```ts +type Func = (test: string) => typeof test; + +declare const fn: Func; +const result = fn('str'); // typeof result === string +``` + +This means that function type arguments shadow value variable names in parent scopes: + +```ts +let test = 1; +type TestType = typeof test; // === number +type Func = (test: string) => typeof test; // this "test" references the argument, not the variable + +declare const fn: Func; +const result = fn('str'); // typeof result === string +``` + +If you do not use the `typeof` operator in a function type return type position, you can safely turn this option on. + +Examples of **correct** code with `{ "ignoreFunctionTypeParameterNameValueShadow": true }`: + +::: correct + +```ts +/*eslint no-shadow: ["error", { "ignoreFunctionTypeParameterNameValueShadow": true }]*/ + +const test = 1; +type Func = (test: string) => typeof test; +``` + +::: + +### Why does the rule report on enum members that share the same name as a variable in a parent scope? + +This isn't a bug — the rule is working exactly as intended! The report is correct because of a lesser-known aspect of enums: enum members introduce a variable within the enum's own scope, allowing them to be referenced without needing a qualifier. + +Here's a simple example to explain: + +```ts +const A = 2; +enum Test { + A = 1, + B = A, +} + +console.log(Test.B); // what should be logged? +``` + +At first glance, you might think it should log `2`, because the outer variable A's value is `2`. However, it actually logs `1`, the value of `Test.A`. This happens because inside the enum `B = A` is treated as `B = Test.A`. Due to this behavior, the enum member has shadowed the outer variable declaration. diff --git a/docs/src/rules/no-spaced-func.md b/docs/src/rules/no-spaced-func.md index 099efcf8d1f4..1bba6d337674 100644 --- a/docs/src/rules/no-spaced-func.md +++ b/docs/src/rules/no-spaced-func.md @@ -3,10 +3,6 @@ title: no-spaced-func rule_type: layout --- - - -This rule was **deprecated** in ESLint v3.3.0 and replaced by the [func-call-spacing](func-call-spacing) rule. - While it's possible to have whitespace between the name of a function and the parentheses that execute it, such patterns tend to look more like errors. ## Rule Details diff --git a/docs/src/rules/no-sparse-arrays.md b/docs/src/rules/no-sparse-arrays.md index ec41bb23df45..ddb619a0eeb6 100644 --- a/docs/src/rules/no-sparse-arrays.md +++ b/docs/src/rules/no-sparse-arrays.md @@ -10,13 +10,13 @@ further_reading: Sparse arrays contain empty slots, most frequently due to multiple commas being used in an array literal, such as: ```js -var items = [,,]; +const items = [,,]; ``` While the `items` array in this example has a `length` of 2, there are actually no values in `items[0]` or `items[1]`. The fact that the array literal is valid with only commas inside, coupled with the `length` being set and actual item values not being set, make sparse arrays confusing for many developers. Consider the following: ```js -var colors = [ "red",, "blue" ]; +const colors = [ "red",, "blue" ]; ``` In this example, the `colors` array has a `length` of 3. But did the developer intend for there to be an empty spot in the middle of the array? Or is it a typo? @@ -34,8 +34,8 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-sparse-arrays: "error"*/ -var items = [,]; -var colors = [ "red",, "blue" ]; +const items = [,]; +const colors = [ "red",, "blue" ]; ``` ::: @@ -47,11 +47,11 @@ Examples of **correct** code for this rule: ```js /*eslint no-sparse-arrays: "error"*/ -var items = []; -var items = new Array(23); +const items = []; +const arr = new Array(23); // trailing comma (after the last element) is not a problem -var colors = [ "red", "blue", ]; +const colors = [ "red", "blue", ]; ``` ::: diff --git a/docs/src/rules/no-sync.md b/docs/src/rules/no-sync.md index 254974411e43..cd40e0f4556a 100644 --- a/docs/src/rules/no-sync.md +++ b/docs/src/rules/no-sync.md @@ -3,9 +3,6 @@ title: no-sync rule_type: suggestion --- - -This rule was **deprecated** in ESLint v7.0.0. Please use the corresponding rule in [`eslint-plugin-n`](https://github.com/eslint-community/eslint-plugin-n). - In Node.js, most I/O is done through asynchronous methods. However, there are often synchronous versions of the asynchronous methods. For example, `fs.exists()` and `fs.existsSync()`. In some contexts, using synchronous operations is okay (if, as with ESLint, you are writing a command line utility). However, in other contexts the use of synchronous operations is considered a bad practice that should be avoided. For example, if you are running a high-travel web server on Node.js, you should consider carefully if you want to allow any synchronous operations that could lock up the server. ## Rule Details diff --git a/docs/src/rules/no-tabs.md b/docs/src/rules/no-tabs.md index 848ef34ebc38..9c6f77d92c63 100644 --- a/docs/src/rules/no-tabs.md +++ b/docs/src/rules/no-tabs.md @@ -2,9 +2,6 @@ title: no-tabs rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/no-tabs) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Some style guides don't allow the use of tab characters at all, including within comments. ## Rule Details diff --git a/docs/src/rules/no-ternary.md b/docs/src/rules/no-ternary.md index b4ff25846340..d5c6558b5e90 100644 --- a/docs/src/rules/no-ternary.md +++ b/docs/src/rules/no-ternary.md @@ -10,7 +10,7 @@ related_rules: The ternary operator is used to conditionally assign a value to a variable. Some believe that the use of ternary operators leads to unclear code. ```js -var foo = isBar ? baz : qux; +const foo = isBar ? baz : qux; ``` ## Rule Details @@ -24,7 +24,7 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-ternary: "error"*/ -var foo = isBar ? baz : qux; +const foo = isBar ? baz : qux; function quux() { return foo ? bar() : baz(); @@ -40,7 +40,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-ternary: "error"*/ -var foo; +let foo; if (isBar) { foo = baz; diff --git a/docs/src/rules/no-this-before-super.md b/docs/src/rules/no-this-before-super.md index a06b5735553e..b26c920dd739 100644 --- a/docs/src/rules/no-this-before-super.md +++ b/docs/src/rules/no-this-before-super.md @@ -22,7 +22,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-this-before-super: "error"*/ -/*eslint-env es6*/ class A1 extends B { constructor() { @@ -60,7 +59,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-this-before-super: "error"*/ -/*eslint-env es6*/ class A1 { constructor() { diff --git a/docs/src/rules/no-throw-literal.md b/docs/src/rules/no-throw-literal.md index 1ab939ff3a00..e8651558586d 100644 --- a/docs/src/rules/no-throw-literal.md +++ b/docs/src/rules/no-throw-literal.md @@ -19,7 +19,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-throw-literal: "error"*/ -/*eslint-env es6*/ throw "error"; @@ -29,12 +28,12 @@ throw undefined; throw null; -var err = new Error(); +const err = new Error(); throw "an " + err; // err is recast to a string literal -var err = new Error(); -throw `${err}` +const er2 = new Error(); +throw `${err2}` ``` @@ -51,7 +50,7 @@ throw new Error(); throw new Error("error"); -var e = new Error("error"); +const e = new Error("error"); throw e; try { @@ -74,7 +73,7 @@ Examples of **correct** code for this rule, but which do not throw an `Error` ob ```js /*eslint no-throw-literal: "error"*/ -var err = "error"; +const err = "error"; throw err; function foo(bar) { @@ -84,7 +83,7 @@ throw foo("error"); throw new String("error"); -var baz = { +const baz = { bar: "error" }; throw baz.bar; diff --git a/docs/src/rules/no-trailing-spaces.md b/docs/src/rules/no-trailing-spaces.md index 17fb34dc7906..bd35242f6472 100644 --- a/docs/src/rules/no-trailing-spaces.md +++ b/docs/src/rules/no-trailing-spaces.md @@ -2,9 +2,6 @@ title: no-trailing-spaces rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/no-trailing-spaces) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Sometimes in the course of editing files, you can end up with extra whitespace at the end of lines. These whitespace differences can be picked up by source control systems and flagged as diffs, causing frustration for developers. While this extra whitespace causes no functional issues, many code conventions require that trailing spaces be removed before check-in. ## Rule Details diff --git a/docs/src/rules/no-unassigned-vars.md b/docs/src/rules/no-unassigned-vars.md new file mode 100644 index 000000000000..aa9489dc3760 --- /dev/null +++ b/docs/src/rules/no-unassigned-vars.md @@ -0,0 +1,146 @@ +--- +title: no-unassigned-vars +rule_type: problem +related_rules: +- init-declarations +- no-unused-vars +- prefer-const +--- + + +This rule flags `let` or `var` declarations that are never assigned a value but are still read or used in the code. Since these variables will always be `undefined`, their usage is likely a programming mistake. + +For example, if you check the value of a `status` variable, but it was never given a value, it will always be `undefined`: + +```js +let status; + +// ...forgot to assign a value to status... + +if (status === 'ready') { + console.log('Ready!'); +} +``` + +## Rule Details + +Examples of **incorrect** code for this rule: + +::: incorrect + +```js +/*eslint no-unassigned-vars: "error"*/ + +let status; +if (status === 'ready') { + console.log('Ready!'); +} + +let user; +greet(user); + +function test() { + let error; + return error || "Unknown error"; +} + +let options; +const { debug } = options || {}; + +let flag; +while (!flag) { + // Do something... +} + +let config; +function init() { + return config?.enabled; +} +``` + +::: + +In TypeScript: + +::: incorrect + +```ts +/*eslint no-unassigned-vars: "error"*/ + +let value: number | undefined; +console.log(value); +``` + +::: + +Examples of **correct** code for this rule: + +::: correct + +```js +/*eslint no-unassigned-vars: "error"*/ + +let message = "hello"; +console.log(message); + +let user; +user = getUser(); +console.log(user.name); + +let count; +count = 1; +count++; + +// Variable is unused (should be reported by `no-unused-vars` only) +let temp; + +let error; +if (somethingWentWrong) { + error = "Something went wrong"; +} +console.log(error); + +let item; +for (item of items) { + process(item); +} + +let config; +function setup() { + config = { debug: true }; +} +setup(); +console.log(config); + +let one = undefined; +if (one === two) { + // Noop +} +``` + +::: + +In TypeScript: + +::: correct + +```ts +/*eslint no-unassigned-vars: "error"*/ + +declare let value: number | undefined; +console.log(value); + +declare module "my-module" { + let value: string; + export = value; +} +``` + +::: + +## When Not To Use It + +You can disable this rule if your code intentionally uses variables that are declared and used, but are never assigned a value. This might be the case in: + +- Legacy codebases where uninitialized variables are used as placeholders. +- Certain TypeScript use cases where variables are declared with a type and intentionally left unassigned (though using `declare` is preferred). diff --git a/docs/src/rules/no-undef-init.md b/docs/src/rules/no-undef-init.md index f474562ca200..8c94efcf5146 100644 --- a/docs/src/rules/no-undef-init.md +++ b/docs/src/rules/no-undef-init.md @@ -54,7 +54,7 @@ let bar; ::: -Please note that this rule does not check `const` declarations, destructuring patterns, function parameters, and class fields. +Please note that this rule does not check `const` declarations, `using` declarations, `await using` declarations, destructuring patterns, function parameters, and class fields. Examples of additional **correct** code for this rule: @@ -65,6 +65,10 @@ Examples of additional **correct** code for this rule: const foo = undefined; +using foo1 = undefined; + +await using foo2 = undefined; + let { bar = undefined } = baz; [quux = undefined] = quuux; @@ -80,7 +84,9 @@ class Foo { ## When Not To Use It -There is one situation where initializing to `undefined` behaves differently than omitting the initialization, and that's when a `var` declaration occurs inside of a loop. For example: +There are situations where initializing to `undefined` behaves differently than omitting the initialization. + +One such case is when a `var` declaration occurs inside of a loop. For example: Example of **incorrect** code for this rule: @@ -150,3 +156,22 @@ for (i = 0; i < 10; i++) { ``` ::: + +Another such case is when a variable is redeclared using `var`. For example: + +```js +function foo() { + var x = 1; + console.log(x); // output: 1 + + var x; + console.log(x); // output: 1 + + var x = undefined; + console.log(x); // output: undefined +} + +foo(); +``` + +In this case, you can avoid redeclaration by changing it to an assignment (`x = undefined;`), or use an eslint disable comment on a specific line. diff --git a/docs/src/rules/no-undef.md b/docs/src/rules/no-undef.md index 72ea516f966b..d9af2902e21a 100644 --- a/docs/src/rules/no-undef.md +++ b/docs/src/rules/no-undef.md @@ -13,7 +13,7 @@ This rule can help you locate potential ReferenceErrors resulting from misspelli ## Rule Details -Any reference to an undeclared variable causes a warning, unless the variable is explicitly mentioned in a `/*global ...*/` comment, or specified in the [`globals` key in the configuration file](../use/configure/language-options#using-configuration-files-1). A common use case for these is if you intentionally use globals that are defined elsewhere (e.g. in a script sourced from HTML). +Any reference to an undeclared variable causes a warning, unless the variable is explicitly mentioned in a `/*global ...*/` comment, or specified in the [`globals` key in the configuration file](../use/configure/language-options#specifying-globals). A common use case for these is if you intentionally use globals that are defined elsewhere (e.g. in a script sourced from HTML). Examples of **incorrect** code for this rule: @@ -22,8 +22,8 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-undef: "error"*/ -var foo = someFunction(); -var bar = a + 1; +const foo = someFunction(); +const bar = a + 1; ``` ::: @@ -36,8 +36,8 @@ Examples of **correct** code for this rule with `global` declaration: /*global someFunction, a*/ /*eslint no-undef: "error"*/ -var foo = someFunction(); -var bar = a + 1; +const foo = someFunction(); +const bar = a + 1; ``` ::: @@ -95,45 +95,6 @@ if(typeof a === "string"){} ::: -## Environments - -For convenience, ESLint provides shortcuts that pre-define global variables exposed by popular libraries and runtime environments. This rule supports these environments, as listed in [Specifying Environments](../use/configure/language-options#specifying-environments). A few examples are given below. - -### browser - -Examples of **correct** code for this rule with `browser` environment: - -::: correct - -```js -/*eslint no-undef: "error"*/ -/*eslint-env browser*/ - -setTimeout(function() { - alert("Hello"); -}); -``` - -::: - -### Node.js - -Examples of **correct** code for this rule with `node` environment: - -::: correct - -```js -/*eslint no-undef: "error"*/ -/*eslint-env node*/ - -var fs = require("fs"); -module.exports = function() { - console.log(fs); -}; -``` - -::: - ## When Not To Use It If explicit declaration of global variables is not to your taste. diff --git a/docs/src/rules/no-undefined.md b/docs/src/rules/no-undefined.md index 10096533a646..5fb7533e60e5 100644 --- a/docs/src/rules/no-undefined.md +++ b/docs/src/rules/no-undefined.md @@ -17,7 +17,7 @@ The `undefined` variable in JavaScript is actually a property of the global obje ```js function doSomething(data) { - var undefined = "hi"; + const undefined = "hi"; // doesn't do what you think it does if (data === undefined) { @@ -46,9 +46,9 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-undefined: "error"*/ -var foo = undefined; +const foo = undefined; -var undefined = "foo"; +const undefined = "foo"; if (foo === undefined) { // ... @@ -70,9 +70,9 @@ Examples of **correct** code for this rule: ```js /*eslint no-undefined: "error"*/ -var foo = void 0; +const foo = void 0; -var Undefined = "foo"; +const Undefined = "foo"; if (typeof foo === "undefined") { // ... diff --git a/docs/src/rules/no-underscore-dangle.md b/docs/src/rules/no-underscore-dangle.md index 19157dfb55f7..70757c09da2b 100644 --- a/docs/src/rules/no-underscore-dangle.md +++ b/docs/src/rules/no-underscore-dangle.md @@ -6,7 +6,7 @@ rule_type: suggestion As far as naming conventions for identifiers go, dangling underscores may be the most polarizing in JavaScript. Dangling underscores are underscores at either the beginning or end of an identifier, such as: ```js -var _foo; +let _foo; ``` There is a long history of marking "private" members with dangling underscores in JavaScript, beginning with SpiderMonkey adding nonstandard methods such as `__defineGetter__()`. Since that time, using a single underscore prefix has become the most popular convention for indicating a member is not part of the public interface of an object. @@ -26,8 +26,8 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-underscore-dangle: "error"*/ -var foo_; -var __proto__ = {}; +let foo_; +const __proto__ = {}; foo._bar(); ``` @@ -40,10 +40,10 @@ Examples of **correct** code for this rule: ```js /*eslint no-underscore-dangle: "error"*/ -var _ = require('underscore'); -var obj = _.contains(items, item); +const _ = require('underscore'); +const obj = _.contains(items, item); obj.__proto__ = {}; -var file = __filename; +const file = __filename; function foo(_bar) {}; const bar = { onClick(_bar) {} }; const baz = (_bar) => {}; @@ -74,7 +74,7 @@ Examples of additional **correct** code for this rule with the `{ "allow": ["foo ```js /*eslint no-underscore-dangle: ["error", { "allow": ["foo_", "_bar"] }]*/ -var foo_; +let foo_; foo._bar(); ``` @@ -89,7 +89,7 @@ Examples of **correct** code for this rule with the `{ "allowAfterThis": true }` ```js /*eslint no-underscore-dangle: ["error", { "allowAfterThis": true }]*/ -var a = this.foo_; +const a = this.foo_; this._bar(); ``` @@ -106,7 +106,7 @@ Examples of **correct** code for this rule with the `{ "allowAfterSuper": true } class Foo extends Bar { doSomething() { - var a = super.foo_; + const a = super.foo_; super._bar(); } } @@ -123,7 +123,7 @@ Examples of **correct** code for this rule with the `{ "allowAfterThisConstructo ```js /*eslint no-underscore-dangle: ["error", { "allowAfterThisConstructor": true }]*/ -var a = this.constructor.foo_; +const a = this.constructor.foo_; this.constructor._bar(); ``` diff --git a/docs/src/rules/no-unexpected-multiline.md b/docs/src/rules/no-unexpected-multiline.md index b8f3582fb39d..d134fe7f344f 100644 --- a/docs/src/rules/no-unexpected-multiline.md +++ b/docs/src/rules/no-unexpected-multiline.md @@ -31,20 +31,20 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-unexpected-multiline: "error"*/ -var foo = bar +const foo = bar (1 || 2).baz(); -var hello = 'world' +const hello = 'world' [1, 2, 3].forEach(addNumber); -let x = function() {} +const x = function() {} `hello` -let y = function() {} +const y = function() {} y `hello` -let z = foo +const z = foo /regex/g.test(bar) ``` @@ -57,22 +57,22 @@ Examples of **correct** code for this rule: ```js /*eslint no-unexpected-multiline: "error"*/ -var foo = bar; +const foo = bar; (1 || 2).baz(); -var foo = bar +const baz = bar ;(1 || 2).baz() -var hello = 'world'; +const hello = 'world'; [1, 2, 3].forEach(addNumber); -var hello = 'world' +const hi = 'world' void [1, 2, 3].forEach(addNumber); -let x = function() {}; +const x = function() {}; `hello` -let tag = function() {} +const tag = function() {} tag `hello` ``` diff --git a/docs/src/rules/no-unmodified-loop-condition.md b/docs/src/rules/no-unmodified-loop-condition.md index 86a8212d7d92..b2ebe673b04c 100644 --- a/docs/src/rules/no-unmodified-loop-condition.md +++ b/docs/src/rules/no-unmodified-loop-condition.md @@ -37,14 +37,14 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-unmodified-loop-condition: "error"*/ -var node = something; +let node = something; while (node) { doSomething(node); } node = other; -for (var j = 0; j < 5;) { +for (let j = 0; j < 5;) { doSomething(j); } @@ -67,7 +67,7 @@ while (node) { node = node.parent; } -for (var j = 0; j < items.length; ++j) { +for (let j = 0; j < items.length; ++j) { doSomething(items[j]); } diff --git a/docs/src/rules/no-unneeded-ternary.md b/docs/src/rules/no-unneeded-ternary.md index ca275810e027..22f42026ce11 100644 --- a/docs/src/rules/no-unneeded-ternary.md +++ b/docs/src/rules/no-unneeded-ternary.md @@ -8,21 +8,21 @@ related_rules: -It's a common mistake in JavaScript to use a conditional expression to select between two Boolean values instead of using ! to convert the test to a Boolean. +It's a common mistake in JavaScript to use a conditional expression to select between two Boolean values instead of using `!` to convert the test to a Boolean. Here are some examples: ```js // Bad -var isYes = answer === 1 ? true : false; +const isYes = answer === 1 ? true : false; // Good -var isYes = answer === 1; +const isYes = answer === 1; // Bad -var isNo = answer === 1 ? false : true; +const isNo = answer === 1 ? false : true; // Good -var isNo = answer !== 1; +const isNo = answer !== 1; ``` Another common mistake is using a single variable as both the conditional test and the consequent. In such cases, the logical `OR` can be used to provide the same functionality. @@ -47,9 +47,9 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-unneeded-ternary: "error"*/ -var a = x === 2 ? true : false; +const a = x === 2 ? true : false; -var a = x ? true : false; +const b = x ? true : false; ``` ::: @@ -61,13 +61,13 @@ Examples of **correct** code for this rule: ```js /*eslint no-unneeded-ternary: "error"*/ -var a = x === 2 ? "Yes" : "No"; +const a = x === 2 ? "Yes" : "No"; -var a = x !== false; +const b = x !== false; -var a = x ? "Yes" : "No"; +const c = x ? "Yes" : "No"; -var a = x ? y : x; +const d = x ? y : x; f(x ? x : 1); // default assignment - would be disallowed if defaultAssignment option set to false. See option details below. ``` @@ -83,7 +83,7 @@ This rule has an object option: ### defaultAssignment -When set to `true`, which it is by default, The defaultAssignment option allows expressions of the form `x ? x : expr` (where `x` is any identifier and `expr` is any expression). +When set to `true`, which it is by default, The `defaultAssignment` option allows expressions of the form `x ? x : expr` (where `x` is any identifier and `expr` is any expression). Examples of additional **incorrect** code for this rule with the `{ "defaultAssignment": false }` option: @@ -92,7 +92,7 @@ Examples of additional **incorrect** code for this rule with the `{ "defaultAssi ```js /*eslint no-unneeded-ternary: ["error", { "defaultAssignment": false }]*/ -var a = x ? x : 1; +const a = x ? x : 1; f(x ? x : 1); ``` diff --git a/docs/src/rules/no-unreachable-loop.md b/docs/src/rules/no-unreachable-loop.md index 89b9a76bdd83..5851a2fe77c3 100644 --- a/docs/src/rules/no-unreachable-loop.md +++ b/docs/src/rules/no-unreachable-loop.md @@ -56,7 +56,7 @@ function verifyList(head) { } function findSomething(arr) { - for (var i = 0; i < arr.length; i++) { + for (let i = 0; i < arr.length; i++) { if (isSomething(arr[i])) { return arr[i]; } else { @@ -110,7 +110,7 @@ function verifyList(head) { } function findSomething(arr) { - for (var i = 0; i < arr.length; i++) { + for (let i = 0; i < arr.length; i++) { if (isSomething(arr[i])) { return arr[i]; } @@ -184,7 +184,7 @@ Examples of **correct** code for this rule with the `"ignore"` option: ```js /*eslint no-unreachable-loop: ["error", { "ignore": ["ForInStatement", "ForOfStatement"] }]*/ -for (var key in obj) { +for (let key in obj) { hasEnumerableProperties = true; break; } diff --git a/docs/src/rules/no-unreachable.md b/docs/src/rules/no-unreachable.md index 15f77f8173ee..c17a6866e810 100644 --- a/docs/src/rules/no-unreachable.md +++ b/docs/src/rules/no-unreachable.md @@ -2,11 +2,15 @@ title: no-unreachable rule_type: problem handled_by_typescript: true +extra_typescript_info: >- + TypeScript must be configured with + [`allowUnreachableCode: false`](https://www.typescriptlang.org/tsconfig#allowUnreachableCode) + for it to consider unreachable code an error. --- -Because the `return`, `throw`, `break`, and `continue` statements unconditionally exit a block of code, any statements after them cannot be executed. Unreachable statements are usually a mistake. +Because the `return`, `throw`, `continue`, and `break` statements unconditionally exit a block of code, any statements after them cannot be executed. Unreachable statements are usually a mistake. ```js function fn() { diff --git a/docs/src/rules/no-unsafe-optional-chaining.md b/docs/src/rules/no-unsafe-optional-chaining.md index 57ad6de87805..c011c4dbb874 100644 --- a/docs/src/rules/no-unsafe-optional-chaining.md +++ b/docs/src/rules/no-unsafe-optional-chaining.md @@ -8,7 +8,7 @@ rule_type: problem The optional chaining (`?.`) expression can short-circuit with a return value of `undefined`. Therefore, treating an evaluated optional chaining expression as a function, object, number, etc., can cause TypeError or unexpected results. For example: ```js -var obj = undefined; +const obj = undefined; 1 in obj?.foo; // TypeError with (obj?.foo); // TypeError @@ -20,7 +20,7 @@ const { bar } = obj?.foo; // TypeError Also, parentheses limit the scope of short-circuiting in chains. For example: ```js -var obj = undefined; +const obj = undefined; (obj?.foo)(); // TypeError (obj?.foo).bar; // TypeError @@ -77,7 +77,7 @@ with (obj?.foo); class A extends obj?.foo {} -var a = class A extends obj?.foo {}; +const a = class A extends obj?.foo {}; async function foo () { const { bar } = await obj?.foo; @@ -111,7 +111,7 @@ foo?.()?.bar; new (obj?.foo ?? bar)(); -var baz = {...obj?.foo}; +const baz = {...obj?.foo}; const { bar } = obj?.foo || baz; diff --git a/docs/src/rules/no-unused-expressions.md b/docs/src/rules/no-unused-expressions.md index 52379726dcea..7654d2506022 100644 --- a/docs/src/rules/no-unused-expressions.md +++ b/docs/src/rules/no-unused-expressions.md @@ -15,16 +15,16 @@ This rule aims to eliminate unused expressions which have no effect on the state This rule does not apply to function calls or constructor calls with the `new` operator, because they could have *side effects* on the state of the program. ```js -var i = 0; +let i = 0; function increment() { i += 1; } increment(); // return value is unused, but i changed as a side effect -var nThings = 0; +let nThings = 0; function Thing() { nThings += 1; } new Thing(); // constructed object is unused, but nThings changed as a side effect ``` -This rule does not apply to directives (which are in the form of literal string expressions such as `"use strict";` at the beginning of a script, module, or function). +This rule does not apply to directives (which are in the form of literal string expressions such as `"use strict";` at the beginning of a script, module, or function) when using ES5+ environments. In ES3 environments, directives are treated as unused expressions by default, but this behavior can be changed using the `ignoreDirectives` option. Sequence expressions (those using a comma, such as `a = 1, b = 2`) are always considered unused unless their return value is assigned or used in a condition evaluation, or a function call is made with the sequence expression value. @@ -36,6 +36,7 @@ This rule, in its default state, does not require any arguments. If you would li * `allowTernary` set to `true` will enable you to use ternary operators in your expressions similarly to short circuit evaluations (Default: `false`). * `allowTaggedTemplates` set to `true` will enable you to use tagged template literals in your expressions (Default: `false`). * `enforceForJSX` set to `true` will flag unused JSX element expressions (Default: `false`). +* `ignoreDirectives` set to `true` will prevent directives from being reported as unused expressions when linting with `ecmaVersion: 3` (Default: `false`). These options allow unused expressions *only if all* of the code paths either directly change the state (for example, assignment statement) or could have *side effects* (for example, function call). @@ -251,7 +252,7 @@ JSX is most-commonly used in the React ecosystem, where it is compiled to `React Examples of **incorrect** code for the `{ "enforceForJSX": true }` option: -::: incorrect { "ecmaFeatures": { "jsx": true } } +::: incorrect { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /*eslint no-unused-expressions: ["error", { "enforceForJSX": true }]*/ @@ -265,14 +266,125 @@ Examples of **incorrect** code for the `{ "enforceForJSX": true }` option: Examples of **correct** code for the `{ "enforceForJSX": true }` option: -::: correct { "ecmaFeatures": { "jsx": true } } +::: correct { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```jsx /*eslint no-unused-expressions: ["error", { "enforceForJSX": true }]*/ -var myComponentPartial = ; +const myComponentPartial = ; -var myFragment = <>; +const myFragment = <>; +``` + +::: + +### ignoreDirectives + +When set to `false` (default), this rule reports directives (like `"use strict"`) as unused expressions when linting with `ecmaVersion: 3`. This default behavior exists because ES3 environments do not formally support directives, meaning such strings are effectively unused expressions in that specific context. + +Set this option to `true` to prevent directives from being reported as unused, even when `ecmaVersion: 3` is specified. This option is primarily useful for projects that need to maintain a single codebase containing directives while supporting both older ES3 environments and modern (ES5+) environments. + +**Note:** In ES5+ environments, directives are always ignored regardless of this setting. + +Examples of **incorrect** code for the `{ "ignoreDirectives": false }` option and `ecmaVersion: 3`: + +::: incorrect { "ecmaVersion": 3, "sourceType": "script" } + +```js +/*eslint no-unused-expressions: ["error", { "ignoreDirectives": false }]*/ + +"use strict"; +"use asm" +"use stricter"; +"use babel" +"any other strings like this in the directive prologue"; +"this is still the directive prologue"; + +function foo() { + "bar"; +} +``` + +::: + +Examples of **correct** code for the `{ "ignoreDirectives": true }` option and `ecmaVersion: 3`: + +::: correct { "ecmaVersion": 3, "sourceType": "script" } + +```js +/*eslint no-unused-expressions: ["error", { "ignoreDirectives": true }]*/ + +"use strict"; +"use asm" +"use stricter"; +"use babel" +"any other strings like this in the directive prologue"; +"this is still the directive prologue"; + +function foo() { + "bar"; +} +``` + +::: + +### TypeScript Support + +This rule supports TypeScript-specific expressions and follows these guidelines: + +1. Directives (like `'use strict'`) are allowed in module and namespace declarations +2. Type-related expressions are treated as unused if their wrapped value expressions are unused: + * Type assertions (`x as number`, `x`) + * Non-null assertions (`x!`) + * Type instantiations (`Set`) + +**Note**: Although type expressions never have runtime side effects (e.g., `x!` is equivalent to `x` at runtime), they can be used to assert types for testing purposes. + +Examples of **correct** code for this rule when using TypeScript: + +::: correct + +```ts +/* eslint no-unused-expressions: "error" */ + +// Type expressions wrapping function calls are allowed +function getSet() { + return Set; +} +getSet(); +getSet() as Set; +getSet()!; + +// Directives in modules and namespaces +module Foo { + 'use strict'; + 'hello world'; +} + +namespace Bar { + 'use strict'; + export class Baz {} +} +``` + +::: + +Examples of **incorrect** code for this rule when using TypeScript: + +::: incorrect + +```ts +/* eslint no-unused-expressions: "error" */ + +// Standalone type expressions +Set; +1 as number; +window!; + +// Expressions inside namespaces +namespace Bar { + 123; +} ``` ::: diff --git a/docs/src/rules/no-unused-vars.md b/docs/src/rules/no-unused-vars.md index f88b1ccb307e..872a888bed50 100644 --- a/docs/src/rules/no-unused-vars.md +++ b/docs/src/rules/no-unused-vars.md @@ -1,6 +1,9 @@ --- title: no-unused-vars rule_type: problem +related_rules: +- no-unassigned-vars +- no-useless-assignment --- @@ -14,11 +17,11 @@ This rule is aimed at eliminating unused variables, functions, and function para A variable `foo` is considered to be used if any of the following are true: * It is called (`foo()`) or constructed (`new foo()`) -* It is read (`var bar = foo`) +* It is read (`let bar = foo`) * It is passed into a function as an argument (`doSomething(foo)`) * It is read inside of a function that is passed to another function (`doSomething(function() { foo(); })`) -A variable is *not* considered to be used if it is only ever declared (`var foo = 5`) or assigned to (`foo = 7`). +A variable is *not* considered to be used if it is only ever declared (`let foo = 5`) or assigned to (`foo = 7`). Examples of **incorrect** code for this rule: @@ -31,14 +34,14 @@ Examples of **incorrect** code for this rule: // It checks variables you have defined as global some_unused_var = 42; -var x; +let x; // Write-only variables are not considered as used. -var y = 10; +let y = 10; y = 5; // A read for a modification of itself is not considered as used. -var z = 0; +let z = 0; z = z + 1; // By default, unused arguments cause warnings. @@ -68,7 +71,7 @@ Examples of **correct** code for this rule: ```js /*eslint no-unused-vars: "error"*/ -var x = 10; +const x = 10; alert(x); // foo is considered used here @@ -101,9 +104,8 @@ In environments outside of CommonJS or ECMAScript modules, you may use `var` to Note that `/* exported */` has no effect for any of the following: -* when the environment is `node` or `commonjs` -* when `parserOptions.sourceType` is `module` -* when `ecmaFeatures.globalReturn` is `true` +* when `languageOptions.sourceType` is `module` (default) or `commonjs` +* when `languageOptions.parserOptions.ecmaFeatures.globalReturn` is `true` The line comment `// exported variableName` will not work as `exported` is not line-specific. @@ -130,12 +132,18 @@ var global_var = 42; This rule takes one argument which can be a string or an object. The string settings are the same as those of the `vars` property (explained below). -By default this rule is enabled with `all` option for variables and `after-used` for arguments. +By default this rule is enabled with `all` option for caught errors and variables, and `after-used` for arguments. ```json { "rules": { - "no-unused-vars": ["error", { "vars": "all", "args": "after-used", "ignoreRestSiblings": false }] + "no-unused-vars": ["error", { + "vars": "all", + "args": "after-used", + "caughtErrors": "all", + "ignoreRestSiblings": false, + "reportUsedIgnorePattern": false + }] } } ``` @@ -144,7 +152,7 @@ By default this rule is enabled with `all` option for variables and `after-used` The `vars` option has two settings: -* `all` checks all variables for usage, including those in the global scope. This is the default setting. +* `all` checks all variables for usage, including those in the global scope. However, it excludes variables targeted by other options like `args` and `caughtErrors`. This is the default setting. * `local` checks only that locally-declared variables are used but will allow global variables to be unused. #### vars: local @@ -164,7 +172,7 @@ some_unused_var = 42; ### varsIgnorePattern -The `varsIgnorePattern` option specifies exceptions not to check for usage: variables whose names match a regexp pattern. For example, variables whose names contain `ignored` or `Ignored`. +The `varsIgnorePattern` option specifies exceptions not to check for usage: variables whose names match a regexp pattern. For example, variables whose names contain `ignored` or `Ignored`. However, it excludes variables targeted by other options like `argsIgnorePattern` and `caughtErrorsIgnorePattern`. Examples of **correct** code for the `{ "varsIgnorePattern": "[iI]gnored" }` option: @@ -173,8 +181,8 @@ Examples of **correct** code for the `{ "varsIgnorePattern": "[iI]gnored" }` opt ```js /*eslint no-unused-vars: ["error", { "varsIgnorePattern": "[iI]gnored" }]*/ -var firstVarIgnored = 1; -var secondVar = 2; +const firstVarIgnored = 1; +const secondVar = 2; console.log(secondVar); ``` @@ -281,20 +289,22 @@ The `caughtErrors` option is used for `catch` block arguments validation. It has two settings: -* `none` - do not check error objects. This is the default setting. -* `all` - all named arguments must be used. +* `all` - all named arguments must be used. This is the default setting. +* `none` - do not check error objects. -#### caughtErrors: none +#### caughtErrors: all -Not specifying this rule is equivalent of assigning it to `none`. +Not specifying this option is equivalent of assigning it to `all`. -Examples of **correct** code for the `{ "caughtErrors": "none" }` option: +Examples of **incorrect** code for the `{ "caughtErrors": "all" }` option: -::: correct +::: incorrect ```js -/*eslint no-unused-vars: ["error", { "caughtErrors": "none" }]*/ +/*eslint no-unused-vars: ["error", { "caughtErrors": "all" }]*/ +// 1 error +// "err" is defined but never used try { //... } catch (err) { @@ -304,17 +314,15 @@ try { ::: -#### caughtErrors: all +#### caughtErrors: none -Examples of **incorrect** code for the `{ "caughtErrors": "all" }` option: +Examples of **correct** code for the `{ "caughtErrors": "none" }` option: -::: incorrect +::: correct ```js -/*eslint no-unused-vars: ["error", { "caughtErrors": "all" }]*/ +/*eslint no-unused-vars: ["error", { "caughtErrors": "none" }]*/ -// 1 error -// "err" is defined but never used try { //... } catch (err) { @@ -333,7 +341,7 @@ Examples of **correct** code for the `{ "caughtErrorsIgnorePattern": "^ignore" } ::: correct ```js -/*eslint no-unused-vars: ["error", { "caughtErrorsIgnorePattern": "^ignore" }]*/ +/*eslint no-unused-vars: ["error", { "caughtErrors": "all", "caughtErrorsIgnorePattern": "^ignore" }]*/ try { //... @@ -397,17 +405,97 @@ Examples of **correct** code for the `{ "ignoreRestSiblings": true }` option: /*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/ // 'foo' and 'bar' were ignored because they have a rest property sibling. -var { foo, ...rest } = data; +const { foo, ...rest } = data; console.log(rest); // OR -var bar; +let bar; ({ bar, ...rest } = data); ``` ::: +### ignoreClassWithStaticInitBlock + +The `ignoreClassWithStaticInitBlock` option is a boolean (default: `false`). Static initialization blocks allow you to initialize static variables and execute code during the evaluation of a class definition, meaning the static block code is executed without creating a new instance of the class. When set to `true`, this option ignores classes containing static initialization blocks. + +Examples of **incorrect** code for the `{ "ignoreClassWithStaticInitBlock": true }` option + +::: incorrect + +```js +/*eslint no-unused-vars: ["error", { "ignoreClassWithStaticInitBlock": true }]*/ + +class Foo { + static myProperty = "some string"; + static mymethod() { + return "some string"; + } +} + +class Bar { + static { + let baz; // unused variable + } +} +``` + +::: + +Examples of **correct** code for the `{ "ignoreClassWithStaticInitBlock": true }` option + +::: correct + +```js +/*eslint no-unused-vars: ["error", { "ignoreClassWithStaticInitBlock": true }]*/ + +class Foo { + static { + let bar = "some string"; + + console.log(bar); + } +} +``` + +::: + +### reportUsedIgnorePattern + +The `reportUsedIgnorePattern` option is a boolean (default: `false`). +Using this option will report variables that match any of the valid ignore +pattern options (`varsIgnorePattern`, `argsIgnorePattern`, `caughtErrorsIgnorePattern`, or +`destructuredArrayIgnorePattern`) if they have been used. + +Examples of **incorrect** code for the `{ "reportUsedIgnorePattern": true }` option: + +::: incorrect + +```js +/*eslint no-unused-vars: ["error", { "reportUsedIgnorePattern": true, "varsIgnorePattern": "[iI]gnored" }]*/ + +const firstVarIgnored = 1; +const secondVar = 2; +console.log(firstVarIgnored, secondVar); +``` + +::: + +Examples of **correct** code for the `{ "reportUsedIgnorePattern": true }` option: + +::: correct + +```js +/*eslint no-unused-vars: ["error", { "reportUsedIgnorePattern": true, "varsIgnorePattern": "[iI]gnored" }]*/ + +const firstVar = 1; +const secondVar = 2; +console.log(firstVar, secondVar); +``` + +::: + ## When Not To Use It If you don't want to be notified about unused variables or function arguments, you can safely turn this rule off. diff --git a/docs/src/rules/no-use-before-define.md b/docs/src/rules/no-use-before-define.md index 726f5fd861b6..dbd8034bcfc2 100644 --- a/docs/src/rules/no-use-before-define.md +++ b/docs/src/rules/no-use-before-define.md @@ -136,20 +136,20 @@ export { foo }; ``` * `functions` (`boolean`) - - The flag which shows whether or not this rule checks function declarations. - If this is `true`, this rule warns every reference to a function before the function declaration. - Otherwise, ignores those references. - Function declarations are hoisted, so it's safe. + This flag determines whether or not the rule checks function declarations. + If this is `true`, the rule warns on every reference to a function before the function declaration. + Otherwise, the rule ignores those references. + Function declarations are hoisted, so it's safe to disable this option (note that some idiomatic patterns, such as [mutual recursion](https://en.wikipedia.org/wiki/Mutual_recursion), are incompatible with enabling this option). Default is `true`. * `classes` (`boolean`) - - The flag which shows whether or not this rule checks class declarations of upper scopes. - If this is `true`, this rule warns every reference to a class before the class declaration. - Otherwise, ignores those references if the declaration is in upper function scopes. - Class declarations are not hoisted, so it might be danger. + This flag determines whether or not the rule checks class declarations of upper scopes. + If this is `true`, the rule warns on every reference to a class before the class declaration. + Otherwise, the rule ignores such references, provided the declaration is in an upper function scope. + Class declarations are not hoisted, so it might be dangerous to disable this option. Default is `true`. * `variables` (`boolean`) - This flag determines whether or not the rule checks variable declarations in upper scopes. - If this is `true`, the rule warns every reference to a variable before the variable declaration. + If this is `true`, the rule warns on every reference to a variable before the variable declaration. Otherwise, the rule ignores a reference if the declaration is in an upper scope, while still reporting the reference if it's in the same scope as the declaration. Default is `true`. * `allowNamedExports` (`boolean`) - @@ -157,6 +157,18 @@ export { foo }; These references are safe even if the variables are declared later in the code. Default is `false`. +This rule additionally supports TypeScript type syntax. The following options enable checking for the references to `type`, `interface` and `enum` declarations: + +* `enums` (`boolean`) - + If it is `true`, the rule warns every reference to an `enum` before it is defined. + Defult is `true`. +* `typedefs` (`boolean`) - + If it is `true`, this rule warns every reference to a type `alias` or `interface` before its declaration. If `false`, the rule allows using type `alias`es and `interface`s before they are defined. + Default is `true`. +* `ignoreTypeReferences` (`boolean`) - + If it is `true`, rule will ignore all type references, such as in type annotations and assertions. + Default is `true`. + This rule accepts `"nofunc"` string as an option. `"nofunc"` is the same as `{ "functions": false, "classes": true, "variables": true, "allowNamedExports": false }`. @@ -350,3 +362,129 @@ const d = 1; ``` ::: + +### enums (TypeScript only) + +Examples of **incorrect** code for the `{ "enums": true }` option: + +::: incorrect + +```ts +/*eslint no-use-before-define: ["error", { "enums": true }]*/ + +const x = Foo.FOO; + +enum Foo { + FOO, +} +``` + +::: + +Examples of **correct** code for the `{ "enums": true }` option: + +::: correct + +```ts +/*eslint no-use-before-define: ["error", { "enums": true }]*/ + +enum Foo { + FOO, +} + +const x = Foo.FOO; +``` + +::: + +### typedefs (TypeScript only) + +Examples of **incorrect** code for the `{ "enums": true }` with `{ "ignoreTypeReferences": false }` option: + +::: incorrect + +```ts +/*eslint no-use-before-define: ["error", { "typedefs": true, "ignoreTypeReferences": false }]*/ + +let myVar: StringOrNumber; + +type StringOrNumber = string | number; + +const x: Foo = {}; + +interface Foo {} +``` + +::: + +Examples of **correct** code for the `{ "typedefs": true }` with `{ "ignoreTypeReferences": false }` option: + +::: correct + +```ts +/*eslint no-use-before-define: ["error", { "typedefs": true, "ignoreTypeReferences": false }]*/ + +type StringOrNumber = string | number; + +let myVar: StringOrNumber; + +interface Foo {} + +const x: Foo = {}; +``` + +::: + +### ignoreTypeReferences (TypeScript only) + +Examples of **incorrect** code for the `{ "ignoreTypeReferences": false }` option: + +::: incorrect + +```ts +/*eslint no-use-before-define: ["error", { "ignoreTypeReferences": false }]*/ + +let var1: StringOrNumber; + +type StringOrNumber = string | number; + +let var2: Enum; + +enum Enum {} +``` + +::: + +Examples of **correct** code for the `{ "ignoreTypeReferences": false }` option: + +::: correct + +```ts +/*eslint no-use-before-define: ["error", { "ignoreTypeReferences": false }]*/ + +type StringOrNumber = string | number; + +let myVar: StringOrNumber; + +enum Enum {} + +let var2: Enum; +``` + +Examples of **correct** code for the `{ "ignoreTypeReferences": false }` with `{ "typedefs": false }` option: + +::: correct + +```ts +/*eslint no-use-before-define: ["error", { "ignoreTypeReferences": false, "typedefs": false, }]*/ + +let myVar: StringOrNumber; + +type StringOrNumber = string | number; + +const x: Foo = {}; + +interface Foo {} +``` + +::: diff --git a/docs/src/rules/no-useless-assignment.md b/docs/src/rules/no-useless-assignment.md new file mode 100644 index 000000000000..3cabc76cb444 --- /dev/null +++ b/docs/src/rules/no-useless-assignment.md @@ -0,0 +1,191 @@ +--- +title: no-useless-assignment +rule_type: suggestion +related_rules: +- no-unused-vars +further_reading: +- https://en.wikipedia.org/wiki/Dead_store +- https://rules.sonarsource.com/javascript/RSPEC-1854/ +- https://cwe.mitre.org/data/definitions/563.html +- https://wiki.sei.cmu.edu/confluence/display/c/MSC13-C.+Detect+and+remove+unused+values +- https://wiki.sei.cmu.edu/confluence/display/java/MSC56-J.+Detect+and+remove+superfluous+code+and+values +--- + + +[Wikipedia describes a "dead store"](https://en.wikipedia.org/wiki/Dead_store) as follows: + +> In computer programming, a local variable that is assigned a value but is not read by any subsequent instruction is referred to as a **dead store**. + +"Dead stores" waste processing and memory, so it is better to remove unnecessary assignments to variables. + +Also, if the author intended the variable to be used, there is likely a mistake around the dead store. +For example, + +* you should have used a stored value but forgot to do so. +* you made a mistake in the name of the variable to be stored. + +```js +let id = "x1234"; // this is a "dead store" - this value ("x1234") is never read + +id = generateId(); + +doSomethingWith(id); +``` + +## Rule Details + +This rule aims to report variable assignments when the value is not used. + +Examples of **incorrect** code for this rule: + +::: incorrect + +```js +/* eslint no-useless-assignment: "error" */ + +function fn1() { + let v = 'used'; + doSomething(v); + v = 'unused'; +} + +function fn2() { + let v = 'used'; + if (condition) { + v = 'unused'; + return + } + doSomething(v); +} + +function fn3() { + let v = 'used'; + if (condition) { + doSomething(v); + } else { + v = 'unused'; + } +} + +function fn4() { + let v = 'unused'; + if (condition) { + v = 'used'; + doSomething(v); + return + } +} + +function fn5() { + let v = 'used'; + if (condition) { + let v = 'used'; + console.log(v); + v = 'unused'; + } + console.log(v); +} +``` + +::: + +Examples of **correct** code for this rule: + +::: correct + +```js +/* eslint no-useless-assignment: "error" */ + +function fn1() { + let v = 'used'; + doSomething(v); + v = 'used-2'; + doSomething(v); +} + +function fn2() { + let v = 'used'; + if (condition) { + v = 'used-2'; + doSomething(v); + return + } + doSomething(v); +} + +function fn3() { + let v = 'used'; + if (condition) { + doSomething(v); + } else { + v = 'used-2'; + doSomething(v); + } +} + +function fn4() { + let v = 'used'; + for (let i = 0; i < 10; i++) { + doSomething(v); + v = 'used in next iteration'; + } +} +``` + +::: + +This rule will not report variables that are never read. +Because it's clearly an unused variable. If you want it reported, please enable the [no-unused-vars](./no-unused-vars) rule. + +::: correct + +```js +/* eslint no-useless-assignment: "error" */ + +function fn() { + let v = 'unused'; + v = 'unused-2' + doSomething(); +} +``` + +::: + +## Known Limitations + +This rule does not report certain variable reassignments when they occur inside the `try` block. This is intentional because such assignments may still be observed within the corresponding `catch` block or after the `try-catch` structure, due to potential early exits or error handling logic. + +```js +function foo() { + let bar; + try { + bar = 2; + unsafeFn(); + return { error: undefined }; + } catch { + return { bar }; // `bar` is observed in the catch block + } +} +function unsafeFn() { + throw new Error(); +} + +function foo() { + let bar; + try { + bar = 2; // This assignment is relevant if unsafeFn() throws an error + unsafeFn(); + bar = 4; + } catch { + // Error handling + } + return bar; +} +function unsafeFn() { + throw new Error(); +} +``` + +## When Not To Use It + +If you don't want to be notified about values that are never read, you can safely disable this rule. diff --git a/docs/src/rules/no-useless-backreference.md b/docs/src/rules/no-useless-backreference.md index d97f98960960..9e3df76b15f9 100644 --- a/docs/src/rules/no-useless-backreference.md +++ b/docs/src/rules/no-useless-backreference.md @@ -16,13 +16,13 @@ In JavaScript regular expressions, it's syntactically valid to define a backrefe Backreferences that always successfully match zero-length and cannot match anything else are useless. They are basically ignored and can be removed without changing the behavior of the regular expression. ```js -var regex = /^(?:(a)|\1b)$/; +const regex = /^(?:(a)|\1b)$/; regex.test("a"); // true regex.test("b"); // true! regex.test("ab"); // false -var equivalentRegex = /^(?:(a)|b)$/; +const equivalentRegex = /^(?:(a)|b)$/; equivalentRegex.test("a"); // true equivalentRegex.test("b"); // true diff --git a/docs/src/rules/no-useless-computed-key.md b/docs/src/rules/no-useless-computed-key.md index f0acb66cc0dc..2cff67729663 100644 --- a/docs/src/rules/no-useless-computed-key.md +++ b/docs/src/rules/no-useless-computed-key.md @@ -8,13 +8,13 @@ rule_type: suggestion It's unnecessary to use computed properties with literals such as: ```js -var foo = {["a"]: "b"}; +const foo = {["a"]: "b"}; ``` The code can be rewritten as: ```js -var foo = {"a": "b"}; +const foo = {"a": "b"}; ``` ## Rule Details @@ -28,11 +28,27 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-useless-computed-key: "error"*/ -var a = { ['0']: 0 }; -var a = { ['0+1,234']: 0 }; -var a = { [0]: 0 }; -var a = { ['x']: 0 }; -var a = { ['x']() {} }; +const a = { ['0']: 0 }; +const b = { ['0+1,234']: 0 }; +const c = { [0]: 0 }; +const d = { ['x']: 0 }; +const e = { ['x']() {} }; + +const { [0]: foo } = obj; +const { ['x']: bar } = obj; + +class Foo { + ["foo"] = "bar"; + + [0]() {} + ['a']() {} + get ['b']() {} + set ['c'](value) {} + + static ["foo"] = "bar"; + + static ['a']() {} +} ``` ::: @@ -44,11 +60,27 @@ Examples of **correct** code for this rule: ```js /*eslint no-useless-computed-key: "error"*/ -var c = { 'a': 0 }; -var c = { 0: 0 }; -var a = { x() {} }; -var c = { a: 0 }; -var c = { '0+1,234': 0 }; +const a = { 'a': 0 }; +const b = { 0: 0 }; +const c = { x() {} }; +const d = { a: 0 }; +const e = { '0+1,234': 0 }; + +const { 0: foo } = obj; +const { 'x': bar } = obj; + +class Foo { + "foo" = "bar"; + + 0() {} + 'a'() {} + get 'b'() {} + set 'c'(value) {} + + static "foo" = "bar"; + + static 'a'() {} +} ``` ::: @@ -60,11 +92,23 @@ Examples of additional **correct** code for this rule: ```js /*eslint no-useless-computed-key: "error"*/ -var c = { +const c = { "__proto__": foo, // defines object's prototype ["__proto__"]: bar // defines a property named "__proto__" }; + +class Foo { + ["constructor"]; // instance field named "constructor" + + "constructor"() {} // the constructor of this class + + ["constructor"]() {} // method named "constructor" + + static ["constructor"]; // static field named "constructor" + + static ["prototype"]; // runtime error, it would be a parsing error without `[]` +} ``` ::: @@ -73,78 +117,51 @@ var c = { This rule has an object option: -* `enforceForClassMembers` set to `true` additionally applies this rule to class members (Default `false`). +* `enforceForClassMembers` set to `false` disables this rule for class members (Default `true`). ### enforceForClassMembers -By default, this rule does not check class declarations and class expressions, -as the default value for `enforceForClassMembers` is `false`. +By default, this rule also checks class declarations and class expressions, +as the default value for `enforceForClassMembers` is `true`. -When `enforceForClassMembers` is set to `true`, the rule will also disallow unnecessary computed keys inside of class fields, class methods, class getters, and class setters. +When `enforceForClassMembers` is set to `false`, the rule will allow unnecessary computed keys inside of class fields, class methods, class getters, and class setters. -Examples of **incorrect** code for this rule with the `{ "enforceForClassMembers": true }` option: +Examples of **incorrect** code for this rule with the `{ "enforceForClassMembers": false }` option: ::: incorrect ```js -/*eslint no-useless-computed-key: ["error", { "enforceForClassMembers": true }]*/ +/*eslint no-useless-computed-key: ["error", { "enforceForClassMembers": false }]*/ -class Foo { - ["foo"] = "bar"; +const obj = { + ["foo"]: "bar", + [42]: "baz", - [0]() {} - ['a']() {} - get ['b']() {} + ['a']() {}, + get ['b']() {}, set ['c'](value) {} - - static ["foo"] = "bar"; - - static ['a']() {} -} -``` - -::: - -Examples of **correct** code for this rule with the `{ "enforceForClassMembers": true }` option: - -::: correct - -```js -/*eslint no-useless-computed-key: ["error", { "enforceForClassMembers": true }]*/ - -class Foo { - "foo" = "bar"; - - 0() {} - 'a'() {} - get 'b'() {} - set 'c'(value) {} - - static "foo" = "bar"; - - static 'a'() {} -} +}; ``` ::: -Examples of additional **correct** code for this rule with the `{ "enforceForClassMembers": true }` option: +Examples of **correct** code for this rule with the `{ "enforceForClassMembers": false }` option: ::: correct ```js -/*eslint no-useless-computed-key: ["error", { "enforceForClassMembers": true }]*/ - -class Foo { - ["constructor"]; // instance field named "constructor" +/*eslint no-useless-computed-key: ["error", { "enforceForClassMembers": false }]*/ - "constructor"() {} // the constructor of this class - - ["constructor"]() {} // method named "constructor" +class SomeClass { + ["foo"] = "bar"; + [42] = "baz"; - static ["constructor"]; // static field named "constructor" + ['a']() {} + get ['b']() {} + set ['c'](value) {} - static ["prototype"]; // runtime error, it would be a parsing error without `[]` + static ["foo"] = "bar"; + static ['baz']() {} } ``` diff --git a/docs/src/rules/no-useless-concat.md b/docs/src/rules/no-useless-concat.md index 4cdf32d5d71f..a5d1a86d0429 100644 --- a/docs/src/rules/no-useless-concat.md +++ b/docs/src/rules/no-useless-concat.md @@ -7,13 +7,13 @@ rule_type: suggestion It's unnecessary to concatenate two strings together, such as: ```js -var foo = "a" + "b"; +const foo = "a" + "b"; ``` This code is likely the result of refactoring where a variable was removed from the concatenation (such as `"a" + b + "b"`). In such a case, the concatenation isn't important and the code can be rewritten as: ```js -var foo = "ab"; +const foo = "ab"; ``` ## Rule Details @@ -26,15 +26,14 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-useless-concat: "error"*/ -/*eslint-env es6*/ -var a = `some` + `string`; +const a = `some` + `string`; // these are the same as "10" -var a = '1' + '0'; -var a = '1' + `0`; -var a = `1` + '0'; -var a = `1` + `0`; +const b = '1' + '0'; +const c = '1' + `0`; +const d = `1` + '0'; +const e = `1` + `0`; ``` ::: @@ -47,12 +46,12 @@ Examples of **correct** code for this rule: /*eslint no-useless-concat: "error"*/ // when a non string is included -var c = a + b; -var c = '1' + a; -var a = 1 + '1'; -var c = 1 - 2; +const a = a + b; +const b = '1' + a; +const c = 1 + '1'; +const d = 1 - 2; // when the string concatenation is multiline -var c = "foo" + +const e = "foo" + "bar"; ``` diff --git a/docs/src/rules/no-useless-constructor.md b/docs/src/rules/no-useless-constructor.md index bf2c4c329846..3009a33e56db 100644 --- a/docs/src/rules/no-useless-constructor.md +++ b/docs/src/rules/no-useless-constructor.md @@ -31,7 +31,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint no-useless-constructor: "error"*/ -/*eslint-env es6*/ class A { constructor () { @@ -78,6 +77,44 @@ class D extends A { ::: +This rule additionally supports TypeScript type syntax. + +Examples of **incorrect** TypeScript code for this rule: + +::: incorrect + +```ts +/* eslint no-useless-constructor: "error" */ + +class A { + public constructor() {} +} +``` + +::: + +Examples of **correct** TypeScript code for this rule: + +::: correct + +```ts +/* eslint no-useless-constructor: "error" */ + +class A { + protected constructor() {} +} + +class B extends A { + public constructor() { + super(); + } +} + +class C { + constructor(@decorated param) {} +} +``` + ## When Not To Use It If you don't want to be notified about unnecessary constructors, you can safely disable this rule. diff --git a/docs/src/rules/no-useless-escape.md b/docs/src/rules/no-useless-escape.md index ff173c229c2f..05375e3df4ad 100644 --- a/docs/src/rules/no-useless-escape.md +++ b/docs/src/rules/no-useless-escape.md @@ -67,6 +67,42 @@ Examples of **correct** code for this rule: ::: +## Options + +This rule has an object option: + +* `allowRegexCharacters` - An array of characters that should be allowed to have unnecessary escapes in regular expressions. This is useful for characters like `-` where escaping can prevent accidental character ranges. For example, in `/[0\-]/`, the escape is technically unnecessary but helps prevent the pattern from becoming a range if another character is added later (e.g., `/[0\-9]/` vs `/[0-9]/`). + +### allowRegexCharacters + +Examples of **incorrect** code for the `{ "allowRegexCharacters": ["-"] }` option: + +::: incorrect + +```js +/*eslint no-useless-escape: ["error", { "allowRegexCharacters": ["-"] }]*/ + +/\!/; +/\@/; +/[a-z\^]/; +``` + +::: + +Examples of **correct** code for the `{ "allowRegexCharacters": ["-"] }` option: + +::: correct + +```js +/*eslint no-useless-escape: ["error", { "allowRegexCharacters": ["-"] }]*/ + +/[0\-]/; +/[\-9]/; +/a\-b/; +``` + +::: + ## When Not To Use It If you don't want to be notified about unnecessary escapes, you can safely disable this rule. diff --git a/docs/src/rules/no-useless-return.md b/docs/src/rules/no-useless-return.md index de7045347c2e..5b1664772ae6 100644 --- a/docs/src/rules/no-useless-return.md +++ b/docs/src/rules/no-useless-return.md @@ -18,23 +18,23 @@ Examples of **incorrect** code for this rule: ```js /* eslint no-useless-return: "error" */ -var foo = function() { return; } +const foo = function() { return; } -var foo = function() { +const bar = function() { doSomething(); return; } -var foo = function() { +const baz = function() { if (condition) { - bar(); + qux(); return; } else { - baz(); + quux(); } } -var foo = function() { +const item = function() { switch (bar) { case 1: doSomething(); @@ -55,23 +55,23 @@ Examples of **correct** code for this rule: ```js /* eslint no-useless-return: "error" */ -var foo = function() { return 5; } +const foo = function() { return 5; } -var foo = function() { +const bar = function() { return doSomething(); } -var foo = function() { +const baz = function() { if (condition) { - bar(); + qux(); return; } else { - baz(); + quux(); } qux(); } -var foo = function() { +const item = function() { switch (bar) { case 1: doSomething(); @@ -81,7 +81,7 @@ var foo = function() { } } -var foo = function() { +const func = function() { for (const foo of bar) { return; } diff --git a/docs/src/rules/no-var.md b/docs/src/rules/no-var.md index d1e02ad14d72..5b9d1d6f9df4 100644 --- a/docs/src/rules/no-var.md +++ b/docs/src/rules/no-var.md @@ -47,7 +47,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-var: "error"*/ -/*eslint-env es6*/ let x = "y"; const CONFIG = {}; @@ -55,6 +54,42 @@ const CONFIG = {}; ::: +This rule additionally supports TypeScript type syntax. There are multiple ways to declare global variables in TypeScript. Only using `var` works for all cases. See this [TypeScript playground](https://www.typescriptlang.org/play/?#code/PQgEB4CcFMDNpgOwMbVAGwJYCMC8AiAEwHsBbfUYAPgFgAoew6ZdAQxlAHN1jtX1QAb3qhRGaABdQAGUkAuUAGcJkTIk4ixAN3agAauwXLV6+ptFqJCWK1SgA6mpIB3IebGjHiIyrUa6HgC+9MEMdGDcvPygtqiKivSyEvQGkPReZuHAoM5OxK6ckvS5iC4AdEnFec5lqVWl+WUZYWAlLkpFdG2NSaC4oADkA-XlqX2Dw13VTWrjQ5kRPHzoACoAFpiKXJ2Ry+ubFTtL-PuKtez0uycbZ830i1GrNx3JdFdPB73982-HH2djb6Td6nGaIOaTe7ZRTQdCwbavGFww6I2Gwc5pOhI9F3LIdOEvejYlEQolojGkrHkryU+jQAAeAAdiJApIJAkA) for reference. + +Examples of **incorrect** TypeScript code for this rule: + +:::incorrect + +```ts +/*eslint no-var: "error"*/ + +declare var x: number + +declare namespace ns { + var x: number +} + +declare module 'module' { + var x: number +} +``` + +::: + +Examples of **correct** TypeScript code for this rule: + +:::correct + +```ts +/*eslint no-var: "error"*/ + +declare global { + declare var x: number +} +``` + +::: + ## When Not To Use It In addition to non-ES6 environments, existing JavaScript projects that are beginning to introduce ES6 into their diff --git a/docs/src/rules/no-void.md b/docs/src/rules/no-void.md index 6120cf4d142a..c0b7514ec2b4 100644 --- a/docs/src/rules/no-void.md +++ b/docs/src/rules/no-void.md @@ -43,7 +43,7 @@ foo = undefined; When used with IIFE (immediately-invoked function expression), `void` can be used to force the function keyword to be treated as an expression instead of a declaration: ```js -var foo = 1; +let foo = 1; void function(){ foo = 1; }() // will assign foo a value of 1 +function(){ foo = 1; }() // same as above ``` @@ -56,7 +56,7 @@ Some code styles prohibit `void` operator, marking it as non-obvious and hard to ## Rule Details -This rule aims to eliminate use of void operator. +This rule aims to eliminate use of `void` operator. Examples of **incorrect** code for this rule: @@ -68,7 +68,7 @@ Examples of **incorrect** code for this rule: void foo void someFunction(); -var foo = void bar(); +const foo = void bar(); function baz() { return void 0; } @@ -80,11 +80,11 @@ function baz() { This rule has an object option: -* `allowAsStatement` set to `true` allows the void operator to be used as a statement (Default `false`). +* `allowAsStatement` set to `true` allows the `void` operator to be used as a statement (Default `false`). ### allowAsStatement -When `allowAsStatement` is set to true, the rule will not error on cases that the void operator is used as a statement, i.e. when it's not used in an expression position, like in a variable assignment or a function return. +When `allowAsStatement` is set to true, the rule will not error on cases that the `void` operator is used as a statement, i.e. when it's not used in an expression position, like in a variable assignment or a function return. Examples of **incorrect** code for `{ "allowAsStatement": true }`: @@ -93,7 +93,7 @@ Examples of **incorrect** code for `{ "allowAsStatement": true }`: ```js /*eslint no-void: ["error", { "allowAsStatement": true }]*/ -var foo = void bar(); +const foo = void bar(); function baz() { return void 0; } diff --git a/docs/src/rules/no-whitespace-before-property.md b/docs/src/rules/no-whitespace-before-property.md index ddab906cf835..33a579b51db7 100644 --- a/docs/src/rules/no-whitespace-before-property.md +++ b/docs/src/rules/no-whitespace-before-property.md @@ -2,9 +2,6 @@ title: no-whitespace-before-property rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/no-whitespace-before-property) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - JavaScript allows whitespace between objects and their properties. However, inconsistent spacing can make code harder to read and can lead to errors. ```js diff --git a/docs/src/rules/no-with.md b/docs/src/rules/no-with.md index c4e880f812d8..b2f1f15b7a4e 100644 --- a/docs/src/rules/no-with.md +++ b/docs/src/rules/no-with.md @@ -35,7 +35,6 @@ Examples of **correct** code for this rule: ```js /*eslint no-with: "error"*/ -/*eslint-env es6*/ const r = ({x, y}) => Math.sqrt(x * x + y * y); ``` diff --git a/docs/src/rules/nonblock-statement-body-position.md b/docs/src/rules/nonblock-statement-body-position.md index dee56c7b0a42..52f536ef8b1b 100644 --- a/docs/src/rules/nonblock-statement-body-position.md +++ b/docs/src/rules/nonblock-statement-body-position.md @@ -4,9 +4,6 @@ rule_type: layout further_reading: - https://jscs-dev.github.io/rule/requireNewlineBeforeSingleStatementsInIf --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/nonblock-statement-body-position) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - When writing `if`, `else`, `while`, `do-while`, and `for` statements, the body can be a single statement instead of a block. It can be useful to enforce a consistent location for these single statements. For example, some developers avoid writing code like this: diff --git a/docs/src/rules/object-curly-newline.md b/docs/src/rules/object-curly-newline.md index fd990920c694..dc0a8eee9b89 100644 --- a/docs/src/rules/object-curly-newline.md +++ b/docs/src/rules/object-curly-newline.md @@ -7,9 +7,6 @@ related_rules: - object-curly-spacing - object-property-newline --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/object-curly-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - A number of style guides require or disallow line breaks inside of object braces and other tokens. ## Rule Details @@ -55,7 +52,6 @@ Examples of **incorrect** code for this rule with the `"always"` option: ```js /*eslint object-curly-newline: ["error", "always"]*/ -/*eslint-env es6*/ let a = {}; let b = {foo: 1}; @@ -84,7 +80,6 @@ Examples of **correct** code for this rule with the `"always"` option: ```js /*eslint object-curly-newline: ["error", "always"]*/ -/*eslint-env es6*/ let a = { }; @@ -133,7 +128,6 @@ Examples of **incorrect** code for this rule with the `"never"` option: ```js /*eslint object-curly-newline: ["error", "never"]*/ -/*eslint-env es6*/ let a = { }; @@ -180,7 +174,6 @@ Examples of **correct** code for this rule with the `"never"` option: ```js /*eslint object-curly-newline: ["error", "never"]*/ -/*eslint-env es6*/ let a = {}; let b = {foo: 1}; @@ -211,7 +204,6 @@ Examples of **incorrect** code for this rule with the `{ "multiline": true }` op ```js /*eslint object-curly-newline: ["error", { "multiline": true }]*/ -/*eslint-env es6*/ let a = { }; @@ -250,7 +242,6 @@ Examples of **correct** code for this rule with the `{ "multiline": true }` opti ```js /*eslint object-curly-newline: ["error", { "multiline": true }]*/ -/*eslint-env es6*/ let a = {}; let b = {foo: 1}; @@ -289,7 +280,6 @@ Examples of **incorrect** code for this rule with the `{ "minProperties": 2 }` o ```js /*eslint object-curly-newline: ["error", { "minProperties": 2 }]*/ -/*eslint-env es6*/ let a = { }; @@ -328,7 +318,6 @@ Examples of **correct** code for this rule with the `{ "minProperties": 2 }` opt ```js /*eslint object-curly-newline: ["error", { "minProperties": 2 }]*/ -/*eslint-env es6*/ let a = {}; let b = {foo: 1}; @@ -367,7 +356,6 @@ Examples of **incorrect** code for this rule with the default `{ "consistent": t ```js /*eslint object-curly-newline: ["error", { "consistent": true }]*/ -/*eslint-env es6*/ let a = {foo: 1 }; @@ -415,7 +403,6 @@ Examples of **correct** code for this rule with the default `{ "consistent": tru ```js /*eslint object-curly-newline: ["error", { "consistent": true }]*/ -/*eslint-env es6*/ let empty1 = {}; let empty2 = { @@ -473,7 +460,6 @@ Examples of **incorrect** code for this rule with the `{ "ObjectExpression": "al ```js /*eslint object-curly-newline: ["error", { "ObjectExpression": "always", "ObjectPattern": "never" }]*/ -/*eslint-env es6*/ let a = {}; let b = {foo: 1}; @@ -511,7 +497,6 @@ Examples of **correct** code for this rule with the `{ "ObjectExpression": "alwa ```js /*eslint object-curly-newline: ["error", { "ObjectExpression": "always", "ObjectPattern": "never" }]*/ -/*eslint-env es6*/ let a = { }; @@ -551,7 +536,6 @@ Examples of **incorrect** code for this rule with the `{ "ImportDeclaration": "a ```js /*eslint object-curly-newline: ["error", { "ImportDeclaration": "always", "ExportDeclaration": "never" }]*/ -/*eslint-env es6*/ import {foo, bar} from 'foo-bar'; import {foo as f, baz} from 'foo-bar'; @@ -576,7 +560,6 @@ Examples of **correct** code for this rule with the `{ "ImportDeclaration": "alw ```js /*eslint object-curly-newline: ["error", { "ImportDeclaration": "always", "ExportDeclaration": "never" }]*/ -/*eslint-env es6*/ import { foo, diff --git a/docs/src/rules/object-curly-spacing.md b/docs/src/rules/object-curly-spacing.md index 7288b63e229e..2030cae8f43a 100644 --- a/docs/src/rules/object-curly-spacing.md +++ b/docs/src/rules/object-curly-spacing.md @@ -7,9 +7,6 @@ related_rules: - computed-property-spacing - space-in-parens --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/object-curly-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - While formatting preferences are very personal, a number of style guides require or disallow spaces between curly braces in the following situations: diff --git a/docs/src/rules/object-property-newline.md b/docs/src/rules/object-property-newline.md index fde531a3c783..5237ce12d6d8 100644 --- a/docs/src/rules/object-property-newline.md +++ b/docs/src/rules/object-property-newline.md @@ -7,9 +7,6 @@ related_rules: - key-spacing - object-curly-spacing --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/object-property-newline) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - This rule permits you to restrict the locations of property specifications in object literals. You may prohibit any part of any property specification from appearing on the same line as any part of any other property specification. You may make this prohibition absolute, or, by invoking an object option, you may allow an exception, permitting an object literal to have all parts of all of its property specifications on a single line. ## Rule Details diff --git a/docs/src/rules/object-shorthand.md b/docs/src/rules/object-shorthand.md index 299d46b9664c..2fa59c103c3a 100644 --- a/docs/src/rules/object-shorthand.md +++ b/docs/src/rules/object-shorthand.md @@ -16,14 +16,14 @@ Here are a few common examples using the ES5 syntax: ```js // properties -var foo = { +const foo = { x: x, y: y, z: z, }; // methods -var foo = { +const bar = { a: function() {}, b: function() {} }; @@ -32,13 +32,11 @@ var foo = { Now here are ES6 equivalents: ```js -/*eslint-env es6*/ - // properties -var foo = {x, y, z}; +const foo = {x, y, z}; // methods -var foo = { +const bar = { a() {}, b() {} }; @@ -54,9 +52,8 @@ Each of the following properties would warn: ```js /*eslint object-shorthand: "error"*/ -/*eslint-env es6*/ -var foo = { +const foo = { w: function() {}, x: function *() {}, [y]: function() {}, @@ -68,9 +65,8 @@ In that case the expected syntax would have been: ```js /*eslint object-shorthand: "error"*/ -/*eslint-env es6*/ -var foo = { +const foo = { w() {}, *x() {}, [y]() {}, @@ -83,9 +79,8 @@ The following will *not* warn: ```js /*eslint object-shorthand: "error"*/ -/*eslint-env es6*/ -var foo = { +const foo = { x: (y) => y }; ``` @@ -130,9 +125,8 @@ Example of **incorrect** code for this rule with the `"always", { "avoidQuotes": ```js /*eslint object-shorthand: ["error", "always", { "avoidQuotes": true }]*/ -/*eslint-env es6*/ -var foo = { +const foo = { "bar-baz"() {} }; ``` @@ -145,9 +139,8 @@ Example of **correct** code for this rule with the `"always", { "avoidQuotes": t ```js /*eslint object-shorthand: ["error", "always", { "avoidQuotes": true }]*/ -/*eslint-env es6*/ -var foo = { +const foo = { "bar-baz": function() {}, "qux": qux }; @@ -169,9 +162,8 @@ Example of **correct** code for this rule with the `"always", { "ignoreConstruct ```js /*eslint object-shorthand: ["error", "always", { "ignoreConstructors": true }]*/ -/*eslint-env es6*/ -var foo = { +const foo = { ConstructorFunction: function() {} }; ``` @@ -187,7 +179,7 @@ Example of **correct** code for this rule with the `"always", { "methodsIgnorePa ```js /*eslint object-shorthand: ["error", "always", { "methodsIgnorePattern": "^bar$" }]*/ -var foo = { +const foo = { bar: function() {} }; ``` @@ -208,9 +200,8 @@ Example of **incorrect** code for this rule with the `"always", { "avoidExplicit ```js /*eslint object-shorthand: ["error", "always", { "avoidExplicitReturnArrows": true }]*/ -/*eslint-env es6*/ -var foo = { +const foo = { foo: (bar, baz) => { return bar + baz; }, @@ -229,9 +220,8 @@ Example of **correct** code for this rule with the `"always", { "avoidExplicitRe ```js /*eslint object-shorthand: ["error", "always", { "avoidExplicitReturnArrows": true }]*/ -/*eslint-env es6*/ -var foo = { +const foo = { foo(bar, baz) { return bar + baz; }, @@ -248,9 +238,8 @@ Example of **incorrect** code for this rule with the `"consistent"` option: ```js /*eslint object-shorthand: [2, "consistent"]*/ -/*eslint-env es6*/ -var foo = { +const foo = { a, b: "foo", }; @@ -264,14 +253,13 @@ Examples of **correct** code for this rule with the `"consistent"` option: ```js /*eslint object-shorthand: [2, "consistent"]*/ -/*eslint-env es6*/ -var foo = { +const foo = { a: a, b: "foo" }; -var bar = { +const bar = { a, b, }; @@ -285,9 +273,8 @@ Example of **incorrect** code with the `"consistent-as-needed"` option, which is ```js /*eslint object-shorthand: [2, "consistent-as-needed"]*/ -/*eslint-env es6*/ -var foo = { +const foo = { a: a, b: b, }; diff --git a/docs/src/rules/one-var-declaration-per-line.md b/docs/src/rules/one-var-declaration-per-line.md index 8f06275780f9..4f5ddc060550 100644 --- a/docs/src/rules/one-var-declaration-per-line.md +++ b/docs/src/rules/one-var-declaration-per-line.md @@ -4,9 +4,6 @@ rule_type: suggestion related_rules: - one-var --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/one-var-declaration-per-line) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Some developers declare multiple var statements on the same line: ```js @@ -42,7 +39,6 @@ Examples of **incorrect** code for this rule with the default `"initializations" ```js /*eslint one-var-declaration-per-line: ["error", "initializations"]*/ -/*eslint-env es6*/ var a, b, c = 0; @@ -58,7 +54,6 @@ Examples of **correct** code for this rule with the default `"initializations"` ```js /*eslint one-var-declaration-per-line: ["error", "initializations"]*/ -/*eslint-env es6*/ var a, b; @@ -79,7 +74,6 @@ Examples of **incorrect** code for this rule with the `"always"` option: ```js /*eslint one-var-declaration-per-line: ["error", "always"]*/ -/*eslint-env es6*/ var a, b; @@ -96,7 +90,6 @@ Examples of **correct** code for this rule with the `"always"` option: ```js /*eslint one-var-declaration-per-line: ["error", "always"]*/ -/*eslint-env es6*/ var a, b; diff --git a/docs/src/rules/one-var.md b/docs/src/rules/one-var.md index 0066757e1dab..8f9e5c450ec3 100644 --- a/docs/src/rules/one-var.md +++ b/docs/src/rules/one-var.md @@ -347,7 +347,6 @@ Examples of **incorrect** code for this rule with the `{ var: "always", let: "ne ```js /*eslint one-var: ["error", { var: "always", let: "never", const: "never" }]*/ -/*eslint-env es6*/ function foo1() { var bar; @@ -372,7 +371,6 @@ Examples of **correct** code for this rule with the `{ var: "always", let: "neve ```js /*eslint one-var: ["error", { var: "always", let: "never", const: "never" }]*/ -/*eslint-env es6*/ function foo1() { var bar, @@ -397,7 +395,6 @@ Examples of **incorrect** code for this rule with the `{ var: "never" }` option: ```js /*eslint one-var: ["error", { var: "never" }]*/ -/*eslint-env es6*/ function foo() { var bar, @@ -413,7 +410,6 @@ Examples of **correct** code for this rule with the `{ var: "never" }` option: ```js /*eslint one-var: ["error", { var: "never" }]*/ -/*eslint-env es6*/ function foo() { var bar; @@ -437,7 +433,6 @@ Examples of **incorrect** code for this rule with the `{ separateRequires: true ```js /*eslint one-var: ["error", { separateRequires: true, var: "always" }]*/ -/*eslint-env node*/ var foo = require("foo"), bar = "bar"; @@ -451,7 +446,6 @@ Examples of **correct** code for this rule with the `{ separateRequires: true }` ```js /*eslint one-var: ["error", { separateRequires: true, var: "always" }]*/ -/*eslint-env node*/ var foo = require("foo"); var bar = "bar"; @@ -463,7 +457,6 @@ var bar = "bar"; ```js /*eslint one-var: ["error", { separateRequires: true, var: "always" }]*/ -/*eslint-env node*/ var foo = require("foo"), bar = require("bar"); @@ -477,7 +470,6 @@ Examples of **incorrect** code for this rule with the `{ var: "never", let: "con ```js /*eslint one-var: ["error", { var: "never", let: "consecutive", const: "consecutive" }]*/ -/*eslint-env es6*/ function foo1() { let a, @@ -506,7 +498,6 @@ Examples of **correct** code for this rule with the `{ var: "never", let: "conse ```js /*eslint one-var: ["error", { var: "never", let: "consecutive", const: "consecutive" }]*/ -/*eslint-env es6*/ function foo1() { let a, @@ -537,7 +528,6 @@ Examples of **incorrect** code for this rule with the `{ var: "consecutive" }` o ```js /*eslint one-var: ["error", { var: "consecutive" }]*/ -/*eslint-env es6*/ function foo() { var a; @@ -553,7 +543,6 @@ Examples of **correct** code for this rule with the `{ var: "consecutive" }` opt ```js /*eslint one-var: ["error", { var: "consecutive" }]*/ -/*eslint-env es6*/ function foo() { var a, @@ -575,7 +564,6 @@ Examples of **incorrect** code for this rule with the `{ "initialized": "always" ```js /*eslint one-var: ["error", { "initialized": "always", "uninitialized": "never" }]*/ -/*eslint-env es6*/ function foo() { var a, b, c; @@ -619,7 +607,6 @@ Examples of **incorrect** code for this rule with the `{ "initialized": "never" ```js /*eslint one-var: ["error", { "initialized": "never" }]*/ -/*eslint-env es6*/ function foo() { var foo = true, diff --git a/docs/src/rules/operator-linebreak.md b/docs/src/rules/operator-linebreak.md index 8b2dacf1edb5..4b48172f12ca 100644 --- a/docs/src/rules/operator-linebreak.md +++ b/docs/src/rules/operator-linebreak.md @@ -4,9 +4,6 @@ rule_type: layout related_rules: - comma-style --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/operator-linebreak) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - When a statement is too long to fit on a single line, line breaks are generally inserted next to the operators separating expressions. The first style coming to mind would be to place the operator at the end of the line, following the English punctuation rules. ```js diff --git a/docs/src/rules/padded-blocks.md b/docs/src/rules/padded-blocks.md index 8d3129202052..66ceaa2b0485 100644 --- a/docs/src/rules/padded-blocks.md +++ b/docs/src/rules/padded-blocks.md @@ -5,9 +5,6 @@ related_rules: - lines-between-class-members - padding-line-between-statements --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/padded-blocks) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Some style guides require block statements to start and end with blank lines. The goal is to improve readability by visually separating the block content and the surrounding code. diff --git a/docs/src/rules/padding-line-between-statements.md b/docs/src/rules/padding-line-between-statements.md index 1311416fd085..5ab781171454 100644 --- a/docs/src/rules/padding-line-between-statements.md +++ b/docs/src/rules/padding-line-between-statements.md @@ -2,9 +2,6 @@ title: padding-line-between-statements rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/padding-line-between-statements) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - This rule requires or disallows blank lines between the given 2 kinds of statements. Properly blank lines help developers to understand the code. diff --git a/docs/src/rules/prefer-arrow-callback.md b/docs/src/rules/prefer-arrow-callback.md index d3e9000561c0..6e5f72a0ec1a 100644 --- a/docs/src/rules/prefer-arrow-callback.md +++ b/docs/src/rules/prefer-arrow-callback.md @@ -45,7 +45,6 @@ The following examples **will not** be flagged: ```js /* eslint prefer-arrow-callback: "error" */ -/* eslint-env es6 */ // arrow function callback foo(a => a); // OK @@ -54,7 +53,7 @@ foo(a => a); // OK foo(function*() { yield; }); // OK // function expression not used as callback or function argument -var foo = function foo(a) { return a; }; // OK +const foo = function foo(a) { return a; }; // OK // unbound function expression callback foo(function() { return this.a; }); // OK @@ -65,6 +64,36 @@ foo(function bar(n) { return n && n + bar(n - 1); }); // OK ::: +This rule additionally supports TypeScript type syntax. + +Examples of **incorrect** TypeScript code for this rule: + +::: incorrect + +```ts +/*eslint prefer-arrow-callback: "error"*/ + +foo(function bar(a: string) { a; }); + +test('foo', function (this: any) {}); +``` + +::: + +Examples of **correct** TypeScript code for this rule: + +::: correct + +```ts +/*eslint prefer-arrow-callback: "error"*/ + +foo((a: string) => a); + +const foo = function foo(bar: any) {}; +``` + +::: + ## Options Access further control over this rule's behavior via an options object. @@ -89,6 +118,30 @@ foo(function bar() {}); ::: +Examples of **incorrect** TypeScript code for this rule with `{ "allowNamedFunctions": true }`: + +::: incorrect + +```ts +/* eslint prefer-arrow-callback: [ "error", { "allowNamedFunctions": true } ] */ + +foo(function(a: string) {}); +``` + +::: + +Examples of **correct** TypeScript code for this rule with `{ "allowNamedFunctions": true }`: + +::: correct + +```ts +/* eslint prefer-arrow-callback: [ "error", { "allowNamedFunctions": true } ] */ + +foo(function bar(a: string) {}); +``` + +::: + ### allowUnboundThis By default `{ "allowUnboundThis": true }`, this `boolean` option allows function expressions containing `this` to be used as callbacks, as long as the function in question has not been explicitly bound. @@ -101,7 +154,6 @@ When set to `false` this option prohibits the use of function expressions as cal ```js /* eslint prefer-arrow-callback: [ "error", { "allowUnboundThis": false } ] */ -/* eslint-env es6 */ foo(function() { this.a; }); @@ -112,6 +164,20 @@ someArray.map(function(item) { return this.doSomething(item); }, someObject); ::: +Examples of **incorrect** TypeScript code for this rule with `{ "allowUnboundThis": false }`: + +::: incorrect + +```ts +/* eslint prefer-arrow-callback: [ "error", { "allowUnboundThis": false } ] */ + +foo(function(a: string) { this; }); + +foo(function(a: string) { (() => this); }); +``` + +::: + ## When Not To Use It * In environments that have not yet adopted ES6 language features (ES3/5). diff --git a/docs/src/rules/prefer-const.md b/docs/src/rules/prefer-const.md index 41b144fe4660..449a41b82a6e 100644 --- a/docs/src/rules/prefer-const.md +++ b/docs/src/rules/prefer-const.md @@ -3,6 +3,7 @@ title: prefer-const rule_type: suggestion related_rules: - no-var +- no-unassigned-vars - no-use-before-define --- @@ -149,7 +150,6 @@ Examples of **incorrect** code for the default `{"destructuring": "any"}` option ```js /*eslint prefer-const: "error"*/ -/*eslint-env es6*/ let {a, b} = obj; /*error 'b' is never reassigned, use 'const' instead.*/ a = a + 1; @@ -163,7 +163,6 @@ Examples of **correct** code for the default `{"destructuring": "any"}` option: ```js /*eslint prefer-const: "error"*/ -/*eslint-env es6*/ // using const. const {a: a0, b} = obj; @@ -183,7 +182,6 @@ Examples of **incorrect** code for the `{"destructuring": "all"}` option: ```js /*eslint prefer-const: ["error", {"destructuring": "all"}]*/ -/*eslint-env es6*/ // all of `a` and `b` should be const, so those are warned. let {a, b} = obj; /*error 'a' is never reassigned, use 'const' instead. @@ -198,7 +196,6 @@ Examples of **correct** code for the `{"destructuring": "all"}` option: ```js /*eslint prefer-const: ["error", {"destructuring": "all"}]*/ -/*eslint-env es6*/ // 'b' is never reassigned, but all of `a` and `b` should not be const, so those are ignored. let {a, b} = obj; @@ -219,7 +216,6 @@ Examples of **correct** code for the `{"ignoreReadBeforeAssign": true}` option: ```js /*eslint prefer-const: ["error", {"ignoreReadBeforeAssign": true}]*/ -/*eslint-env es6*/ let timer; function initialize() { @@ -238,7 +234,6 @@ Examples of **correct** code for the default `{"ignoreReadBeforeAssign": false}` ```js /*eslint prefer-const: ["error", {"ignoreReadBeforeAssign": false}]*/ -/*eslint-env es6*/ const timer = setInterval(initialize, 100); function initialize() { diff --git a/docs/src/rules/prefer-destructuring.md b/docs/src/rules/prefer-destructuring.md index 4a313922ed31..83079bff6128 100644 --- a/docs/src/rules/prefer-destructuring.md +++ b/docs/src/rules/prefer-destructuring.md @@ -14,22 +14,30 @@ With JavaScript ES6, a new syntax was added for creating variables from an array ### Options -This rule takes two sets of configuration objects. The first object parameter determines what types of destructuring the rule applies to. +This rule takes two arguments, both of which are objects. The first object parameter determines what types of destructuring the rule applies to. -The two properties, `array` and `object`, can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are true. +In the first object, there are two properties, `array` and `object`, that can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are `true`. -Alternatively, you can use separate configurations for different assignment types. It accepts 2 other keys instead of `array` and `object`. - -One key is `VariableDeclarator` and the other is `AssignmentExpression`, which can be used to control the destructuring requirement for each of those types independently. Each property accepts an object that accepts two properties, `array` and `object`, which can be used to control the destructuring requirement for each of `array` and `object` independently for variable declarations and assignment expressions. By default, `array` and `object` are set to true for both `VariableDeclarator` and `AssignmentExpression`. - -The rule has a second object with a single key, `enforceForRenamedProperties`, which determines whether the `object` destructuring applies to renamed variables. - -**Note**: It is not possible to determine if a variable will be referring to an object or an array at runtime. This rule therefore guesses the assignment type by checking whether the key being accessed is an integer. This can lead to the following possibly confusing situations: +```json +{ + "rules": { + "prefer-destructuring": ["error", { + "array": true, + "object": true + }] + } +} +``` -* Accessing an object property whose key is an integer will fall under the category `array` destructuring. -* Accessing an array element through a computed index will fall under the category `object` destructuring. +For example, the following configuration enforces only object destructuring, but not array destructuring: -The `--fix` option on the command line fixes only problems reported in variable declarations, and among them only those that fall under the category `object` destructuring. Furthermore, the name of the declared variable has to be the same as the name used for non-computed member access in the initializer. For example, `var foo = object.foo` can be automatically fixed by this rule. Problems that involve computed member access (e.g., `var foo = object[foo]`) or renamed properties (e.g., `var foo = object.bar`) are not automatically fixed. +```json +{ + "rules": { + "prefer-destructuring": ["error", {"object": true, "array": false}] + } +} +``` Examples of **incorrect** code for this rule: @@ -39,12 +47,12 @@ Examples of **incorrect** code for this rule: /* eslint prefer-destructuring: "error" */ // With `array` enabled -var foo = array[0]; +const foo = array[0]; bar.baz = array[0]; // With `object` enabled -var foo = object.foo; -var foo = object['foo']; +const qux = object.qux; +const quux = object['quux']; ``` ::: @@ -57,15 +65,15 @@ Examples of **correct** code for this rule: /* eslint prefer-destructuring: "error" */ // With `array` enabled -var [ foo ] = array; -var foo = array[someIndex]; +const [ foo ] = array; +const arr = array[someIndex]; [bar.baz] = array; // With `object` enabled -var { foo } = object; +const { baz } = object; -var foo = object.bar; +const obj = object.bar; let bar; ({ bar } = object); @@ -73,138 +81,112 @@ let bar; ::: -Examples of **incorrect** code when `enforceForRenamedProperties` is enabled: +Alternatively, you can use separate configurations for different assignment types. The first argument accepts two other keys instead of `array` and `object`. -::: incorrect +One key is `VariableDeclarator` and the other is `AssignmentExpression`, which can be used to control the destructuring requirement for each of those types independently. Each property is an object containing two properties, `array` and `object`, which can be used to control the destructuring requirement for each of `array` and `object` independently for variable declarations and assignment expressions. By default, `array` and `object` are set to `true` for both `VariableDeclarator` and `AssignmentExpression`. -```javascript -/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ -var foo = object.bar; +```json +{ + "rules": { + "prefer-destructuring": ["error", { + "VariableDeclarator": { + "array": true, + "object": true + }, + "AssignmentExpression": { + "array": true, + "object": true + } + }] + } +} ``` -::: - -Examples of **correct** code when `enforceForRenamedProperties` is enabled: +Examples of **correct** code when object destructuring in `VariableDeclarator` is enforced: ::: correct ```javascript -/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ -var { bar: foo } = object; +/* eslint prefer-destructuring: ["error", {VariableDeclarator: {object: true}}] */ +const {bar: foo} = object; ``` ::: -Examples of additional **correct** code when `enforceForRenamedProperties` is enabled: +Examples of **correct** code when array destructuring in `AssignmentExpression` is enforced: ::: correct ```javascript -/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ -class C { - #x; - foo() { - const bar = this.#x; // private identifiers are not allowed in destructuring - } -} +/* eslint prefer-destructuring: ["error", {AssignmentExpression: {array: true}}] */ +[bar] = array; ``` ::: -An example configuration, with the defaults `array` and `object` filled in, looks like this: +#### enforceForRenamedProperties + +The rule has a second object argument with a single key, `enforceForRenamedProperties`, which determines whether the `object` destructuring applies to renamed variables. ```json { "rules": { - "prefer-destructuring": ["error", { - "array": true, + "prefer-destructuring": ["error", + { "object": true - }, { - "enforceForRenamedProperties": false + }, + { + "enforceForRenamedProperties": true }] } } ``` -The two properties, `array` and `object`, which can be used to turn on or off the destructuring requirement for each of those types independently. By default, both are true. - -For example, the following configuration enforces only object destructuring, but not array destructuring: - -```json -{ - "rules": { - "prefer-destructuring": ["error", {"object": true, "array": false}] - } -} -``` +Examples of **incorrect** code when `enforceForRenamedProperties` is enabled: -An example configuration, with the defaults `VariableDeclarator` and `AssignmentExpression` filled in, looks like this: +::: incorrect -```json -{ - "rules": { - "prefer-destructuring": ["error", { - "VariableDeclarator": { - "array": false, - "object": true - }, - "AssignmentExpression": { - "array": true, - "object": true - } - }, { - "enforceForRenamedProperties": false - }] - } -} +```javascript +/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ +const foo = object.bar; ``` -The two properties, `VariableDeclarator` and `AssignmentExpression`, which can be used to turn on or off the destructuring requirement for `array` and `object`. By default, all values are true. - -For example, the following configuration enforces object destructuring in variable declarations and enforces array destructuring in assignment expressions. - -```json -{ - "rules": { - "prefer-destructuring": ["error", { - "VariableDeclarator": { - "array": false, - "object": true - }, - "AssignmentExpression": { - "array": true, - "object": false - } - }, { - "enforceForRenamedProperties": false - }] - } -} - -``` +::: -Examples of **correct** code when object destructuring in `VariableDeclarator` is enforced: +Examples of **correct** code when `enforceForRenamedProperties` is enabled: ::: correct ```javascript -/* eslint prefer-destructuring: ["error", {VariableDeclarator: {object: true}}] */ -var {bar: foo} = object; +/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ +const { bar: foo } = object; ``` ::: -Examples of **correct** code when array destructuring in `AssignmentExpression` is enforced: +Examples of additional **correct** code when `enforceForRenamedProperties` is enabled: ::: correct ```javascript -/* eslint prefer-destructuring: ["error", {AssignmentExpression: {array: true}}] */ -[bar] = array; +/* eslint "prefer-destructuring": ["error", { "object": true }, { "enforceForRenamedProperties": true }] */ +class C { + #x; + foo() { + const bar = this.#x; // private identifiers are not allowed in destructuring + } +} ``` ::: +**Note**: It is not possible to determine if a variable will be referring to an object or an array at runtime. This rule therefore guesses the assignment type by checking whether the key being accessed is an integer. This can lead to the following possibly confusing situations: + +* Accessing an object property whose key is an integer will fall under the category `array` destructuring. +* Accessing an array element through a computed index will fall under the category `object` destructuring. + +The `--fix` option on the command line fixes only problems reported in variable declarations, and among them only those that fall under the category `object` destructuring. Furthermore, the name of the declared variable has to be the same as the name used for non-computed member access in the initializer. For example, `const foo = object.foo` can be automatically fixed by this rule. Problems that involve computed member access (e.g., `const foo = object[foo]`) or renamed properties (e.g., `const foo = object.bar`) are not automatically fixed. + ## When Not To Use It If you want to be able to access array indices or object properties directly, you can either configure the rule to your tastes or disable the rule entirely. @@ -212,7 +194,7 @@ If you want to be able to access array indices or object properties directly, yo Additionally, if you intend to access large array indices directly, like: ```javascript -var foo = array[100]; +const foo = array[100]; ``` Then the `array` part of this rule is not recommended, as destructuring does not match this use case very well. @@ -220,7 +202,7 @@ Then the `array` part of this rule is not recommended, as destructuring does not Or for non-iterable 'array-like' objects: ```javascript -var $ = require('jquery'); -var foo = $('body')[0]; -var [bar] = $('body'); // fails with a TypeError +const $ = require('jquery'); +const foo = $('body')[0]; +const [bar] = $('body'); // fails with a TypeError ``` diff --git a/docs/src/rules/prefer-numeric-literals.md b/docs/src/rules/prefer-numeric-literals.md index a90fd7dffd23..ad5796401405 100644 --- a/docs/src/rules/prefer-numeric-literals.md +++ b/docs/src/rules/prefer-numeric-literals.md @@ -40,7 +40,6 @@ Examples of **correct** code for this rule: ```js /*eslint prefer-numeric-literals: "error"*/ -/*eslint-env es6*/ parseInt(1); parseInt(1, 3); diff --git a/docs/src/rules/prefer-object-spread.md b/docs/src/rules/prefer-object-spread.md index b1927350ece1..402926efa713 100644 --- a/docs/src/rules/prefer-object-spread.md +++ b/docs/src/rules/prefer-object-spread.md @@ -5,7 +5,7 @@ rule_type: suggestion -When Object.assign is called using an object literal as the first argument, this rule requires using the object spread syntax instead. This rule also warns on cases where an `Object.assign` call is made using a single argument that is an object literal, in this case, the `Object.assign` call is not needed. +When `Object.assign` is called using an object literal as the first argument, this rule requires using the object spread syntax instead. This rule also warns on cases where an `Object.assign` call is made using a single argument that is an object literal, in this case, the `Object.assign` call is not needed. Introduced in ES2018, object spread is a declarative alternative which may perform better than the more dynamic, imperative `Object.assign`. diff --git a/docs/src/rules/prefer-promise-reject-errors.md b/docs/src/rules/prefer-promise-reject-errors.md index c4874c28b153..becd4e94d031 100644 --- a/docs/src/rules/prefer-promise-reject-errors.md +++ b/docs/src/rules/prefer-promise-reject-errors.md @@ -60,7 +60,7 @@ new Promise(function(resolve, reject) { reject(new Error("something bad happened")); }); -var foo = getUnknownValue(); +const foo = getUnknownValue(); Promise.reject(foo); ``` diff --git a/docs/src/rules/prefer-reflect.md b/docs/src/rules/prefer-reflect.md index d6e3d87330a1..80dcd3f5a799 100644 --- a/docs/src/rules/prefer-reflect.md +++ b/docs/src/rules/prefer-reflect.md @@ -7,8 +7,7 @@ related_rules: - no-delete-var --- - -This rule was **deprecated** in ESLint v3.9.0 and will not be replaced. The original intent of this rule now seems misguided as we have come to understand that `Reflect` methods are not actually intended to replace the `Object` counterparts the rule suggests, but rather exist as low-level primitives to be used with proxies in order to replicate the default behavior of various previously existing functionality. +The original intent of this rule now seems misguided as we have come to understand that `Reflect` methods are not actually intended to replace the `Object` counterparts the rule suggests, but rather exist as low-level primitives to be used with proxies in order to replicate the default behavior of various previously existing functionality. **Please note**: This rule contains an incorrect behavior - it will suggest you to use `Reflect.getOwnPropertyNames` rather than `Object.getOwnPropertyNames`, but the former one doesn't exist in the [specification](https://www.ecma-international.org/ecma-262/6.0/index.html#sec-reflection). We suggest using the `exceptions` option with `"getOwnPropertyNames"` to avoid this false suggestion. diff --git a/docs/src/rules/prefer-rest-params.md b/docs/src/rules/prefer-rest-params.md index 4406b5468dcd..4bf747e40f33 100644 --- a/docs/src/rules/prefer-rest-params.md +++ b/docs/src/rules/prefer-rest-params.md @@ -29,12 +29,12 @@ function foo() { } function foo(action) { - var args = Array.prototype.slice.call(arguments, 1); + const args = Array.prototype.slice.call(arguments, 1); action.apply(null, args); } function foo(action) { - var args = [].slice.call(arguments, 1); + const args = [].slice.call(arguments, 1); action.apply(null, args); } ``` @@ -61,7 +61,7 @@ function foo(arguments) { console.log(arguments); // This is the first argument. } function foo() { - var arguments = 0; + const arguments = 0; console.log(arguments); // This is a local variable. } ``` diff --git a/docs/src/rules/prefer-spread.md b/docs/src/rules/prefer-spread.md index 4b696b55cac5..49a0bf94fb62 100644 --- a/docs/src/rules/prefer-spread.md +++ b/docs/src/rules/prefer-spread.md @@ -9,16 +9,14 @@ related_rules: Before ES2015, one must use `Function.prototype.apply()` to call variadic functions. ```js -var args = [1, 2, 3, 4]; +const args = [1, 2, 3, 4]; Math.max.apply(Math, args); ``` In ES2015, one can use spread syntax to call variadic functions. ```js -/*eslint-env es6*/ - -var args = [1, 2, 3, 4]; +const args = [1, 2, 3, 4]; Math.max(...args); ``` @@ -67,7 +65,7 @@ obj.foo.apply(obj, [1, 2, 3]); ::: -Known limitations: +## Known Limitations This rule analyzes code statically to check whether or not the `this` argument is changed. So, if the `this` argument is computed in a dynamic expression, this rule cannot detect a violation. diff --git a/docs/src/rules/prefer-template.md b/docs/src/rules/prefer-template.md index c2397b5b2f7f..1554ac2df556 100644 --- a/docs/src/rules/prefer-template.md +++ b/docs/src/rules/prefer-template.md @@ -11,13 +11,11 @@ related_rules: In ES2015 (ES6), we can use template literals instead of string concatenation. ```js -var str = "Hello, " + name + "!"; +const str = "Hello, " + name + "!"; ``` ```js -/*eslint-env es6*/ - -var str = `Hello, ${name}!`; +const str = `Hello, ${name}!`; ``` ## Rule Details @@ -33,8 +31,8 @@ Examples of **incorrect** code for this rule: ```js /*eslint prefer-template: "error"*/ -var str = "Hello, " + name + "!"; -var str = "Time: " + (12 * 60 * 60 * 1000); +const str = "Hello, " + name + "!"; +const str1 = "Time: " + (12 * 60 * 60 * 1000); ``` ::: @@ -45,14 +43,13 @@ Examples of **correct** code for this rule: ```js /*eslint prefer-template: "error"*/ -/*eslint-env es6*/ -var str = "Hello World!"; -var str = `Hello, ${name}!`; -var str = `Time: ${12 * 60 * 60 * 1000}`; +const str = "Hello World!"; +const str1 = `Hello, ${name}!`; +const str2 = `Time: ${12 * 60 * 60 * 1000}`; // This is reported by `no-useless-concat`. -var str = "Hello, " + "World!"; +const str4 = "Hello, " + "World!"; ``` ::: diff --git a/docs/src/rules/quote-props.md b/docs/src/rules/quote-props.md index 8a22c1a38683..319a94437a3a 100644 --- a/docs/src/rules/quote-props.md +++ b/docs/src/rules/quote-props.md @@ -5,9 +5,6 @@ further_reading: - https://kangax.github.io/compat-table/es5/#Reserved_words_as_property_names - https://mathiasbynens.be/notes/javascript-properties --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/quote-props) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Object literal property names can be defined in two ways: using literals or using strings. For example, these two objects are equivalent: ```js @@ -83,7 +80,6 @@ Examples of **correct** code for this rule with the default `"always"` option: ```js /*eslint quote-props: ["error", "always"]*/ -/*eslint-env es6*/ var object1 = { "foo": "bar", @@ -131,7 +127,6 @@ Examples of **correct** code for this rule with the `"as-needed"` option: ```js /*eslint quote-props: ["error", "as-needed"]*/ -/*eslint-env es6*/ var object1 = { "a-b": 0, diff --git a/docs/src/rules/quotes.md b/docs/src/rules/quotes.md index 27dede284d6c..69599de90a3f 100644 --- a/docs/src/rules/quotes.md +++ b/docs/src/rules/quotes.md @@ -2,14 +2,9 @@ title: quotes rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/quotes) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - JavaScript allows you to define strings in one of three ways: double quotes, single quotes, and backticks (as of ECMAScript 6). For example: ```js -/*eslint-env es6*/ - var double = "double"; var single = 'single'; var backtick = `backtick`; // ES6 only @@ -64,7 +59,6 @@ Examples of **correct** code for this rule with the default `"double"` option: ```js /*eslint quotes: ["error", "double"]*/ -/*eslint-env es6*/ var double = "double"; var backtick = `back @@ -95,7 +89,6 @@ Examples of **correct** code for this rule with the `"single"` option: ```js /*eslint quotes: ["error", "single"]*/ -/*eslint-env es6*/ var single = 'single'; var backtick = `back${x}tick`; // backticks are allowed due to substitution @@ -125,7 +118,6 @@ Examples of **correct** code for this rule with the `"backtick"` option: ```js /*eslint quotes: ["error", "backtick"]*/ -/*eslint-env es6*/ "use strict"; // directives must use single or double quotes var backtick = `backtick`; diff --git a/docs/src/rules/radix.md b/docs/src/rules/radix.md index 8c11f8736f2a..2321071e617c 100644 --- a/docs/src/rules/radix.md +++ b/docs/src/rules/radix.md @@ -12,13 +12,13 @@ When using the `parseInt()` function it is common to omit the second argument, t This confusion led to the suggestion that you always use the radix parameter to `parseInt()` to eliminate unintended consequences. So instead of doing this: ```js -var num = parseInt("071"); // 57 +const num = parseInt("071"); // 57 ``` Do this: ```js -var num = parseInt("071", 10); // 71 +const num = parseInt("071", 10); // 71 ``` ECMAScript 5 changed the behavior of `parseInt()` so that it no longer autodetects octal literals and instead treats them as decimal literals. However, the differences between hexadecimal and decimal interpretation of the first parameter causes many developers to continue using the radix parameter to ensure the string is interpreted in the intended way. @@ -45,15 +45,15 @@ Examples of **incorrect** code for the default `"always"` option: ```js /*eslint radix: "error"*/ -var num = parseInt("071"); +const num = parseInt("071"); -var num = parseInt(someValue); +const num1 = parseInt(someValue); -var num = parseInt("071", "abc"); +const num2 = parseInt("071", "abc"); -var num = parseInt("071", 37); +const num3 = parseInt("071", 37); -var num = parseInt(); +const num4 = parseInt(); ``` ::: @@ -65,11 +65,11 @@ Examples of **correct** code for the default `"always"` option: ```js /*eslint radix: "error"*/ -var num = parseInt("071", 10); +const num = parseInt("071", 10); -var num = parseInt("071", 8); +const num1 = parseInt("071", 8); -var num = parseFloat(someValue); +const num2 = parseFloat(someValue); ``` ::: @@ -83,11 +83,11 @@ Examples of **incorrect** code for the `"as-needed"` option: ```js /*eslint radix: ["error", "as-needed"]*/ -var num = parseInt("071", 10); +const num = parseInt("071", 10); -var num = parseInt("071", "abc"); +const num1 = parseInt("071", "abc"); -var num = parseInt(); +const num2 = parseInt(); ``` ::: @@ -99,11 +99,11 @@ Examples of **correct** code for the `"as-needed"` option: ```js /*eslint radix: ["error", "as-needed"]*/ -var num = parseInt("071"); +const num = parseInt("071"); -var num = parseInt("071", 8); +const num1 = parseInt("071", 8); -var num = parseFloat(someValue); +const num2 = parseFloat(someValue); ``` ::: diff --git a/docs/src/rules/require-jsdoc.md b/docs/src/rules/require-jsdoc.md index 82ea4b432222..baf135016275 100644 --- a/docs/src/rules/require-jsdoc.md +++ b/docs/src/rules/require-jsdoc.md @@ -5,17 +5,18 @@ related_rules: - valid-jsdoc --- - -This rule was [**deprecated**](https://eslint.org/blog/2018/11/jsdoc-end-of-life) in ESLint v5.10.0. +:::important +This rule was removed in ESLint v9.0.0 and replaced by the [`eslint-plugin-jsdoc`](https://github.com/gajus/eslint-plugin-jsdoc) equivalent. +::: [JSDoc](http://usejsdoc.org) is a JavaScript API documentation generator. It uses specially-formatted comments inside of code to generate API documentation automatically. For example, this is what a JSDoc comment looks like for a function: ```js /** * Adds two numbers together. - * @param {int} num1 The first number. - * @param {int} num2 The second number. - * @returns {int} The sum of the two numbers. + * @param {number} num1 The first number. + * @param {number} num2 The second number. + * @returns {number} The sum of the two numbers. */ function sum(num1, num2) { return num1 + num2; @@ -128,8 +129,8 @@ function foo() { /** * It returns test + 10 - * @params {int} test - some number - * @returns {int} sum of test and 10 + * @params {number} test - some number + * @returns {number} sum of test and 10 */ var bar = (test) => { return test + 10; diff --git a/docs/src/rules/require-unicode-regexp.md b/docs/src/rules/require-unicode-regexp.md index 768f34a71d35..502010424329 100644 --- a/docs/src/rules/require-unicode-regexp.md +++ b/docs/src/rules/require-unicode-regexp.md @@ -98,6 +98,111 @@ function i(flags) { ::: +## Options + +This rule has one object option: + +* `"requireFlag": "u"|"v"` requires a particular Unicode regex flag + +### requireFlag: "u" + +The `u` flag may be preferred in environments that do not support the `v` flag. + +Examples of **incorrect** code for this rule with the `{ "requireFlag": "u" }` option: + +:::incorrect + +```js +/*eslint require-unicode-regexp: ["error", { "requireFlag": "u" }] */ + +const fooEmpty = /foo/; + +const fooEmptyRegexp = new RegExp('foo'); + +const foo = /foo/v; + +const fooRegexp = new RegExp('foo', 'v'); +``` + +::: + +Examples of **correct** code for this rule with the `{ "requireFlag": "u" }` option: + +:::correct + +```js +/*eslint require-unicode-regexp: ["error", { "requireFlag": "u" }] */ + +const foo = /foo/u; + +const fooRegexp = new RegExp('foo', 'u'); +``` + +::: + +### requireFlag: "v" + +The `v` flag may be a better choice when it is supported because it has more +features than the `u` flag (e.g., the ability to test Unicode properties of strings). It +does have a stricter syntax, however (e.g., the need to escape certain +characters within character classes). + +Examples of **incorrect** code for this rule with the `{ "requireFlag": "v" }` option: + +:::incorrect + +```js +/*eslint require-unicode-regexp: ["error", { "requireFlag": "v" }] */ + +const fooEmpty = /foo/; + +const fooEmptyRegexp = new RegExp('foo'); + +const foo = /foo/u; + +const fooRegexp = new RegExp('foo', 'u'); +``` + +::: + +Examples of **correct** code for this rule with the `{ "requireFlag": "v" }` option: + +:::correct + +```js +/*eslint require-unicode-regexp: ["error", { "requireFlag": "v" }] */ + +const foo = /foo/v; + +const fooRegexp = new RegExp('foo', 'v'); +``` + +::: + ## When Not To Use It If you don't want to warn on regular expressions without either a `u` or a `v` flag, then it's safe to disable this rule. + +### Note on `i` flag and `\w` + +In some cases, adding the `u` flag to a regular expression using both the `i` flag and the `\w` character class can change its behavior due to Unicode case folding. + +For example: + +```js +const regexWithoutU = /^\w+$/i; +const regexWithU = /^\w+$/iu; + +const str = "\u017f\u212a"; // Example Unicode characters + +console.log(regexWithoutU.test(str)); // false +console.log(regexWithU.test(str)); // true +``` + +If you prefer to use a non-Unicode-aware regex in this specific case, you can disable this rule using an `eslint-disable` comment: + +```js +/* eslint-disable require-unicode-regexp */ +const regex = /^\w+$/i; +/* eslint-enable require-unicode-regexp */ +``` diff --git a/docs/src/rules/require-yield.md b/docs/src/rules/require-yield.md index eec9d0cfc941..2ad53fd4d84e 100644 --- a/docs/src/rules/require-yield.md +++ b/docs/src/rules/require-yield.md @@ -19,7 +19,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint require-yield: "error"*/ -/*eslint-env es6*/ function* foo() { return 10; @@ -34,7 +33,6 @@ Examples of **correct** code for this rule: ```js /*eslint require-yield: "error"*/ -/*eslint-env es6*/ function* foo() { yield 5; diff --git a/docs/src/rules/rest-spread-spacing.md b/docs/src/rules/rest-spread-spacing.md index 13b0e3704c86..6d09d99d9645 100644 --- a/docs/src/rules/rest-spread-spacing.md +++ b/docs/src/rules/rest-spread-spacing.md @@ -4,9 +4,6 @@ rule_type: layout further_reading: - https://github.com/tc39/proposal-object-rest-spread --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/rest-spread-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - ES2015 introduced the rest and spread operators, which expand an iterable structure into its individual parts. Some examples of their usage are as follows: ```js diff --git a/docs/src/rules/semi-spacing.md b/docs/src/rules/semi-spacing.md index 2c00517bfa3b..127d7548da33 100644 --- a/docs/src/rules/semi-spacing.md +++ b/docs/src/rules/semi-spacing.md @@ -8,9 +8,6 @@ related_rules: - block-spacing - space-in-parens --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/semi-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - JavaScript allows you to place unnecessary spaces before or after a semicolon. Disallowing or enforcing space around a semicolon can improve the readability of your program. diff --git a/docs/src/rules/semi-style.md b/docs/src/rules/semi-style.md index 483ee44edaed..6edd02564d02 100644 --- a/docs/src/rules/semi-style.md +++ b/docs/src/rules/semi-style.md @@ -6,9 +6,6 @@ related_rules: - semi - semi-spacing --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/semi-style) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Generally, semicolons are at the end of lines. However, in semicolon-less style, semicolons are at the beginning of lines. This rule enforces that semicolons are at the configured location. ## Rule Details diff --git a/docs/src/rules/semi.md b/docs/src/rules/semi.md index 8a58ad764685..84a2dbcde8e3 100644 --- a/docs/src/rules/semi.md +++ b/docs/src/rules/semi.md @@ -9,9 +9,6 @@ further_reading: - https://blog.izs.me/2010/12/an-open-letter-to-javascript-leaders-regarding/ - https://web.archive.org/web/20200420230322/http://inimino.org/~inimino/blog/javascript_semicolons --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/semi) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - JavaScript doesn't require semicolons at the end of each statement. In many cases, the JavaScript engine can determine that a semicolon should be in a certain spot and will automatically add it. This feature is known as **automatic semicolon insertion (ASI)** and is considered one of the more controversial features of JavaScript. For example, the following lines are both valid: ```js diff --git a/docs/src/rules/sort-imports.md b/docs/src/rules/sort-imports.md index 762e4acfa439..6d59e89a56a9 100644 --- a/docs/src/rules/sort-imports.md +++ b/docs/src/rules/sort-imports.md @@ -8,7 +8,7 @@ related_rules: -The import statement is used to import members (functions, objects or primitives) that have been exported from an external module. Using a specific member syntax: +The `import` statement is used to import members (functions, objects or primitives) that have been exported from an external module. Using a specific member syntax: ```js // single - Import single member. @@ -22,18 +22,18 @@ import {foo, bar} from "my-module.js"; import * as myModule from "my-module.js"; ``` -The import statement can also import a module without exported bindings. Used when the module does not export anything, but runs it own code or changes the global context object. +The `import` statement can also import a module without exported bindings. Used when the module does not export anything, but runs it own code or changes the global context object. ```js // none - Import module without exported bindings. import "my-module.js" ``` -When declaring multiple imports, a sorted list of import declarations make it easier for developers to read the code and find necessary imports later. This rule is purely a matter of style. +When declaring multiple imports, a sorted list of `import` declarations make it easier for developers to read the code and find necessary imports later. This rule is purely a matter of style. ## Rule Details -This rule checks all import declarations and verifies that all imports are first sorted by the used member syntax and then alphabetically by the first member or alias name. +This rule checks all `import` declarations and verifies that all imports are first sorted by the used member syntax and then alphabetically by the first member or alias name. The `--fix` option on the command line automatically fixes some problems reported by this rule: multiple members on a single line are automatically sorted (e.g. `import { b, a } from 'foo.js'` is corrected to `import { a, b } from 'foo.js'`), but multiple lines are not reordered. @@ -192,40 +192,64 @@ import {b, a, c} from 'foo.js' ### `ignoreCase` -When `true` the rule ignores the case-sensitivity of the imports local name. +When `false` (default), uppercase letters of the alphabet must always precede lowercase letters. -Examples of **incorrect** code for this rule with the `{ "ignoreCase": true }` option: +When `true`, the rule ignores the case-sensitivity of the imports local name. + +Examples of **incorrect** code for this rule with the default `{ "ignoreCase": false }` option: ::: incorrect ```js -/*eslint sort-imports: ["error", { "ignoreCase": true }]*/ - -import B from 'foo.js'; +/*eslint sort-imports: ["error", { "ignoreCase": false }]*/ import a from 'bar.js'; +import B from 'foo.js'; +import c from 'baz.js'; ``` ::: -Examples of **correct** code for this rule with the `{ "ignoreCase": true }` option: +Examples of **correct** code for this rule with the default `{ "ignoreCase": false }` option: ::: correct ```js -/*eslint sort-imports: ["error", { "ignoreCase": true }]*/ - -import a from 'foo.js'; +/*eslint sort-imports: ["error", { "ignoreCase": false }]*/ import B from 'bar.js'; +import a from 'foo.js'; +import c from 'baz.js'; +``` + +::: + +Examples of **correct** code for this rule with `{ "ignoreCase": true }` option: + +::: correct + +```js +/*eslint sort-imports: ["error", { "ignoreCase": true }]*/ +import a from 'bar.js'; +import B from 'foo.js'; import c from 'baz.js'; ``` ::: -Default is `false`. +Examples of **incorrect** code for this rule with the `{ "ignoreCase": true }` option: + +::: incorrect + +```js +/*eslint sort-imports: ["error", { "ignoreCase": true }]*/ +import B from 'foo.js'; +import a from 'bar.js'; +``` + +::: ### `ignoreDeclarationSort` -Ignores the sorting of import declaration statements. +When `true`, the rule ignores the sorting of import declaration statements. Default is `false`. Examples of **incorrect** code for this rule with the default `{ "ignoreDeclarationSort": false }` option: @@ -239,18 +263,20 @@ import a from 'bar.js' ::: -Examples of **correct** code for this rule with the `{ "ignoreDeclarationSort": true }` option: +Examples of **correct** code for this rule with the default `{ "ignoreDeclarationSort": false }` option: ::: correct ```js -/*eslint sort-imports: ["error", { "ignoreDeclarationSort": true }]*/ -import a from 'foo.js' -import b from 'bar.js' +/*eslint sort-imports: ["error", { "ignoreDeclarationSort": false }]*/ +import a from 'bar.js'; +import b from 'foo.js'; ``` ::: +Examples of **correct** code for this rule with the `{ "ignoreDeclarationSort": true }` option: + ::: correct ```js @@ -261,11 +287,20 @@ import a from 'bar.js' ::: -Default is `false`. +Examples of **incorrect** code for this rule with the `{ "ignoreDeclarationSort": true }` option: + +::: incorrect + +```js +/*eslint sort-imports: ["error", { "ignoreDeclarationSort": true }]*/ +import {b, a, c} from 'foo.js'; +``` + +::: ### `ignoreMemberSort` -Ignores the member sorting within a `multiple` member import declaration. +When `true`, the rule ignores the member sorting within a `multiple` member import declaration. Default is `false`. Examples of **incorrect** code for this rule with the default `{ "ignoreMemberSort": false }` option: @@ -278,6 +313,17 @@ import {b, a, c} from 'foo.js' ::: +Examples of **correct** code for this rule with the default `{ "ignoreMemberSort": false }` option: + +::: correct + +```js +/*eslint sort-imports: ["error", { "ignoreMemberSort": false }]*/ +import {a, b, c} from 'foo.js'; +``` + +::: + Examples of **correct** code for this rule with the `{ "ignoreMemberSort": true }` option: ::: correct @@ -289,10 +335,24 @@ import {b, a, c} from 'foo.js' ::: -Default is `false`. +Examples of **incorrect** code for this rule with the `{ "ignoreMemberSort": true }` option: + +::: incorrect + +```js +/*eslint sort-imports: ["error", { "ignoreMemberSort": true }]*/ +import b from 'foo.js'; +import a from 'bar.js'; +``` + +::: ### `memberSyntaxSortOrder` +This option takes an array with four predefined elements, the order of elements specifies the order of import styles. + +Default order is `["none", "all", "multiple", "single"]`. + There are four different styles and the default member syntax sort order is: * `none` - import module without exported bindings. @@ -341,11 +401,9 @@ import {a, b} from 'foo.js'; ::: -Default is `["none", "all", "multiple", "single"]`. - ### `allowSeparatedGroups` -When `true` the rule checks the sorting of import declaration statements only for those that appear on consecutive lines. +When `true`, the rule checks the sorting of import declaration statements only for those that appear on consecutive lines. Default is `false`. In other words, a blank line or a comment line or line with any other statement after an import declaration statement will reset the sorting of import declaration statements. @@ -404,8 +462,6 @@ import a from 'baz.js'; ::: -Default is `false`. - ## When Not To Use It This rule is a formatting preference and not following it won't negatively affect the quality of your code. If alphabetizing imports isn't a part of your coding standards, then you can leave this rule disabled. diff --git a/docs/src/rules/sort-keys.md b/docs/src/rules/sort-keys.md index 14cdc12f8f30..89d01a0a8c9a 100644 --- a/docs/src/rules/sort-keys.md +++ b/docs/src/rules/sort-keys.md @@ -19,22 +19,21 @@ Examples of **incorrect** code for this rule: ```js /*eslint sort-keys: "error"*/ -/*eslint-env es6*/ -let obj1 = {a: 1, c: 3, b: 2}; -let obj2 = {a: 1, "c": 3, b: 2}; +const obj1 = {a: 1, c: 3, b: 2}; +const obj2 = {a: 1, "c": 3, b: 2}; // Case-sensitive by default. -let obj3 = {a: 1, b: 2, C: 3}; +const obj3 = {a: 1, b: 2, C: 3}; // Non-natural order by default. -let obj4 = {1: a, 2: c, 10: b}; +const obj4 = {1: a, 2: c, 10: b}; // This rule checks computed properties which have a simple name as well. // Simple names are names which are expressed by an Identifier node or a Literal node. const S = Symbol("s") -let obj5 = {a: 1, ["c"]: 3, b: 2}; -let obj6 = {a: 1, [S]: 3, b: 2}; +const obj5 = {a: 1, ["c"]: 3, b: 2}; +const obj6 = {a: 1, [S]: 3, b: 2}; ``` ::: @@ -45,29 +44,28 @@ Examples of **correct** code for this rule: ```js /*eslint sort-keys: "error"*/ -/*eslint-env es6*/ -let obj1 = {a: 1, b: 2, c: 3}; -let obj2 = {a: 1, "b": 2, c: 3}; +const obj1 = {a: 1, b: 2, c: 3}; +const obj2 = {a: 1, "b": 2, c: 3}; // Case-sensitive by default. -let obj3 = {C: 3, a: 1, b: 2}; +const obj3 = {C: 3, a: 1, b: 2}; // Non-natural order by default. -let obj4 = {1: a, 10: b, 2: c}; +const obj4 = {1: a, 10: b, 2: c}; // This rule checks computed properties which have a simple name as well. -let obj5 = {a: 1, ["b"]: 2, c: 3}; -let obj6 = {a: 1, [b]: 2, c: 3}; +const obj5 = {a: 1, ["b"]: 2, c: 3}; +const obj6 = {a: 1, [b]: 2, c: 3}; // This rule ignores computed properties which have a non-simple name. -let obj7 = {a: 1, [c + d]: 3, b: 2}; -let obj8 = {a: 1, ["c" + "d"]: 3, b: 2}; -let obj9 = {a: 1, [`${c}`]: 3, b: 2}; -let obj10 = {a: 1, [tag`c`]: 3, b: 2}; +const obj7 = {a: 1, [c + d]: 3, b: 2}; +const obj8 = {a: 1, ["c" + "d"]: 3, b: 2}; +const obj9 = {a: 1, [`${c}`]: 3, b: 2}; +const obj10 = {a: 1, [tag`c`]: 3, b: 2}; // This rule does not report unsorted properties that are separated by a spread property. -let obj11 = {b: 1, ...c, a: 2}; +const obj11 = {b: 1, ...c, a: 2}; ``` ::: @@ -85,12 +83,13 @@ The 1st option is `"asc"` or `"desc"`. * `"asc"` (default) - enforce properties to be in ascending order. * `"desc"` - enforce properties to be in descending order. -The 2nd option is an object which has 3 properties. +The 2nd option is an object which has the following properties. * `caseSensitive` - if `true`, enforce properties to be in case-sensitive order. Default is `true`. * `minKeys` - Specifies the minimum number of keys that an object should have in order for the object's unsorted keys to produce an error. Default is `2`, which means by default all objects with unsorted keys will result in lint errors. * `natural` - if `true`, enforce properties to be in natural order. Default is `false`. Natural Order compares strings containing combination of letters and numbers in the way a human being would sort. It basically sorts numerically, instead of sorting alphabetically. So the number 10 comes after the number 3 in Natural Sorting. * `allowLineSeparatedGroups` - if `true`, the rule allows to group object keys through line breaks. In other words, a blank line after a property will reset the sorting of keys. Default is `false`. +* `ignoreComputedKeys` - if `true`, the rule ignores all computed keys and doesn't report unsorted properties separated by them. A computed key will reset the sorting of the following non-computed keys. Default is `false`. Example for a list: @@ -116,16 +115,15 @@ Examples of **incorrect** code for the `"desc"` option: ```js /*eslint sort-keys: ["error", "desc"]*/ -/*eslint-env es6*/ -let obj1 = {b: 2, c: 3, a: 1}; -let obj2 = {"b": 2, c: 3, a: 1}; +const obj1 = {b: 2, c: 3, a: 1}; +const obj2 = {"b": 2, c: 3, a: 1}; // Case-sensitive by default. -let obj3 = {C: 1, b: 3, a: 2}; +const obj3 = {C: 1, b: 3, a: 2}; // Non-natural order by default. -let obj4 = {10: b, 2: c, 1: a}; +const obj4 = {10: b, 2: c, 1: a}; ``` ::: @@ -136,21 +134,20 @@ Examples of **correct** code for the `"desc"` option: ```js /*eslint sort-keys: ["error", "desc"]*/ -/*eslint-env es6*/ -let obj1 = {c: 3, b: 2, a: 1}; -let obj2 = {c: 3, "b": 2, a: 1}; +const obj1 = {c: 3, b: 2, a: 1}; +const obj2 = {c: 3, "b": 2, a: 1}; // Case-sensitive by default. -let obj3 = {b: 3, a: 2, C: 1}; +const obj3 = {b: 3, a: 2, C: 1}; // Non-natural order by default. -let obj4 = {2: c, 10: b, 1: a}; +const obj4 = {2: c, 10: b, 1: a}; ``` ::: -### insensitive +### caseSensitive Examples of **incorrect** code for the `{caseSensitive: false}` option: @@ -158,10 +155,9 @@ Examples of **incorrect** code for the `{caseSensitive: false}` option: ```js /*eslint sort-keys: ["error", "asc", {caseSensitive: false}]*/ -/*eslint-env es6*/ -let obj1 = {a: 1, c: 3, C: 4, b: 2}; -let obj2 = {a: 1, C: 3, c: 4, b: 2}; +const obj1 = {a: 1, c: 3, C: 4, b: 2}; +const obj2 = {a: 1, C: 3, c: 4, b: 2}; ``` ::: @@ -172,10 +168,9 @@ Examples of **correct** code for the `{caseSensitive: false}` option: ```js /*eslint sort-keys: ["error", "asc", {caseSensitive: false}]*/ -/*eslint-env es6*/ -let obj1 = {a: 1, b: 2, c: 3, C: 4}; -let obj2 = {a: 1, b: 2, C: 3, c: 4}; +const obj1 = {a: 1, b: 2, c: 3, C: 4}; +const obj2 = {a: 1, b: 2, C: 3, c: 4}; ``` ::: @@ -188,9 +183,8 @@ Examples of **incorrect** code for the `{natural: true}` option: ```js /*eslint sort-keys: ["error", "asc", {natural: true}]*/ -/*eslint-env es6*/ -let obj = {1: a, 10: c, 2: b}; +const obj = {1: a, 10: c, 2: b}; ``` ::: @@ -201,9 +195,8 @@ Examples of **correct** code for the `{natural: true}` option: ```js /*eslint sort-keys: ["error", "asc", {natural: true}]*/ -/*eslint-env es6*/ -let obj = {1: a, 2: b, 10: c}; +const obj = {1: a, 2: b, 10: c}; ``` ::: @@ -216,10 +209,9 @@ Examples of **incorrect** code for the `{minKeys: 4}` option: ```js /*eslint sort-keys: ["error", "asc", {minKeys: 4}]*/ -/*eslint-env es6*/ // 4 keys -let obj1 = { +const obj1 = { b: 2, a: 1, // not sorted correctly (should be 1st key) c: 3, @@ -227,7 +219,7 @@ let obj1 = { }; // 5 keys -let obj2 = { +const obj2 = { 2: 'a', 1: 'b', // not sorted correctly (should be 1st key) 3: 'c', @@ -244,17 +236,16 @@ Examples of **correct** code for the `{minKeys: 4}` option: ```js /*eslint sort-keys: ["error", "asc", {minKeys: 4}]*/ -/*eslint-env es6*/ // 3 keys -let obj1 = { +const obj1 = { b: 2, a: 1, c: 3, }; // 2 keys -let obj2 = { +const obj2 = { 2: 'b', 1: 'a', }; @@ -270,9 +261,8 @@ Examples of **incorrect** code for the `{allowLineSeparatedGroups: true}` option ```js /*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*/ -/*eslint-env es6*/ -let obj1 = { +const obj1 = { b: 1, c () { @@ -280,7 +270,7 @@ let obj1 = { a: 3 } -let obj2 = { +const obj2 = { b: 1, c: 2, @@ -290,7 +280,7 @@ let obj2 = { y: 3 } -let obj3 = { +const obj3 = { b: 1, c: 2, @@ -301,7 +291,7 @@ let obj3 = { y: 3, } -let obj4 = { +const obj4 = { b: 1 // comment before comma , a: 2 @@ -316,9 +306,8 @@ Examples of **correct** code for the `{allowLineSeparatedGroups: true}` option: ```js /*eslint sort-keys: ["error", "asc", {allowLineSeparatedGroups: true}]*/ -/*eslint-env es6*/ -let obj1 = { +const obj1 = { e: 1, f: 2, g: 3, @@ -328,7 +317,7 @@ let obj1 = { c: 6 } -let obj2 = { +const obj2 = { b: 1, // comment @@ -336,17 +325,17 @@ let obj2 = { c: 5, } -let obj3 = { +const obj3 = { c: 1, d: 2, b () { - }, + }, e: 3, } -let obj4 = { +const obj4 = { c: 1, d: 2, // comment @@ -358,14 +347,14 @@ let obj4 = { e: 4 } -let obj5 = { +const obj5 = { b, [foo + bar]: 1, a } -let obj6 = { +const obj6 = { b: 1 // comment before comma @@ -373,7 +362,7 @@ let obj6 = { a: 2 }; -var obj7 = { +const obj7 = { b: 1, a: 2, @@ -384,6 +373,35 @@ var obj7 = { ::: +### ignoreComputedKeys + +Examples of **correct** code for the `{ignoreComputedKeys: true}` option: + +::: correct + +```js +/*eslint sort-keys: ["error", "asc", {ignoreComputedKeys: true}]*/ + +const obj1 = { + [b]: 1, + a: 2 +} + +const obj2 = { + c: 1, + [b]: 2, + a: 3 +} + +const obj3 = { + c: 1, + ["b"]: 2, + a: 3 +} +``` + +::: + ## When Not To Use It If you don't want to notify about properties' order, then it's safe to disable this rule. diff --git a/docs/src/rules/sort-vars.md b/docs/src/rules/sort-vars.md index e9527045f425..479b91f62ea1 100644 --- a/docs/src/rules/sort-vars.md +++ b/docs/src/rules/sort-vars.md @@ -22,11 +22,11 @@ Examples of **incorrect** code for this rule: ```js /*eslint sort-vars: "error"*/ -var b, a; +let b, a; -var a, B, c; +let c, D, e; -var a, A; +let f, F; ``` ::: @@ -38,14 +38,14 @@ Examples of **correct** code for this rule: ```js /*eslint sort-vars: "error"*/ -var a, b, c, d; +let a, b, c, d; -var _a = 10; -var _b = 20; +let _a = 10; +let _b = 20; -var A, a; +let E, e; -var B, a, c; +let G, f, h; ``` ::: @@ -55,7 +55,7 @@ Alphabetical list is maintained starting from the first variable and excluding a ```js /*eslint sort-vars: "error"*/ -var c, d, a, b; +let c, d, a, b; ``` But this one, will only produce one: @@ -63,7 +63,7 @@ But this one, will only produce one: ```js /*eslint sort-vars: "error"*/ -var c, d, a, e; +let c, d, a, e; ``` ## Options @@ -81,9 +81,9 @@ Examples of **correct** code for this rule with the `{ "ignoreCase": true }` opt ```js /*eslint sort-vars: ["error", { "ignoreCase": true }]*/ -var a, A; +let a, A; -var a, B, c; +let c, D, e; ``` ::: diff --git a/docs/src/rules/space-before-blocks.md b/docs/src/rules/space-before-blocks.md index 61c60eb51ad8..6e9bbb4a27bb 100644 --- a/docs/src/rules/space-before-blocks.md +++ b/docs/src/rules/space-before-blocks.md @@ -8,9 +8,6 @@ related_rules: - block-spacing - brace-style --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/space-before-blocks) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Consistency is an important part of any style guide. While it is a personal preference where to put the opening brace of blocks, it should be consistent across a whole project. @@ -151,7 +148,6 @@ Examples of **incorrect** code for this rule when configured `{ "functions": "ne ```js /*eslint space-before-blocks: ["error", { "functions": "never", "keywords": "always", "classes": "never" }]*/ -/*eslint-env es6*/ function a() {} @@ -170,7 +166,6 @@ Examples of **correct** code for this rule when configured `{ "functions": "neve ```js /*eslint space-before-blocks: ["error", { "functions": "never", "keywords": "always", "classes": "never" }]*/ -/*eslint-env es6*/ for (;;) { // ... @@ -193,7 +188,6 @@ Examples of **incorrect** code for this rule when configured `{ "functions": "al ```js /*eslint space-before-blocks: ["error", { "functions": "always", "keywords": "never", "classes": "never" }]*/ -/*eslint-env es6*/ function a(){} @@ -212,7 +206,6 @@ Examples of **correct** code for this rule when configured `{ "functions": "alwa ```js /*eslint space-before-blocks: ["error", { "functions": "always", "keywords": "never", "classes": "never" }]*/ -/*eslint-env es6*/ if (a){ b(); @@ -233,7 +226,6 @@ Examples of **incorrect** code for this rule when configured `{ "functions": "ne ```js /*eslint space-before-blocks: ["error", { "functions": "never", "keywords": "never", "classes": "always" }]*/ -/*eslint-env es6*/ class Foo{ constructor(){} @@ -248,7 +240,6 @@ Examples of **correct** code for this rule when configured `{ "functions": "neve ```js /*eslint space-before-blocks: ["error", { "functions": "never", "keywords": "never", "classes": "always" }]*/ -/*eslint-env es6*/ class Foo { constructor(){} diff --git a/docs/src/rules/space-before-function-paren.md b/docs/src/rules/space-before-function-paren.md index 1736ff44ef12..1f8a5ee87f25 100644 --- a/docs/src/rules/space-before-function-paren.md +++ b/docs/src/rules/space-before-function-paren.md @@ -4,9 +4,6 @@ rule_type: layout related_rules: - keyword-spacing --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/space-before-function-paren) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - When formatting a function, whitespace is allowed between the function name or `function` keyword and the opening paren. Named functions also require a space between the `function` keyword and the function name, but anonymous functions require no whitespace. For example: ```js @@ -65,7 +62,6 @@ Examples of **incorrect** code for this rule with the default `"always"` option: ```js /*eslint space-before-function-paren: "error"*/ -/*eslint-env es6*/ function foo() { // ... @@ -102,7 +98,6 @@ Examples of **correct** code for this rule with the default `"always"` option: ```js /*eslint space-before-function-paren: "error"*/ -/*eslint-env es6*/ function foo () { // ... @@ -141,7 +136,6 @@ Examples of **incorrect** code for this rule with the `"never"` option: ```js /*eslint space-before-function-paren: ["error", "never"]*/ -/*eslint-env es6*/ function foo () { // ... @@ -178,7 +172,6 @@ Examples of **correct** code for this rule with the `"never"` option: ```js /*eslint space-before-function-paren: ["error", "never"]*/ -/*eslint-env es6*/ function foo() { // ... @@ -217,7 +210,6 @@ Examples of **incorrect** code for this rule with the `{"anonymous": "always", " ```js /*eslint space-before-function-paren: ["error", {"anonymous": "always", "named": "never", "asyncArrow": "always"}]*/ -/*eslint-env es6*/ function foo () { // ... @@ -250,7 +242,6 @@ Examples of **correct** code for this rule with the `{"anonymous": "always", "na ```js /*eslint space-before-function-paren: ["error", {"anonymous": "always", "named": "never", "asyncArrow": "always"}]*/ -/*eslint-env es6*/ function foo() { // ... @@ -285,7 +276,6 @@ Examples of **incorrect** code for this rule with the `{"anonymous": "never", "n ```js /*eslint space-before-function-paren: ["error", { "anonymous": "never", "named": "always" }]*/ -/*eslint-env es6*/ function foo() { // ... @@ -316,7 +306,6 @@ Examples of **correct** code for this rule with the `{"anonymous": "never", "nam ```js /*eslint space-before-function-paren: ["error", { "anonymous": "never", "named": "always" }]*/ -/*eslint-env es6*/ function foo () { // ... @@ -349,7 +338,6 @@ Examples of **incorrect** code for this rule with the `{"anonymous": "ignore", " ```js /*eslint space-before-function-paren: ["error", { "anonymous": "ignore", "named": "always" }]*/ -/*eslint-env es6*/ function foo() { // ... @@ -376,7 +364,6 @@ Examples of **correct** code for this rule with the `{"anonymous": "ignore", "na ```js /*eslint space-before-function-paren: ["error", { "anonymous": "ignore", "named": "always" }]*/ -/*eslint-env es6*/ var bar = function() { // ... diff --git a/docs/src/rules/space-before-function-parentheses.md b/docs/src/rules/space-before-function-parentheses.md index 4d3e84fb139e..d083f6d4efb1 100644 --- a/docs/src/rules/space-before-function-parentheses.md +++ b/docs/src/rules/space-before-function-parentheses.md @@ -41,8 +41,6 @@ Examples of **incorrect** code for this rule with the default `"always"` option: ::: incorrect ```js -/*eslint-env es6*/ - function foo() { // ... } @@ -75,8 +73,6 @@ Examples of **correct** code for this rule with the default `"always"` option: ::: correct ```js -/*eslint-env es6*/ - function foo () { // ... } @@ -109,8 +105,6 @@ Examples of **incorrect** code for this rule with the `"never"` option: ::: incorrect ```js -/*eslint-env es6*/ - function foo () { // ... } @@ -143,8 +137,6 @@ Examples of **correct** code for this rule with the `"never"` option: ::: correct ```js -/*eslint-env es6*/ - function foo() { // ... } @@ -177,8 +169,6 @@ Examples of **incorrect** code for this rule with the `{"anonymous": "always", " ::: incorrect ```js -/*eslint-env es6*/ - function foo () { // ... } @@ -207,8 +197,6 @@ Examples of **correct** code for this rule with the `{"anonymous": "always", "na ::: correct ```js -/*eslint-env es6*/ - function foo() { // ... } @@ -237,8 +225,6 @@ Examples of **incorrect** code for this rule with the `{"anonymous": "never", "n ::: incorrect ```js -/*eslint-env es6*/ - function foo() { // ... } @@ -267,8 +253,6 @@ Examples of **correct** code for this rule with the `{"anonymous": "never", "nam ::: correct ```js -/*eslint-env es6*/ - function foo () { // ... } diff --git a/docs/src/rules/space-before-keywords.md b/docs/src/rules/space-before-keywords.md index b49fe4b8c36b..01c1ac85a68b 100644 --- a/docs/src/rules/space-before-keywords.md +++ b/docs/src/rules/space-before-keywords.md @@ -49,7 +49,6 @@ Examples of **incorrect** code for this rule with the default `"always"` option: ```js /*eslint space-before-keywords: ["error", "always"]*/ -/*eslint-env es6*/ if (foo) { // ... @@ -68,11 +67,10 @@ function bar() { Examples of **correct** code for this rule with the default `"always"` option: -::: correct { "ecmaFeatures": { "jsx": true } } +::: correct { "parserOptions": { "ecmaFeatures": { "jsx": true } } } ```js /*eslint space-before-keywords: ["error", "always"]*/ -/*eslint-env es6*/ if (foo) { // ... diff --git a/docs/src/rules/space-in-brackets.md b/docs/src/rules/space-in-brackets.md index 05b9ed881031..15262ed21fea 100644 --- a/docs/src/rules/space-in-brackets.md +++ b/docs/src/rules/space-in-brackets.md @@ -50,8 +50,6 @@ Examples of **incorrect** code for this rule with the default `"never"` option: ::: incorrect ```js -/*eslint-env es6*/ - foo[ 'bar' ]; foo['bar' ]; @@ -122,8 +120,6 @@ Examples of **incorrect** code for this rule with the `"always"` option: ::: incorrect ```js -/*eslint-env es6*/ - foo['bar']; foo['bar' ]; foo[ 'bar']; diff --git a/docs/src/rules/space-in-parens.md b/docs/src/rules/space-in-parens.md index 16d0104fbd97..0facb1a2a80d 100644 --- a/docs/src/rules/space-in-parens.md +++ b/docs/src/rules/space-in-parens.md @@ -6,9 +6,6 @@ related_rules: - object-curly-spacing - computed-property-spacing --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/space-in-parens) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Some style guides require or disallow spaces inside of parentheses: ```js diff --git a/docs/src/rules/space-infix-ops.md b/docs/src/rules/space-infix-ops.md index 821a6e276c04..77b033b0262d 100644 --- a/docs/src/rules/space-infix-ops.md +++ b/docs/src/rules/space-infix-ops.md @@ -2,9 +2,6 @@ title: space-infix-ops rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/space-infix-ops) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - While formatting preferences are very personal, a number of style guides require spaces around operators, such as: ```js @@ -45,7 +42,6 @@ Examples of **incorrect** code for this rule: ```js /*eslint space-infix-ops: "error"*/ -/*eslint-env es6*/ a+b @@ -70,7 +66,6 @@ Examples of **correct** code for this rule: ```js /*eslint space-infix-ops: "error"*/ -/*eslint-env es6*/ a + b diff --git a/docs/src/rules/space-unary-ops.md b/docs/src/rules/space-unary-ops.md index 8379cfb7219b..1a20d048b564 100644 --- a/docs/src/rules/space-unary-ops.md +++ b/docs/src/rules/space-unary-ops.md @@ -2,9 +2,6 @@ title: space-unary-ops rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/space-unary-ops) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Some style guides require or disallow spaces before or after unary operators. This is mainly a stylistic issue, however, some JavaScript expressions can be written without spacing which makes it harder to read and maintain. ## Rule Details @@ -96,7 +93,6 @@ foo --; ```js /*eslint space-unary-ops: "error"*/ -/*eslint-env es6*/ function *foo() { yield(0) @@ -155,7 +151,6 @@ foo--; ```js /*eslint space-unary-ops: "error"*/ -/*eslint-env es6*/ function *foo() { yield (0) diff --git a/docs/src/rules/spaced-comment.md b/docs/src/rules/spaced-comment.md index 6cdaba307799..9c578bfc3c1d 100644 --- a/docs/src/rules/spaced-comment.md +++ b/docs/src/rules/spaced-comment.md @@ -4,9 +4,6 @@ rule_type: suggestion related_rules: - spaced-line-comment --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/spaced-comment) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Some style guides require or disallow a whitespace immediately after the initial `//` or `/*` of a comment. Whitespace after the `//` or `/*` makes it easier to read text in comments. On the other hand, commenting out code is easier without having to put a whitespace right after the `//` or `/*`. diff --git a/docs/src/rules/strict.md b/docs/src/rules/strict.md index 330e9141b573..ccfda9f31027 100644 --- a/docs/src/rules/strict.md +++ b/docs/src/rules/strict.md @@ -47,10 +47,10 @@ In **ECMAScript** modules, which always have strict mode semantics, the directiv This rule requires or disallows strict mode directives. -This rule disallows strict mode directives, no matter which option is specified, if ESLint configuration specifies either of the following as [parser options](../use/configure/language-options#specifying-parser-options): +This rule disallows strict mode directives, no matter which option is specified, if ESLint configuration specifies either of the following as [parser options](../use/configure/language-options#specifying-javascript-options): -* `"sourceType": "module"` that is, files are **ECMAScript** modules -* `"impliedStrict": true` property in the `ecmaFeatures` object +* `"sourceType": "module"` that is, files are **ECMAScript** modules. +* `"impliedStrict": true` property in the `ecmaFeatures` object. This rule disallows strict mode directives, no matter which option is specified, in functions with non-simple parameter lists (for example, parameter lists with default parameter values) because that is a syntax error in **ECMAScript 2016** and later. See the examples of the [function](#function) option. @@ -73,10 +73,10 @@ This rule has a string option: The `"safe"` option corresponds to the `"global"` option if ESLint considers a file to be a **Node.js** or **CommonJS** module because the configuration specifies either of the following: -* `node` or `commonjs` [environments](../use/configure/language-options#specifying-environments) +* `"sourceType": "commonjs"` in [language options](../use/configure/language-options#specifying-javascript-options) * `"globalReturn": true` property in the `ecmaFeatures` object of [parser options](../use/configure/language-options#specifying-parser-options) -Otherwise the `"safe"` option corresponds to the `"function"` option. Note that if `"globalReturn": false` is explicitly specified in the configuration, the `"safe"` option will correspond to the `"function"` option regardless of the specified environment. +Otherwise the `"safe"` option corresponds to the `"function"` option. ### global @@ -170,11 +170,10 @@ function foo() { ::: -::: incorrect { "ecmaVersion": 6, "sourceType": "script" } +::: incorrect { "ecmaVersion": 2015, "sourceType": "script" } ```js /*eslint strict: ["error", "function"]*/ -/*eslint-env es6*/ // Illegal "use strict" directive in function with non-simple parameter list. // This is a syntax error since ES2016. @@ -211,7 +210,7 @@ function foo() { } }()); -var foo = (function() { +const foo2 = (function() { "use strict"; return function foo(a = 1) { diff --git a/docs/src/rules/switch-colon-spacing.md b/docs/src/rules/switch-colon-spacing.md index 63836a4222ba..8270fb4a4a51 100644 --- a/docs/src/rules/switch-colon-spacing.md +++ b/docs/src/rules/switch-colon-spacing.md @@ -2,9 +2,6 @@ title: switch-colon-spacing rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/switch-colon-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - Spacing around colons improves readability of `case`/`default` clauses. ## Rule Details diff --git a/docs/src/rules/symbol-description.md b/docs/src/rules/symbol-description.md index 438cb013ae9c..9322bc32f74b 100644 --- a/docs/src/rules/symbol-description.md +++ b/docs/src/rules/symbol-description.md @@ -9,16 +9,16 @@ further_reading: The `Symbol` function may have an optional description: ```js -var foo = Symbol("some description"); +const foo = Symbol("some description"); -var someString = "some description"; -var bar = Symbol(someString); +const someString = "some description"; +const bar = Symbol(someString); ``` Using `description` promotes easier debugging: when a symbol is logged the description is used: ```js -var foo = Symbol("some description"); +const foo = Symbol("some description"); > console.log(foo); // Symbol(some description) @@ -38,9 +38,8 @@ Examples of **incorrect** code for this rule: ```js /*eslint symbol-description: "error"*/ -/*eslint-env es6*/ -var foo = Symbol(); +const foo = Symbol(); ``` ::: @@ -51,12 +50,11 @@ Examples of **correct** code for this rule: ```js /*eslint symbol-description: "error"*/ -/*eslint-env es6*/ -var foo = Symbol("some description"); +const foo = Symbol("some description"); -var someString = "some description"; -var bar = Symbol(someString); +const someString = "some description"; +const bar = Symbol(someString); ``` ::: diff --git a/docs/src/rules/template-curly-spacing.md b/docs/src/rules/template-curly-spacing.md index 4ac71420fbef..0ba50a965b37 100644 --- a/docs/src/rules/template-curly-spacing.md +++ b/docs/src/rules/template-curly-spacing.md @@ -2,9 +2,6 @@ title: template-curly-spacing rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/template-curly-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - We can embed expressions in template strings with using a pair of `${` and `}`. This rule can force usage of spacing _within_ the curly brace pair according to style guides. diff --git a/docs/src/rules/template-tag-spacing.md b/docs/src/rules/template-tag-spacing.md index 1c3bb0867a3c..5172b7b352ee 100644 --- a/docs/src/rules/template-tag-spacing.md +++ b/docs/src/rules/template-tag-spacing.md @@ -6,7 +6,6 @@ further_reading: - https://exploringjs.com/es6/ch_template-literals.html#_examples-of-using-tagged-template-literals --- -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/template-tag-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). With ES6, it's possible to create functions called [tagged template literals](#further-reading) where the function parameters consist of a template literal's strings and expressions. diff --git a/docs/src/rules/unicode-bom.md b/docs/src/rules/unicode-bom.md index 6fcde6c3304e..148846773a6f 100644 --- a/docs/src/rules/unicode-bom.md +++ b/docs/src/rules/unicode-bom.md @@ -35,7 +35,7 @@ Example of **correct** code for this rule with the `"always"` option: /*eslint unicode-bom: ["error", "always"]*/ -var abc; +let abc; ``` ::: @@ -47,7 +47,7 @@ Example of **incorrect** code for this rule with the `"always"` option: ```js /*eslint unicode-bom: ["error", "always"]*/ -var abc; +let abc; ``` ::: @@ -61,7 +61,7 @@ Example of **correct** code for this rule with the default `"never"` option: ```js /*eslint unicode-bom: ["error", "never"]*/ -var abc; +let abc; ``` ::: @@ -75,7 +75,7 @@ Example of **incorrect** code for this rule with the `"never"` option: /*eslint unicode-bom: ["error", "never"]*/ -var abc; +let abc; ``` ::: diff --git a/docs/src/rules/use-isnan.md b/docs/src/rules/use-isnan.md index d6eda9d5d919..30cd375fd964 100644 --- a/docs/src/rules/use-isnan.md +++ b/docs/src/rules/use-isnan.md @@ -9,14 +9,14 @@ In JavaScript, `NaN` is a special value of the `Number` type. It's used to repre Because `NaN` is unique in JavaScript by not being equal to anything, including itself, the results of comparisons to `NaN` are confusing: -* `NaN === NaN` or `NaN == NaN` evaluate to false -* `NaN !== NaN` or `NaN != NaN` evaluate to true +* `NaN === NaN` or `NaN == NaN` evaluate to `false` +* `NaN !== NaN` or `NaN != NaN` evaluate to `true` Therefore, use `Number.isNaN()` or global `isNaN()` functions to test whether a value is `NaN`. ## Rule Details -This rule disallows comparisons to 'NaN'. +This rule disallows comparisons to `NaN`. Examples of **incorrect** code for this rule: @@ -219,11 +219,17 @@ Examples of **incorrect** code for this rule with `"enforceForIndexOf"` option s ```js /*eslint use-isnan: ["error", {"enforceForIndexOf": true}]*/ -var hasNaN = myArray.indexOf(NaN) >= 0; +const hasNaN = myArray.indexOf(NaN) >= 0; -var firstIndex = myArray.indexOf(NaN); +const firstIndex = myArray.indexOf(NaN); -var lastIndex = myArray.lastIndexOf(NaN); +const lastIndex = myArray.lastIndexOf(NaN); + +const indexWithSequenceExpression = myArray.indexOf((doStuff(), NaN)); + +const firstIndexFromSecondElement = myArray.indexOf(NaN, 1); + +const lastIndexFromSecondElement = myArray.lastIndexOf(NaN, 1); ``` ::: @@ -240,7 +246,7 @@ function myIsNaN(val) { } function indexOfNaN(arr) { - for (var i = 0; i < arr.length; i++) { + for (let i = 0; i < arr.length; i++) { if (myIsNaN(arr[i])) { return i; } @@ -249,7 +255,7 @@ function indexOfNaN(arr) { } function lastIndexOfNaN(arr) { - for (var i = arr.length - 1; i >= 0; i--) { + for (let i = arr.length - 1; i >= 0; i--) { if (myIsNaN(arr[i])) { return i; } @@ -257,22 +263,22 @@ function lastIndexOfNaN(arr) { return -1; } -var hasNaN = myArray.some(myIsNaN); +const hasNaN = myArray.some(myIsNaN); -var hasNaN = indexOfNaN(myArray) >= 0; +const hasNaN1 = indexOfNaN(myArray) >= 0; -var firstIndex = indexOfNaN(myArray); +const firstIndex = indexOfNaN(myArray); -var lastIndex = lastIndexOfNaN(myArray); +const lastIndex = lastIndexOfNaN(myArray); // ES2015 -var hasNaN = myArray.some(Number.isNaN); +const hasNaN2 = myArray.some(Number.isNaN); // ES2015 -var firstIndex = myArray.findIndex(Number.isNaN); +const firstIndex1 = myArray.findIndex(Number.isNaN); // ES2016 -var hasNaN = myArray.includes(NaN); +const hasNaN3 = myArray.includes(NaN); ``` ::: diff --git a/docs/src/rules/valid-jsdoc.md b/docs/src/rules/valid-jsdoc.md index 33b48dcbf3d1..2d9a806ec4a2 100644 --- a/docs/src/rules/valid-jsdoc.md +++ b/docs/src/rules/valid-jsdoc.md @@ -7,11 +7,11 @@ further_reading: - https://jsdoc.app --- +:::important +This rule was removed in ESLint v9.0.0 and replaced by the [`eslint-plugin-jsdoc`](https://github.com/gajus/eslint-plugin-jsdoc) equivalent. +::: - -This rule was [**deprecated**](https://eslint.org/blog/2018/11/jsdoc-end-of-life) in ESLint v5.10.0. - -[JSDoc](http://usejsdoc.org) generates application programming interface (API) documentation from specially-formatted comments in JavaScript code. For example, this is a JSDoc comment for a function: +[JSDoc](https://jsdoc.app) generates application programming interface (API) documentation from specially-formatted comments in JavaScript code. For example, this is a JSDoc comment for a function: ```js /** @@ -94,7 +94,6 @@ Examples of **correct** code for this rule: ```js /*eslint valid-jsdoc: "error"*/ -/*eslint-env es6*/ /** * Add two numbers. @@ -192,13 +191,12 @@ Examples of additional **incorrect** code for this rule with sample `"prefer": { ```js /*eslint valid-jsdoc: ["error", { "prefer": { "arg": "param", "argument": "param", "class": "constructor", "return": "returns", "virtual": "abstract" } }]*/ -/*eslint-env es6*/ /** * Add two numbers. - * @arg {int} num1 The first number. - * @arg {int} num2 The second number. - * @return {int} The sum of the two numbers. + * @arg {number} num1 The first number. + * @arg {number} num2 The second number. + * @return {number} The sum of the two numbers. */ function add(num1, num2) { return num1 + num2; @@ -238,7 +236,6 @@ Examples of additional **incorrect** code for this rule with sample `"preferType ```js /*eslint valid-jsdoc: ["error", { "preferType": { "Boolean": "boolean", "Number": "number", "object": "Object", "String": "string" } }]*/ -/*eslint-env es6*/ /** * Add two numbers. @@ -400,9 +397,9 @@ Example of additional **correct** code for this rule with the `"requireParamDesc /** * Add two numbers. - * @param {int} num1 - * @param {int} num2 - * @returns {int} The sum of the two numbers. + * @param {number} num1 + * @param {number} num2 + * @returns {number} The sum of the two numbers. */ function add(num1, num2) { return num1 + num2; diff --git a/docs/src/rules/valid-typeof.md b/docs/src/rules/valid-typeof.md index 5269cfddba71..4c01f582920f 100644 --- a/docs/src/rules/valid-typeof.md +++ b/docs/src/rules/valid-typeof.md @@ -15,12 +15,6 @@ For a vast majority of use cases, the result of the `typeof` operator is one of This rule enforces comparing `typeof` expressions to valid string literals. -## Options - -This rule has an object option: - -* `"requireStringLiterals": true` requires `typeof` expressions to only be compared to string literals or other `typeof` expressions, and disallows comparisons to any other value. - Examples of **incorrect** code for this rule: ::: incorrect @@ -51,6 +45,14 @@ typeof bar === typeof qux ::: +## Options + +This rule has an object option: + +* `"requireStringLiterals": true` allows the comparison of `typeof` expressions with only string literals or other `typeof` expressions, and disallows comparisons to any other value. Default is `false`. + +### requireStringLiterals + Examples of **incorrect** code with the `{ "requireStringLiterals": true }` option: ::: incorrect diff --git a/docs/src/rules/wrap-iife.md b/docs/src/rules/wrap-iife.md index 634d6acc593d..31484e819986 100644 --- a/docs/src/rules/wrap-iife.md +++ b/docs/src/rules/wrap-iife.md @@ -2,9 +2,6 @@ title: wrap-iife rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/wrap-iife) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - You can immediately invoke function expressions, but not function declarations. A common technique to create an immediately-invoked function expression (IIFE) is to wrap a function declaration in parentheses. The opening parentheses causes the contained function to be parsed as an expression, rather than a declaration. ```js diff --git a/docs/src/rules/wrap-regex.md b/docs/src/rules/wrap-regex.md index 39c6f4694b27..268e962acf21 100644 --- a/docs/src/rules/wrap-regex.md +++ b/docs/src/rules/wrap-regex.md @@ -2,9 +2,6 @@ title: wrap-regex rule_type: layout --- - -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/wrap-regex) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - When a regular expression is used in certain situations, it can end up looking like a division operator. For example: ```js diff --git a/docs/src/rules/yield-star-spacing.md b/docs/src/rules/yield-star-spacing.md index 73b8dae08366..479a4e37f39d 100644 --- a/docs/src/rules/yield-star-spacing.md +++ b/docs/src/rules/yield-star-spacing.md @@ -5,8 +5,6 @@ further_reading: - https://leanpub.com/understandinges6/read/#leanpub-auto-generators --- -This rule was **deprecated** in ESLint v8.53.0. Please use the [corresponding rule](https://eslint.style/rules/js/yield-star-spacing) in [`@stylistic/eslint-plugin-js`](https://eslint.style/packages/js). - ## Rule Details This rule enforces spacing around the `*` in `yield*` expressions. @@ -48,7 +46,6 @@ Examples of **correct** code for this rule with the default `"after"` option: ```js /*eslint yield-star-spacing: ["error", "after"]*/ -/*eslint-env es6*/ function* generator() { yield* other(); @@ -65,7 +62,6 @@ Examples of **correct** code for this rule with the `"before"` option: ```js /*eslint yield-star-spacing: ["error", "before"]*/ -/*eslint-env es6*/ function *generator() { yield *other(); @@ -82,7 +78,6 @@ Examples of **correct** code for this rule with the `"both"` option: ```js /*eslint yield-star-spacing: ["error", "both"]*/ -/*eslint-env es6*/ function * generator() { yield * other(); @@ -99,7 +94,6 @@ Examples of **correct** code for this rule with the `"neither"` option: ```js /*eslint yield-star-spacing: ["error", "neither"]*/ -/*eslint-env es6*/ function*generator() { yield*other(); diff --git a/docs/src/rules/yoda.md b/docs/src/rules/yoda.md index 28b86dd7d11a..5dfd2b171bc0 100644 --- a/docs/src/rules/yoda.md +++ b/docs/src/rules/yoda.md @@ -43,8 +43,8 @@ This rule can take a string option: The default `"never"` option can have exception options in an object literal: -* If the `"exceptRange"` property is `true`, the rule *allows* yoda conditions in range comparisons which are wrapped directly in parentheses, including the parentheses of an `if` or `while` condition. The default value is `false`. A *range* comparison tests whether a variable is inside or outside the range between two literal values. -* If the `"onlyEquality"` property is `true`, the rule reports yoda conditions *only* for the equality operators `==` and `===`. The default value is `false`. +* If the `"exceptRange"` property is `true`, the rule *allows* Yoda conditions in range comparisons which are wrapped directly in parentheses, including the parentheses of an `if` or `while` condition. The default value is `false`. A *range* comparison tests whether a variable is inside or outside the range between two literal values. +* If the `"onlyEquality"` property is `true`, the rule reports Yoda conditions *only* for the equality operators `==` and `===`. The default value is `false`. The `onlyEquality` option allows a superset of the exceptions which `exceptRange` allows, thus both options are not useful together. diff --git a/docs/src/src.json b/docs/src/src.json index be7b61608521..f9564fa00a57 100644 --- a/docs/src/src.json +++ b/docs/src/src.json @@ -1,3 +1,3 @@ { - "permalink": "{{ page.filePathStem }}.html" + "permalink": "{{ page.filePathStem }}.html" } diff --git a/docs/src/static/apple-touch-icon.png b/docs/src/static/apple-touch-icon.png index da15497bb789..5c83cec991ce 100644 Binary files a/docs/src/static/apple-touch-icon.png and b/docs/src/static/apple-touch-icon.png differ diff --git a/docs/src/static/favicon-32x32.png b/docs/src/static/favicon-32x32.png index 670b0cc1a0fc..5b30689b0462 100644 Binary files a/docs/src/static/favicon-32x32.png and b/docs/src/static/favicon-32x32.png differ diff --git a/docs/src/static/icon-192.png b/docs/src/static/icon-192.png index 0aa62e256ab4..4e1f7b79e67e 100644 Binary files a/docs/src/static/icon-192.png and b/docs/src/static/icon-192.png differ diff --git a/docs/src/static/icon.svg b/docs/src/static/icon.svg index c1024b19c1bf..07fe75811661 100644 --- a/docs/src/static/icon.svg +++ b/docs/src/static/icon.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/docs/src/use/command-line-interface.md b/docs/src/use/command-line-interface.md index e07ff80ed04a..c31fb03bfe1c 100644 --- a/docs/src/use/command-line-interface.md +++ b/docs/src/use/command-line-interface.md @@ -5,38 +5,62 @@ eleventyNavigation: parent: use eslint title: Command Line Interface Reference order: 4 - --- +{%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} +{%- from 'components/npx_tabs.macro.html' import npx_tabs %} + The ESLint Command Line Interface (CLI) lets you execute linting from the terminal. The CLI has a variety of options that you can pass to configure ESLint. ## Run the CLI -ESLint requires Node.js for installation. Follow the instructions in the [Getting Started Guide](getting-started) to install ESLint. +ESLint requires [Node.js](https://nodejs.org/) for installation. Follow the instructions in the [Getting Started Guide](getting-started) to install ESLint. Most users use [`npx`](https://docs.npmjs.com/cli/v8/commands/npx) to run ESLint on the command line like this: -```shell -npx eslint [options] [file|dir|glob]* -``` +{{ npx_tabs ({ + package: "eslint", + args: ["[options]", "[file|dir|glob]*"] +}) }} Such as: -```shell -# Run on two files -npx eslint file1.js file2.js +{{ npx_tabs ({ + package: "eslint", + args: ["file1.js", "file2.js"], + comment: "Run on two files" +}) }} -# Run on multiple files -npx eslint lib/** -``` +or + +{{ npx_tabs ({ + package: "eslint", + args: ["lib/**"], + comment: "Run on multiple files" +}) }} Please note that when passing a glob as a parameter, it is expanded by your shell. The results of the expansion can vary depending on your shell, and its configuration. If you want to use node `glob` syntax, you have to quote your parameter (using double quotes if you need it to run in Windows), as follows: -```shell -npx eslint "lib/**" -``` +{{ npx_tabs ({ + package: "eslint", + args: ["\"lib/**\""] +}) }} + +If you are using a [flat configuration file](./configure/configuration-files) (`eslint.config.js`), you can also omit the file arguments and ESLint will use `.`. For instance, these two lines perform the same operation: + +{{ npx_tabs ({ + package: "eslint", + args: ["."] +}) }} -**Note:** You can also use alternative package managers such as [Yarn](https://yarnpkg.com/) or [pnpm](https://pnpm.io/) to run ESLint. Please refer to your package manager's documentation for the correct syntax. +{{ npx_tabs ({ + package: "eslint", + args: [] +}) }} + +If you are not using a flat configuration file, running ESLint without file arguments results in an error. + +**Note:** You can also use alternative package managers such as [Yarn](https://yarnpkg.com/) or [pnpm](https://pnpm.io/) to run ESLint. For pnpm use `pnpm dlx eslint` and for Yarn use `yarn dlx eslint`. ## Pass Multiple Values to an Option @@ -44,11 +68,17 @@ Options that accept multiple values can be specified by repeating the option or Examples of options that accept multiple values: -```shell -npx eslint --ext .jsx --ext .js lib/ -# OR -npx eslint --ext .jsx,.js lib/ -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--global", "describe", "--global", "it", "tests/"] +}) }} + +OR + +{{ npx_tabs ({ + package: "eslint", + args: ["--global", "describe,it", "tests/"] +}) }} ## Options @@ -58,35 +88,33 @@ You can view all the CLI options by running `npx eslint -h`. eslint [options] file.js [file.js] [dir] Basic configuration: - --no-eslintrc Disable use of configuration from .eslintrc.* - -c, --config path::String Use this configuration, overriding .eslintrc.* config options if present - --env [String] Specify environments - --ext [String] Specify JavaScript file extensions + --no-config-lookup Disable look up for eslint.config.js + -c, --config path::String Use this configuration instead of eslint.config.js, eslint.config.mjs, or + eslint.config.cjs + --inspect-config Open the config inspector with the current configuration + --ext [String] Specify additional file extensions to lint --global [String] Define global variables --parser String Specify the parser to be used --parser-options Object Specify parser options - --resolve-plugins-relative-to path::String A folder where plugins should be resolved from, CWD by default -Specify rules and plugins: +Specify Rules and Plugins: --plugin [String] Specify plugins --rule Object Specify rules - --rulesdir [path::String] Load additional rules from this directory. Deprecated: Use rules from plugins -Fix problems: +Fix Problems: --fix Automatically fix problems --fix-dry-run Automatically fix problems without saving the changes to the file system --fix-type Array Specify the types of fixes to apply (directive, problem, suggestion, layout) -Ignore files: - --ignore-path path::String Specify path of ignore file +Ignore Files: --no-ignore Disable use of ignore files and patterns - --ignore-pattern [String] Pattern of files to ignore (in addition to those in .eslintignore) + --ignore-pattern [String] Patterns of files to ignore Use stdin: --stdin Lint code provided on - default: false --stdin-filename String Specify filename to process STDIN as -Handle warnings: +Handle Warnings: --quiet Report errors only - default: false --max-warnings Int Number of warnings to trigger nonzero exit code - default: -1 @@ -98,24 +126,38 @@ Output: Inline configuration comments: --no-inline-config Prevent comments from changing config or rules --report-unused-disable-directives Adds reported errors for unused eslint-disable and eslint-enable directives - --report-unused-disable-directives-severity String Chooses severity level for reporting unused eslint-disable and eslint-enable directives - either: off, warn, error, 0, 1, or 2 + --report-unused-disable-directives-severity String Chooses severity level for reporting unused eslint-disable and + eslint-enable directives - either: off, warn, error, 0, 1, or 2 + --report-unused-inline-configs String Adds reported errors for unused eslint inline config comments - either: off, warn, error, 0, 1, or 2 Caching: --cache Only check changed files - default: false --cache-file path::String Path to the cache file. Deprecated: use --cache-location - default: .eslintcache --cache-location path::String Path to the cache file or directory - --cache-strategy String Strategy to use for detecting changed files in the cache - either: metadata or content - default: metadata + --cache-strategy String Strategy to use for detecting changed files in the cache - either: metadata or + content - default: metadata + +Suppressing Violations: + --suppress-all Suppress all violations - default: false + --suppress-rule [String] Suppress specific rules + --suppressions-location path::String Specify the location of the suppressions file + --prune-suppressions Prune unused suppressions - default: false + --pass-on-unpruned-suppressions Ignore unused suppressions - default: false Miscellaneous: --init Run config initialization wizard - default: false --env-info Output execution environment information - default: false --no-error-on-unmatched-pattern Prevent errors when pattern is unmatched --exit-on-fatal-error Exit with exit code 2 in case of fatal error - default: false - --no-warn-ignored Suppress warnings when the file list includes ignored files. *Flat Config Mode Only* + --no-warn-ignored Suppress warnings when the file list includes ignored files + --pass-on-no-patterns Exit with exit code 0 in case no file patterns are passed --debug Output debugging information -h, --help Show help -v, --version Output the version number --print-config path::String Print the configuration for the given file + --stats Add statistics to the lint report - default: false + --flag [String] Enable a feature flag + --mcp Start the ESLint MCP server ``` ### Basic Configuration @@ -124,134 +166,174 @@ Miscellaneous: **eslintrc Mode Only.** Disables use of configuration from `.eslintrc.*` and `package.json` files. For flat config mode, use `--no-config-lookup` instead. -* **Argument Type**: No argument. +- **Argument Type**: No argument. ##### `--no-eslintrc` example -```shell -npx eslint --no-eslintrc file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--no-eslintrc", "file.js"] +}) }} #### `-c`, `--config` This option allows you to specify an additional configuration file for ESLint (see [Configure ESLint](configure/) for more). -* **Argument Type**: String. Path to file. -* **Multiple Arguments**: No +- **Argument Type**: String. Path to file. +- **Multiple Arguments**: No ##### `-c`, `--config` example -```shell -npx eslint -c ~/my-eslint.json file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["-c", "~/my.eslint.config.js", "file.js"] +}) }} -This example uses the configuration file at `~/my-eslint.json`. +This example uses the configuration file at `~/my.eslint.config.js`, which is used instead of searching for an `eslint.config.js` file. -If `.eslintrc.*` and/or `package.json` files are also used for configuration (i.e., `--no-eslintrc` was not specified), the configurations are merged. Options from this configuration file have precedence over the options from `.eslintrc.*` and `package.json` files. +#### `--inspect-config` + +**Flat Config Mode Only.** This option runs `npx @eslint/config-inspector@latest` to start the [config inspector](https://github.com/eslint/config-inspector). You can use the config inspector to better understand what your configuration is doing and which files it applies to. When you use this flag, the CLI does not perform linting. + +- **Argument Type**: No argument. + +##### `--inspect-config` example + +{{ npx_tabs ({ + package: "eslint", + args: ["--inspect-config"] +}) }} #### `--env` **eslintrc Mode Only.** This option enables specific environments. -* **Argument Type**: String. One of the available environments. -* **Multiple Arguments**: Yes +- **Argument Type**: String. One of the available environments. +- **Multiple Arguments**: Yes -Details about the global variables defined by each environment are available in the [Specifying Environments](configure/language-options#specifying-environments) documentation. This option only enables environments. It does not disable environments set in other configuration files. To specify multiple environments, separate them using commas, or use the option multiple times. +Details about the global variables defined by each environment are available in the [Specifying Environments](configure/language-options-deprecated#specifying-environments) documentation. This option only enables environments. It does not disable environments set in other configuration files. To specify multiple environments, separate them using commas, or use the option multiple times. ##### `--env` example -```shell -npx eslint --env browser,node file.js -npx eslint --env browser --env node file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--env", "browser,node", "file.js"] +}) }} + +{{ npx_tabs ({ + package: "eslint", + args: ["--env", "browser", "--env", "node", "file.js"] +}) }} #### `--ext` -**eslintrc Mode Only.** This option allows you to specify which file extensions ESLint uses when searching for target files in the directories you specify. +This option allows you to specify additional file extensions to lint. -* **Argument Type**: String. File extension. -* **Multiple Arguments**: Yes -* **Default Value**: `.js` and the files that match the `overrides` entries of your configuration. +- **Argument Type**: String. File extension. +- **Multiple Arguments**: Yes +- **Default Value**: By default, ESLint lints files with extensions `.js`, `.mjs`, `.cjs`, and additional extensions [specified in the configuration file](configure/configuration-files#specifying-files-with-arbitrary-extensions). -`--ext` is only used when the patterns to lint are directories. If you use glob patterns or file names, then `--ext` is ignored. For example, `npx eslint "lib/*" --ext .js` matches all files within the `lib/` directory, regardless of extension. +This option is primarily intended for use in combination with the `--no-config-lookup` option, since in that case there is no configuration file in which the additional extensions would be specified. ##### `--ext` example -```shell -# Use only .ts extension -npx eslint . --ext .ts +{{ npx_tabs ({ + package: "eslint", + args: [".", "--ext", ".ts"], + comment: "Include .ts files" +}) }} -# Use both .js and .ts -npx eslint . --ext .js --ext .ts +{{ npx_tabs ({ + package: "eslint", + args: [".", "--ext", ".ts", "--ext", ".tsx"], + comment: "Include .ts and .tsx files" +}) }} -# Also use both .js and .ts -npx eslint . --ext .js,.ts -``` +{{ npx_tabs ({ + package: "eslint", + args: [".", "--ext", ".ts,.tsx"], + comment: "Also include .ts and .tsx files" +}) }} #### `--global` -This option defines global variables so that they are not flagged as undefined by the [`no-undef`](../rules/no-undef) rule. +This option defines global variables so that they are not flagged as undefined by the [`no-undef`](../rules/no-undef) rule. -* **Argument Type**: String. Name of the global variable. Any specified global variables are assumed to be read-only by default, but appending `:true` to a variable's name ensures that `no-undef` also allows writes. -* **Multiple Arguments**: Yes +- **Argument Type**: String. Name of the global variable. Any specified global variables are assumed to be read-only by default, but appending `:true` to a variable's name ensures that `no-undef` also allows writes. +- **Multiple Arguments**: Yes ##### `--global` example -```shell -npx eslint --global require,exports:true file.js -npx eslint --global require --global exports:true -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--global", "require,exports:true", "file.js"] +}) }} + +{{ npx_tabs ({ + package: "eslint", + args: ["--global", "require", "--global", "exports:true"] +}) }} #### `--parser` This option allows you to specify a parser to be used by ESLint. -* **Argument Type**: String. Parser to be used by ESLint. -* **Multiple Arguments**: No -* **Default Value**: `espree` +- **Argument Type**: String. Parser to be used by ESLint. +- **Multiple Arguments**: No +- **Default Value**: `espree` ##### `--parser` example -```shell -# Use TypeScript ESLint parser -npx eslint --parser @typescript-eslint/parser file.ts -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--parser", "@typescript-eslint/parser", "file.ts"], + comment: "Use TypeScript ESLint parser" +}) }} #### `--parser-options` This option allows you to specify parser options to be used by ESLint. The available parser options are determined by the parser being used. -* **Argument Type**: Key/value pair separated by colon (`:`). -* **Multiple Arguments**: Yes +- **Argument Type**: Key/value pair separated by colon (`:`). +- **Multiple Arguments**: Yes ##### `--parser-options` example -```shell -echo '3 ** 4' | npx eslint --stdin --parser-options ecmaVersion:6 # fails with a parsing error -echo '3 ** 4' | npx eslint --stdin --parser-options ecmaVersion:7 # succeeds, yay! -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--stdin", "--parser-options", "ecmaVersion:6"], + comment: "fails with a parsing error", + previousCommands: ["echo \'3 ** 4\'"] +}) }} + +{{ npx_tabs ({ + package: "eslint", + args: ["--stdin", "--parser-options", "ecmaVersion:7"], + comment: "succeeds, yay!", + previousCommands: ["echo \'3 ** 4\'"] +}) }} #### `--resolve-plugins-relative-to` **eslintrc Mode Only.** Changes the directory where plugins are resolved from. -* **Argument Type**: String. Path to directory. -* **Multiple Arguments**: No -* **Default Value**: By default, plugins are resolved from the directory in which your configuration file is found. +- **Argument Type**: String. Path to directory. +- **Multiple Arguments**: No +- **Default Value**: By default, plugins are resolved from the directory in which your configuration file is found. This option should be used when plugins were installed by someone other than the end user. It should be set to the project directory of the project that has a dependency on the necessary plugins. For example: -* When using a config file that is located outside of the current project (with the `--config` flag), if the config uses plugins which are installed locally to itself, `--resolve-plugins-relative-to` should be set to the directory containing the config file. -* If an integration has dependencies on ESLint and a set of plugins, and the tool invokes ESLint on behalf of the user with a preset configuration, the tool should set `--resolve-plugins-relative-to` to the top-level directory of the tool. +- When using a config file that is located outside of the current project (with the `--config` flag), if the config uses plugins which are installed locally to itself, `--resolve-plugins-relative-to` should be set to the directory containing the config file. +- If an integration has dependencies on ESLint and a set of plugins, and the tool invokes ESLint on behalf of the user with a preset configuration, the tool should set `--resolve-plugins-relative-to` to the top-level directory of the tool. ##### `--resolve-plugins-relative-to` example -```shell -npx eslint --config ~/personal-eslintrc.js \ ---resolve-plugins-relative-to /usr/local/lib/ -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--config", "~/personal-eslintrc.js", "--resolve-plugins-relative-to", "/usr/local/lib/"] +}) }} ### Specify Rules and Plugins @@ -259,24 +341,29 @@ npx eslint --config ~/personal-eslintrc.js \ This option specifies a plugin to load. -* **Argument Type**: String. Plugin name. You can optionally omit the prefix `eslint-plugin-` from the plugin name. -* **Multiple Arguments**: Yes +- **Argument Type**: String. Plugin name. You can optionally omit the prefix `eslint-plugin-` from the plugin name. +- **Multiple Arguments**: Yes Before using the plugin, you have to install it using npm. ##### `--plugin` example -```shell -npx eslint --plugin jquery file.js -npx eslint --plugin eslint-plugin-mocha file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--plugin", "jquery", "file.js"] +}) }} + +{{ npx_tabs ({ + package: "eslint", + args: ["--plugin", "eslint-plugin-mocha", "file.js"] +}) }} #### `--rule` This option specifies the rules to be used. -* **Argument Type**: Rules and their configuration specified with [levn](https://github.com/gkz/levn#levn--) format. -* **Multiple Arguments**: Yes +- **Argument Type**: Rules and their configuration specified with [levn](https://github.com/gkz/levn#levn--) format. +- **Multiple Arguments**: Yes These rules are merged with any rules specified with configuration files. If the rule is defined in a plugin, you have to prefix the rule ID with the plugin name and a `/`. @@ -284,42 +371,60 @@ To ignore rules in `.eslintrc` configuration files and only run rules specified ##### `--rule` example -```shell -# Apply single rule -npx eslint --rule 'quotes: [error, double]' -# Apply multiple rules -npx eslint --rule 'guard-for-in: error' --rule 'brace-style: [error, 1tbs]' -# Apply rule from jquery plugin -npx eslint --rule 'jquery/dollar-sign: error' -# Only apply rule from the command line -npx eslint --rule 'quotes: [error, double]' --no-eslintrc -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--rule", "\'quotes: [error, double]\'"], + comment: "Apply single rule" +}) }} + +{{ npx_tabs ({ + package: "eslint", + args: ["--rule", "\'guard-for-in: error\'", "--rule", "\'brace-style: [error, 1tbs]\'"], + comment: "Apply multiple rules" +}) }} + +{{ npx_tabs ({ + package: "eslint", + args: ["--rule", "\'jquery/dollar-sign: error\'"], + comment: "Apply rule from jquery plugin" +}) }} + +{{ npx_tabs ({ + package: "eslint", + args: ["--rule", "\'quotes: [error, double]\'", "--no-eslintrc"], + comment: "Only apply rule from the command line" +}) }} #### `--rulesdir` **Deprecated**: Use rules from plugins instead. -This option allows you to specify another directory from which to load rules files. This allows you to dynamically load new rules at run time. This is useful when you have custom rules that aren't suitable for being bundled with ESLint. +**eslintrc Mode Only.** This option allows you to specify another directory from which to load rules files. This allows you to dynamically load new rules at run time. This is useful when you have custom rules that aren't suitable for being bundled with ESLint. -* **Argument Type**: String. Path to directory. The rules in your custom rules directory must follow the same format as bundled rules to work properly. -* **Multiple Arguments**: Yes. +- **Argument Type**: String. Path to directory. The rules in your custom rules directory must follow the same format as bundled rules to work properly. +- **Multiple Arguments**: Yes Note that, as with core rules and plugin rules, you still need to enable the rules in configuration or via the `--rule` CLI option in order to actually run those rules during linting. Specifying a rules directory with `--rulesdir` does not automatically enable the rules within that directory. ##### `--rulesdir` example -```shell -npx eslint --rulesdir my-rules/ file.js -npx eslint --rulesdir my-rules/ --rulesdir my-other-rules/ file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--rulesdir", "my-rules/", "file.js"] +}) }} + +{{ npx_tabs ({ + package: "eslint", + args: ["--rulesdir", "my-rules/", "--rulesdir", "my-other-rules/", "file.js"] +}) }} ### Fix Problems #### `--fix` -This option instructs ESLint to try to fix as many issues as possible. The fixes are made to the actual files themselves and only the remaining unfixed issues are output. +This option instructs ESLint to try to [fix](core-concepts#rule-fixes) as many issues as possible. The fixes are made to the actual files themselves and only the remaining unfixed issues are output. -* **Argument Type**: No argument. +- **Argument Type**: No argument. Not all problems are fixable using this option, and the option does not work in these situations: @@ -330,15 +435,16 @@ If you want to fix code from `stdin` or otherwise want to get the fixes without ##### `--fix` example -```shell -npx eslint --fix file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--fix", "file.js"] +}) }} #### `--fix-dry-run` This option has the same effect as `--fix` with the difference that the fixes are not saved to the file system. Because the default formatter does not output the fixed code, you'll have to use another formatter (e.g. `--format json`) to get the fixes. -* **Argument Type**: No argument. +- **Argument Type**: No argument. This makes it possible to fix code from `stdin` when used with the `--stdin` flag. @@ -346,30 +452,41 @@ This flag can be useful for integrations (e.g. editor plugins) which need to aut ##### `--fix-dry-run` example -```shell -getSomeText | npx eslint --stdin --fix-dry-run --format json -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--stdin", "--fix-dry-run", "--format", "json"], + previousCommands: ["getSomeText"] +}) }} #### `--fix-type` This option allows you to specify the type of fixes to apply when using either `--fix` or `--fix-dry-run`. -* **Argument Type**: String. One of the following fix types: - 1. `problem` - fix potential errors in the code - 1. `suggestion` - apply fixes to the code that improve it - 1. `layout` - apply fixes that do not change the program structure (AST) - 1. `directive` - apply fixes to inline directives such as `// eslint-disable` -* **Multiple Arguments**: Yes +- **Argument Type**: String. One of the following fix types: + 1. `problem` - fix potential errors in the code + 1. `suggestion` - apply fixes to the code that improve it + 1. `layout` - apply fixes that do not change the program structure (AST) + 1. `directive` - apply fixes to inline directives such as `// eslint-disable` +- **Multiple Arguments**: Yes This option is helpful if you are using another program to format your code, but you would still like ESLint to apply other types of fixes. ##### `--fix-type` example -```shell -npx eslint --fix --fix-type suggestion . -npx eslint --fix --fix-type suggestion --fix-type problem . -npx eslint --fix --fix-type suggestion,layout . -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--fix", "--fix-type", "suggestion", "."] +}) }} + +{{ npx_tabs ({ + package: "eslint", + args: ["--fix", "--fix-type", "suggestion", "--fix-type", "problem", "."] +}) }} + +{{ npx_tabs ({ + package: "eslint", + args: ["--fix", "--fix-type", "suggestion,layout", "."] +}) }} ### Ignore Files @@ -377,43 +494,50 @@ npx eslint --fix --fix-type suggestion,layout . **eslintrc Mode Only.** This option allows you to specify the file to use as your `.eslintignore`. -* **Argument Type**: String. Path to file. -* **Multiple Arguments**: No -* **Default Value**: By default, ESLint looks for `.eslintignore` in the current working directory. +- **Argument Type**: String. Path to file. +- **Multiple Arguments**: No +- **Default Value**: By default, ESLint looks for `.eslintignore` in the current working directory. -**Note:** `--ignore-path` is not supported when using [flat configuration](./configure/configuration-files-new) (`eslint.config.js`). +**Note:** `--ignore-path` is only supported when using [deprecated configuration](./configure/configuration-files-deprecated). If you want to include patterns from a `.gitignore` file in your `eslint.config.js` file, please see [including `.gitignore` files](./configure/ignore#including-gitignore-files). ##### `--ignore-path` example -```shell -npx eslint --ignore-path tmp/.eslintignore file.js -npx eslint --ignore-path .gitignore file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--ignore-path", "tmp/.eslintignore", "file.js"] +}) }} + +{{ npx_tabs ({ + package: "eslint", + args: ["--ignore-path", ".gitignore", "file.js"] +}) }} #### `--no-ignore` Disables excluding of files from `.eslintignore` files, `--ignore-path` flags, `--ignore-pattern` flags, and the `ignorePatterns` property in config files. -* **Argument Type**: No argument. +- **Argument Type**: No argument. ##### `--no-ignore` example -```shell -npx eslint --no-ignore file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--no-ignore", "file.js"] +}) }} #### `--ignore-pattern` -This option allows you to specify patterns of files to ignore (in addition to those in `.eslintignore`). +This option allows you to specify patterns of files to ignore. In eslintrc mode, these are in addition to `.eslintignore`. -* **Argument Type**: String. The supported syntax is the same as for [`.eslintignore` files](configure/ignore#the-eslintignore-file), which use the same patterns as the [`.gitignore` specification](https://git-scm.com/docs/gitignore). You should quote your patterns in order to avoid shell interpretation of glob patterns. -* **Multiple Arguments**: Yes +- **Argument Type**: String. The supported syntax is the same as for [`.eslintignore` files](configure/ignore-deprecated#the-eslintignore-file), which use the same patterns as the [`.gitignore` specification](https://git-scm.com/docs/gitignore). You should quote your patterns in order to avoid shell interpretation of glob patterns. +- **Multiple Arguments**: Yes ##### `--ignore-pattern` example -```shell -npx eslint --ignore-pattern "/lib/" --ignore-pattern "/src/vendor/*" . -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--ignore-pattern", "\"/lib/\"", "--ignore-pattern", "\"/src/vendor/*\"", "."] +}) }} ### Use stdin @@ -421,57 +545,67 @@ npx eslint --ignore-pattern "/lib/" --ignore-pattern "/src/vendor/*" . This option tells ESLint to read and lint source code from STDIN instead of from files. You can use this to pipe code to ESLint. -* **Argument Type**: No argument. +- **Argument Type**: No argument. ##### `--stdin` example -```shell -cat myfile.js | npx eslint --stdin -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--stdin"], + previousCommands: ["cat myFile.js"] +}) }} #### `--stdin-filename` This option allows you to specify a filename to process STDIN as. -* **Argument Type**: String. Path to file. -* **Multiple Arguments**: No +- **Argument Type**: String. Path to file. +- **Multiple Arguments**: No This is useful when processing files from STDIN and you have rules which depend on the filename. ##### `--stdin-filename` example -```shell -cat myfile.js | npx eslint --stdin --stdin-filename myfile.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--stdin", "--stdin-filename", "myfile.js"], + previousCommands: ["cat myFile.js"] +}) }} ### Handle Warnings #### `--quiet` -This option allows you to disable reporting on warnings. If you enable this option, only errors are reported by ESLint. +This option allows you to disable reporting on warnings and running of rules set to warn. If you enable this option, only errors are reported by ESLint and only rules set to error will be run. -* **Argument Type**: No argument. +- **Argument Type**: No argument. ##### `--quiet` example -```shell -npx eslint --quiet file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--quiet", "file.js"] +}) }} #### `--max-warnings` This option allows you to specify a warning threshold, which can be used to force ESLint to exit with an error status if there are too many warning-level rule violations in your project. -* **Argument Type**: Integer. The maximum number of warnings to allow. To prevent this behavior, do not use this option or specify `-1` as the argument. -* **Multiple Arguments**: No +- **Argument Type**: Integer. The maximum number of warnings to allow. To prevent this behavior, do not use this option or specify `-1` as the argument. +- **Multiple Arguments**: No Normally, if ESLint runs and finds no errors (only warnings), it exits with a success exit status. However, if `--max-warnings` is specified and the total warning count is greater than the specified threshold, ESLint exits with an error status. +::: important +When used alongside `--quiet`, this will cause rules marked as warn to still be run, but not reported. +::: + ##### `--max-warnings` example -```shell -npx eslint --max-warnings 10 file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--max-warnings", "10", "file.js"] +}) }} ### Output @@ -479,22 +613,23 @@ npx eslint --max-warnings 10 file.js Write the output of linting results to a specified file. -* **Argument Type**: String. Path to file. -* **Multiple Arguments**: No +- **Argument Type**: String. Path to file. +- **Multiple Arguments**: No ##### `-o`, `--output-file` example -```shell -npx eslint -o ./test/test.html -``` +{{ npx_tabs ({ + package: "eslint", + args: ["-o", "./test/test.html"] +}) }} #### `-f`, `--format` This option specifies the output format for the console. -* **Argument Type**: String. One of the [built-in formatters](formatters/) or a custom formatter. -* **Multiple Arguments**: No -* **Default Value**: [`stylish`](formatters/#stylish) +- **Argument Type**: String. One of the [built-in formatters](formatters/) or a custom formatter. +- **Multiple Arguments**: No +- **Default Value**: [`stylish`](formatters/#stylish) If you are using a custom formatter defined in a local file, you can specify the path to the custom formatter file. @@ -502,50 +637,69 @@ An npm-installed formatter is resolved with or without `eslint-formatter-` prefi When specified, the given format is output to the console. If you'd like to save that output into a file, you can do so on the command line like so: -```shell -# Saves the output into the `results.txt` file. -npx eslint -f compact file.js > results.txt -``` +{{ npx_tabs ({ + package: "eslint", + args: ["-f", "json", "file.js", ">", "results.json"], + comment: "Saves the output into the `results.json` file." +}) }} ##### `-f`, `--format` example -Use the built-in `compact` formatter: +Use the built-in `json` formatter: -```shell -npx eslint --format compact file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--format", "json", "file.js"] +}) }} Use a local custom formatter: -```shell -npx eslint -f ./customformat.js file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["-f", "./customformat.js", "file.js"] +}) }} Use an npm-installed formatter: -```shell -npm install eslint-formatter-pretty +{{ npm_tabs({ + command: "install", + packages: ["eslint-formatter-pretty"], + args: [] +}) }} -# Then run one of the following commands -npx eslint -f pretty file.js -# or alternatively -npx eslint -f eslint-formatter-pretty file.js -``` +Then run one of the following commands + +{{ npx_tabs ({ + package: "eslint", + args: ["-f", "pretty", "file.js"] +}) }} + +or alternatively + +{{ npx_tabs ({ + package: "eslint", + args: ["-f", "eslint-formatter-pretty", "file.js"] +}) }} #### `--color` and `--no-color` These options force the enabling/disabling of colorized output. -* **Argument Type**: No argument. +- **Argument Type**: No argument. You can use these options to override the default behavior, which is to enable colorized output unless no TTY is detected, such as when piping `eslint` through `cat` or `less`. ##### `--color` and `--no-color` example -```shell -npx eslint --color file.js | cat -npx eslint --no-color file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--color", "file.js", "|", "cat"] +}) }} + +{{ npx_tabs ({ + package: "eslint", + args: ["--no-color", "file.js"] +}) }} ### Inline Configuration Comments @@ -554,29 +708,30 @@ npx eslint --no-color file.js This option prevents inline comments like `/*eslint-disable*/` or `/*global foo*/` from having any effect. -* **Argument Type**: No argument. +- **Argument Type**: No argument. This allows you to set an ESLint config without files modifying it. All inline config comments are ignored, such as: -* `/*eslint-disable*/` -* `/*eslint-enable*/` -* `/*global*/` -* `/*eslint*/` -* `/*eslint-env*/` -* `// eslint-disable-line` -* `// eslint-disable-next-line` +- `/*eslint-disable*/` +- `/*eslint-enable*/` +- `/*global*/` +- `/*eslint*/` +- `/*eslint-env*/` +- `// eslint-disable-line` +- `// eslint-disable-next-line` ##### `--no-inline-config` example -```shell -npx eslint --no-inline-config file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--no-inline-config", "file.js"] +}) }} #### `--report-unused-disable-directives` This option causes ESLint to report directive comments like `// eslint-disable-line` when no errors would have been reported on that line anyway. -* **Argument Type**: No argument. +- **Argument Type**: No argument. This can be useful to prevent future errors from unexpectedly being suppressed, by cleaning up old `eslint-disable` and `eslint-enable` comments which are no longer applicable. @@ -588,25 +743,47 @@ For example, suppose a rule has a bug that causes it to report a false positive, ##### `--report-unused-disable-directives` example -```shell -npx eslint --report-unused-disable-directives file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--report-unused-disable-directives", "file.js"] +}) }} #### `--report-unused-disable-directives-severity` Same as [`--report-unused-disable-directives`](#--report-unused-disable-directives), but allows you to specify the severity level (`error`, `warn`, `off`) of the reported errors. Only one of these two options can be used at a time. -* **Argument Type**: String. One of the following values: - 1. `off` (or `0`) - 1. `warn` (or `1`) - 1. `error` (or `2`) -* **Multiple Arguments**: No -* **Default Value**: By default, `linterOptions.reportUnusedDisableDirectives` configuration setting is used. +- **Argument Type**: String. One of the following values: + 1. `off` (or `0`) + 1. `warn` (or `1`) + 1. `error` (or `2`) +- **Multiple Arguments**: No +- **Default Value**: By default, `linterOptions.reportUnusedDisableDirectives` configuration setting is used (which defaults to `"warn"`). ##### `--report-unused-disable-directives-severity` example +{{ npx_tabs ({ + package: "eslint", + args: ["--report-unused-disable-directives-severity", "warn", "file.js"] +}) }} + +#### `--report-unused-inline-configs` + +This option causes ESLint to report inline config comments like `/* eslint rule-name: "error" */` whose rule severity and any options match what's already been configured. + +- **Argument Type**: String. One of the following values: + 1. `off` (or `0`) + 1. `warn` (or `1`) + 1. `error` (or `2`) +- **Multiple Arguments**: No +- **Default Value**: By default, `linterOptions.reportUnusedInlineConfigs` configuration setting is used (which defaults to `"off"`). + +This can be useful to keep files clean and devoid of misleading clutter. +Inline config comments are meant to change ESLint's behavior in some way: if they change nothing, there is no reason to leave them in. + +##### `--report-unused-inline-configs` example + ```shell -npx eslint --report-unused-disable-directives-severity warn file.js +npx eslint --report-unused-inline-configs error file.js ``` ### Caching @@ -616,7 +793,7 @@ npx eslint --report-unused-disable-directives-severity warn file.js Store the info about processed files in order to only operate on the changed ones. Enabling this option can dramatically improve ESLint's run time performance by ensuring that only changed files are linted. The cache is stored in `.eslintcache` by default. -* **Argument Type**: No argument. +- **Argument Type**: No argument. If you run ESLint with `--cache` and then run ESLint without `--cache`, the `.eslintcache` file will be deleted. This is necessary because the results of the lint might change and make `.eslintcache` invalid. If you want to control when the cache file is deleted, then use `--cache-location` to specify an alternate location for the cache file. @@ -624,9 +801,10 @@ Autofixed files are not placed in the cache. Subsequent linting that does not tr ##### `--cache` example -```shell -npx eslint --cache file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--cache", "file.js"] +}) }} #### `--cache-file` @@ -638,157 +816,294 @@ Path to the cache file. If none specified `.eslintcache` is used. The file is cr Specify the path to the cache location. Can be a file or a directory. -* **Argument Type**: String. Path to file or directory. If a directory is specified, a cache file is created inside the specified folder. The name of the file is based on the hash of the current working directory, e.g.: `.cache_hashOfCWD`. -* **Multiple Arguments**: No -* **Default Value**: If no location is specified, `.eslintcache` is used. The file is created in the directory where the `eslint` command is executed. +- **Argument Type**: String. Path to file or directory. If a directory is specified, a cache file is created inside the specified folder. The name of the file is based on the hash of the current working directory, e.g.: `.cache_hashOfCWD`. +- **Multiple Arguments**: No +- **Default Value**: If no location is specified, `.eslintcache` is used. The file is created in the directory where the `eslint` command is executed. If the directory for the cache does not exist make sure you add a trailing `/` on \*nix systems or `\` on Windows. Otherwise, the path is assumed to be a file. ##### `--cache-location` example -```shell -npx eslint "src/**/*.js" --cache --cache-location "/Users/user/.eslintcache/" -``` +{{ npx_tabs ({ + package: "eslint", + args: ["\"src/**/*.js\"", "--cache", "--cache-location", "\"/Users/user/.eslintcache/\""] +}) }} #### `--cache-strategy` Strategy for the cache to use for detecting changed files. -* **Argument Type**: String. One of the following values: - 1. `metadata` - 1. `content` -* **Multiple Arguments**: No -* **Default Value**: `metadata` +- **Argument Type**: String. One of the following values: + 1. `metadata` + 1. `content` +- **Multiple Arguments**: No +- **Default Value**: `metadata` -The `content` strategy can be useful in cases where the modification time of your files changes even if their contents have not. For example, this can happen during git operations like `git clone` because git does not track file modification time. +The `content` strategy can be useful in cases where the modification time of your files changes even if their contents have not. For example, this can happen during git operations like [`git clone`](https://git-scm.com/docs/git-clone) because git does not track file modification time. ##### `--cache-strategy` example -```shell -npx eslint "src/**/*.js" --cache --cache-strategy content -``` +{{ npx_tabs ({ + package: "eslint", + args: ["\"src/**/*.js\"", "--cache", "--cache-strategy", "content"] +}) }} + +### Suppressing Violations + +#### `--suppress-all` + +Suppresses existing violations, so that they are not being reported in subsequent runs. It allows you to enable one or more lint rules and be notified only when new violations show up. The suppressions are stored in `eslint-suppressions.json` by default, unless otherwise specified by `--suppressions-location`. The file gets updated with the new suppressions. + +- **Argument Type**: No argument. + +##### `--suppress-all` example + +{{ npx_tabs ({ + package: "eslint", + args: ["\"src/**/*.js\"", "--suppress-all"] +}) }} + +#### `--suppress-rule` + +Suppresses violations for specific rules, so that they are not being reported in subsequent runs. Similar to `--suppress-all`, the suppressions are stored in `eslint-suppressions.json` by default, unless otherwise specified by `--suppressions-location`. The file gets updated with the new suppressions. + +- **Argument Type**: String. Rule ID. +- **Multiple Arguments**: Yes + +##### `--suppress-rule` example + +{{ npx_tabs ({ + package: "eslint", + args: ["\"src/**/*.js\"", "--suppress-rule", "no-console", "--suppress-rule", "indent"] +}) }} + +#### `--suppressions-location` + +Specify the path to the suppressions location. Can be a file or a directory. + +- **Argument Type**: String. Path to file. If a directory is specified, a cache file is created inside the specified folder. The name of the file is based on the hash of the current working directory, e.g.: `suppressions_hashOfCWD` +- **Multiple Arguments**: No +- **Default Value**: If no location is specified, `eslint-suppressions.json` is used. The file is created in the directory where the `eslint` command is executed. + +##### `--suppressions-location` example + +{{ npx_tabs ({ + package: "eslint", + args: ["\"src/**/*.js\"", "--suppressions-location", "\".eslint-suppressions-example.json\""] +}) }} + +#### `--prune-suppressions` + +Prune unused suppressions from the suppressions file. This option is useful when you addressed one or more of the suppressed violations. + +- **Argument Type**: No argument. + +##### `--prune-suppressions` example + +{{ npx_tabs ({ + package: "eslint", + args: ["\"src/**/*.js\"", "--prune-suppressions"] +}) }} + +#### `--pass-on-unpruned-suppressions` + +Ignore unused suppressions. By default, ESLint exits with exit code `2` and displays an error message if there are unused suppressions in the suppressions file. When you use this flag, unused suppressions do not affect the exit code and ESLint doesn't output an error about unused suppressions. + +- **Argument Type**: No argument. + +##### `--pass-on-unpruned-suppressions` example + +{{ npx_tabs ({ + package: "eslint", + args: ["\"src/**/*.js\"", "--pass-on-unpruned-suppressions"] +}) }} ### Miscellaneous #### `--init` -This option runs `npm init @eslint/config` to start the config initialization wizard. It's designed to help new users quickly create an `.eslintrc` file by answering a few questions. When you use this flag, the CLI does not perform linting. +This option runs `npm init @eslint/config` to start the config initialization wizard. It's designed to help new users quickly create an `eslint.config.js` file by answering a few questions. When you use this flag, the CLI does not perform linting. -* **Argument Type**: No argument. +- **Argument Type**: No argument. The resulting configuration file is created in the current directory. ##### `--init` example -```shell -npx eslint --init -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--init"] +}) }} #### `--env-info` This option outputs information about the execution environment, including the version of Node.js, npm, and local and global installations of ESLint. -* **Argument Type**: No argument. +- **Argument Type**: No argument. The ESLint team may ask for this information to help solve bugs. When you use this flag, the CLI does not perform linting. ##### `--env-info` example -```shell -npx eslint --env-info -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--env-info"] +}) }} #### `--no-error-on-unmatched-pattern` This option prevents errors when a quoted glob pattern or `--ext` is unmatched. This does not prevent errors when your shell can't match a glob. -* **Argument Type**: No argument. +- **Argument Type**: No argument. ##### `--no-error-on-unmatched-pattern` example -```shell -npx eslint --no-error-on-unmatched-pattern --ext .ts "lib/*" -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--no-error-on-unmatched-pattern", "--ext", ".ts", "\"lib/*\""] +}) }} #### `--exit-on-fatal-error` This option causes ESLint to exit with exit code 2 if one or more fatal parsing errors occur. Without this option, ESLint reports fatal parsing errors as rule violations. -* **Argument Type**: No argument. +- **Argument Type**: No argument. ##### `--exit-on-fatal-error` example -```shell -npx eslint --exit-on-fatal-error file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--exit-on-fatal-error", "file.js"] +}) }} #### `--no-warn-ignored` **Flat Config Mode Only.** This option suppresses both `File ignored by default` and `File ignored because of a matching ignore pattern` warnings when an ignored filename is passed explicitly. It is useful when paired with `--max-warnings 0` as it will prevent exit code 1 due to the aforementioned warning. -* **Argument Type**: No argument. +- **Argument Type**: No argument. ##### `--no-warn-ignored` example -```shell -npx eslint --no-warn-ignored --max-warnings 0 ignored-file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--no-warn-ignored", "--max-warnings", "0", "ignored-file.js"] +}) }} + +#### `--pass-on-no-patterns` + +This option allows ESLint to exit with code 0 when no file or directory patterns are passed. Without this option, ESLint assumes you want to use `.` as the pattern. (When running in legacy eslintrc mode, ESLint will exit with code 1.) + +- **Argument Type**: No argument. + +##### `--pass-on-no-patterns` example + +{{ npx_tabs ({ + package: "eslint", + args: ["--pass-on-no-patterns"] +}) }} #### `--debug` This option outputs debugging information to the console. Add this flag to an ESLint command line invocation in order to get extra debugging information while the command runs. -* **Argument Type**: No argument. +- **Argument Type**: No argument. This information is useful when you're seeing a problem and having a hard time pinpointing it. The ESLint team may ask for this debugging information to help solve bugs. ##### `--debug` example -```shell -npx eslint --debug test.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--debug", "test.js"] +}) }} #### `-h`, `--help` This option outputs the help menu, displaying all of the available options. All other options are ignored when this is present. When you use this flag, the CLI does not perform linting. -* **Argument Type**: No argument. +- **Argument Type**: No argument. ##### `-h`, `--help` example -```shell -npx eslint --help -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--help"] +}) }} #### `-v`, `--version` This option outputs the current ESLint version onto the console. All other options are ignored when this is present. When you use this flag, the CLI does not perform linting. -* **Argument Type**: No argument. +- **Argument Type**: No argument. ##### `-v`, `--version` example -```shell -npx eslint --version -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--version"] +}) }} #### `--print-config` This option outputs the configuration to be used for the file passed. When present, no linting is performed and only config-related options are valid. When you use this flag, the CLI does not perform linting. -* **Argument Type**: String. Path to file. -* **Multiple Arguments**: No +- **Argument Type**: String. Path to file. +- **Multiple Arguments**: No ##### `--print-config` example -```shell -npx eslint --print-config file.js -``` +{{ npx_tabs ({ + package: "eslint", + args: ["--print-config", "file.js"] +}) }} + +#### `--stats` + +This option adds a series of detailed performance statistics (see [Stats type](../extend/stats#-stats-type)) such as the _parse_-, _fix_- and _lint_-times (time per rule) to [`result`](../extend/custom-formatters#the-result-object) objects that are passed to the formatter (see [Stats CLI usage](../extend/stats#cli-usage)). + +- **Argument Type**: No argument. + +This option is intended for use with custom formatters that display statistics. It can also be used with the built-in `json` formatter. + +##### `--stats` example + +{{ npx_tabs ({ + package: "eslint", + args: ["--stats", "--format", "json", "file.js"] +}) }} + +#### `--flag` + +This option enables one or more feature flags for ESLint. + +- **Argument Type**: String. A feature identifier. +- **Multiple Arguments**: Yes + +##### `--flag` example + +{{ npx_tabs ({ + package: "eslint", + args: ["--flag", "x_feature", "file.js"] +}) }} + +#### `--mcp` + +This option starts the ESLint MCP server for use with AI agents. + +- **Argument Type**: No argument. +- **Multiple Arguments**: No + +##### `--mcp` example + +{{ npx_tabs ({ + package: "eslint", + args: ["--mcp"] +}) }} ## Exit Codes When linting files, ESLint exits with one of the following exit codes: -* `0`: Linting was successful and there are no linting errors. If the [`--max-warnings`](#--max-warnings) flag is set to `n`, the number of linting warnings is at most `n`. -* `1`: Linting was successful and there is at least one linting error, or there are more linting warnings than allowed by the `--max-warnings` option. -* `2`: Linting was unsuccessful due to a configuration problem or an internal error. +- `0`: Linting was successful and there are no linting errors. If the [`--max-warnings`](#--max-warnings) flag is set to `n`, the number of linting warnings is at most `n`. +- `1`: Linting was successful and there is at least one linting error, or there are more linting warnings than allowed by the [`--max-warnings`](#--max-warnings) option. +- `2`: Linting was unsuccessful due to a configuration problem or an internal error. diff --git a/docs/src/use/configure/combine-configs.md b/docs/src/use/configure/combine-configs.md new file mode 100644 index 000000000000..e5a5b8047b2d --- /dev/null +++ b/docs/src/use/configure/combine-configs.md @@ -0,0 +1,119 @@ +--- +title: Combine Configs +eleventyNavigation: + key: combine configs + parent: configure + title: Combine Configs + order: 6 +--- + +In many cases, you won't write an ESLint config file from scratch, but rather, you'll use a combination of predefined and shareable configs along with your own overrides to create the config for your project. This page explains some of the patterns you can use to combine configs in your configuration file. + +## Apply a Config Object + +If you are importing a [config object](../core-concepts/glossary#config-object) from another module, in most cases, you can just pass the object directly to the `defineConfig()` helper. For example, you can use the recommended rule configurations for JavaScript by importing the `recommended` config and using it in your array: + +```js +// eslint.config.js +import js from "@eslint/js"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + js.configs.recommended, + { + rules: { + "no-unused-vars": "warn", + }, + }, +]); +``` + +Here, the `js.configs.recommended` predefined configuration is applied first and then another configuration object adds the desired configuration for `no-unused-vars`. + +### Apply a Configuration to a Subset of Files + +You can apply a config object to just a subset of files by creating a new object with a `files` key and using the `extends` key to merge in the rest of the properties from the config object. For example: + +```js +// eslint.config.js +import js from "@eslint/js"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/src/safe/*.js"], + plugins: { + js, + }, + extends: ["js/recommended"], + }, +]); +``` + +Here, the `js/recommended` config object is applied only to files that match the pattern "`**/src/safe/*.js"`. + +## Apply a Config Array + +If you are importing a [config array](../core-concepts/glossary#config-array) from another module, pass the array directly to the `defineConfig()` helper. Here's an example: + +```js +// eslint.config.js +import exampleConfigs from "eslint-config-example"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + // insert array directly + exampleConfigs, + + // your modifications + { + rules: { + "no-unused-vars": "warn", + }, + }, +]); +``` + +Here, the `exampleConfigs` shareable configuration is applied first and then another configuration object adds the desired configuration for `no-unused-vars`. This is equivalent to inserting the individual elements of `exampleConfigs` in order, such as: + +```js +// eslint.config.js +import exampleConfigs from "eslint-config-example"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + // insert individual elements instead of an array + exampleConfigs[0], + exampleConfigs[1], + exampleConfigs[2], + + // your modifications + { + rules: { + "no-unused-vars": "warn", + }, + }, +]); +``` + +### Apply a Config Array to a Subset of Files + +You can apply a config array (an array of configuration objects) to just a subset of files by using the `extends` key. For example: + +```js +// eslint.config.js +import exampleConfigs from "eslint-config-example"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/src/safe/*.js"], + extends: [exampleConfigs], + rules: { + "no-unused-vars": "warn", + }, + }, +]); +``` + +Here, each config object in `exampleConfigs` is applied only to files that match the pattern "`**/src/safe/*.js"`. diff --git a/docs/src/use/configure/configuration-files-deprecated.md b/docs/src/use/configure/configuration-files-deprecated.md new file mode 100644 index 000000000000..a50c729702bf --- /dev/null +++ b/docs/src/use/configure/configuration-files-deprecated.md @@ -0,0 +1,435 @@ +--- +title: Configuration Files (Deprecated) +--- + +::: warning +This config system is deprecated and not enabled by default. To opt-in, set the `ESLINT_USE_FLAT_CONFIG` environment variable to `false`. [View the updated documentation](configuration-files). +::: + +You can put your ESLint project configuration in a configuration file. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. + +## Configuration File Formats + +ESLint supports configuration files in several formats: + +- **JavaScript** - use `.eslintrc.js` and export an object containing your configuration. +- **JavaScript (ESM)** - use `.eslintrc.cjs` when running ESLint in JavaScript packages that specify `"type":"module"` in their `package.json`. Note that ESLint does not support ESM configuration at this time. +- **YAML** - use `.eslintrc.yaml` or `.eslintrc.yml` to define the configuration structure. +- **JSON** - use `.eslintrc.json` to define the configuration structure. ESLint's JSON files also allow JavaScript-style comments. +- **package.json** - create an `eslintConfig` property in your `package.json` file and define your configuration there. + +If there are multiple configuration files in the same directory, ESLint only uses one. The priority order is as follows: + +1. `.eslintrc.js` +1. `.eslintrc.cjs` +1. `.eslintrc.yaml` +1. `.eslintrc.yml` +1. `.eslintrc.json` +1. `package.json` + +## Using Configuration Files + +There are two ways to use configuration files. + +The first way to use configuration files is via `.eslintrc.*` and `package.json` files. ESLint automatically looks for them in the directory of the file to be linted, and in successive parent directories all the way up to the root directory of the filesystem (`/`), the home directory of the current user (`~/`), or when `root: true` is specified. See [Cascading and Hierarchy](#cascading-and-hierarchy) below for more details on this. Configuration files can be useful when you want different configurations for different parts of a project or when you want others to be able to use ESLint directly without needing to remember to pass in the configuration file. + +The second way to use configuration files is to save the file wherever you would like and pass its location to the CLI using the `--config` option, such as: + +```shell +eslint -c myconfig.json myfiletotest.js +``` + +If you are using one configuration file and want ESLint to ignore any `.eslintrc.*` files, make sure to use [`--no-eslintrc`](../command-line-interface#--no-eslintrc) along with the [`--config`](../../use/command-line-interface#-c---config) flag. + +Here's an example JSON configuration file that uses the `typescript-eslint` parser to support TypeScript syntax: + +```json +{ + "root": true, + "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "parser": "@typescript-eslint/parser", + "parserOptions": { "project": ["./tsconfig.json"] }, + "plugins": ["@typescript-eslint"], + "rules": { + "@typescript-eslint/strict-boolean-expressions": [ + 2, + { + "allowString": false, + "allowNumber": false + } + ] + }, + "ignorePatterns": ["src/**/*.test.ts", "src/frontend/generated/*"] +} +``` + +### Comments in configuration files + +Both the JSON and YAML configuration file formats support comments (`package.json` files should not include them). You can use JavaScript-style comments for JSON files and YAML-style comments for YAML files. ESLint safely ignores comments in configuration files. This allows your configuration files to be more human-friendly. + +For JavaScript-style comments: + +```js +{ + "env": { + "browser": true + }, + "rules": { + // Override our default settings just for this directory + "eqeqeq": "warn", + "strict": "off" + } +} +``` + +For YAML-style comments: + +```yaml +env: + browser: true +rules: + # Override default settings + eqeqeq: warn + strict: off +``` + +## Adding Shared Settings + +ESLint supports adding shared settings into configuration files. Plugins use `settings` to specify the information that should be shared across all of its rules. You can add a `settings` object to the ESLint configuration file and it is supplied to every executed rule. This may be useful if you are adding custom rules and want them to have access to the same information and be easily configurable. + +In JSON: + +```json +{ + "settings": { + "sharedData": "Hello" + } +} +``` + +And in YAML: + +```yaml +--- +settings: + sharedData: "Hello" +``` + +## Cascading and Hierarchy + +When using `.eslintrc.*` and `package.json` files for configuration, you can take advantage of configuration cascading. Suppose your project has the following structure: + +```text +your-project +├── .eslintrc.json +├── lib +│ └── source.js +└─â”Ŧ tests + ├── .eslintrc.json + └── test.js +``` + +The configuration cascade works based on the location of the file being linted. If there is an `.eslintrc` file in the same directory as the file being linted, then that configuration takes precedence. ESLint then searches up the directory structure, merging any `.eslintrc` files it finds along the way until reaching either an `.eslintrc` file with `root: true` or the root directory. + +In the same way, if there is a `package.json` file in the root directory with an `eslintConfig` field, the configuration it describes is applied to all subdirectories beneath it. However, the configuration described by the `.eslintrc` file in the `tests/` directory overrides conflicting specifications. + +```text +your-project +├── package.json +├── lib +│ └── source.js +└─â”Ŧ tests + ├── .eslintrc.json + └── test.js +``` + +If there is an `.eslintrc` and a `package.json` file found in the same directory, `.eslintrc` takes priority and the `package.json` file is not used. + +By default, ESLint looks for configuration files in all parent folders up to the root directory. This can be useful if you want all of your projects to follow a certain convention, but can sometimes lead to unexpected results. To limit ESLint to a specific project, place `"root": true` inside the `.eslintrc.*` file or `eslintConfig` field of the `package.json` file or in the `.eslintrc.*` file at your project's root level. ESLint stops looking in parent folders once it finds a configuration with `"root": true`. + +```js +{ + "root": true +} +``` + +And in YAML: + +```yaml +--- +root: true +``` + +For example, consider `projectA` which has `"root": true` set in the `.eslintrc` file in the `lib/` directory. In this case, while linting `main.js`, the configurations within `lib/` are used, but the `.eslintrc` file in `projectA/` is not. + +```text +home +└── user + └── projectA + ├── .eslintrc.json <- Not used + └── lib + ├── .eslintrc.json <- { "root": true } + └── main.js +``` + +The complete configuration hierarchy, from highest to lowest precedence, is as follows: + +1. Inline configuration + 1. `/*eslint-disable*/` and `/*eslint-enable*/` + 1. `/*global*/` + 1. `/*eslint*/` + 1. `/*eslint-env*/` +1. Command line options (or CLIEngine equivalents): + 1. `--global` + 1. `--rule` + 1. `--env` + 1. `-c`, `--config` +1. Project-level configuration: + 1. `.eslintrc.*` or `package.json` file in the same directory as the linted file + 1. Continue searching for `.eslintrc.*` and `package.json` files in ancestor directories up to and including the root directory or until a config with `"root": true` is found. + +Please note that the [home directory of the current user on your preferred operating system](https://nodejs.org/api/os.html#os_os_homedir) (`~/`) is also considered a root directory in this context and searching for configuration files stops there as well. And with the [removal of support for Personal Configuration Files](configuration-files-deprecated#personal-configuration-files-deprecated) from the 8.0.0 release forward, configuration files present in that directory are ignored. + +## Extending Configuration Files + +A configuration file, once extended, can inherit all the traits of another configuration file (including rules, plugins, and language options) and modify all the options. As a result, there are three configurations, as defined below: + +- Base config: the configuration that is extended. +- Derived config: the configuration that extends the base configuration. +- Resulting actual config: the result of merging the derived configuration into the base configuration. + +The `extends` property value is either: + +- a string that specifies a configuration (either a path to a config file, the name of a shareable config, `eslint:recommended`, or `eslint:all`). +- an array of strings where each additional configuration extends the preceding configurations. + +ESLint extends configurations recursively, so a base configuration can also have an `extends` property. Relative paths and shareable config names in an `extends` property are resolved from the location of the config file where they appear. + +The `eslint-config-` prefix can be omitted from the configuration name. For example, `airbnb` resolves as `eslint-config-airbnb`. + +The `rules` property can do any of the following to extend (or override) the set of rules: + +- enable additional rules +- change an inherited rule's severity without changing its options: + - Base config: `"eqeqeq": ["error", "allow-null"]` + - Derived config: `"eqeqeq": "warn"` + - Resulting actual config: `"eqeqeq": ["warn", "allow-null"]` +- override options for rules from base configurations: + - Base config: `"quotes": ["error", "single", "avoid-escape"]` + - Derived config: `"quotes": ["error", "single"]` + - Resulting actual config: `"quotes": ["error", "single"]` +- override options for rules given as object from base configurations: + - Base config: `"max-lines": ["error", { "max": 200, "skipBlankLines": true, "skipComments": true }]` + - Derived config: `"max-lines": ["error", { "max": 100 }]` + - Resulting actual config: `"max-lines": ["error", { "max": 100 }]` where `skipBlankLines` and `skipComments` default to `false` + +### Using a shareable configuration package + +A [sharable configuration](../../extend/shareable-configs) is an npm package that exports a configuration object. Make sure that you have installed the package in your project root directory, so that ESLint can require it. + +The `extends` property value can omit the `eslint-config-` prefix of the package name. + +The `npm init @eslint/config` command can create a configuration so you can extend a popular style guide (for example, `eslint-config-standard`). + +Example of a configuration file in YAML format: + +```yaml +extends: standard +rules: + comma-dangle: + - error + - always + no-empty: warn +``` + +### Using `eslint:recommended` + +Using `"eslint:recommended"` in the `extends` property enables a subset of core rules that report common problems (these rules are identified with a checkmark (recommended) on the [rules page](../../rules/)). + +Here's an example of extending `eslint:recommended` and overriding some of the set configuration options: + +Example of a configuration file in JavaScript format: + +```js +module.exports = { + extends: "eslint:recommended", + rules: { + // enable additional rules + indent: ["error", 4], + "linebreak-style": ["error", "unix"], + quotes: ["error", "double"], + semi: ["error", "always"], + + // override configuration set by extending "eslint:recommended" + "no-empty": "warn", + "no-cond-assign": ["error", "always"], + + // disable rules from base configurations + "for-direction": "off", + }, +}; +``` + +### Using a configuration from a plugin + +A [plugin](../../extend/plugins) is an npm package that can add various extensions to ESLint. A plugin can perform numerous functions, including but not limited to adding new rules and exporting [shareable configurations](../../extend/plugins#configs-in-plugins). Make sure the package has been installed in a directory where ESLint can require it. + +The `plugins` [property value](./plugins#configure-plugins) can omit the `eslint-plugin-` prefix of the package name. + +The `extends` property value can consist of: + +- `plugin:` +- the package name (from which you can omit the prefix, for example, `react` is short for `eslint-plugin-react`) +- `/` +- the configuration name (for example, `recommended`) + +Example of a configuration file in JSON format: + +```json +{ + "plugins": ["react"], + "extends": ["eslint:recommended", "plugin:react/recommended"], + "rules": { + "react/no-set-state": "off" + } +} +``` + +### Using a configuration file + +The `extends` property value can be an absolute or relative path to a base [configuration file](#using-configuration-files). ESLint resolves a relative path to a base configuration file relative to the configuration file that uses it. + +Example of a configuration file in JSON format: + +```json +{ + "extends": [ + "./node_modules/coding-standard/eslintDefaults.js", + "./node_modules/coding-standard/.eslintrc-es6", + "./node_modules/coding-standard/.eslintrc-jsx" + ], + "rules": { + "eqeqeq": "warn" + } +} +``` + +### Using `"eslint:all"` + +The `extends` property value can be `"eslint:all"` to enable all core rules in the currently installed version of ESLint. The set of core rules can change at any minor or major version of ESLint. + +**Important:** This configuration is **not recommended for production use** because it changes with every minor and major version of ESLint. Use it at your own risk. + +You might enable all core rules as a shortcut to explore rules and options while you decide on the configuration for a project, especially if you rarely override options or disable rules. The default options for rules are not endorsements by ESLint (for example, the default option for the [`quotes`](../../rules/quotes) rule does not mean double quotes are better than single quotes). + +If your configuration extends `eslint:all`, after you upgrade to a newer major or minor version of ESLint, review the reported problems before you use the `--fix` option on the [command line](../command-line-interface#--fix), so you know if a new fixable rule will make changes to the code. + +Example of a configuration file in JavaScript format: + +```js +module.exports = { + extends: "eslint:all", + rules: { + // override default options + "comma-dangle": ["error", "always"], + indent: ["error", 2], + "no-cond-assign": ["error", "always"], + + // disable now, but enable in the future + "one-var": "off", // ["error", "never"] + + // disable + "init-declarations": "off", + "no-console": "off", + "no-inline-comments": "off", + }, +}; +``` + +## Configuration Based on Glob Patterns + +**v4.1.0+.** Sometimes a more fine-controlled configuration is necessary, like if the configuration for files within the same directory has to be different. In this case, you can provide configurations under the `overrides` key that only apply to files that match specific glob patterns, using the same format you would pass on the command line (e.g., `app/**/*.test.js`). + +Glob patterns in overrides use [minimatch syntax](https://github.com/isaacs/minimatch). + +### How do overrides work? + +It is possible to override settings based on file glob patterns in your configuration by using the `overrides` key. An example of using the `overrides` key is as follows: + +In your `.eslintrc.json`: + +```json +{ + "rules": { + "quotes": ["error", "double"] + }, + + "overrides": [ + { + "files": ["bin/*.js", "lib/*.js"], + "excludedFiles": "*.test.js", + "rules": { + "quotes": ["error", "single"] + } + } + ] +} +``` + +Here is how overrides work in a configuration file: + +- The patterns are applied against the file path relative to the directory of the config file. For example, if your config file has the path `/Users/john/workspace/any-project/.eslintrc.js` and the file you want to lint has the path `/Users/john/workspace/any-project/lib/util.js`, then the pattern provided in `.eslintrc.js` is executed against the relative path `lib/util.js`. +- Glob pattern overrides have higher precedence than the regular configuration in the same config file. Multiple overrides within the same config are applied in order. That is, the last override block in a config file always has the highest precedence. +- A glob specific configuration works almost the same as any other ESLint config. Override blocks can contain any configuration options that are valid in a regular config, with the exception of `root` and `ignorePatterns`. + - A glob specific configuration can have an `extends` setting, but the `root` property in the extended configs is ignored. The `ignorePatterns` property in the extended configs is used only for the files the glob specific configuration matched. + - Nested `overrides` settings are applied only if the glob patterns of both the parent config and the child config are matched. This is the same when the extended configs have an `overrides` setting. +- Multiple glob patterns can be provided within a single override block. A file must match at least one of the supplied patterns for the configuration to apply. +- Override blocks can also specify patterns to exclude from matches. If a file matches any of the excluded patterns, the configuration won't apply. + +### Relative glob patterns + +```txt +project-root +├── app +│ ├── lib +│ │ ├── foo.js +│ │ ├── fooSpec.js +│ ├── components +│ │ ├── bar.js +│ │ ├── barSpec.js +│ ├── .eslintrc.json +├── server +│ ├── server.js +│ ├── serverSpec.js +├── .eslintrc.json +``` + +The config in `app/.eslintrc.json` defines the glob pattern `**/*Spec.js`. This pattern is relative to the base directory of `app/.eslintrc.json`. So, this pattern would match `app/lib/fooSpec.js` and `app/components/barSpec.js` but **NOT** `server/serverSpec.js`. If you defined the same pattern in the `.eslintrc.json` file within in the `project-root` folder, it would match all three of the `*Spec` files. + +If a config is provided via the `--config` CLI option, the glob patterns in the config are relative to the current working directory rather than the base directory of the given config. For example, if `--config configs/.eslintrc.json` is present, the glob patterns in the config are relative to `.` rather than `./configs`. + +### Specifying target files to lint + +If you specified directories with CLI (e.g., `eslint lib`), ESLint searches target files in the directory to lint. The target files are `*.js` or the files that match any of `overrides` entries (but exclude entries that are any of `files` end with `*`). + +If you specified the [`--ext`](../command-line-interface#--ext) command line option along with directories, the target files are only the files that have specified file extensions regardless of `overrides` entries. + +## Personal Configuration Files (deprecated) + +âš ī¸ **This feature has been deprecated**. This feature was removed in the 8.0.0 release. If you want to continue to use personal configuration files, please use the [`--config` CLI option](../command-line-interface#-c---config). For more information regarding this decision, please see [RFC 28](https://github.com/eslint/rfcs/pull/28) and [RFC 32](https://github.com/eslint/rfcs/pull/32). + +`~/` refers to [the home directory of the current user on your preferred operating system](https://nodejs.org/api/os.html#os_os_homedir). The personal configuration file being referred to here is `~/.eslintrc.*` file, which is currently handled differently than other configuration files. + +### How does ESLint find personal configuration files? + +If `eslint` could not find any configuration file in the project, `eslint` loads `~/.eslintrc.*` file. + +If `eslint` could find configuration files in the project, `eslint` ignores `~/.eslintrc.*` file even if it's in an ancestor directory of the project directory. + +### How do personal configuration files behave? + +`~/.eslintrc.*` files behave similarly to regular configuration files, with some exceptions: + +`~/.eslintrc.*` files load shareable configs and custom parsers from `~/node_modules/` – similarly to `require()` – in the user's home directory. Please note that it doesn't load global-installed packages. + +`~/.eslintrc.*` files load plugins from `$CWD/node_modules` by default in order to identify plugins uniquely. If you want to use plugins with `~/.eslintrc.*` files, plugins must be installed locally per project. Alternatively, you can use the [`--resolve-plugins-relative-to` CLI option](../command-line-interface#--resolve-plugins-relative-to) to change the location from which ESLint loads plugins. diff --git a/docs/src/use/configure/configuration-files-new.md b/docs/src/use/configure/configuration-files-new.md deleted file mode 100644 index a79fc110a4cf..000000000000 --- a/docs/src/use/configure/configuration-files-new.md +++ /dev/null @@ -1,679 +0,0 @@ ---- -title: Configuration Files (New) -eleventyNavigation: - key: configuration files - parent: configure - title: Configuration Files (New) - order: 1 - ---- - -::: warning -This config system is feature complete but not enabled by default. To opt-in, place an `eslint.config.js` file in the root of your project or set the `ESLINT_USE_FLAT_CONFIG` environment variable to `true`. To opt-out, even in the presence of an `eslint.config.js` file, set the environment variable to `false`. If you are using the API, you can use the configuration system described on this page by using the `FlatESLint` class, the `FlatRuleTester` class, or by setting `configType: "flat"` in the `Linter` class. -::: - -You can put your ESLint project configuration in a configuration file. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. - -## Configuration File - -The ESLint configuration file is named `eslint.config.js`. It should be placed in the root directory of your project and export an array of [configuration objects](#configuration-objects). Here's an example: - -```js -export default [ - { - rules: { - semi: "error", - "prefer-const": "error" - } - } -]; -``` - -In this example, the configuration array contains just one configuration object. The configuration object enables two rules: `semi` and `prefer-const`. These rules are applied to all of the files ESLint processes using this config file. - -If your project does not specify `"type":"module"` in its `package.json` file, then `eslint.config.js` must be in CommonJS format, such as: - -```js -module.exports = [ - { - rules: { - semi: "error", - "prefer-const": "error" - } - } -]; -``` - -The configuration file can also export a promise that resolves to the configuration array. This can be useful for using ESM dependencies in CommonJS configuration files, as in this example: - -```js -module.exports = (async () => { - - const someDependency = await import("some-esm-dependency"); - - return [ - // ... use `someDependency` here - ]; - -})(); -``` - -::: warning -ESLint only automatically looks for a config file named `eslint.config.js` and does not look for `eslint.config.cjs` or `eslint.config.mjs`. If you'd like to specify a different config filename than the default, use the `--config` command line option. -::: - -## Configuration Objects - -Each configuration object contains all of the information ESLint needs to execute on a set of files. Each configuration object is made up of these properties: - -* `files` - An array of glob patterns indicating the files that the configuration object should apply to. If not specified, the configuration object applies to all files matched by any other configuration object. -* `ignores` - An array of glob patterns indicating the files that the configuration object should not apply to. If not specified, the configuration object applies to all files matched by `files`. -* `languageOptions` - An object containing settings related to how JavaScript is configured for linting. - * `ecmaVersion` - The version of ECMAScript to support. May be any year (i.e., `2022`) or version (i.e., `5`). Set to `"latest"` for the most recent supported version. (default: `"latest"`) - * `sourceType` - The type of JavaScript source code. Possible values are `"script"` for traditional script files, `"module"` for ECMAScript modules (ESM), and `"commonjs"` for CommonJS files. (default: `"module"` for `.js` and `.mjs` files; `"commonjs"` for `.cjs` files) - * `globals` - An object specifying additional objects that should be added to the global scope during linting. - * `parser` - An object containing a `parse()` method or a `parseForESLint()` method. (default: [`espree`](https://github.com/eslint/espree)) - * `parserOptions` - An object specifying additional options that are passed directly to the `parse()` or `parseForESLint()` method on the parser. The available options are parser-dependent. -* `linterOptions` - An object containing settings related to the linting process. - * `noInlineConfig` - A Boolean value indicating if inline configuration is allowed. - * `reportUnusedDisableDirectives` - A severity string indicating if and how unused disable and enable directives should be tracked and reported. For legacy compatibility, `true` is equivalent to `"warn"` and `false` is equivalent to `"off"`. (default: `"off"`) -* `processor` - Either an object containing `preprocess()` and `postprocess()` methods or a string indicating the name of a processor inside of a plugin (i.e., `"pluginName/processorName"`). -* `plugins` - An object containing a name-value mapping of plugin names to plugin objects. When `files` is specified, these plugins are only available to the matching files. -* `rules` - An object containing the configured rules. When `files` or `ignores` are specified, these rule configurations are only available to the matching files. -* `settings` - An object containing name-value pairs of information that should be available to all rules. - -### Specifying `files` and `ignores` - -::: tip -Patterns specified in `files` and `ignores` use [`minimatch`](https://www.npmjs.com/package/minimatch) syntax and are evaluated relative to the location of the `eslint.config.js` file. -::: - -You can use a combination of `files` and `ignores` to determine which files should apply the configuration object and which should not. By default, ESLint matches `**/*.js`, `**/*.cjs`, and `**/*.mjs`. Because config objects that don't specify `files` or `ignores` apply to all files that have been matched by any other configuration object, those config objects apply to any JavaScript files passed to ESLint by default. For example: - -```js -export default [ - { - rules: { - semi: "error" - } - } -]; -``` - -With this configuration, the `semi` rule is enabled for all files that match the default files in ESLint. So if you pass `example.js` to ESLint, the `semi` rule is applied. If you pass a non-JavaScript file, like `example.txt`, the `semi` rule is not applied because there are no other configuration objects that match that filename. (ESLint outputs an error message letting you know that the file was ignored due to missing configuration.) - -#### Excluding files with `ignores` - -You can limit which files a configuration object applies to by specifying a combination of `files` and `ignores` patterns. For example, you may want certain rules to apply only to files in your `src` directory: - -```js -export default [ - { - files: ["src/**/*.js"], - rules: { - semi: "error" - } - } -]; -``` - -Here, only the JavaScript files in the `src` directory have the `semi` rule applied. If you run ESLint on files in another directory, this configuration object is skipped. By adding `ignores`, you can also remove some of the files in `src` from this configuration object: - -```js -export default [ - { - files: ["src/**/*.js"], - ignores: ["**/*.config.js"], - rules: { - semi: "error" - } - } -]; -``` - -This configuration object matches all JavaScript files in the `src` directory except those that end with `.config.js`. You can also use negation patterns in `ignores` to exclude files from the ignore patterns, such as: - -```js -export default [ - { - files: ["src/**/*.js"], - ignores: ["**/*.config.js", "!**/eslint.config.js"], - rules: { - semi: "error" - } - } -]; -``` - -Here, the configuration object excludes files ending with `.config.js` except for `eslint.config.js`. That file still has `semi` applied. - -Non-global `ignores` patterns can only match file names. A pattern like `"dir-to-exclude/"` will not ignore anything. To ignore everything in a particular directory, a pattern like `"dir-to-exclude/**"` should be used instead. - -If `ignores` is used without `files` and there are other keys (such as `rules`), then the configuration object applies to all files except the ones specified in `ignores`, for example: - -```js -export default [ - { - ignores: ["**/*.config.js"], - rules: { - semi: "error" - } - } -]; -``` - -This configuration object applies to all files except those ending with `.config.js`. Effectively, this is like having `files` set to `**/*`. In general, it's a good idea to always include `files` if you are specifying `ignores`. - -#### Globally ignoring files with `ignores` - -If `ignores` is used without any other keys in the configuration object, then the patterns act as global ignores. Here's an example: - -```js -export default [ - { - ignores: [".config/*"] - } -]; -``` - -This configuration specifies that all of the files in the `.config` directory should be ignored. This pattern is added after the default patterns, which are `["**/node_modules/", ".git/"]`. - -You can also unignore files and directories that are ignored by the default patterns. For example, this config unignores `node_modules/mylibrary`: - -```js -export default [ - { - ignores: [ - "!node_modules/", // unignore `node_modules/` directory - "node_modules/*", // ignore its content - "!node_modules/mylibrary/" // unignore `node_modules/mylibrary` directory - ] - } -]; -``` - -Note that only global `ignores` patterns can match directories. -`ignores` patterns that are specific to a configuration will only match file names. - -#### Cascading configuration objects - -When more than one configuration object matches a given filename, the configuration objects are merged with later objects overriding previous objects when there is a conflict. For example: - -```js -export default [ - { - files: ["**/*.js"], - languageOptions: { - globals: { - MY_CUSTOM_GLOBAL: "readonly" - } - } - }, - { - files: ["tests/**/*.js"], - languageOptions: { - globals: { - it: "readonly", - describe: "readonly" - } - } - } -]; -``` - -Using this configuration, all JavaScript files define a custom global object defined called `MY_CUSTOM_GLOBAL` while those JavaScript files in the `tests` directory have `it` and `describe` defined as global objects in addition to `MY_CUSTOM_GLOBAL`. For any JavaScript file in the tests directory, both configuration objects are applied, so `languageOptions.globals` are merged to create a final result. - -### Configuring linter options - -Options specific to the linting process can be configured using the `linterOptions` object. These effect how linting proceeds and does not affect how the source code of the file is interpreted. - -#### Disabling inline configuration - -Inline configuration is implemented using an `/*eslint*/` comment, such as `/*eslint semi: error*/`. You can disallow inline configuration by setting `noInlineConfig` to `true`. When enabled, all inline configuration is ignored. Here's an example: - -```js -export default [ - { - files: ["**/*.js"], - linterOptions: { - noInlineConfig: true - } - } -]; -``` - -#### Reporting unused disable directives - -Disable and enable directives such as `/*eslint-disable*/`, `/*eslint-enable*/` and `/*eslint-disable-next-line*/` are used to disable ESLint rules around certain portions of code. As code changes, it's possible for these directives to no longer be needed because the code has changed in such a way that the rule is no longer triggered. You can enable reporting of these unused disable directives by setting the `reportUnusedDisableDirectives` option to a severity string, as in this example: - -```js -export default [ - { - files: ["**/*.js"], - linterOptions: { - reportUnusedDisableDirectives: "error" - } - } -]; -``` - -You can override this setting using the [`--report-unused-disable-directives`](../command-line-interface#--report-unused-disable-directives) or the [`--report-unused-disable-directives-severity`](../command-line-interface#--report-unused-disable-directives-severity) command line options. - -For legacy compatibility, `true` is equivalent to `"warn"` and `false` is equivalent to `"off"`. - -### Configuring language options - -Options specific to how ESLint evaluates your JavaScript code can be configured using the `languageOptions` object. - -#### Configuring the JavaScript version - -To configure the version of JavaScript (ECMAScript) that ESLint uses to evaluate your JavaScript, use the `ecmaVersion` property. This property determines which global variables and syntax are valid in your code and can be set to the version number (such as `6`), the year number (such as `2022`), or `"latest"` (for the most recent version that ESLint supports). By default, `ecmaVersion` is set to `"latest"` and it's recommended to keep this default unless you need to ensure that your JavaScript code is evaluated as an older version. For example, some older runtimes might only allow ECMAScript 5, in which case you can configure ESLint like this: - -```js -export default [ - { - files: ["**/*.js"], - languageOptions: { - ecmaVersion: 5 - } - } -]; -``` - -#### Configuring the JavaScript source type - -ESLint can evaluate your code in one of three ways: - -1. ECMAScript module (ESM) - Your code has a module scope and is run in strict mode. -1. CommonJS - Your code has a top-level function scope and runs in non-strict mode. -1. Script - Your code has a shared global scope and runs in non-strict mode. - -You can specify which of these modes your code is intended to run in by specifying the `sourceType` property. This property can be set to `"module"`, `"commonjs"`, or `"script"`. By default, `sourceType` is set to `"module"` for `.js` and `.mjs` files and is set to `"commonjs"` for `.cjs` files. Here's an example: - -```js -export default [ - { - files: ["**/*.js"], - languageOptions: { - sourceType: "script" - } - } -]; -``` - -#### Configuring a custom parser and its options - -In many cases, you can use the default parser that ESLint ships with for parsing your JavaScript code. You can optionally override the default parser by using the `parser` property. The `parser` property must be an object containing either a `parse()` method or a `parseForESLint()` method. For example, you can use the [`@babel/eslint-parser`](https://www.npmjs.com/package/@babel/eslint-parser) package to allow ESLint to parse experimental syntax: - -```js -import babelParser from "@babel/eslint-parser"; - -export default [ - { - files: ["**/*.js", "**/*.mjs"], - languageOptions: { - parser: babelParser - } - } -]; -``` - -This configuration ensures that the Babel parser, rather than the default Espree parser, is used to parse all files ending with `.js` and `.mjs`. - -You can also pass options directly to the custom parser by using the `parserOptions` property. This property is an object whose name-value pairs are specific to the parser that you are using. For the Babel parser, you might pass in options like this: - -```js -import babelParser from "@babel/eslint-parser"; - -export default [ - { - files: ["**/*.js", "**/*.mjs"], - languageOptions: { - parser: babelParser, - parserOptions: { - requireConfigFile: false, - babelOptions: { - babelrc: false, - configFile: false, - // your babel options - presets: ["@babel/preset-env"], - } - } - } - } -]; -``` - -#### Configuring global variables - -To configure global variables inside of a configuration object, set the `globals` configuration property to an object containing keys named for each of the global variables you want to use. For each global variable key, set the corresponding value equal to `"writable"` to allow the variable to be overwritten or `"readonly"` to disallow overwriting. For example: - -```js -export default [ - { - files: ["**/*.js"], - languageOptions: { - globals: { - var1: "writable", - var2: "readonly" - } - } - } -]; -``` - -These examples allow `var1` to be overwritten in your code, but disallow it for `var2`. - -Globals can be disabled with the string `"off"`. For example, in an environment where most ES2015 globals are available but `Promise` is unavailable, you might use this config: - -```js -export default [ - { - languageOptions: { - globals: { - Promise: "off" - } - } - } -]; -``` - -For historical reasons, the boolean value `false` and the string value `"readable"` are equivalent to `"readonly"`. Similarly, the boolean value `true` and the string value `"writeable"` are equivalent to `"writable"`. However, the use of older values is deprecated. - -##### Predefined global variables - -Apart from the ECMAScript standard built-in globals, which are automatically enabled based on the configured `languageOptions.ecmaVersion`, ESLint doesn't provide predefined sets of global variables. You can use the [`globals`](https://www.npmjs.com/package/globals) package to additionally enable all globals for a specific environment. For example, here is how you can add `console`, amongst other browser globals, into your configuration. - -```js -import globals from "globals"; -export default [ - { - languageOptions: { - globals: { - ...globals.browser - } - } - } -]; -``` - -### Using plugins in your configuration - -Plugins are used to share rules, processors, configurations, parsers, and more across ESLint projects. - -#### Using plugin rules - -You can use specific rules included in a plugin. To do this, specify the plugin -in a configuration object using the `plugins` key. The value for the `plugin` key -is an object where the name of the plugin is the property name and the value is the plugin object itself. Here's an example: - -```js -import jsdoc from "eslint-plugin-jsdoc"; - -export default [ - { - files: ["**/*.js"], - plugins: { - jsdoc: jsdoc - }, - rules: { - "jsdoc/require-description": "error", - "jsdoc/check-values": "error" - } - } -]; -``` - -In this configuration, the JSDoc plugin is defined to have the name `jsdoc`. The prefix `jsdoc/` in each rule name indicates that the rule is coming from the plugin with that name rather than from ESLint itself. - -Because the name of the plugin and the plugin object are both `jsdoc`, you can also shorten the configuration to this: - -```js -import jsdoc from "eslint-plugin-jsdoc"; - -export default [ - { - files: ["**/*.js"], - plugins: { - jsdoc - }, - rules: { - "jsdoc/require-description": "error", - "jsdoc/check-values": "error" - } - } -]; -``` - -While this is the most common convention, you don't need to use the same name that the plugin prescribes. You can specify any prefix that you'd like, such as: - -```js -import jsdoc from "eslint-plugin-jsdoc"; - -export default [ - { - files: ["**/*.js"], - plugins: { - jsd: jsdoc - }, - rules: { - "jsd/require-description": "error", - "jsd/check-values": "error" - } - } -]; -``` - -This configuration object uses `jsd` as the prefix plugin instead of `jsdoc`. - -#### Using configurations included in plugins - -You can use a configuration included in a plugin by adding that configuration -directly to the `eslint.config.js` configurations array. -Often, you do this for a plugin's recommended configuration. Here's an example: - -```js -import jsdoc from "eslint-plugin-jsdoc"; - -export default [ - // configuration included in plugin - jsdoc.configs["flat/recommended"], - // other configuration objects... - { - files: ["**/*.js"], - plugins: { - jsdoc: jsdoc - }, - rules: { - "jsdoc/require-description": "warn", - } - } -]; -``` - -### Using processors - -Processors allow ESLint to transform text into pieces of code that ESLint can lint. You can specify the processor to use for a given file type by defining a `processor` property that contains either the processor name in the format `"pluginName/processorName"` to reference a processor in a plugin or an object containing both a `preprocess()` and a `postprocess()` method. For example, to extract JavaScript code blocks from a Markdown file, you might add this to your configuration: - -```js -import markdown from "eslint-plugin-markdown"; - -export default [ - { - files: ["**/*.md"], - plugins: { - markdown - }, - processor: "markdown/markdown", - settings: { - sharedData: "Hello" - } - } -]; -``` - -This configuration object specifies that there is a processor called `"markdown"` contained in the plugin named `"markdown"`. The configuration applies the processor to all files ending with `.md`. - -Processors may make named code blocks that function as filenames in configuration objects, such as `0.js` and `1.js`. ESLint handles such a named code block as a child of the original file. You can specify additional configuration objects for named code blocks. For example, the following disables the `strict` rule for the named code blocks which end with `.js` in markdown files. - -```js -import markdown from "eslint-plugin-markdown"; - -export default [ - { - files: ["**/*.md"], - plugins: { - markdown - }, - processor: "markdown/markdown", - settings: { - sharedData: "Hello" - } - }, - - // applies only to code blocks - { - files: ["**/*.md/*.js"], - rules: { - strict: "off" - } - } -]; -``` - -### Configuring rules - -You can configure any number of rules in a configuration object by add a `rules` property containing an object with your rule configurations. The names in this object are the names of the rules and the values are the configurations for each of those rules. Here's an example: - -```js -export default [ - { - rules: { - semi: "error" - } - } -]; -``` - -This configuration object specifies that the [`semi`](../../rules/semi) rule should be enabled with a severity of `"error"`. You can also provide options to a rule by specifying an array where the first item is the severity and each item after that is an option for the rule. For example, you can switch the `semi` rule to disallow semicolons by passing `"never"` as an option: - -```js -export default [ - { - rules: { - semi: ["error", "never"] - } - } -]; -``` - -Each rule specifies its own options and can be any valid JSON data type. Please check the documentation for the rule you want to configure for more information about its available options. - -#### Rule severities - -There are three possible severities you can specify for a rule - -* `"error"` (or `2`) - the reported problem should be treated as an error. When using the ESLint CLI, errors cause the CLI to exit with a nonzero code. -* `"warn"` (or `1`) - the reported problem should be treated as a warning. When using the ESLint CLI, warnings are reported but do not change the exit code. If only warnings are reported, the exit code is 0. -* `"off"` (or `0`) - the rule should be turned off completely. - -#### Rule configuration cascade - -When more than one configuration object specifies the same rule, the rule configuration is merged with the later object taking precedence over any previous objects. For example: - -```js -export default [ - { - rules: { - semi: ["error", "never"] - } - }, - { - rules: { - semi: ["warn", "always"] - } - } -]; -``` - -Using this configuration, the final rule configuration for `semi` is `["warn", "always"]` because it appears last in the array. The array indicates that the configuration is for the severity and any options. You can change just the severity by defining only a string or number, as in this example: - -```js -export default [ - { - rules: { - semi: ["error", "never"] - } - }, - { - rules: { - semi: "warn" - } - } -]; -``` - -Here, the second configuration object only overrides the severity, so the final configuration for `semi` is `["warn", "never"]`. - -### Configuring shared settings - -ESLint supports adding shared settings into configuration files. When you add a `settings` object to a configuration object, it is supplied to every rule. By convention, plugins namespace the settings they are interested in to avoid collisions with others. Plugins can use `settings` to specify the information that should be shared across all of their rules. This may be useful if you are adding custom rules and want them to have access to the same information. Here's an example: - -```js -export default [ - { - settings: { - sharedData: "Hello" - } - } -]; -``` - -### Using predefined configurations - -ESLint has two predefined configurations for JavaScript: - -* `js.configs.recommended` - enables the rules that ESLint recommends everyone use to avoid potential errors -* `js.configs.all` - enables all of the rules shipped with ESLint - -To include these predefined configurations, install the `@eslint/js` package and then make any modifications to other properties in subsequent configuration objects: - -```js -import js from "@eslint/js"; - -export default [ - js.configs.recommended, - { - rules: { - semi: ["warn", "always"] - } - } -]; -``` - -Here, the `js.configs.recommended` predefined configuration is applied first and then another configuration object adds the desired configuration for `semi`. - -You can apply these predefined configs to just a subset of files by specifying a config object with a `files` key, like this: - -```js -import js from "@eslint/js"; - -export default [ - { - files: ["**/src/safe/*.js"], - ...js.configs.recommended - } -]; -``` - -## Configuration File Resolution - -When ESLint is run on the command line, it first checks the current working directory for `eslint.config.js`. If the file is not found, it looks to the next parent directory for the file. This search continues until either the file is found or the root directory is reached. - -You can prevent this search for `eslint.config.js` by setting the `ESLINT_USE_FLAT_CONFIG` environment variable to `true` and using the `-c` or `--config` option on the command line to specify an alternate configuration file, such as: - -```shell -ESLINT_USE_FLAT_CONFIG=true npx eslint --config some-other-file.js **/*.js -``` - -In this case, ESLint does not search for `eslint.config.js` and instead uses `some-other-file.js`. diff --git a/docs/src/use/configure/configuration-files.md b/docs/src/use/configure/configuration-files.md index b2d51e09a3be..330189b9c82e 100644 --- a/docs/src/use/configure/configuration-files.md +++ b/docs/src/use/configure/configuration-files.md @@ -4,448 +4,825 @@ eleventyNavigation: key: configuration files parent: configure title: Configuration Files - order: 2 - + order: 1 --- -::: warning -We are transitioning to a new config system in ESLint v9.0.0. The config system shared on this page is currently the default but will be deprecated in v9.0.0. You can opt-in to the new config system by following the instructions in the [documentation](configuration-files-new). +{%- from 'components/npx_tabs.macro.html' import npx_tabs %} +{%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} + +::: tip +This page explains how to use flat config files. For the deprecated eslintrc format, [see the deprecated documentation](configuration-files-deprecated). ::: You can put your ESLint project configuration in a configuration file. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. -## Configuration File Formats +## Configuration File -ESLint supports configuration files in several formats: +The ESLint configuration file may be named any of the following: -* **JavaScript** - use `.eslintrc.js` and export an object containing your configuration. -* **JavaScript (ESM)** - use `.eslintrc.cjs` when running ESLint in JavaScript packages that specify `"type":"module"` in their `package.json`. Note that ESLint does not support ESM configuration at this time. -* **YAML** - use `.eslintrc.yaml` or `.eslintrc.yml` to define the configuration structure. -* **JSON** - use `.eslintrc.json` to define the configuration structure. ESLint's JSON files also allow JavaScript-style comments. -* **package.json** - create an `eslintConfig` property in your `package.json` file and define your configuration there. +- `eslint.config.js` +- `eslint.config.mjs` +- `eslint.config.cjs` +- `eslint.config.ts` (requires [additional setup](#typescript-configuration-files)) +- `eslint.config.mts` (requires [additional setup](#typescript-configuration-files)) +- `eslint.config.cts` (requires [additional setup](#typescript-configuration-files)) -If there are multiple configuration files in the same directory, ESLint only uses one. The priority order is as follows: +It should be placed in the root directory of your project and export an array of [configuration objects](#configuration-objects). Here's an example: -1. `.eslintrc.js` -1. `.eslintrc.cjs` -1. `.eslintrc.yaml` -1. `.eslintrc.yml` -1. `.eslintrc.json` -1. `package.json` +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + rules: { + semi: "error", + "prefer-const": "error", + }, + }, +]); +``` + +In this example, the `defineConfig()` helper is used to define a configuration array with just one configuration object. The configuration object enables two rules: `semi` and `prefer-const`. These rules are applied to all of the files ESLint processes using this config file. -## Using Configuration Files +If your project does not specify `"type":"module"` in its `package.json` file, then `eslint.config.js` must be in CommonJS format, such as: -There are two ways to use configuration files. +```js +// eslint.config.js +const { defineConfig } = require("eslint/config"); + +module.exports = defineConfig([ + { + rules: { + semi: "error", + "prefer-const": "error", + }, + }, +]); +``` -The first way to use configuration files is via `.eslintrc.*` and `package.json` files. ESLint automatically looks for them in the directory of the file to be linted, and in successive parent directories all the way up to the root directory of the filesystem (`/`), the home directory of the current user (`~/`), or when `root: true` is specified. See [Cascading and Hierarchy](#cascading-and-hierarchy) below for more details on this. Configuration files can be useful when you want different configurations for different parts of a project or when you want others to be able to use ESLint directly without needing to remember to pass in the configuration file. +## Configuration Objects + +Each configuration object contains all of the information ESLint needs to execute on a set of files. Each configuration object is made up of these properties: + +- `name` - A name for the configuration object. This is used in error messages and [config inspector](https://github.com/eslint/config-inspector) to help identify which configuration object is being used. ([Naming Convention](#configuration-naming-conventions)) +- `basePath` - A string specifying the path to a subdirectory to which the configuration object should apply to. It can be a relative or an absolute path. +- `files` - An array of glob patterns indicating the files that the configuration object should apply to. If not specified, the configuration object applies to all files matched by any other configuration object. +- `ignores` - An array of glob patterns indicating the files that the configuration object should not apply to. If not specified, the configuration object applies to all files matched by `files`. If `ignores` is used without any other keys in the configuration object, then the patterns act as [global ignores](#globally-ignoring-files-with-ignores) and it gets applied to every configuration object. +- `extends` - An array of strings, configuration objects, or configuration arrays that contain additional configuration to apply. +- `languageOptions` - An object containing settings related to how JavaScript is configured for linting. + - `ecmaVersion` - The version of ECMAScript to support. May be any year (i.e., `2022`) or version (i.e., `5`). Set to `"latest"` for the most recent supported version. (default: `"latest"`) + - `sourceType` - The type of JavaScript source code. Possible values are `"script"` for traditional script files, `"module"` for ECMAScript modules (ESM), and `"commonjs"` for CommonJS files. (default: `"module"` for `.js` and `.mjs` files; `"commonjs"` for `.cjs` files) + - `globals` - An object specifying additional objects that should be added to the global scope during linting. + - `parser` - An object containing a `parse()` method or a `parseForESLint()` method. (default: [`espree`](https://github.com/eslint/js/tree/main/packages/espree)) + - `parserOptions` - An object specifying additional options that are passed directly to the `parse()` or `parseForESLint()` method on the parser. The available options are parser-dependent. +- `linterOptions` - An object containing settings related to the linting process. + - `noInlineConfig` - A Boolean value indicating if inline configuration is allowed. + - `reportUnusedDisableDirectives` - A severity string indicating if and how unused disable and enable directives should be tracked and reported. For legacy compatibility, `true` is equivalent to `"warn"` and `false` is equivalent to `"off"`. (default: `"warn"`). + - `reportUnusedInlineConfigs` - A severity string indicating if and how unused inline configs should be tracked and reported. (default: `"off"`) +- `processor` - Either an object containing `preprocess()` and `postprocess()` methods or a string indicating the name of a processor inside of a plugin (i.e., `"pluginName/processorName"`). +- `plugins` - An object containing a name-value mapping of plugin names to plugin objects. When `files` is specified, these plugins are only available to the matching files. +- `rules` - An object containing the configured rules. When `files` or `ignores` are specified, these rule configurations are only available to the matching files. +- `settings` - An object containing name-value pairs of information that should be available to all rules. + +### Specifying `files` and `ignores` + +::: tip +Patterns specified in `files` and `ignores` use [`minimatch`](https://www.npmjs.com/package/minimatch) syntax and are evaluated relative to the location of the `eslint.config.js` file. If using an alternate config file via the `--config` command line option, then all patterns are evaluated relative to the current working directory. In case the configuration object has the `basePath` property with a relative path, the subdirectory it specifies is evaluated relative to the location of the `eslint.config.js` file (or relative to the current working directory if using an alternate config file via the `--config` command line option). In configuration objects with the `basePath` property, patterns specified in `files` and `ignores` are evaluated relative to the subdirectory represented by the `basePath`. +::: -The second way to use configuration files is to save the file wherever you would like and pass its location to the CLI using the `--config` option, such as: +You can use a combination of `files` and `ignores` to determine which files the configuration object should apply to and which not. Here's an example: -```shell -eslint -c myconfig.json myfiletotest.js +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + // matches all files ending with .js + { + files: ["**/*.js"], + rules: { + semi: "error", + }, + }, + + // matches all files ending with .js except those in __tests + { + files: ["**/*.js"], + ignores: ["__tests/**"], + rules: { + "no-console": "error", + }, + }, +]); ``` -If you are using one configuration file and want ESLint to ignore any `.eslintrc.*` files, make sure to use [`--no-eslintrc`](../command-line-interface#--no-eslintrc) along with the [`--config`](../../use/command-line-interface#-c---config) flag. - -Here's an example JSON configuration file that uses the `typescript-eslint` parser to support TypeScript syntax: - -```json -{ - "root": true, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended" - ], - "parser": "@typescript-eslint/parser", - "parserOptions": { "project": ["./tsconfig.json"] }, - "plugins": [ - "@typescript-eslint" - ], - "rules": { - "@typescript-eslint/strict-boolean-expressions": [ - 2, - { - "allowString" : false, - "allowNumber" : false - } - ] - }, - "ignorePatterns": ["src/**/*.test.ts", "src/frontend/generated/*"] -} +Configuration objects without `files` or `ignores` are automatically applied to any file that is matched by any other configuration object. For example: + +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + // matches all files because it doesn't specify the `files` or `ignores` key + { + rules: { + semi: "error", + }, + }, +]); ``` -### Comments in configuration files +With this configuration, the `semi` rule is enabled for all files that match the default files in ESLint. So if you pass `example.js` to ESLint, the `semi` rule is applied. If you pass a non-JavaScript file, like `example.txt`, the `semi` rule is not applied because there are no other configuration objects that match that filename. (ESLint outputs an error message letting you know that the file was ignored due to missing configuration.) -Both the JSON and YAML configuration file formats support comments (`package.json` files should not include them). You can use JavaScript-style comments for JSON files and YAML-style comments for YAML files. ESLint safely ignores comments in configuration files. This allows your configuration files to be more human-friendly. +::: important +By default, ESLint lints files that match the patterns `**/*.js`, `**/*.cjs`, and `**/*.mjs`. Those files are always matched unless you explicitly exclude them using [global ignores](#globally-ignoring-files-with-ignores). +::: + +#### Specifying files with arbitrary extensions -For JavaScript-style comments: +To lint files with extensions other than the default `.js`, `.cjs` and `.mjs`, include them in `files` with a pattern in the format of `"**/*.extension"`. Any pattern will work except if it is `*` or if it ends with `/*` or `/**`. +For example, to lint TypeScript files with `.ts`, `.cts` and `.mts` extensions, you would specify a configuration object like this: ```js -{ - "env": { - "browser": true - }, - "rules": { - // Override our default settings just for this directory - "eqeqeq": "warn", - "strict": "off" - } -} +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.ts", "**/*.cts", "**.*.mts"], + }, + // ...other config +]); ``` -For YAML-style comments: +#### Specifying files without extension + +Files without an extension can be matched with the pattern `!(*.*)`. For example: -```yaml -env: - browser: true -rules: - # Override default settings - eqeqeq: warn - strict: off +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/!(*.*)"], + }, + // ...other config +]); ``` -## Adding Shared Settings +The above config lints files without extension besides the default `.js`, `.cjs` and `.mjs` extensions in all directories. +::: tip +Filenames starting with a dot, such as `.gitignore`, are considered to have only an extension without a base name. In the case of `.gitignore`, the extension is `gitignore`, so the file matches the pattern `"**/.gitignore"` but not `"**/*.gitignore"`. +::: -ESLint supports adding shared settings into configuration files. Plugins use `settings` to specify the information that should be shared across all of its rules. You can add a `settings` object to the ESLint configuration file and it is supplied to every executed rule. This may be useful if you are adding custom rules and want them to have access to the same information and be easily configurable. +#### Specifying files with an AND operation -In JSON: +Multiple patterns can be matched against the same file by using an array of strings inside of the `files` array. For example: -```json -{ - "settings": { - "sharedData": "Hello" - } -} +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: [["src/*", "**/.js"]], + }, + // ...other config +]); ``` -And in YAML: +The pattern `["src/*", "**/.js"]` matches when a file is both inside of the `src` directory and also ends with `.js`. This approach can be helpful when you're dynamically calculating the value of the `files` array and want to avoid potential errors by trying to combine multiple glob patterns into a single string. -```yaml ---- - settings: - sharedData: "Hello" +#### Excluding files with `ignores` + +You can limit which files a configuration object applies to by specifying a combination of `files` and `ignores` patterns. For example, you may want certain rules to apply only to files in your `src` directory: + +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["src/**/*.js"], + rules: { + semi: "error", + }, + }, +]); ``` -## Cascading and Hierarchy +Here, only the JavaScript files in the `src` directory have the `semi` rule applied. If you run ESLint on files in another directory, this configuration object is skipped. By adding `ignores`, you can also remove some of the files in `src` from this configuration object: + +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["src/**/*.js"], + ignores: ["**/*.config.js"], + rules: { + semi: "error", + }, + }, +]); +``` -When using `.eslintrc.*` and `package.json` files for configuration, you can take advantage of configuration cascading. Suppose your project has the following structure: +This configuration object matches all JavaScript files in the `src` directory except those that end with `.config.js`. You can also use negation patterns in `ignores` to exclude files from the ignore patterns, such as: -```text -your-project -├── .eslintrc.json -├── lib -│ └── source.js -└─â”Ŧ tests - ├── .eslintrc.json - └── test.js +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["src/**/*.js"], + ignores: ["**/*.config.js", "!**/eslint.config.js"], + rules: { + semi: "error", + }, + }, +]); ``` -The configuration cascade works based on the location of the file being linted. If there is an `.eslintrc` file in the same directory as the file being linted, then that configuration takes precedence. ESLint then searches up the directory structure, merging any `.eslintrc` files it finds along the way until reaching either an `.eslintrc` file with `root: true` or the root directory. +Here, the configuration object excludes files ending with `.config.js` except for `eslint.config.js`. That file still has `semi` applied. -In the same way, if there is a `package.json` file in the root directory with an `eslintConfig` field, the configuration it describes is applied to all subdirectories beneath it. However, the configuration described by the `.eslintrc` file in the `tests/` directory overrides conflicting specifications. +Non-global `ignores` patterns can only match file names. A pattern like `"dir-to-exclude/"` will not ignore anything. To ignore everything in a particular directory, a pattern like `"dir-to-exclude/**"` should be used instead. -```text -your-project -├── package.json -├── lib -│ └── source.js -└─â”Ŧ tests - ├── .eslintrc.json - └── test.js +If `ignores` is used without `files` and there are other keys (such as `rules`), then the configuration object applies to all linted files except the ones excluded by `ignores`, for example: + +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + ignores: ["**/*.config.js"], + rules: { + semi: "error", + }, + }, +]); ``` -If there is an `.eslintrc` and a `package.json` file found in the same directory, `.eslintrc` takes priority and the `package.json` file is not used. +This configuration object applies to all JavaScript files except those ending with `.config.js`. Effectively, this is like having `files` set to `**/*`. In general, it's a good idea to always include `files` if you are specifying `ignores`. + +Note that when `files` is not specified, negated `ignores` patterns do not cause any matching files to be linted automatically. +ESLint only lints files that are matched either by default or by a `files` pattern that is not `*` and does not end with `/*` or `/**`. + +::: tip +Use the [config inspector](https://github.com/eslint/config-inspector) (`--inspect-config` in the CLI) to test which config objects apply to a specific file. +::: + +#### Globally ignoring files with `ignores` + +Depending on how the `ignores` property is used, it can behave as non-global `ignores` or as global `ignores`. + +- When `ignores` is used without any other keys (besides `name`) in the configuration object, then the patterns act as global ignores. This means they apply to every configuration object (not only to the configuration object in which it is defined). Global `ignores` allows you not to have to copy and keep the `ignores` property synchronized in more than one configuration object. +- If `ignores` is used with other properties in the same configuration object, then the patterns act as non-global ignores. This way `ignores` applies only to the configuration object in which it is defined. + +Global and non-global `ignores` have some usage differences: -By default, ESLint looks for configuration files in all parent folders up to the root directory. This can be useful if you want all of your projects to follow a certain convention, but can sometimes lead to unexpected results. To limit ESLint to a specific project, place `"root": true` inside the `.eslintrc.*` file or `eslintConfig` field of the `package.json` file or in the `.eslintrc.*` file at your project's root level. ESLint stops looking in parent folders once it finds a configuration with `"root": true`. +- patterns in non-global `ignores` only match the files (`dir/filename.js`) or files within directories (`dir/**`) +- patterns in global `ignores` can match directories (`dir/`) in addition to the patterns that non-global ignores supports. + +For all uses of `ignores`: + +- The patterns you define are added after the default ESLint patterns, which are `["**/node_modules/", ".git/"]`. +- The patterns always match files and directories that begin with a dot, such as `.foo.js` or `.fixtures`, unless those files are explicitly ignored. The only dot directory ignored by default is `.git`. ```js -{ - "root": true -} -``` +// eslint.config.js +import { defineConfig } from "eslint/config"; -And in YAML: +// Example of global ignores +export default defineConfig([ + { + ignores: [".config/", "dist/", "tsconfig.json"] // acts as global ignores, due to the absence of other properties + }, + { ... }, // ... other configuration object, inherit global ignores + { ... }, // ... other configuration object, inherit global ignores +]); -```yaml ---- - root: true +// Example of non-global ignores +export default defineConfig([ + { + ignores: [".config/**", "dir1/script1.js"], + rules: { ... } // the presence of this property dictates non-global ignores + }, + { + ignores: ["other-dir/**", "dist/script2.js"], + rules: { ... } // the presence of this property dictates non-global ignores + }, +]); ``` -For example, consider `projectA` which has `"root": true` set in the `.eslintrc` file in the `lib/` directory. In this case, while linting `main.js`, the configurations within `lib/` are used, but the `.eslintrc` file in `projectA/` is not. +To avoid confusion, use the `globalIgnores()` helper function to clearly indicate which ignores are meant to be global. Here's the previous example rewritten to use `globalIgnores()`: -```text -home -└── user - └── projectA - ├── .eslintrc.json <- Not used - └── lib - ├── .eslintrc.json <- { "root": true } - └── main.js +```js +// eslint.config.js +import { defineConfig, globalIgnores } from "eslint/config"; + +// Example of global ignores +export default defineConfig([ + globalIgnores([".config/", "dist/", "tsconfig.json"]), + { ... }, // ... other configuration object, inherit global ignores + { ... }, // ... other configuration object, inherit global ignores +]); + +// Example of non-global ignores +export default defineConfig([ + { + ignores: [".config/**", "dir1/script1.js"], + rules: { ... } // the presence of this property dictates non-global ignores + }, + { + ignores: ["other-dir/**", "dist/script2.js"], + rules: { ... } // the presence of this property dictates non-global ignores + }, +]); ``` -The complete configuration hierarchy, from highest to lowest precedence, is as follows: +For more information and examples on configuring rules regarding `ignores`, see [Ignore Files](ignore). -1. Inline configuration - 1. `/*eslint-disable*/` and `/*eslint-enable*/` - 1. `/*global*/` - 1. `/*eslint*/` - 1. `/*eslint-env*/` -1. Command line options (or CLIEngine equivalents): - 1. `--global` - 1. `--rule` - 1. `--env` - 1. `-c`, `--config` -1. Project-level configuration: - 1. `.eslintrc.*` or `package.json` file in the same directory as the linted file - 1. Continue searching for `.eslintrc.*` and `package.json` files in ancestor directories up to and including the root directory or until a config with `"root": true` is found. +#### Specifying base path -Please note that the [home directory of the current user on your preferred operating system](https://nodejs.org/api/os.html#os_os_homedir) (`~/`) is also considered a root directory in this context and searching for configuration files stops there as well. And with the [removal of support for Personal Configuration Files](configuration-files#personal-configuration-files-deprecated) from the 8.0.0 release forward, configuration files present in that directory are ignored. +You can optionally specify `basePath` to apply the configuration object to a specific subdirectory (including its subdirectories). -## Extending Configuration Files +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + // matches all files in tests and its subdirectories + { + basePath: "tests", + rules: { + "no-undef": "error", + }, + }, + + // matches all files ending with spec.js in tests and its subdirectories + { + basePath: "tests", + files: ["**/*.spec.js"], + languageOptions: { + globals: { + it: "readonly", + describe: "readonly", + }, + }, + }, + + // globally ignores tests/fixtures directory + { + basePath: "tests", + ignores: ["fixtures/"], + }, +]); +``` -A configuration file, once extended, can inherit all the traits of another configuration file (including rules, plugins, and language options) and modify all the options. As a result, there are three configurations, as defined below: +In combination with [`extends`](#extending-configurations), multiple configuration objects can be applied to the same subdirectory by specifying `basePath` only once, like this: -* Base config: the configuration that is extended. -* Derived config: the configuration that extends the base configuration. -* Resulting actual config: the result of merging the derived configuration into the base configuration. +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + basePath: "tests", + extends: [ + // matches all files in tests and its subdirectories + { + rules: { + "no-undef": "error", + }, + }, + + // matches all files ending with spec.js in tests and its subdirectories + { + files: ["**/*.spec.js"], + languageOptions: { + globals: { + it: "readonly", + describe: "readonly", + }, + }, + }, + + // globally ignores tests/fixtures directory + { + ignores: ["fixtures/"], + }, + ], + }, +]); +``` -The `extends` property value is either: +#### Cascading Configuration Objects -* a string that specifies a configuration (either a path to a config file, the name of a shareable config, `eslint:recommended`, or `eslint:all`) -* an array of strings where each additional configuration extends the preceding configurations +When more than one configuration object matches a given filename, the configuration objects are merged with later objects overriding previous objects when there is a conflict. For example: -ESLint extends configurations recursively, so a base configuration can also have an `extends` property. Relative paths and shareable config names in an `extends` property are resolved from the location of the config file where they appear. +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js"], + languageOptions: { + globals: { + MY_CUSTOM_GLOBAL: "readonly", + }, + }, + }, + { + files: ["tests/**/*.js"], + languageOptions: { + globals: { + it: "readonly", + describe: "readonly", + }, + }, + }, +]); +``` -The `eslint-config-` prefix can be omitted from the configuration name. For example, `airbnb` resolves as `eslint-config-airbnb`. +Using this configuration, all JavaScript files define a custom global object defined called `MY_CUSTOM_GLOBAL` while those JavaScript files in the `tests` directory have `it` and `describe` defined as global objects in addition to `MY_CUSTOM_GLOBAL`. For any JavaScript file in the `tests` directory, both configuration objects are applied, so `languageOptions.globals` are merged to create a final result. -The `rules` property can do any of the following to extend (or override) the set of rules: +### Configuring Linter Options -* enable additional rules -* change an inherited rule's severity without changing its options: - * Base config: `"eqeqeq": ["error", "allow-null"]` - * Derived config: `"eqeqeq": "warn"` - * Resulting actual config: `"eqeqeq": ["warn", "allow-null"]` -* override options for rules from base configurations: - * Base config: `"quotes": ["error", "single", "avoid-escape"]` - * Derived config: `"quotes": ["error", "single"]` - * Resulting actual config: `"quotes": ["error", "single"]` -* override options for rules given as object from base configurations: - * Base config: `"max-lines": ["error", { "max": 200, "skipBlankLines": true, "skipComments": true }]` - * Derived config: `"max-lines": ["error", { "max": 100 }]` - * Resulting actual config: `"max-lines": ["error", { "max": 100 }]` where `skipBlankLines` and `skipComments` default to `false` +Options specific to the linting process can be configured using the `linterOptions` object. These effect how linting proceeds and does not affect how the source code of the file is interpreted. -### Using a shareable configuration package +#### Disabling Inline Configuration -A [sharable configuration](../../extend/shareable-configs) is an npm package that exports a configuration object. Make sure that you have installed the package in your project root directory, so that ESLint can require it. +Inline configuration is implemented using an `/*eslint*/` comment, such as `/*eslint semi: error*/`. You can disallow inline configuration by setting `noInlineConfig` to `true`. When enabled, all inline configuration is ignored. Here's an example: -The `extends` property value can omit the `eslint-config-` prefix of the package name. +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js"], + linterOptions: { + noInlineConfig: true, + }, + }, +]); +``` -The `npm init @eslint/config` command can create a configuration so you can extend a popular style guide (for example, `eslint-config-standard`). +#### Reporting Unused Disable Directives -Example of a configuration file in YAML format: +Disable and enable directives such as `/*eslint-disable*/`, `/*eslint-enable*/` and `/*eslint-disable-next-line*/` are used to disable ESLint rules around certain portions of code. As code changes, it's possible for these directives to no longer be needed because the code has changed in such a way that the rule is no longer triggered. You can enable reporting of these unused disable directives by setting the `reportUnusedDisableDirectives` option to a severity string, as in this example: -```yaml -extends: standard -rules: - comma-dangle: - - error - - always - no-empty: warn +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js"], + linterOptions: { + reportUnusedDisableDirectives: "error", + }, + }, +]); ``` -### Using `eslint:recommended` +This setting defaults to `"warn"`. + +You can override this setting using the [`--report-unused-disable-directives`](../command-line-interface#--report-unused-disable-directives) or the [`--report-unused-disable-directives-severity`](../command-line-interface#--report-unused-disable-directives-severity) command line options. -Using `"eslint:recommended"` in the `extends` property enables a subset of core rules that report common problems (these rules are identified with a checkmark (recommended) on the [rules page](../../rules/)). +For legacy compatibility, `true` is equivalent to `"warn"` and `false` is equivalent to `"off"`. -Here's an example of extending `eslint:recommended` and overriding some of the set configuration options: +#### Reporting Unused Inline Configs -Example of a configuration file in JavaScript format: +Inline config comments such as `/* eslint rule-name: "error" */` are used to change ESLint rule severity and/or options around certain portions of code. +As a project's ESLint configuration file changes, it's possible for these directives to no longer be different from what was already set. +You can enable reporting of these unused inline config comments by setting the `reportUnusedInlineConfigs` option to a severity string, as in this example: ```js -module.exports = { - "extends": "eslint:recommended", - "rules": { - // enable additional rules - "indent": ["error", 4], - "linebreak-style": ["error", "unix"], - "quotes": ["error", "double"], - "semi": ["error", "always"], - - // override configuration set by extending "eslint:recommended" - "no-empty": "warn", - "no-cond-assign": ["error", "always"], - - // disable rules from base configurations - "for-direction": "off", - } -} +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js"], + linterOptions: { + reportUnusedInlineConfigs: "error", + }, + }, +]); ``` -### Using a configuration from a plugin +You can override this setting using the [`--report-unused-inline-configs`](../command-line-interface#--report-unused-inline-configs) command line option. -A [plugin](../../extend/plugins) is an npm package that can add various extensions to ESLint. A plugin can perform numerous functions, including but not limited to adding new rules and exporting [shareable configurations](../../extend/plugins#configs-in-plugins). Make sure the package has been installed in a directory where ESLint can require it. +### Configuring Rules -The `plugins` [property value](./plugins#configure-plugins) can omit the `eslint-plugin-` prefix of the package name. +You can configure any number of rules in a configuration object by add a `rules` property containing an object with your rule configurations. The names in this object are the names of the rules and the values are the configurations for each of those rules. Here's an example: -The `extends` property value can consist of: +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + rules: { + semi: "error", + }, + }, +]); +``` -* `plugin:` -* the package name (from which you can omit the prefix, for example, `react` is short for `eslint-plugin-react`) -* `/` -* the configuration name (for example, `recommended`) +This configuration object specifies that the [`semi`](../../rules/semi) rule should be enabled with a severity of `"error"`. You can also provide options to a rule by specifying an array where the first item is the severity and each item after that is an option for the rule. For example, you can switch the `semi` rule to disallow semicolons by passing `"never"` as an option: + +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + rules: { + semi: ["error", "never"], + }, + }, +]); +``` -Example of a configuration file in JSON format: +Each rule specifies its own options and can be any valid JSON data type. Please check the documentation for the rule you want to configure for more information about its available options. -```json -{ - "plugins": [ - "react" - ], - "extends": [ - "eslint:recommended", - "plugin:react/recommended" - ], - "rules": { - "react/no-set-state": "off" - } -} +For more information on configuring rules, see [Configure Rules](rules). + +### Configuring Shared Settings + +ESLint supports adding shared settings into configuration files. When you add a `settings` object to a configuration object, it is supplied to every rule. By convention, plugins namespace the settings they are interested in to avoid collisions with others. Plugins can use `settings` to specify the information that should be shared across all of their rules. This may be useful if you are adding custom rules and want them to have access to the same information. Here's an example: + +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + settings: { + sharedData: "Hello", + }, + plugins: { + customPlugin: { + rules: { + "my-rule": { + meta: { + // custom rule's meta information + }, + create(context) { + const sharedData = context.settings.sharedData; + return { + // code + }; + }, + }, + }, + }, + }, + rules: { + "customPlugin/my-rule": "error", + }, + }, +]); ``` -### Using a configuration file +### Extending Configurations -The `extends` property value can be an absolute or relative path to a base [configuration file](#using-configuration-files). ESLint resolves a relative path to a base configuration file relative to the configuration file that uses it. +A configuration object uses `extends` to inherit all the traits of another configuration object or array (including rules, plugins, and language options) and can then modify all the options. The `extends` key is an array of values indicating which configurations to extend from. The elements of the `extends` array can be one of three values: -Example of a configuration file in JSON format: +- a string that specifies the name of a configuration in a plugin +- a configuration object +- a configuration array -```json -{ - "extends": [ - "./node_modules/coding-standard/eslintDefaults.js", - "./node_modules/coding-standard/.eslintrc-es6", - "./node_modules/coding-standard/.eslintrc-jsx" - ], - "rules": { - "eqeqeq": "warn" - } -} +#### Using Configurations from Plugins + +ESLint plugins can export predefined configurations. These configurations are referenced using a string and follow the pattern `pluginName/configName`. The plugin must be specified in the `plugins` key first. Here's an example: + +```js +// eslint.config.js +import examplePlugin from "eslint-plugin-example"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js"], + plugins: { + example: examplePlugin, + }, + extends: ["example/recommended"], + }, +]); ``` -### Using `"eslint:all"` +In this example, the configuration named `recommended` from `eslint-plugin-example` is loaded. The plugin configurations can also be referenced by name inside of the configuration array. -The `extends` property value can be `"eslint:all"` to enable all core rules in the currently installed version of ESLint. The set of core rules can change at any minor or major version of ESLint. +You can also insert plugin configurations directly into the `extends` array. For example: + +```js +// eslint.config.js +import pluginExample from "eslint-plugin-example"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js"], + plugins: { + example: pluginExample, + }, + extends: [pluginExample.configs.recommended], + }, +]); +``` + +In this case, the configuration named `recommended` from `eslint-plugin-example` is accessed directly through the plugin object's `configs` property. + +::: important +It's recommended to always use a `files` key when you use the `extends` key to ensure that your configuration applies to the correct files. By omitting the `files` key, the extended configuration may end up applied to all files. +::: -**Important:** This configuration is **not recommended for production use** because it changes with every minor and major version of ESLint. Use it at your own risk. +#### Using Predefined Configurations -You might enable all core rules as a shortcut to explore rules and options while you decide on the configuration for a project, especially if you rarely override options or disable rules. The default options for rules are not endorsements by ESLint (for example, the default option for the [`quotes`](../../rules/quotes) rule does not mean double quotes are better than single quotes). +ESLint has two predefined configurations for JavaScript: -If your configuration extends `eslint:all`, after you upgrade to a newer major or minor version of ESLint, review the reported problems before you use the `--fix` option on the [command line](../command-line-interface#--fix), so you know if a new fixable rule will make changes to the code. +- `js/recommended` - enables the rules that ESLint recommends everyone use to avoid potential errors. +- `js/all` - enables all of the rules shipped with ESLint. This configuration is **not recommended** for production use because it changes with every minor and major version of ESLint. Use at your own risk. -Example of a configuration file in JavaScript format: +To include these predefined configurations, install the `@eslint/js` package and then make any modifications to other properties in subsequent configuration objects: ```js -module.exports = { - "extends": "eslint:all", - "rules": { - // override default options - "comma-dangle": ["error", "always"], - "indent": ["error", 2], - "no-cond-assign": ["error", "always"], - - // disable now, but enable in the future - "one-var": "off", // ["error", "never"] - - // disable - "init-declarations": "off", - "no-console": "off", - "no-inline-comments": "off", - } -} +// eslint.config.js +import js from "@eslint/js"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js"], + plugins: { + js, + }, + extends: ["js/recommended"], + rules: { + "no-unused-vars": "warn", + }, + }, +]); ``` -## Configuration Based on Glob Patterns +Here, the `js/recommended` predefined configuration is applied first and then another configuration object adds the desired configuration for [`no-unused-vars`](../../rules/no-unused-vars). -**v4.1.0+.** Sometimes a more fine-controlled configuration is necessary, like if the configuration for files within the same directory has to be different. In this case, you can provide configurations under the `overrides` key that only apply to files that match specific glob patterns, using the same format you would pass on the command line (e.g., `app/**/*.test.js`). +For more information on how to combine predefined configs with your preferences, please see [Combine Configs](combine-configs). -Glob patterns in overrides use [minimatch syntax](https://github.com/isaacs/minimatch). +#### Using a Shareable Configuration Package -### How do overrides work? +A sharable configuration is an npm package that exports a configuration object or array. This package should be installed as a dependency in your project and then referenced from inside of your `eslint.config.js` file. For example, to use a shareable configuration named `eslint-config-example`, your configuration file would look like this: -It is possible to override settings based on file glob patterns in your configuration by using the `overrides` key. An example of using the `overrides` key is as follows: +```js +// eslint.config.js +import exampleConfig from "eslint-config-example"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js"], + extends: [exampleConfig], + rules: { + "no-unused-vars": "warn", + }, + }, +]); +``` -In your `.eslintrc.json`: +In this example, `exampleConfig` can be either an object or an array, and either way it can be inserted directly into the `extends` array. -```json -{ - "rules": { - "quotes": ["error", "double"] - }, +For more information on how to combine shareable configs with your preferences, please see [Combine Configs](combine-configs). - "overrides": [ - { - "files": ["bin/*.js", "lib/*.js"], - "excludedFiles": "*.test.js", - "rules": { - "quotes": ["error", "single"] - } - } - ] -} +### Configuration Naming Conventions + +The `name` property is optional, but it is recommended to provide a name for each configuration object, especially when you are creating shared configurations. The name is used in error messages and the config inspector to help identify which configuration object is being used. + +The name should be descriptive of the configuration object's purpose and scoped with the configuration name or plugin name using `/` as a separator. ESLint does not enforce the names to be unique at runtime, but it is recommended that unique names be set to avoid confusion. + +For example, if you are creating a configuration object for a plugin named `eslint-plugin-example`, you might add `name` to the configuration objects with the `example/` prefix: + +```js +export default { + configs: { + recommended: { + name: "example/recommended", + rules: { + "no-unused-vars": "warn", + }, + }, + strict: { + name: "example/strict", + rules: { + "no-unused-vars": "error", + }, + }, + }, +}; +``` + +When exposing arrays of configuration objects, the `name` may have extra scoping levels to help identify the configuration object. For example: + +```js +export default { + configs: { + strict: [ + { + name: "example/strict/language-setup", + languageOptions: { + ecmaVersion: 2024, + }, + }, + { + name: "example/strict/sub-config", + files: ["src/**/*.js"], + rules: { + "no-unused-vars": "error", + }, + }, + ], + }, +}; ``` -Here is how overrides work in a configuration file: - -* The patterns are applied against the file path relative to the directory of the config file. For example, if your config file has the path `/Users/john/workspace/any-project/.eslintrc.js` and the file you want to lint has the path `/Users/john/workspace/any-project/lib/util.js`, then the pattern provided in `.eslintrc.js` is executed against the relative path `lib/util.js`. -* Glob pattern overrides have higher precedence than the regular configuration in the same config file. Multiple overrides within the same config are applied in order. That is, the last override block in a config file always has the highest precedence. -* A glob specific configuration works almost the same as any other ESLint config. Override blocks can contain any configuration options that are valid in a regular config, with the exception of `root` and `ignorePatterns`. - * A glob specific configuration can have an `extends` setting, but the `root` property in the extended configs is ignored. The `ignorePatterns` property in the extended configs is used only for the files the glob specific configuration matched. - * Nested `overrides` settings are applied only if the glob patterns of both the parent config and the child config are matched. This is the same when the extended configs have an `overrides` setting. -* Multiple glob patterns can be provided within a single override block. A file must match at least one of the supplied patterns for the configuration to apply. -* Override blocks can also specify patterns to exclude from matches. If a file matches any of the excluded patterns, the configuration won't apply. - -### Relative glob patterns - -```txt -project-root -├── app -│ ├── lib -│ │ ├── foo.js -│ │ ├── fooSpec.js -│ ├── components -│ │ ├── bar.js -│ │ ├── barSpec.js -│ ├── .eslintrc.json -├── server -│ ├── server.js -│ ├── serverSpec.js -├── .eslintrc.json +## Configuration File Resolution + +When ESLint is run on the command line, it first checks the current working directory for `eslint.config.js`. If that file is found, then the search stops, otherwise it checks for `eslint.config.mjs`. If that file is found, then the search stops, otherwise it checks for `eslint.config.cjs`. If none of the files are found, it checks the parent directory for each file. This search continues until either a config file is found or the root directory is reached. + +You can prevent this search for `eslint.config.js` by using the `-c` or `--config` option on the command line to specify an alternate configuration file, such as: + +{{ npx_tabs({ + package: "eslint", + args: ["--config", "some-other-file.js", "**/*.js"] +}) }} + +In this case, ESLint does not search for `eslint.config.js` and instead uses `some-other-file.js`. + +### Experimental Configuration File Resolution + +::: warning +This feature is experimental and its details may change before being finalized. This behavior will be the new lookup behavior starting in v10.0.0, but you can try it today using a feature flag. +::: + +You can use the `v10_config_lookup_from_file` flag to change the way ESLint searches for configuration files. Instead of searching from the current working directory, ESLint will search for a configuration file by first starting in the directory of the file being linted and then searching up its ancestor directories until it finds a `eslint.config.js` file (or any other extension of configuration file). This behavior is better for monorepos, where each subdirectory may have its own configuration file. + +To use this feature on the command line, use the `--flag` flag: + +```shell +npx eslint --flag v10_config_lookup_from_file . ``` -The config in `app/.eslintrc.json` defines the glob pattern `**/*Spec.js`. This pattern is relative to the base directory of `app/.eslintrc.json`. So, this pattern would match `app/lib/fooSpec.js` and `app/components/barSpec.js` but **NOT** `server/serverSpec.js`. If you defined the same pattern in the `.eslintrc.json` file within in the `project-root` folder, it would match all three of the `*Spec` files. +For more information about using feature flags, see [Feature Flags](../../flags/). -If a config is provided via the `--config` CLI option, the glob patterns in the config are relative to the current working directory rather than the base directory of the given config. For example, if `--config configs/.eslintrc.json` is present, the glob patterns in the config are relative to `.` rather than `./configs`. +## TypeScript Configuration Files -### Specifying target files to lint +For Deno and Bun, TypeScript configuration files are natively supported; for Node.js, you must install the optional dev dependency [`jiti`](https://github.com/unjs/jiti) in version 2.0.0 or later in your project (this dependency is not automatically installed by ESLint): -If you specified directories with CLI (e.g., `eslint lib`), ESLint searches target files in the directory to lint. The target files are `*.js` or the files that match any of `overrides` entries (but exclude entries that are any of `files` end with `*`). +{{ npm_tabs({ + command: "install", + packages: ["jiti"], + args: ["--save-dev"] +}) }} -If you specified the [`--ext`](../command-line-interface#--ext) command line option along with directories, the target files are only the files that have specified file extensions regardless of `overrides` entries. +You can then create a configuration file with a `.ts`, `.mts`, or `.cts` extension, and export an array of [configuration objects](#configuration-objects). -## Personal Configuration Files (deprecated) +::: important +ESLint does not perform type checking on your configuration file and does not apply any settings from `tsconfig.json`. +::: -âš ī¸ **This feature has been deprecated**. This feature was removed in the 8.0.0 release. If you want to continue to use personal configuration files, please use the [`--config` CLI option](../command-line-interface#-c---config). For more information regarding this decision, please see [RFC 28](https://github.com/eslint/rfcs/pull/28) and [RFC 32](https://github.com/eslint/rfcs/pull/32). +### Native TypeScript Support -`~/` refers to [the home directory of the current user on your preferred operating system](https://nodejs.org/api/os.html#os_os_homedir). The personal configuration file being referred to here is `~/.eslintrc.*` file, which is currently handled differently than other configuration files. +If you're using **Node.js >= 22.10.0**, you can load TypeScript configuration files natively without requiring [`jiti`](https://github.com/unjs/jiti). This is possible thanks to the [**`--experimental-strip-types`**](https://nodejs.org/docs/latest-v22.x/api/cli.html#--experimental-strip-types) flag. -### How does ESLint find personal configuration files? +Since this feature is still experimental, you must also enable the `unstable_native_nodejs_ts_config` flag. -If `eslint` could not find any configuration file in the project, `eslint` loads `~/.eslintrc.*` file. +```bash +npx --node-options='--experimental-strip-types' eslint --flag unstable_native_nodejs_ts_config +``` -If `eslint` could find configuration files in the project, `eslint` ignores `~/.eslintrc.*` file even if it's in an ancestor directory of the project directory. +### Configuration File Precedence -### How do personal configuration files behave? +If you have multiple ESLint configuration files, ESLint prioritizes JavaScript files over TypeScript files. The order of precedence is as follows: -`~/.eslintrc.*` files behave similarly to regular configuration files, with some exceptions: +1. `eslint.config.js` +2. `eslint.config.mjs` +3. `eslint.config.cjs` +4. `eslint.config.ts` +5. `eslint.config.mts` +6. `eslint.config.cts` -`~/.eslintrc.*` files load shareable configs and custom parsers from `~/node_modules/` – similarly to `require()` – in the user's home directory. Please note that it doesn't load global-installed packages. +To override this behavior, use the `--config` or `-c` command line option to specify a different configuration file: -`~/.eslintrc.*` files load plugins from `$CWD/node_modules` by default in order to identify plugins uniquely. If you want to use plugins with `~/.eslintrc.*` files, plugins must be installed locally per project. Alternatively, you can use the [`--resolve-plugins-relative-to` CLI option](../command-line-interface#--resolve-plugins-relative-to) to change the location from which ESLint loads plugins. +{{ npx_tabs({ + package: "eslint", + args: ["--config", "eslint.config.ts"] +}) }} diff --git a/docs/src/use/configure/debug.md b/docs/src/use/configure/debug.md new file mode 100644 index 000000000000..966b84f13902 --- /dev/null +++ b/docs/src/use/configure/debug.md @@ -0,0 +1,83 @@ +--- +title: Debug Your Configuration +eleventyNavigation: + key: debug config + parent: configure + title: Debug Your Configuration + order: 8 +--- + +{%- from 'components/npx_tabs.macro.html' import npx_tabs %} + +ESLint creates a configuration for each file that is linted based on your configuration file and command line options. The larger the configuration file, the more difficult it can be to determine why a file isn't linted as expected. To aid in debugging your configuration, ESLint provides several tools. + +## Run the CLI in Debug Mode + +**Use When:** You aren't sure if the correct configuration file is being read. This may happen if you have multiple configuration files in the same project. + +**What To Do:** Run ESLint with the [`--debug`](../command-line-interface#--debug) command line flag and pass the file to check, like this: + +{{ npx_tabs({ + package: "eslint", + args: ["--debug", "file.js"] +}) }} + +This outputs all of ESLint's debugging information onto the console. You should copy this output to a file and then search for `eslint.config.js` to see which file is loaded. Here's some example output: + +```text +eslint:eslint Using file patterns: bin/eslint.js +0ms +eslint:eslint Searching for eslint.config.js +0ms +eslint:eslint Loading config from C:\Users\nzakas\projects\eslint\eslint\eslint.config.js +5ms +eslint:eslint Config file URL is file:///C:/Users/nzakas/projects/eslint/eslint/eslint.config.js +0ms +``` + +## Print a File's Calculated Configuration + +**Use When:** You aren't sure why linting isn't producing the expected results, either because it seems like your rule configuration isn't being honored or the wrong language options are being used. + +**What To Do:** Run ESLint with the [`--print-config`](../command-line-interface#--print-config) command line flag and pass the file to check, like this: + +{{ npx_tabs({ + package: "eslint", + args: ["--print-config", "file.js"] +}) }} + +This outputs a JSON representation of the file's calculated config, such as: + +```json +{ + "linterOptions": { + "reportUnusedDisableDirectives": 1 + }, + "language": "@/js", + "languageOptions": { + "sourceType": "module", + "ecmaVersion": "latest" + }, + "plugins": ["@"], + "rules": { + "prefer-const": 2 + } +} +``` + +::: tip +You won't see any entries for `files`, `ignores`, or `name`, because those are only used in calculating the final configuration and so do not appear in the result. You will see any default configuration applied by ESLint itself. +::: + +## Use the Config Inspector + +**Use When:** You aren't sure if certain configuration objects in your configuration file match a given filename. + +**What To Do:** Run ESLint with the [`--inspect-config`](../command-line-interface#--inspect-config) command line flag and pass the file to check, like this: + +{{ npx_tabs({ + package: "eslint", + args: ["--inspect-config"] +}) }} + +This initiates the config inspector by installing and starting [`@eslint/config-inspector`](https://github.com/eslint/config-inspector). You can then type in the filename in question to see which configuration objects will apply. + +![Config inspector screenshot showing which config objects match index.js](../../assets/images/configure/config-inspector.png) + +The config inspector also shows you when rules are deprecated, how many available rules you're using, and more. diff --git a/docs/src/use/configure/ignore-deprecated.md b/docs/src/use/configure/ignore-deprecated.md new file mode 100644 index 000000000000..7647e0d4c84b --- /dev/null +++ b/docs/src/use/configure/ignore-deprecated.md @@ -0,0 +1,172 @@ +--- +title: Ignore Files (Deprecated) +--- + +::: warning +This documentation is for ignoring files using the deprecated eslintrc configuration format. [View the updated documentation](ignore). +::: + +You can configure ESLint to ignore certain files and directories while linting by specifying one or more glob patterns. +You can ignore files in the following ways: + +- Add `ignorePatterns` to a configuration file. +- Create a dedicated file that contains the ignore patterns (`.eslintignore` by default). + +## `ignorePatterns` in Config Files + +You can tell ESLint to ignore specific files and directories using `ignorePatterns` in your config files. `ignorePatterns` patterns follow the same rules as `.eslintignore`. Please see the [`.eslintignore` file documentation](#the-eslintignore-file) to learn more. + +```json +{ + "ignorePatterns": ["temp.js", "**/vendor/*.js"], + "rules": { + //... + } +} +``` + +- Glob patterns in `ignorePatterns` are relative to the directory that the config file is placed in. +- You cannot write `ignorePatterns` property under `overrides` property. +- Patterns defined in `.eslintignore` take precedence over the `ignorePatterns` property of config files. + +If a glob pattern starts with `/`, the pattern is relative to the base directory of the config file. For example, `/foo.js` in `lib/.eslintrc.json` matches to `lib/foo.js` but not `lib/subdir/foo.js`. + +If a config is provided via the `--config` CLI option, the ignore patterns that start with `/` in the config are relative to the current working directory rather than the base directory of the given config. For example, if `--config configs/.eslintrc.json` is present, the ignore patterns in the config are relative to `.` rather than `./configs`. + +## The `.eslintignore` File + +You can tell ESLint to ignore specific files and directories by creating an `.eslintignore` file in your project's root directory. The `.eslintignore` file is a plain text file where each line is a glob pattern indicating which paths should be omitted from linting. For example, the following omits all JavaScript files: + +```text +**/*.js +``` + +When ESLint is run, it looks in the current working directory to find an `.eslintignore` file before determining which files to lint. If this file is found, then those preferences are applied when traversing directories. Only one `.eslintignore` file can be used at a time, so `.eslintignore` files other than the one in the current working directory are not used. + +Globs are matched using [node-ignore](https://github.com/kaelzhang/node-ignore), so a number of features are available: + +- Lines beginning with `#` are treated as comments and do not affect the ignore patterns. +- Paths are relative to the current working directory. This is also true of paths passed in via the `--ignore-pattern` [command](../command-line-interface#--ignore-pattern). +- Lines preceded by `!` are negated patterns that re-include a pattern that was ignored by an earlier pattern. +- Ignore patterns behave according to the `.gitignore` [specification](https://git-scm.com/docs/gitignore). + +Of particular note is that like `.gitignore` files, all paths used as patterns for both `.eslintignore` and `--ignore-pattern` must use forward slashes as their path separators. + +```text +# Valid +/root/src/*.js + +# Invalid +\root\src\*.js +``` + +Please see [`.gitignore`](https://git-scm.com/docs/gitignore)'s specification for further examples of valid syntax. + +In addition to any patterns in the `.eslintignore` file, ESLint always follows a couple of implicit ignore rules even if the `--no-ignore` flag is passed. The implicit rules are as follows: + +- `node_modules/` is ignored. +- dot-files (except for `.eslintrc.*`) as well as dot-folders and their contents are ignored. + +There are also some exceptions to these rules: + +- If the path to lint is a glob pattern or directory path and contains a dot-folder, all dot-files and dot-folders are linted. This includes dot-files and dot-folders that are buried deeper in the directory structure. + + For example, `eslint .config/` would lint all dot-folders and dot-files in the `.config` directory, including immediate children as well as children that are deeper in the directory structure. + +- If the path to lint is a specific file path and the `--no-ignore` flag has been passed, ESLint would lint the file regardless of the implicit ignore rules. + + For example, `eslint .config/my-config-file.js --no-ignore` would cause `my-config-file.js` to be linted. It should be noted that the same command without the `--no-ignore` line would not lint the `my-config-file.js` file. + +- Allowlist and denylist rules specified via `--ignore-pattern` or `.eslintignore` are prioritized above implicit ignore rules. + + For example, in this scenario, `.build/test.js` is the desired file to allowlist. Because all dot-folders and their children are ignored by default, `.build` must first be allowlisted so that eslint becomes aware of its children. Then, `.build/test.js` must be explicitly allowlisted, while the rest of the content is denylisted. This is done with the following `.eslintignore` file: + + ```text + # Allowlist 'test.js' in the '.build' folder + # But do not allow anything else in the '.build' folder to be linted + !.build + .build/* + !.build/test.js + ``` + + The following `--ignore-pattern` is also equivalent: + + ```shell + eslint --ignore-pattern '!.build' --ignore-pattern '.build/*' --ignore-pattern '!.build/test.js' parent-folder/ + ``` + +## Using an Alternate File + +If you'd prefer to use a different file than the `.eslintignore` in the current working directory, you can specify it on the command line using the `--ignore-path` option. For example, you can use `.jshintignore` file because it has the same format: + +```shell +eslint --ignore-path .jshintignore file.js +``` + +You can also use your `.gitignore` file: + +```shell +eslint --ignore-path .gitignore file.js +``` + +Any file that follows the standard ignore file format can be used. Keep in mind that specifying `--ignore-path` means that the existing `.eslintignore` file is not used. Note that globbing rules in `.eslintignore` follow those of `.gitignore`. + +## Using eslintIgnore in package.json + +If an `.eslintignore` file is not found and an alternate file is not specified, ESLint looks in `package.json` for the `eslintIgnore` key to check for files to ignore. + +```json +{ + "name": "mypackage", + "version": "0.0.1", + "eslintConfig": { + "env": { + "browser": true, + "node": true + } + }, + "eslintIgnore": ["hello.js", "world.js"] +} +``` + +## Ignored File Warnings + +When you pass directories to ESLint, files and directories are silently ignored. If you pass a specific file to ESLint, then ESLint creates a warning that the file was skipped. For example, suppose you have an `.eslintignore` file that looks like this: + +```text +foo.js +``` + +And then you run: + +```shell +eslint foo.js +``` + +You'll see this warning: + +```text +foo.js + 0:0 warning File ignored because of a matching ignore pattern. Use "--no-ignore" to disable file ignore settings or use "--no-warn-ignored" to suppress this warning. + +✖ 1 problem (0 errors, 1 warning) +``` + +This message occurs because ESLint is unsure if you wanted to actually lint the file or not. As the message indicates, you can use `--no-ignore` to omit using the ignore rules. + +Consider another scenario where you want to run ESLint on a specific dot-file or dot-folder, but have forgotten to specifically allow those files in your `.eslintignore` file. You would run something like this: + +```shell +eslint .config/foo.js +``` + +You would see this warning: + +```text +.config/foo.js + 0:0 warning File ignored by default. Use a negated ignore pattern (like "--ignore-pattern '!'") to override + +✖ 1 problem (0 errors, 1 warning) +``` + +This message occurs because, normally, this file would be ignored by ESLint's implicit ignore rules (as mentioned above). A negated ignore rule in your `.eslintignore` file would override the implicit rule and reinclude this file for linting. Additionally, in this case, `--no-ignore` could be used to lint the file as well. diff --git a/docs/src/use/configure/ignore.md b/docs/src/use/configure/ignore.md index 16f1bfbcdc92..9aee7606d5e4 100644 --- a/docs/src/use/configure/ignore.md +++ b/docs/src/use/configure/ignore.md @@ -5,145 +5,164 @@ eleventyNavigation: parent: configure title: Ignore Files order: 7 - --- -You can configure ESLint to ignore certain files and directories while linting by specifying one or more glob patterns. -You can ignore files in the following ways: +{%- from 'components/npx_tabs.macro.html' import npx_tabs %} -* Add `ignorePatterns` to a configuration file. -* Create a dedicated file that contains the ignore patterns (`.eslintignore` by default). +::: tip +This page explains how to ignore files using the flat config format. For the deprecated eslintrc format, [see the deprecated documentation](ignore-deprecated). +::: -## `ignorePatterns` in Config Files +::: tip +This page explains how to use the `globalIgnores()` function to completely ignore files and directories. For more information on non-global ignores, see [Specifying files and ignores](configuration-files#specifying-files-and-ignores). For more information on the differences between global and non-global ignores, see [Globally ignoring files with `ignores`](configuration-files#globally-ignoring-files-with-ignores). +::: +You can configure ESLint to ignore certain files and directories while linting by specifying one or more glob patterns in the following ways: -You can tell ESLint to ignore specific files and directories using `ignorePatterns` in your config files. `ignorePatterns` patterns follow the same rules as `.eslintignore`. Please see the [`.eslintignore` file documentation](#the-eslintignore-file) to learn more. +- Inside of your `eslint.config.js` file. +- On the command line using `--ignore-pattern`. -```json -{ - "ignorePatterns": ["temp.js", "**/vendor/*.js"], - "rules": { - //... - } -} -``` +## Ignoring Files -* Glob patterns in `ignorePatterns` are relative to the directory that the config file is placed in. -* You cannot write `ignorePatterns` property under `overrides` property. -* Patterns defined in `.eslintignore` take precedence over the `ignorePatterns` property of config files. +In your `eslint.config.js` file, you can use the `globalIgnores()` helper function to indicate patterns of files to be ignored. Here's an example: -If a glob pattern starts with `/`, the pattern is relative to the base directory of the config file. For example, `/foo.js` in `lib/.eslintrc.json` matches to `lib/foo.js` but not `lib/subdir/foo.js`. +```js +// eslint.config.js +import { defineConfig, globalIgnores } from "eslint/config"; -If a config is provided via the `--config` CLI option, the ignore patterns that start with `/` in the config are relative to the current working directory rather than the base directory of the given config. For example, if `--config configs/.eslintrc.json` is present, the ignore patterns in the config are relative to `.` rather than `./configs`. +export default defineConfig([globalIgnores([".config/*"])]); +``` -## The `.eslintignore` File +This configuration specifies that all of the files in the `.config` directory should be ignored. This pattern is added after the default patterns, which are `["**/node_modules/", ".git/"]`. -You can tell ESLint to ignore specific files and directories by creating an `.eslintignore` file in your project's root directory. The `.eslintignore` file is a plain text file where each line is a glob pattern indicating which paths should be omitted from linting. For example, the following omits all JavaScript files: +You can also ignore files on the command line using [`--ignore-pattern`](../command-line-interface#--ignore-pattern), such as: -```text -**/*.js -``` +{{ npx_tabs({ + package: "eslint", + args: [".", "--ignore-pattern", "\'.config/*\'"] +}) }} -When ESLint is run, it looks in the current working directory to find an `.eslintignore` file before determining which files to lint. If this file is found, then those preferences are applied when traversing directories. Only one `.eslintignore` file can be used at a time, so `.eslintignore` files other than the one in the current working directory are not used. +## Ignoring Directories -Globs are matched using [node-ignore](https://github.com/kaelzhang/node-ignore), so a number of features are available: +Ignoring directories works the same way as ignoring files, by passing a pattern to the `globalIgnores()` helper function. For example, the following ignores the `.config` directory as a whole (meaning file search will not traverse into it at all): -* Lines beginning with `#` are treated as comments and do not affect the ignore patterns. -* Paths are relative to the current working directory. This is also true of paths passed in via the `--ignore-pattern` [command](../command-line-interface#--ignore-pattern). -* Lines preceded by `!` are negated patterns that re-include a pattern that was ignored by an earlier pattern. -* Ignore patterns behave according to the `.gitignore` [specification](https://git-scm.com/docs/gitignore). +```js +// eslint.config.js +import { defineConfig, globalIgnores } from "eslint/config"; -Of particular note is that like `.gitignore` files, all paths used as patterns for both `.eslintignore` and `--ignore-pattern` must use forward slashes as their path separators. +export default defineConfig([globalIgnores([".config/"])]); +``` -```text -# Valid -/root/src/*.js +Unlike `.gitignore`, an ignore pattern like `.config` will only ignore the `.config` directory in the same directory as the configuration file. If you want to recursively ignore all directories named `.config`, you need to use `**/.config/`, as in this example: + +```js +// eslint.config.js +import { defineConfig, globalIgnores } from "eslint/config"; -# Invalid -\root\src\*.js +export default defineConfig([globalIgnores(["**/.config/"])]); ``` -Please see [`.gitignore`](https://git-scm.com/docs/gitignore)'s specification for further examples of valid syntax. +## Unignoring Files and Directories -In addition to any patterns in the `.eslintignore` file, ESLint always follows a couple of implicit ignore rules even if the `--no-ignore` flag is passed. The implicit rules are as follows: +You can also unignore files and directories that are ignored by previous patterns, including the default patterns. For example, this config unignores `node_modules/mylibrary`: -* `node_modules/` is ignored. -* dot-files (except for `.eslintrc.*`) as well as dot-folders and their contents are ignored. +```js +// eslint.config.js +import { defineConfig, globalIgnores } from "eslint/config"; -There are also some exceptions to these rules: +export default defineConfig([ + globalIgnores([ + "!node_modules/", // unignore `node_modules/` directory + "node_modules/*", // ignore its content + "!node_modules/mylibrary/", // unignore `node_modules/mylibrary` directory + ]), +]); +``` -* If the path to lint is a glob pattern or directory path and contains a dot-folder, all dot-files and dot-folders are linted. This includes dot-files and dot-folders that are buried deeper in the directory structure. +If you'd like to ignore a directory except for specific files or subdirectories, then the ignore pattern `directory/**/*` must be used instead of `directory/**`. The pattern `directory/**` ignores the entire directory and its contents, so traversal will skip over the directory completely and you cannot unignore anything inside. - For example, `eslint .config/` would lint all dot-folders and dot-files in the `.config` directory, including immediate children as well as children that are deeper in the directory structure. +For example, `build/**` ignores directory `build` and its contents, whereas `build/**/*` ignores only its contents. If you'd like to ignore everything in the `build` directory except for `build/test.js`, you'd need to create a config like this: -* If the path to lint is a specific file path and the `--no-ignore` flag has been passed, ESLint would lint the file regardless of the implicit ignore rules. +```js +// eslint.config.js +import { defineConfig, globalIgnores } from "eslint/config"; - For example, `eslint .config/my-config-file.js --no-ignore` would cause `my-config-file.js` to be linted. It should be noted that the same command without the `--no-ignore` line would not lint the `my-config-file.js` file. +export default defineConfig([ + globalIgnores([ + "build/**/*", // ignore all contents in and under `build/` directory but not the `build/` directory itself + "!build/test.js", // unignore `!build/test.js` + ]), +]); +``` -* Allowlist and denylist rules specified via `--ignore-pattern` or `.eslintignore` are prioritized above implicit ignore rules. +If you'd like to ignore a directory except for specific files at any level under the directory, you should also ensure that subdirectories are not ignored. Note that while patterns that end with `/` only match directories, patterns that don't end with `/` match both files and directories so it isn't possible to write a single pattern that only ignores files, but you can achieve this with two patterns: one to ignore all contents and another to unignore subdirectories. - For example, in this scenario, `.build/test.js` is the desired file to allowlist. Because all dot-folders and their children are ignored by default, `.build` must first be allowlisted so that eslint becomes aware of its children. Then, `.build/test.js` must be explicitly allowlisted, while the rest of the content is denylisted. This is done with the following `.eslintignore` file: +For example, this config ignores all files in and under `build` directory except for files named `test.js` at any level: - ```text - # Allowlist 'test.js' in the '.build' folder - # But do not allow anything else in the '.build' folder to be linted - !.build - .build/* - !.build/test.js - ``` +```js +// eslint.config.js +import { defineConfig, globalIgnores } from "eslint/config"; - The following `--ignore-pattern` is also equivalent: +export default defineConfig([ + globalIgnores([ + "build/**/*", // ignore all contents in and under `build/` directory but not the `build/` directory itself + "!build/**/*/", // unignore all subdirectories + "!build/**/test.js", // unignore `test.js` files + ]), +]); +``` - ```shell - eslint --ignore-pattern '!.build' --ignore-pattern '.build/*' --ignore-pattern '!.build/test.js' parent-folder/ - ``` +::: important +Note that only global `ignores` patterns can match directories. +`ignores` patterns that are specific to a configuration will only match file names. +::: -## Using an Alternate File +You can also unignore files on the command line using [`--ignore-pattern`](../command-line-interface#--ignore-pattern), such as: -If you'd prefer to use a different file than the `.eslintignore` in the current working directory, you can specify it on the command line using the `--ignore-path` option. For example, you can use `.jshintignore` file because it has the same format: +{{ npx_tabs({ + package: "eslint", + args: [".", "--ignore-pattern", "\'!node_modules/\'"] +}) }} -```shell -eslint --ignore-path .jshintignore file.js -``` +## Glob Pattern Resolution -You can also use your `.gitignore` file: +How glob patterns are evaluated depends on where they are located and how they are used: -```shell -eslint --ignore-path .gitignore file.js -``` +1. When using `globalIgnores()` in an `eslint.config.js` file, glob patterns are evaluated relative to the `eslint.config.js` file. +1. When using `globalIgnores()` in an alternate configuration file specified using the [`--config`](../command-line-interface#-c---config) command line option, glob patterns are evaluated relative to the current working directory. +1. When using [`--ignore-pattern`](../command-line-interface#--ignore-pattern), glob patterns are evaluated relative to the current working directory. -Any file that follows the standard ignore file format can be used. Keep in mind that specifying `--ignore-path` means that the existing `.eslintignore` file is not used. Note that globbing rules in `.eslintignore` follow those of `.gitignore`. +## Name the Global Ignores Config -## Using eslintIgnore in package.json +By default, `globalIgnores()` will assign a name to the config that represents your ignores. You can override this name by providing a second argument to `globalIgnores()`, which is the name you'd like to use instead of the default: -If an `.eslintignore` file is not found and an alternate file is not specified, ESLint looks in `package.json` for the `eslintIgnore` key to check for files to ignore. +```js +// eslint.config.js +import { defineConfig, globalIgnores } from "eslint/config"; -```json -{ - "name": "mypackage", - "version": "0.0.1", - "eslintConfig": { - "env": { - "browser": true, - "node": true - } - }, - "eslintIgnore": ["hello.js", "world.js"] -} +export default defineConfig([ + globalIgnores(["build/**/*"], "Ignore Build Directory"), +]); ``` +The `"Ignore Build Directory"` string in this example is the name of the config created for the global ignores. This is useful for debugging purposes. + ## Ignored File Warnings -When you pass directories to ESLint, files and directories are silently ignored. If you pass a specific file to ESLint, then ESLint creates a warning that the file was skipped. For example, suppose you have an `.eslintignore` file that looks like this: +When you pass directories to the ESLint CLI, files and directories are silently ignored. If you pass a specific file to ESLint, then ESLint creates a warning that the file was skipped. For example, suppose you have an `eslint.config.js` file that looks like this: -```text -foo.js +```js +// eslint.config.js +import { defineConfig, globalIgnores } from "eslint/config"; + +export default defineConfig([globalIgnores(["foo.js"])]); ``` And then you run: -```shell -eslint foo.js -``` +{{ npx_tabs({ + package: "eslint", + args: ["foo.js"] +}) }} You'll see this warning: @@ -156,19 +175,26 @@ foo.js This message occurs because ESLint is unsure if you wanted to actually lint the file or not. As the message indicates, you can use `--no-ignore` to omit using the ignore rules. -Consider another scenario where you want to run ESLint on a specific dot-file or dot-folder, but have forgotten to specifically allow those files in your `.eslintignore` file. You would run something like this: +## Including `.gitignore` Files -```shell -eslint .config/foo.js -``` +If you want to include patterns from a [`.gitignore`](https://git-scm.com/docs/gitignore) file or any other file with gitignore-style patterns, you can use [`includeIgnoreFile`](https://github.com/eslint/rewrite/tree/main/packages/compat#including-ignore-files) utility from the [`@eslint/compat`](https://www.npmjs.com/package/@eslint/compat) package. -You would see this warning: +By default, `includeIgnoreFile()` will assign a name to the config that represents your ignores. You can override this name by providing a second argument to `includeIgnoreFile()`, which is the name you'd like to use instead of the default: -```text -.config/foo.js - 0:0 warning File ignored by default. Use a negated ignore pattern (like "--ignore-pattern '!'") to override +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; +import { includeIgnoreFile } from "@eslint/compat"; +import { fileURLToPath } from "node:url"; -✖ 1 problem (0 errors, 1 warning) +const gitignorePath = fileURLToPath(new URL(".gitignore", import.meta.url)); + +export default defineConfig([ + includeIgnoreFile(gitignorePath, "Imported .gitignore patterns"), + { + // your overrides + }, +]); ``` -This message occurs because, normally, this file would be ignored by ESLint's implicit ignore rules (as mentioned above). A negated ignore rule in your `.eslintignore` file would override the implicit rule and reinclude this file for linting. Additionally, in this case, `--no-ignore` could be used to lint the file as well. +This automatically loads the specified file and translates gitignore-style patterns into `ignores` glob patterns. diff --git a/docs/src/use/configure/index.md b/docs/src/use/configure/index.md index e228383c944f..d6da50396bbe 100644 --- a/docs/src/use/configure/index.md +++ b/docs/src/use/configure/index.md @@ -5,20 +5,18 @@ eleventyNavigation: parent: use eslint title: Configure ESLint order: 3 - --- ESLint is designed to be flexible and configurable for your use case. You can turn off every rule and run only with basic syntax validation or mix and match the bundled rules and your custom rules to fit the needs of your project. There are two primary ways to configure ESLint: 1. **Configuration Comments** - use JavaScript comments to embed configuration information directly into a file. -2. **Configuration Files** - use a JavaScript, JSON, or YAML file to specify configuration information for an entire directory and all of its subdirectories. This can be in the form of a [`.eslintrc.*`](./configuration-files#configuration-file-formats) file or an `eslintConfig` field in a [`package.json`](https://docs.npmjs.com/files/package.json) file, both of which ESLint will look for and read automatically, or you can specify a configuration file on the [command line](../command-line-interface). +2. **Configuration Files** - use a JavaScript file to specify configuration information for an entire directory and all of its subdirectories. This can be in the form of an [`eslint.config.js`](./configuration-files) file which ESLint will look for and read automatically, or you can specify a configuration file on the [command line](../command-line-interface). Here are some of the options that you can configure in ESLint: -* [**Environments**](./language-options#specifying-environments) - which environments your script is designed to run in. Each environment brings with it a certain set of predefined global variables. -* [**Globals**](./language-options#specifying-globals) - the additional global variables your script accesses during execution. -* [**Rules**](rules) - which rules are enabled and at what error level. -* [**Plugins**](plugins) - which third-party plugins define additional rules, environments, configs, etc. for ESLint to use. +- [**Globals**](./language-options#specifying-globals) - the additional global variables your script accesses during execution. +- [**Rules**](rules) - which rules are enabled and at what error level. +- [**Plugins**](plugins) - which third-party plugins define additional rules, languages, configs, etc. for ESLint to use. All of these options give you fine-grained control over how ESLint treats your code. @@ -26,38 +24,33 @@ All of these options give you fine-grained control over how ESLint treats your c [**Configuration Files**](configuration-files) -* [Configuration File Formats](./configuration-files#configuration-file-formats) -* [Using Configuration Files](./configuration-files#using-configuration-files) -* [Adding Shared Settings](./configuration-files#adding-shared-settings) -* [Cascading and Hierarchy](./configuration-files#cascading-and-hierarchy) -* [Extending Configuration Files](./configuration-files#extending-configuration-files) -* [Configuration Based on Glob Patterns](./configuration-files#configuration-based-on-glob-patterns) -* [Personal Configuration Files](./configuration-files#personal-configuration-files-deprecated) +- [Configuration File Format](./configuration-files#configuration-file) +- [Configuration Objects](./configuration-files#configuration-objects) +- [Configuring Shared Settings](./configuration-files#configuring-shared-settings) +- [Configuration File Resolution](./configuration-files#configuration-file-resolution) [**Configure Language Options**](language-options) -* [Specifying Environments](./language-options#specifying-environments) -* [Specifying Globals](./language-options#specifying-globals) -* [Specifying Parser Options](./language-options#specifying-parser-options) +- [Specifying JavaScript Options](./language-options#specifying-javascript-options) +- [Specifying Globals](./language-options#specifying-globals) [**Configure Rules**](rules) -* [Configuring Rules](./rules) -* [Disabling Rules](./rules#disabling-rules) +- [Configuring Rules](./rules) +- [Disabling Rules](./rules#disabling-rules) [**Configure Plugins**](plugins) -* [Configure Plugins](./plugins#configure-plugins) -* [Specify a Processor](./plugins#specify-a-processor) +- [Configure Plugins](./plugins#configure-plugins) +- [Specify a Processor](./plugins#specify-a-processor) [**Configure a Parser**](./parser) -* [Configure a Custom Parser](./parser#configure-a-custom-parser) +- [Configure a Custom Parser](./parser#configure-a-custom-parser) [**Ignore Files**](ignore) -* [`ignorePatterns` in Config Files](./ignore#ignorepatterns-in-config-files) -* [The `.eslintignore` File](./ignore#the-eslintignore-file) -* [Using an Alternate File](./ignore#using-an-alternate-file) -* [Using eslintIgnore in package.json](./ignore#using-eslintignore-in-packagejson) -* [Ignored File Warnings](./ignore#ignored-file-warnings) +- [Ignoring Files](./ignore#ignoring-files) +- [Ignoring Directories](./ignore#ignoring-directories) +- [Unignoring Files and Directories](./ignore#unignoring-files-and-directories) +- [Ignored File Warnings](./ignore#ignored-file-warnings) diff --git a/docs/src/use/configure/language-options-deprecated.md b/docs/src/use/configure/language-options-deprecated.md new file mode 100644 index 000000000000..496abf008679 --- /dev/null +++ b/docs/src/use/configure/language-options-deprecated.md @@ -0,0 +1,221 @@ +--- +title: Configure Language Options (Deprecated) +--- + +::: warning +This documentation is for configuring language options using the deprecated eslintrc configuration format. [View the updated documentation](language-options). +::: + +The JavaScript ecosystem has a variety of runtimes, versions, extensions, and frameworks. Each of these can have different supported syntax and global variables. ESLint lets you configure language options specific to the JavaScript used in your project, like custom global variables. You can also use plugins to extend ESLint to support your project's language options. + +## Specifying Environments + +An environment provides predefined global variables. The available environments are: + +- `browser` - browser global variables. +- `node` - Node.js global variables and Node.js scoping. +- `commonjs` - CommonJS global variables and CommonJS scoping (use this for browser-only code that uses Browserify/WebPack). +- `shared-node-browser` - Globals common to both Node.js and Browser. +- `es6` - enable all ECMAScript 6 features except for modules (this automatically sets the `ecmaVersion` parser option to 6). +- `es2016` - adds all ECMAScript 2016 globals and automatically sets the `ecmaVersion` parser option to 7. +- `es2017` - adds all ECMAScript 2017 globals and automatically sets the `ecmaVersion` parser option to 8. +- `es2018` - adds all ECMAScript 2018 globals and automatically sets the `ecmaVersion` parser option to 9. +- `es2019` - adds all ECMAScript 2019 globals and automatically sets the `ecmaVersion` parser option to 10. +- `es2020` - adds all ECMAScript 2020 globals and automatically sets the `ecmaVersion` parser option to 11. +- `es2021` - adds all ECMAScript 2021 globals and automatically sets the `ecmaVersion` parser option to 12. +- `es2022` - adds all ECMAScript 2022 globals and automatically sets the `ecmaVersion` parser option to 13. +- `es2023` - adds all ECMAScript 2023 globals and automatically sets the `ecmaVersion` parser option to 14. +- `es2024` - adds all ECMAScript 2024 globals and automatically sets the `ecmaVersion` parser option to 15. +- `worker` - web workers global variables. +- `amd` - defines `require()` and `define()` as global variables as per the [amd](https://github.com/amdjs/amdjs-api/blob/master/AMD.md) spec. +- `mocha` - adds all of the Mocha testing global variables. +- `jasmine` - adds all of the Jasmine testing global variables for version 1.3 and 2.0. +- `jest` - Jest global variables. +- `phantomjs` - PhantomJS global variables. +- `protractor` - Protractor global variables. +- `qunit` - QUnit global variables. +- `jquery` - jQuery global variables. +- `prototypejs` - Prototype.js global variables. +- `shelljs` - ShellJS global variables. +- `meteor` - Meteor global variables. +- `mongo` - MongoDB global variables. +- `applescript` - AppleScript global variables. +- `nashorn` - Java 8 Nashorn global variables. +- `serviceworker` - Service Worker global variables. +- `atomtest` - Atom test helper globals. +- `embertest` - Ember test helper globals. +- `webextensions` - WebExtensions globals. +- `greasemonkey` - GreaseMonkey globals. + +These environments are not mutually exclusive, so you can define more than one at a time. + +Environments can be specified inside of a file, in configuration files or using the `--env` [command line](../command-line-interface) flag. + +### Using configuration comments + +To specify environments with a comment inside of a JavaScript file, use the following format: + +```js +/* eslint-env node, mocha */ +``` + +This enables Node.js and Mocha environments. + +### Using configuration files + +To specify environments in a configuration file, use the `env` key. Specify which environments you want to enable by setting each to `true`. For example, the following enables the browser and Node.js environments: + +```json +{ + "env": { + "browser": true, + "node": true + } +} +``` + +Or in a `package.json` file + +```json +{ + "name": "mypackage", + "version": "0.0.1", + "eslintConfig": { + "env": { + "browser": true, + "node": true + } + } +} +``` + +And in YAML: + +```yaml +--- +env: + browser: true + node: true +``` + +### Using a plugin + +If you want to use an environment from a plugin, be sure to specify the plugin name in the `plugins` array and then use the unprefixed plugin name, followed by a slash, followed by the environment name. For example: + +```json +{ + "plugins": ["example"], + "env": { + "example/custom": true + } +} +``` + +Or in a `package.json` file + +```json +{ + "name": "mypackage", + "version": "0.0.1", + "eslintConfig": { + "plugins": ["example"], + "env": { + "example/custom": true + } + } +} +``` + +## Specifying Globals + +Some of ESLint's core rules rely on knowledge of the global variables available to your code at runtime. Since these can vary greatly between different environments as well as be modified at runtime, ESLint makes no assumptions about what global variables exist in your execution environment. If you would like to use rules that require knowledge of what global variables are available, you can define global variables in your configuration file or by using configuration comments in your source code. + +### Using configuration comments + +To specify globals using a comment inside of your JavaScript file, use the following format: + +```js +/* global var1, var2 */ +``` + +This defines two global variables, `var1` and `var2`. If you want to optionally specify that these global variables can be written to (rather than only being read), then you can set each with a `"writable"` flag: + +```js +/* global var1:writable, var2:writable */ +``` + +### Using configuration files + +To configure global variables inside of a configuration file, set the `globals` configuration property to an object containing keys named for each of the global variables you want to use. For each global variable key, set the corresponding value equal to `"writable"` to allow the variable to be overwritten or `"readonly"` to disallow overwriting. For example: + +```json +{ + "globals": { + "var1": "writable", + "var2": "readonly" + } +} +``` + +And in YAML: + +```yaml +--- +globals: + var1: writable + var2: readonly +``` + +These examples allow `var1` to be overwritten in your code, but disallow it for `var2`. + +Globals can be disabled by setting their value to `"off"`. For example, in an environment where most ES2015 globals are available but `Promise` is unavailable, you might use this config: + +```json +{ + "env": { + "es6": true + }, + "globals": { + "Promise": "off" + } +} +``` + +For historical reasons, the boolean value `false` and the string value `"readable"` are equivalent to `"readonly"`. Similarly, the boolean value `true` and the string value `"writeable"` are equivalent to `"writable"`. However, the use of these older values is deprecated. + +## Specifying Parser Options + +ESLint allows you to specify the JavaScript language options you want to support. By default, ESLint expects ECMAScript 5 syntax. You can override that setting to enable support for other ECMAScript versions and JSX using parser options. + +Please note that supporting JSX syntax is not the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) if you are using React. + +By the same token, supporting ES6 syntax is not the same as supporting new ES6 globals (e.g., new types such as `Set`). For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 global variables, use `{ "env": { "es6": true } }`. Setting `{ "env": { "es6": true } }` enables ES6 syntax automatically, but `{ "parserOptions": { "ecmaVersion": 6 } }` does not enable ES6 globals automatically. In summary, to support only ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`, and to support both ES6 syntax and new ES6 global variables, such as `Set` and others, use `{ "env": { "es6": true } }`. + +Parser options are set in your `.eslintrc.*` file with the `parserOptions` property. The available options are: + +- `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, or 17 to specify the version of ECMAScript syntax you want to use. You can also set it to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), 2018 (same as 9), 2019 (same as 10), 2020 (same as 11), 2021 (same as 12), 2022 (same as 13), 2023 (same as 14), 2024 (same as 15), 2025 (same as 16), or 2026 (same as 17) to use the year-based naming. You can also set `"latest"` to use the most recently supported version. +- `sourceType` - set to `"script"` (default) or `"module"` if your code is in ECMAScript modules. +- `allowReserved` - allow the use of reserved words as identifiers (if `ecmaVersion` is 3). +- `ecmaFeatures` - an object indicating which additional language features you'd like to use: + - `globalReturn` - allow `return` statements in the global scope + - `impliedStrict` - enable global [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) (if `ecmaVersion` is 5 or greater) + - `jsx` - enable [JSX](https://facebook.github.io/jsx/) + +Here's an example `.eslintrc.json` file: + +```json +{ + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "rules": { + "semi": "error" + } +} +``` + +Setting parser options helps ESLint determine what is a parsing error. All language options are `false` by default. diff --git a/docs/src/use/configure/language-options.md b/docs/src/use/configure/language-options.md index 4bcd63e3c98f..1c686e2cafca 100644 --- a/docs/src/use/configure/language-options.md +++ b/docs/src/use/configure/language-options.md @@ -4,129 +4,73 @@ eleventyNavigation: key: configure language options parent: configure title: Configure Language Options - order: 3 - + order: 2 --- -The JavaScript ecosystem has a variety of runtimes, versions, extensions, and frameworks. Each of these can have different supported syntax and global variables. ESLint lets you configure language options specific to the JavaScript used in your project, like custom global variables. You can also use plugins to extend ESLint to support your project's language options. - -## Specifying Environments - -An environment provides predefined global variables. The available environments are: - -* `browser` - browser global variables. -* `node` - Node.js global variables and Node.js scoping. -* `commonjs` - CommonJS global variables and CommonJS scoping (use this for browser-only code that uses Browserify/WebPack). -* `shared-node-browser` - Globals common to both Node.js and Browser. -* `es6` - enable all ECMAScript 6 features except for modules (this automatically sets the `ecmaVersion` parser option to 6). -* `es2016` - adds all ECMAScript 2016 globals and automatically sets the `ecmaVersion` parser option to 7. -* `es2017` - adds all ECMAScript 2017 globals and automatically sets the `ecmaVersion` parser option to 8. -* `es2018` - adds all ECMAScript 2018 globals and automatically sets the `ecmaVersion` parser option to 9. -* `es2019` - adds all ECMAScript 2019 globals and automatically sets the `ecmaVersion` parser option to 10. -* `es2020` - adds all ECMAScript 2020 globals and automatically sets the `ecmaVersion` parser option to 11. -* `es2021` - adds all ECMAScript 2021 globals and automatically sets the `ecmaVersion` parser option to 12. -* `es2022` - adds all ECMAScript 2022 globals and automatically sets the `ecmaVersion` parser option to 13. -* `es2023` - adds all ECMAScript 2023 globals and automatically sets the `ecmaVersion` parser option to 14. -* `es2024` - adds all ECMAScript 2024 globals and automatically sets the `ecmaVersion` parser option to 15. -* `worker` - web workers global variables. -* `amd` - defines `require()` and `define()` as global variables as per the [amd](https://github.com/amdjs/amdjs-api/blob/master/AMD.md) spec. -* `mocha` - adds all of the Mocha testing global variables. -* `jasmine` - adds all of the Jasmine testing global variables for version 1.3 and 2.0. -* `jest` - Jest global variables. -* `phantomjs` - PhantomJS global variables. -* `protractor` - Protractor global variables. -* `qunit` - QUnit global variables. -* `jquery` - jQuery global variables. -* `prototypejs` - Prototype.js global variables. -* `shelljs` - ShellJS global variables. -* `meteor` - Meteor global variables. -* `mongo` - MongoDB global variables. -* `applescript` - AppleScript global variables. -* `nashorn` - Java 8 Nashorn global variables. -* `serviceworker` - Service Worker global variables. -* `atomtest` - Atom test helper globals. -* `embertest` - Ember test helper globals. -* `webextensions` - WebExtensions globals. -* `greasemonkey` - GreaseMonkey globals. - -These environments are not mutually exclusive, so you can define more than one at a time. - -Environments can be specified inside of a file, in configuration files or using the `--env` [command line](../command-line-interface) flag. - -### Using configuration comments - -To specify environments with a comment inside of a JavaScript file, use the following format: +::: tip +This page explains how to configure language options using the flat config format. For the deprecated eslintrc format, [see the deprecated documentation](language-options-deprecated). +::: -```js -/* eslint-env node, mocha */ -``` +The JavaScript ecosystem has a variety of runtimes, versions, extensions, and frameworks. Each of these can have different supported syntax and global variables. ESLint lets you configure language options specific to the JavaScript used in your project, like custom global variables. You can also use plugins to extend ESLint to support your project's language options. -This enables Node.js and Mocha environments. +## Specifying JavaScript Options -### Using configuration files +ESLint allows you to specify the JavaScript language options you want to support. By default, ESLint expects the most recent stage 4 ECMAScript syntax and ECMAScript modules (ESM) mode. You can override these settings by using the `languageOptions` key and specifying one or more of these properties: -To specify environments in a configuration file, use the `env` key. Specify which environments you want to enable by setting each to `true`. For example, the following enables the browser and Node.js environments: +- `ecmaVersion` (default: `"latest"`) - Indicates the ECMAScript version of the code being linted, determining both the syntax and the available global variables. Set to `3` or `5` for ECMAScript 3 and 5, respectively. Otherwise, you can use any year between `2015` to present. In most cases, we recommend using the default of `"latest"` to ensure you're always using the most recent ECMAScript version. +- `sourceType` (default: `"module"`) - Indicates the mode of the JavaScript file being used. Possible values are: + - `module` - ESM module (invalid when `ecmaVersion` is `3` or `5`). Your code has a module scope and is run in strict mode. + - `commonjs` - CommonJS module (useful if your code uses `require()`). Your code has a top-level function scope and runs in non-strict mode. + - `script` - non-module. Your code has a shared global scope and runs in non-strict mode. -```json -{ - "env": { - "browser": true, - "node": true - } -} -``` +Here's an example [configuration file](./configuration-files#configuration-file) you might use when linting ECMAScript 5 code: -Or in a `package.json` file - -```json -{ - "name": "mypackage", - "version": "0.0.1", - "eslintConfig": { - "env": { - "browser": true, - "node": true - } - } -} +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, + }, +]); ``` -And in YAML: +## Specifying Parser Options -```yaml ---- - env: - browser: true - node: true -``` +If you are using the built-in ESLint parser, you can additionally change how ESLint interprets your code by specifying the `languageOptions.parserOptions` key. All options are `false` by default: -### Using a plugin +- `allowReserved` - allow the use of reserved words as identifiers (if `ecmaVersion` is `3`). +- `ecmaFeatures` - an object indicating which additional language features you'd like to use: + - `globalReturn` - allow `return` statements in the global scope. + - `impliedStrict` - enable global [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) (if `ecmaVersion` is `5` or greater). + - `jsx` - enable [JSX](https://facebook.github.io/jsx/). -If you want to use an environment from a plugin, be sure to specify the plugin name in the `plugins` array and then use the unprefixed plugin name, followed by a slash, followed by the environment name. For example: +Here's an example [configuration file](./configuration-files#configuration-file) that enables JSX parsing in the default parser: -```json -{ - "plugins": ["example"], - "env": { - "example/custom": true - } -} +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, +]); ``` -Or in a `package.json` file - -```json -{ - "name": "mypackage", - "version": "0.0.1", - "eslintConfig": { - "plugins": ["example"], - "env": { - "example/custom": true - } - } -} -``` +::: important +Please note that supporting JSX syntax is not the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) if you are using React. +::: ## Specifying Globals @@ -148,76 +92,82 @@ This defines two global variables, `var1` and `var2`. If you want to optionally ### Using configuration files -To configure global variables inside of a configuration file, set the `globals` configuration property to an object containing keys named for each of the global variables you want to use. For each global variable key, set the corresponding value equal to `"writable"` to allow the variable to be overwritten or `"readonly"` to disallow overwriting. For example: - -```json -{ - "globals": { - "var1": "writable", - "var2": "readonly" - } -} -``` - -And in YAML: +To configure global variables inside of a [configuration file](./configuration-files#configuration-file), set the `languageOptions.globals` configuration property to an object containing keys named for each of the global variables you want to use. For each global variable key, set the corresponding value equal to `"writable"` to allow the variable to be overwritten or `"readonly"` to disallow overwriting. For example: -```yaml ---- - globals: - var1: writable - var2: readonly +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + languageOptions: { + globals: { + var1: "writable", + var2: "readonly", + }, + }, + }, +]); ``` -These examples allow `var1` to be overwritten in your code, but disallow it for `var2`. +This configuration allows `var1` to be overwritten in your code, but disallow it for `var2`. -Globals can be disabled by setting their value to `"off"`. For example, in an environment where most ES2015 globals are available but `Promise` is unavailable, you might use this config: +Globals can be disabled by setting their value to `"off"`. For example, in an environment where most globals are available but `Promise` is unavailable, you might use this config: -```json -{ - "env": { - "es6": true - }, - "globals": { - "Promise": "off" - } -} +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + languageOptions: { + globals: { + Promise: "off", + }, + }, + }, +]); ``` +::: tip For historical reasons, the boolean value `false` and the string value `"readable"` are equivalent to `"readonly"`. Similarly, the boolean value `true` and the string value `"writeable"` are equivalent to `"writable"`. However, the use of these older values is deprecated. +::: -## Specifying Parser Options - -ESLint allows you to specify the JavaScript language options you want to support. By default, ESLint expects ECMAScript 5 syntax. You can override that setting to enable support for other ECMAScript versions and JSX using parser options. +### Predefined global variables -Please note that supporting JSX syntax is not the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) if you are using React. +Apart from the ECMAScript standard built-in globals, which are automatically enabled based on the configured `languageOptions.ecmaVersion`, ESLint doesn't provide predefined sets of global variables. You can use the [`globals`](https://www.npmjs.com/package/globals) package to additionally enable all globals for a specific environment. For example, here is how you can add `console`, amongst other browser globals, into your configuration. -By the same token, supporting ES6 syntax is not the same as supporting new ES6 globals (e.g., new types such as `Set`). For ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`; for new ES6 global variables, use `{ "env": { "es6": true } }`. Setting `{ "env": { "es6": true } }` enables ES6 syntax automatically, but `{ "parserOptions": { "ecmaVersion": 6 } }` does not enable ES6 globals automatically. In summary, to support only ES6 syntax, use `{ "parserOptions": { "ecmaVersion": 6 } }`, and to support both ES6 syntax and new ES6 global variables, such as `Set` and others, use `{ "env": { "es6": true } }`. - -Parser options are set in your `.eslintrc.*` file with the `parserOptions` property. The available options are: - -* `ecmaVersion` - set to 3, 5 (default), 6, 7, 8, 9, 10, 11, 12, 13, 14, or 15 to specify the version of ECMAScript syntax you want to use. You can also set it to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), 2018 (same as 9), 2019 (same as 10), 2020 (same as 11), 2021 (same as 12), 2022 (same as 13), 2023 (same as 14), or 2024 (same as 15) to use the year-based naming. You can also set `"latest"` to use the most recently supported version. -* `sourceType` - set to `"script"` (default) or `"module"` if your code is in ECMAScript modules. -* `allowReserved` - allow the use of reserved words as identifiers (if `ecmaVersion` is 3). -* `ecmaFeatures` - an object indicating which additional language features you'd like to use: - * `globalReturn` - allow `return` statements in the global scope - * `impliedStrict` - enable global [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) (if `ecmaVersion` is 5 or greater) - * `jsx` - enable [JSX](https://facebook.github.io/jsx/) - -Here's an example `.eslintrc.json` file: - -```json -{ - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module", - "ecmaFeatures": { - "jsx": true - } - }, - "rules": { - "semi": "error" - } -} +```js +// eslint.config.js +import globals from "globals"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + languageOptions: { + globals: { + ...globals.browser, + }, + }, + }, +]); ``` -Setting parser options helps ESLint determine what is a parsing error. All language options are `false` by default. +You can include multiple different collections of globals in the same way. The following example includes globals both for web browsers and for [Jest](https://jestjs.io/): + +```js +// eslint.config.js +import globals from "globals"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + languageOptions: { + globals: { + ...globals.browser, + ...globals.jest, + }, + }, + }, +]); +``` diff --git a/docs/src/use/configure/migration-guide.md b/docs/src/use/configure/migration-guide.md index 2414ff25642b..e5d0e34b94da 100644 --- a/docs/src/use/configure/migration-guide.md +++ b/docs/src/use/configure/migration-guide.md @@ -4,21 +4,39 @@ eleventyNavigation: key: migration guide parent: configure title: Configuration Migration Guide - order: 8 + order: 9 --- +{%- from 'components/npm_tabs.macro.html' import npm_tabs with context %} +{%- from 'components/npx_tabs.macro.html' import npx_tabs %} + This guide provides an overview of how you can migrate your ESLint configuration file from the eslintrc format (typically configured in `.eslintrc.js` or `.eslintrc.json` files) to the new flat config format (typically configured in an `eslint.config.js` file). To learn more about the flat config format, refer to [this blog post](https://eslint.org/blog/2022/08/new-config-system-part-2/). For reference information on these configuration formats, refer to the following documentation: -* [eslintrc configuration files](configuration-files) -* [flat configuration files](configuration-files-new) +- [eslintrc configuration files](configuration-files-deprecated) +- [flat configuration files](configuration-files) + +## Migrate Your Config File + +To get started, use the [configuration migrator](https://npmjs.com/package/@eslint/migrate-config) on your existing configuration file (`.eslintrc`, `.eslintrc.json`, `.eslintrc.yml`), like this: + +{{ npx_tabs({ + package: "@eslint/migrate-config", + args: [".eslintrc.json"] +}) }} + +This will create a starting point for your `eslint.config.js` file but is not guaranteed to work immediately without further modification. It will, however, do most of the conversion work mentioned in this guide automatically. + +::: important +The configuration migrator doesn't yet work well for `.eslintrc.js` files. If you are using `.eslintrc.js`, the migration results in a config file that matches the evaluated output of your configuration and won't include any functions, conditionals, or anything other than the raw data represented in your configuration. +::: ## Start Using Flat Config Files -Starting with ESLint v9.0.0, the flat config file format will be the default configuration file format. Once ESLint v9.0.0 is released, you can start using the flat config file format without any additional configuration. +The flat config file format has been the default configuration file format since ESLint v9.0.0. You can start using the flat config file format without any additional configuration. To use flat config with ESLint v8, place a `eslint.config.js` file in the root of your project **or** set the `ESLINT_USE_FLAT_CONFIG` environment variable to `true`. @@ -26,10 +44,10 @@ To use flat config with ESLint v8, place a `eslint.config.js` file in the root o While the configuration file format has changed from eslintrc to flat config, the following has stayed the same: -* Syntax for configuring rules -* Syntax for configuring processors -* The CLI, except for the flag changes noted in [CLI Flag Changes](#cli-flag-changes). -* Global variables are configured the same way, but on a different property (see [Configuring Language Options](#configuring-language-options)). +- Syntax for configuring rules. +- Syntax for configuring processors. +- The CLI, except for the flag changes noted in [CLI Flag Changes](#cli-flag-changes). +- Global variables are configured the same way, but on a different property (see [Configuring Language Options](#configuring-language-options)). ## Key Differences between Configuration Formats @@ -47,13 +65,13 @@ For example, this eslintrc config file loads `eslint-plugin-jsdoc` and configure // .eslintrc.js module.exports = { - // ...other config - plugins: ["jsdoc"], - rules: { - "jsdoc/require-description": "error", - "jsdoc/check-values": "error" - } - // ...other config + // ...other config + plugins: ["jsdoc"], + rules: { + "jsdoc/require-description": "error", + "jsdoc/check-values": "error", + }, + // ...other config }; ``` @@ -65,19 +83,23 @@ In flat config, you would do the same thing like this: import jsdoc from "eslint-plugin-jsdoc"; export default [ - { - files: ["**/*.js"], - plugins: { - jsdoc: jsdoc - }, - rules: { - "jsdoc/require-description": "error", - "jsdoc/check-values": "error" - } - } + { + files: ["**/*.js"], + plugins: { + jsdoc: jsdoc, + }, + rules: { + "jsdoc/require-description": "error", + "jsdoc/check-values": "error", + }, + }, ]; ``` +::: tip +If you import a plugin and get an error such as "TypeError: context.getScope is not a function", then that means the plugin has not yet been updated to the ESLint v9.x rule API. While you should file an issue with the particular plugin, you can manually patch the plugin to work in ESLint v9.x using the [compatibility utilities](https://eslint.org/blog/2024/05/eslint-compatibility-utilities/). +::: + ### Custom Parsers In eslintrc files, importing a custom parser is similar to importing a plugin: you use a string to specify the name of the parser. @@ -90,9 +112,9 @@ For example, this eslintrc config file uses the `@babel/eslint-parser` parser: // .eslintrc.js module.exports = { - // ...other config - parser: "@babel/eslint-parser", - // ...other config + // ...other config + parser: "@babel/eslint-parser", + // ...other config }; ``` @@ -104,19 +126,19 @@ In flat config, you would do the same thing like this: import babelParser from "@babel/eslint-parser"; export default [ - { - // ...other config - languageOptions: { - parser: babelParser - } - // ...other config - } + { + // ...other config + languageOptions: { + parser: babelParser, + }, + // ...other config + }, ]; ``` ### Processors -In eslintrc files, processors had to be defined in a plugin, and then referenced by name in the configuration. Processors beginning with a dot indicated a [file extension-named processor](../../extend/custom-processors#file-extension-named-processor) which ESLint would automatically configure for that file extension. +In eslintrc files, processors had to be defined in a plugin, and then referenced by name in the configuration. Processors beginning with a dot indicated a [file extension-named processor](../../extend/custom-processors-deprecated#file-extension-named-processor) which ESLint would automatically configure for that file extension. In flat config files, processors can still be referenced from plugins by their name, but they can now also be inserted directly into the configuration. Processors will _never_ be automatically configured, and must be explicitly set in the configuration. @@ -125,16 +147,16 @@ As an example with a custom plugin with processors: ```javascript // node_modules/eslint-plugin-someplugin/index.js module.exports = { - processors: { - ".md": { - preprocess() {}, - postprocess() {} - }, - "someProcessor": { - preprocess() {}, - postprocess() {} - } - } + processors: { + ".md": { + preprocess() {}, + postprocess() {}, + }, + someProcessor: { + preprocess() {}, + postprocess() {}, + }, + }, }; ``` @@ -143,8 +165,8 @@ In eslintrc, you would configure as follows: ```javascript // .eslintrc.js module.exports = { - plugins: ["someplugin"], - processor: "someplugin/someProcessor" + plugins: ["someplugin"], + processor: "someplugin/someProcessor", }; ``` @@ -152,10 +174,12 @@ ESLint would also automatically add the equivalent of the following: ```javascript { - overrides: [{ - files: ["**/*.md"], - processor: "someplugin/.md" - }] + overrides: [ + { + files: ["**/*.md"], + processor: "someplugin/.md", + }, + ]; } ``` @@ -166,19 +190,19 @@ In flat config, the following are all valid ways to express the same: import somePlugin from "eslint-plugin-someplugin"; export default [ - { - plugins: { somePlugin }, - processor: "somePlugin/someProcessor" - }, - { - plugins: { somePlugin }, - // We can embed the processor object in the config directly - processor: somePlugin.processors.someProcessor - }, - { - // We don't need the plugin to be present in the config to use the processor directly - processor: somePlugin.processors.someProcessor - } + { + plugins: { somePlugin }, + processor: "somePlugin/someProcessor", + }, + { + plugins: { somePlugin }, + // We can embed the processor object in the config directly + processor: somePlugin.processors.someProcessor, + }, + { + // We don't need the plugin to be present in the config to use the processor directly + processor: somePlugin.processors.someProcessor, + }, ]; ``` @@ -205,10 +229,10 @@ For example, this eslintrc file applies to all files in the directory where it i // .eslintrc.js module.exports = { - // ...other config - rules: { - semi: ["warn", "always"] - } + // ...other config + rules: { + semi: ["warn", "always"], + }, }; ``` @@ -218,21 +242,21 @@ This eslintrc file supports multiple configs with overrides: // .eslintrc.js module.exports = { - // ...other config - overrides: [ - { - files: ["src/**/*"], - rules: { - semi: ["warn", "always"] - } - }, - { - files:["test/**/*"], - rules: { - "no-console": "off" - } - } - ] + // ...other config + overrides: [ + { + files: ["src/**/*"], + rules: { + semi: ["warn", "always"], + }, + }, + { + files: ["test/**/*"], + rules: { + "no-console": "off", + }, + }, + ], }; ``` @@ -244,19 +268,19 @@ For flat config, here is a configuration with the default glob pattern: import js from "@eslint/js"; export default [ - js.configs.recommended, // Recommended config applied to all files - // Override the recommended config - { - rules: { - indent: ["error", 2], - "no-unused-vars": "warn" - } - // ...other configuration - } + js.configs.recommended, // Recommended config applied to all files + // Override the recommended config + { + rules: { + indent: ["error", 2], + "no-unused-vars": "warn", + }, + // ...other configuration + }, ]; ``` -A flag config example configuration supporting multiple configs for different glob patterns: +A flat config example configuration supporting multiple configs for different glob patterns: ```javascript // eslint.config.js @@ -264,21 +288,21 @@ A flag config example configuration supporting multiple configs for different gl import js from "@eslint/js"; export default [ - js.configs.recommended, // Recommended config applied to all files - // File-pattern specific overrides - { - files: ["src/**/*", "test/**/*"], - rules: { - semi: ["warn", "always"] - } - }, - { - files:["test/**/*"], - rules: { - "no-console": "off" - } - } - // ...other configurations + js.configs.recommended, // Recommended config applied to all files + // File-pattern specific overrides + { + files: ["src/**/*", "test/**/*"], + rules: { + semi: ["warn", "always"], + }, + }, + { + files: ["test/**/*"], + rules: { + "no-console": "off", + }, + }, + // ...other configurations ]; ``` @@ -294,18 +318,19 @@ For example, here's an eslintrc file with language options: // .eslintrc.js module.exports = { - env: { - browser: true, - }, - globals: { - myCustomGlobal: "readonly", - }, - parserOptions: { - ecmaVersion: 2022, - sourceType: "module" - } - // ...other config -} + env: { + browser: true, + node: true, + }, + globals: { + myCustomGlobal: "readonly", + }, + parserOptions: { + ecmaVersion: 2022, + sourceType: "module", + }, + // ...other config +}; ``` Here's the same configuration in flat config: @@ -316,20 +341,25 @@ Here's the same configuration in flat config: import globals from "globals"; export default [ - { - languageOptions: { - ecmaVersion: 2022, - sourceType: "module", - globals: { - ...globals.browser, - myCustomGlobal: "readonly" - } - } - // ...other config - } + { + languageOptions: { + ecmaVersion: 2022, + sourceType: "module", + globals: { + ...globals.browser, + ...globals.node, + myCustomGlobal: "readonly", + }, + }, + // ...other config + }, ]; ``` +::: tip +You'll need to install the `globals` package from npm for this example to work. It is not automatically installed by ESLint. +::: + ### `eslint-env` Configuration Comments In the eslintrc config system it was possible to use `eslint-env` configuration comments to define globals for a file. @@ -345,9 +375,9 @@ For example, when using eslintrc, a file to be linted could look like this: /* eslint-env mocha */ describe("unit tests", () => { - it("should pass", () => { - // ... - }); + it("should pass", () => { + // ... + }); }); ``` @@ -361,9 +391,9 @@ The same effect can be achieved with flat config with a `global` configuration c /* global describe, it -- Globals defined by Mocha */ describe("unit tests", () => { - it("should pass", () => { - // ... - }); + it("should pass", () => { + // ... + }); }); ``` @@ -375,17 +405,15 @@ Another option is to remove the comment from the file being linted and define th import globals from "globals"; export default [ - // ...other config - { - files: [ - "tests/**" - ], - languageOptions: { - globals: { - ...globals.mocha - } - } - } + // ...other config + { + files: ["tests/**"], + languageOptions: { + globals: { + ...globals.mocha, + }, + }, + }, ]; ``` @@ -393,16 +421,18 @@ export default [ In eslintrc files, use the `extends` property to use predefined and shareable configs. ESLint comes with two predefined configs that you can access as strings: -* `"eslint:recommended"`: the rules recommended by ESLint -* `"eslint:all"`: all rules shipped with ESLint +- `"eslint:recommended"`: the rules recommended by ESLint. +- `"eslint:all"`: all rules shipped with ESLint. You can also use the `extends` property to extend a shareable config. Shareable configs can either be paths to local config files or npm package names. In flat config files, predefined configs are imported from separate modules into flat config files. The `recommended` and `all` rules configs are located in the [`@eslint/js`](https://www.npmjs.com/package/@eslint/js) package. You must import this package to use these configs: -```shell -npm install @eslint/js --save-dev -``` +{{ npm_tabs({ + command: "install", + packages: ["@eslint/js"], + args: ["--save-dev"] +}) }} You can add each of these configs to the exported array or expose specific rules from them. You must import the modules for local config files and npm package configs with flat config. @@ -412,13 +442,13 @@ For example, here's an eslintrc file using the built-in `eslint:recommended` con // .eslintrc.js module.exports = { - // ...other config - extends: "eslint:recommended", - rules: { - semi: ["warn", "always"] - }, - // ...other config -} + // ...other config + extends: "eslint:recommended", + rules: { + semi: ["warn", "always"], + }, + // ...other config +}; ``` This eslintrc file uses built-in config, local custom config, and shareable config from an npm package: @@ -427,13 +457,17 @@ This eslintrc file uses built-in config, local custom config, and shareable conf // .eslintrc.js module.exports = { - // ...other config - extends: ["eslint:recommended", "./custom-config.js", "eslint-config-my-config"], - rules: { - semi: ["warn", "always"] - }, - // ...other config -} + // ...other config + extends: [ + "eslint:recommended", + "./custom-config.js", + "eslint-config-my-config", + ], + rules: { + semi: ["warn", "always"], + }, + // ...other config +}; ``` To use the same configs in flat config, you would do the following: @@ -446,15 +480,15 @@ import customConfig from "./custom-config.js"; import myConfig from "eslint-config-my-config"; export default [ - js.configs.recommended, - customConfig, - myConfig, - { - rules: { - semi: ["warn", "always"] - }, - // ...other config - } + js.configs.recommended, + customConfig, + myConfig, + { + rules: { + semi: ["warn", "always"], + }, + // ...other config + }, ]; ``` @@ -467,11 +501,11 @@ import js from "@eslint/js"; import customTestConfig from "./custom-test-config.js"; export default [ - js.configs.recommended, - { - ...customTestConfig, - files: ["**/*.test.js"], - }, + js.configs.recommended, + { + ...customTestConfig, + files: ["**/*.test.js"], + }, ]; ``` @@ -479,9 +513,11 @@ export default [ You may find that there's a shareable config you rely on that hasn't yet been updated to flat config format. In that case, you can use the `FlatCompat` utility to translate the eslintrc format into flat config format. First, install the `@eslint/eslintrc` package: -```shell -npm install @eslint/eslintrc --save-dev -``` +{{ npm_tabs({ + command: "install", + packages: ["@eslint/eslintrc"], + args: ["--save-dev"] +}) }} Then, import `FlatCompat` and create a new instance to convert an existing eslintrc config. For example, if the npm package `eslint-config-my-config` is in eslintrc format, you can write this: @@ -495,13 +531,12 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const compat = new FlatCompat({ - baseDirectory: __dirname + baseDirectory: __dirname, }); export default [ - - // mimic ESLintRC-style extends - ...compat.extends("eslint-config-my-config"), + // mimic ESLintRC-style extends + ...compat.extends("eslint-config-my-config"), ]; ``` @@ -513,7 +548,7 @@ For more information about the `FlatCompat` class, please see the [package READM With eslintrc, you can make ESLint ignore files by creating a separate `.eslintignore` file in the root of your project. The `.eslintignore` file uses the same glob pattern syntax as `.gitignore` files. Alternatively, you can use an `ignorePatterns` property in your eslintrc file. -To ignore files with flat config, you can use the `ignores` property in a config object. The `ignores` property accepts an array of glob patterns. Flat config does not support loading ignore patterns from `.eslintignore` files, so you'll need to migrate those patterns directly into flat config. +To ignore files with flat config, you can use the `ignores` property in a config object with no other properties. The `ignores` property accepts an array of glob patterns. Flat config does not support loading ignore patterns from `.eslintignore` files, so you'll need to migrate those patterns directly into flat config. For example, here's a `.eslintignore` example you can use with an eslintrc config: @@ -524,32 +559,37 @@ config/* # ...other ignored files ``` -`ignorePatterns` example: +Here are the same patterns represented as `ignorePatterns` in a `.eslintrc.js` file: ```javascript // .eslintrc.js module.exports = { - // ...other config - ignorePatterns: ["temp.js", "config/*"], + // ...other config + ignorePatterns: ["temp.js", "config/*"], }; ``` -Here are the same files ignore patterns in flat config: +The equivalent ignore patterns in flat config look like this: ```javascript export default [ - // ...other config - { - ignores: ["**/temp.js", "config/*"] - } + // ...other config + { + // Note: there should be no other properties in this object + ignores: ["**/temp.js", "config/*"], + }, ]; ``` -Also, with flat config, dotfiles (e.g. `.dotfile.js`) are no longer ignored by default. If you want to ignore dotfiles, add files ignore pattern `"**/.*"`. +In `.eslintignore`, `temp.js` ignores all files named `temp.js`, whereas in flat config, you need to specify this as `**/temp.js`. The pattern `temp.js` in flat config only ignores a file named `temp.js` in the same directory as the configuration file. + +::: important +In flat config, dotfiles (e.g. `.dotfile.js`) are no longer ignored by default. If you want to ignore dotfiles, add an ignore pattern of `"**/.*"`. +::: ### Linter Options -ESlintrc files let you configure the linter itself with the `noInlineConfig` and `reportUnusedDisableDirectives` properties. +Eslintrc files let you configure the linter itself with the `noInlineConfig` and `reportUnusedDisableDirectives` properties. The flat config system introduces a new top-level property `linterOptions` that you can use to configure the linter. In the `linterOptions` object, you can include `noInlineConfig` and `reportUnusedDisableDirectives`. @@ -559,10 +599,10 @@ For example, here's an eslintrc file with linter options enabled: // .eslintrc.js module.exports = { - // ...other config - noInlineConfig: true, - reportUnusedDisableDirectives: true -} + // ...other config + noInlineConfig: true, + reportUnusedDisableDirectives: true, +}; ``` Here's the same options in flat config: @@ -571,13 +611,13 @@ Here's the same options in flat config: // eslint.config.js export default [ - { - // ...other config - linterOptions: { - noInlineConfig: true, - reportUnusedDisableDirectives: "warn" - } - } + { + // ...other config + linterOptions: { + noInlineConfig: true, + reportUnusedDisableDirectives: "warn", + }, + }, ]; ``` @@ -585,28 +625,99 @@ export default [ The following CLI flags are no longer supported with the flat config file format: -* `--rulesdir` -* `--ext` -* `--resolve-plugins-relative-to` +- `--rulesdir` +- `--ext` +- `--resolve-plugins-relative-to` The flag `--no-eslintrc` has been replaced with `--no-config-lookup`. +#### `--rulesdir` + +The `--rulesdir` flag was used to load additional rules from a specified directory. This is no longer supported when using flat config. You can instead create a plugin containing the local rules you have directly in your config, like this: + +```js +// eslint.config.js +import myRule from "./rules/my-rule.js"; + +export default [ + { + // define the plugin + plugins: { + local: { + rules: { + "my-rule": myRule, + }, + }, + }, + + // configure the rule + rules: { + "local/my-rule": ["error"], + }, + }, +]; +``` + +#### `--ext` + +The `--ext` flag was used to specify additional file extensions ESLint should search for when a directory was passed on the command line, such as `npx eslint .`. This is no longer supported when using flat config. Instead, specify the file patterns you'd like ESLint to search for directly in your config. For example, if you previously were using `--ext .ts,.tsx`, then you will need to update your config file like this: + +```js +// eslint.config.js +export default [ + { + files: ["**/*.ts", "**/*.tsx"], + + // any additional configuration for these file types here + }, +]; +``` + +ESLint uses the `files` keys from the config file to determine which files should be linted. + +#### `--resolve-plugins-relative-to` + +The `--resolve-plugins-relative-to` flag was used to indicate which directory plugin references in your configuration file should be resolved relative to. This was necessary because shareable configs could only resolve plugins that were peer dependencies or dependencies of parent packages. + +With flat config, shareable configs can specify their dependencies directly, so this flag is no longer needed. + +### `package.json` Configuration No Longer Supported + +With eslintrc, it was possible to use a `package.json` file to configure ESLint using the `eslintConfig` key. + +With flat config, it's no longer possible to use a `package.json` file to configure ESLint. You'll need to move your configuration into a separate file. + ### Additional Changes The following changes have been made from the eslintrc to the flat config file format: -* The `root` option no longer exists. (Flat config files act as if `root: true` is set.) -* The `files` option cannot be a single string anymore, it must be an array. -* The `sourceType` option now supports the new value `"commonjs"` (`.eslintrc` supports it too, but it was never documented). +- The `root` option no longer exists. (Flat config files act as if `root: true` is set.) +- The `files` option cannot be a single string anymore, it must be an array. +- The `sourceType` option now supports the new value `"commonjs"` (`.eslintrc` supports it too, but it was never documented). ## TypeScript Types for Flat Config Files -You can see the TypeScript types for the flat config file format in the DefinitelyTyped project. The interface for the objects in the config’s array is called the `FlatConfig`. +You can see the TypeScript types for the flat config file format in the [`lib/types` source folder on GitHub](https://github.com/eslint/eslint/tree/main/lib/types). The interface for the objects in the config’s array is called `Linter.Config`. + +You can view the type definitions in [`lib/types/index.d.ts`](https://github.com/eslint/eslint/blob/main/lib/types/index.d.ts). + +## Visual Studio Code Support + +ESLint v9.x support was added in the [`vscode-eslint`](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) v3.0.10. + +In versions of `vscode-eslint` prior to v3.0.10, the new configuration system is not enabled by default. To enable support for the new configuration files, edit your `.vscode/settings.json` file and add the following: + +```json +{ + // required in vscode-eslint < v3.0.10 only + "eslint.experimental.useFlatConfig": true +} +``` -You can view the type definitions in the [DefinitelyTyped repository on Github](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/eslint/index.d.ts). +In a future version of the ESLint plugin, you will no longer need to enable this manually. ## Further Reading -* [Overview of the flat config file format blog post](https://eslint.org/blog/2022/08/new-config-system-part-2/) -* [API usage of new configuration system blog post](https://eslint.org/blog/2022/08/new-config-system-part-3/) -* [Background to new configuration system blog post](https://eslint.org/blog/2022/08/new-config-system-part-1/) +- [Overview of the flat config file format blog post](https://eslint.org/blog/2022/08/new-config-system-part-2/) +- [API usage of new configuration system blog post](https://eslint.org/blog/2022/08/new-config-system-part-3/) +- [Background to new configuration system blog post](https://eslint.org/blog/2022/08/new-config-system-part-1/) diff --git a/docs/src/use/configure/parser-deprecated.md b/docs/src/use/configure/parser-deprecated.md new file mode 100644 index 000000000000..8db4d374991c --- /dev/null +++ b/docs/src/use/configure/parser-deprecated.md @@ -0,0 +1,37 @@ +--- +title: Configure a Parser (Deprecated) +--- + +::: warning +This documentation is for configuring parsers using the deprecated eslintrc configuration format. [View the updated documentation](parser). +::: + +You can use custom parsers to convert JavaScript code into an abstract syntax tree for ESLint to evaluate. You might want to add a custom parser if your code isn't compatible with ESLint's default parser, Espree. + +## Configure a Custom Parser + +By default, ESLint uses [Espree](https://github.com/eslint/js/tree/main/packages/espree) as its parser. You can optionally specify that a different parser should be used in your configuration file if the parser meets the following requirements: + +1. It must be a Node module loadable from the config file where the parser is used. Usually, this means you should install the parser package separately using npm. +1. It must conform to the [parser interface](../../extend/custom-parsers). + +Note that even with these compatibilities, there are no guarantees that an external parser works correctly with ESLint. ESLint does not fix bugs related to incompatibilities with other parsers. + +To indicate the npm module to use as your parser, specify it using the `parser` option in your `.eslintrc` file. For example, the following specifies to use Esprima instead of Espree: + +```json +{ + "parser": "esprima", + "rules": { + "semi": "error" + } +} +``` + +The following parsers are compatible with ESLint: + +- [Esprima](https://www.npmjs.com/package/esprima) +- [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) - A wrapper around the [Babel](https://babeljs.io) parser that makes it compatible with ESLint. +- [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser) - A parser that converts TypeScript into an ESTree-compatible form so it can be used in ESLint. + +Note that when using a custom parser, the `parserOptions` configuration property is still required for ESLint to work properly with features not in ECMAScript 5 by default. Parsers are all passed `parserOptions` and may or may not use them to determine which features to enable. diff --git a/docs/src/use/configure/parser.md b/docs/src/use/configure/parser.md index 76d94d15fc17..dcce40406aff 100644 --- a/docs/src/use/configure/parser.md +++ b/docs/src/use/configure/parser.md @@ -4,35 +4,72 @@ eleventyNavigation: key: configure parser parent: configure title: Configure a Parser - order: 6 + order: 5 --- +::: tip +This page explains how to configure parsers using the flat config format. For the deprecated eslintrc format, [see the deprecated documentation](parser-deprecated). +::: + You can use custom parsers to convert JavaScript code into an abstract syntax tree for ESLint to evaluate. You might want to add a custom parser if your code isn't compatible with ESLint's default parser, Espree. ## Configure a Custom Parser -By default, ESLint uses [Espree](https://github.com/eslint/espree) as its parser. You can optionally specify that a different parser should be used in your configuration file if the parser meets the following requirements: +In many cases, you can use the [default parser](https://github.com/eslint/js/tree/main/packages/espree) that ESLint ships with for parsing your JavaScript code. You can optionally override the default parser by using the `parser` property. The `parser` property must be an object that conforms to the [parser interface](../../extend/custom-parsers). For example, you can use the [`@babel/eslint-parser`](https://www.npmjs.com/package/@babel/eslint-parser) package to allow ESLint to parse experimental syntax: + +```js +// eslint.config.js +import babelParser from "@babel/eslint-parser"; +import { defineConfig } from "eslint/config"; -1. It must be a Node module loadable from the config file where the parser is used. Usually, this means you should install the parser package separately using npm. -1. It must conform to the [parser interface](../../extend/custom-parsers). +export default defineConfig([ + { + files: ["**/*.js", "**/*.mjs"], + languageOptions: { + parser: babelParser, + }, + }, +]); +``` -Note that even with these compatibilities, there are no guarantees that an external parser works correctly with ESLint. ESLint does not fix bugs related to incompatibilities with other parsers. +This configuration ensures that the Babel parser, rather than the default Espree parser, is used to parse all files ending with `.js` and `.mjs`. -To indicate the npm module to use as your parser, specify it using the `parser` option in your `.eslintrc` file. For example, the following specifies to use Esprima instead of Espree: +The following third-party parsers are known to be compatible with ESLint: -```json -{ - "parser": "esprima", - "rules": { - "semi": "error" - } -} -``` +- [Esprima](https://www.npmjs.com/package/esprima) +- [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) - A wrapper around the [Babel](https://babeljs.io) parser that makes it compatible with ESLint. +- [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser) - A parser that converts TypeScript into an ESTree-compatible form so it can be used in ESLint. -The following parsers are compatible with ESLint: +::: warning +There are no guarantees that an external parser works correctly with ESLint. ESLint does not fix bugs related to incompatibilities that affect only third-party parsers. +::: -* [Esprima](https://www.npmjs.com/package/esprima) -* [@babel/eslint-parser](https://www.npmjs.com/package/@babel/eslint-parser) - A wrapper around the [Babel](https://babeljs.io) parser that makes it compatible with ESLint. -* [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser) - A parser that converts TypeScript into an ESTree-compatible form so it can be used in ESLint. +## Configure Parser Options + +Parsers may accept options to alter the way they behave. The `languageOptions.parserOptions` is used to pass options directly to parsers. These options are always parser-specific, so you'll need to check the documentation of the parser you're using for available options. Here's an example of setting parser options for the Babel ESLint parser: + +```js +// eslint.config.js +import babelParser from "@babel/eslint-parser"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + languageOptions: { + parser: babelParser, + parserOptions: { + requireConfigFile: false, + babelOptions: { + babelrc: false, + configFile: false, + presets: ["@babel/preset-env"], + }, + }, + }, + }, +]); +``` -Note that when using a custom parser, the `parserOptions` configuration property is still required for ESLint to work properly with features not in ECMAScript 5 by default. Parsers are all passed `parserOptions` and may or may not use them to determine which features to enable. +::: tip +In addition to the options specified in `languageOptions.parserOptions`, ESLint also passes `ecmaVersion` and `sourceType` to all parsers. This allows custom parsers to understand the context in which ESLint is evaluating JavaScript code. +::: diff --git a/docs/src/use/configure/plugins-deprecated.md b/docs/src/use/configure/plugins-deprecated.md new file mode 100644 index 000000000000..e91926f1da95 --- /dev/null +++ b/docs/src/use/configure/plugins-deprecated.md @@ -0,0 +1,156 @@ +--- +title: Configure Plugins (Deprecated) +--- + +::: warning +This documentation is for configuring plugins using the deprecated eslintrc configuration format. [View the updated documentation](plugins). +::: + +You can extend ESLint with plugins in a variety of different ways. Plugins can include: + +- Custom rules to validate if your code meets a certain expectation, and what to do if it does not meet that expectation. +- Custom configurations. +- Custom environments. +- Custom processors to extract JavaScript code from other kinds of files or preprocess code before linting. + +## Configure Plugins + +ESLint supports the use of third-party plugins. Before using a plugin, you have to install it using npm. + +To configure plugins inside of a configuration file, use the `plugins` key, which contains a list of plugin names. The `eslint-plugin-` prefix can be omitted from the plugin name. + +```json +{ + "plugins": ["plugin1", "eslint-plugin-plugin2"] +} +``` + +And in YAML: + +```yaml +--- +plugins: + - plugin1 + - eslint-plugin-plugin2 +``` + +**Notes:** + +1. Plugins are resolved relative to the config file. In other words, ESLint loads the plugin as a user would obtain by running `require('eslint-plugin-pluginname')` in the config file. +2. Plugins in the base configuration (loaded by `extends` setting) are relative to the derived config file. For example, if `./.eslintrc` has `extends: ["foo"]` and the `eslint-config-foo` has `plugins: ["bar"]`, ESLint finds the `eslint-plugin-bar` from `./node_modules/` (rather than `./node_modules/eslint-config-foo/node_modules/`) or ancestor directories. Thus every plugin in the config file and base configurations is resolved uniquely. + +### Naming convention + +#### Include a plugin + +The `eslint-plugin-` prefix can be omitted for both non-scoped and scoped packages. + +A non-scoped package: + +```js +{ + // ... + "plugins": [ + "jquery", // means eslint-plugin-jquery + ] + // ... +} +``` + +A scoped package: + +```js +{ + // ... + "plugins": [ + "@jquery/jquery", // means @jquery/eslint-plugin-jquery + "@foobar" // means @foobar/eslint-plugin + ] + // ... +} +``` + +#### Use a plugin + +Rules, environments, and configurations defined in plugins must be referenced with the following convention: + +- `eslint-plugin-foo` → `foo/a-rule` +- `@foo/eslint-plugin` → `@foo/a-config` +- `@foo/eslint-plugin-bar` → `@foo/bar/a-environment` + +For example: + +```js +{ + // ... + "plugins": [ + "jquery", // eslint-plugin-jquery + "@foo/foo", // @foo/eslint-plugin-foo + "@bar" // @bar/eslint-plugin + ], + "extends": [ + "plugin:@foo/foo/recommended", + "plugin:@bar/recommended" + ], + "rules": { + "jquery/a-rule": "error", + "@foo/foo/some-rule": "error", + "@bar/another-rule": "error" + }, + "env": { + "jquery/jquery": true, + "@foo/foo/env-foo": true, + "@bar/env-bar": true, + } + // ... +} +``` + +### Specify a Processor + +Plugins may provide processors. Processors can extract JavaScript code from other kinds of files, then let ESLint lint the JavaScript code. Alternatively, processors can convert JavaScript code during preprocessing. + +To specify processors in a configuration file, use the `processor` key with the concatenated string of a plugin name and a processor name by a slash. For example, the following enables the processor `a-processor` that the plugin `a-plugin` provided: + +```json +{ + "plugins": ["a-plugin"], + "processor": "a-plugin/a-processor" +} +``` + +To specify processors for specific kinds of files, use the combination of the `overrides` key and the `processor` key. For example, the following uses the processor `a-plugin/markdown` for `*.md` files. + +```json +{ + "plugins": ["a-plugin"], + "overrides": [ + { + "files": ["*.md"], + "processor": "a-plugin/markdown" + } + ] +} +``` + +Processors may make named code blocks such as `0.js` and `1.js`. ESLint handles such a named code block as a child file of the original file. You can specify additional configurations for named code blocks in the `overrides` section of the config. For example, the following disables the `strict` rule for the named code blocks which end with `.js` in markdown files. + +```json +{ + "plugins": ["a-plugin"], + "overrides": [ + { + "files": ["*.md"], + "processor": "a-plugin/markdown" + }, + { + "files": ["**/*.md/*.js"], + "rules": { + "strict": "off" + } + } + ] +} +``` + +ESLint checks the file path of named code blocks then ignores those if any `overrides` entry didn't match the file path. Be sure to add an `overrides` entry if you want to lint named code blocks other than `*.js`. diff --git a/docs/src/use/configure/plugins.md b/docs/src/use/configure/plugins.md index 3026592c418e..351258b374e2 100644 --- a/docs/src/use/configure/plugins.md +++ b/docs/src/use/configure/plugins.md @@ -4,158 +4,282 @@ eleventyNavigation: key: configure plugins parent: configure title: Configure Plugins - order: 5 - + order: 4 --- +::: tip +This page explains how to configure plugins using the flat config format. For the deprecated eslintrc format, [see the deprecated documentation](plugins-deprecated). +::: + You can extend ESLint with plugins in a variety of different ways. Plugins can include: -* Custom rules to validate if your code meets a certain expectation, and what to do if it does not meet that expectation. -* Custom configurations. -* Custom environments. -* Custom processors to extract JavaScript code from other kinds of files or preprocess code before linting. +- Custom rules to validate if your code meets a certain expectation, and what to do if it does not meet that expectation. +- Custom configurations. Please refer to the plugin's documentation for details on how to use these configurations. +- Custom processors to extract JavaScript code from other kinds of files or preprocess code before linting. ## Configure Plugins -ESLint supports the use of third-party plugins. Before using a plugin, you have to install it using npm. +ESLint supports the use of third-party plugins. Plugins are simply objects that conform to a specific interface that ESLint recognizes. -To configure plugins inside of a configuration file, use the `plugins` key, which contains a list of plugin names. The `eslint-plugin-` prefix can be omitted from the plugin name. +To configure plugins inside of a [configuration file](./configuration-files#configuration-file), use the `plugins` key, which contains an object with properties representing plugin namespaces and values equal to the plugin object. -```json -{ - "plugins": [ - "plugin1", - "eslint-plugin-plugin2" - ] -} +```js +// eslint.config.js +import example from "eslint-plugin-example"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + plugins: { + example, + }, + rules: { + "example/rule1": "warn", + }, + }, +]); ``` -And in YAML: +::: tip +When creating a namespace for a plugin, the convention is to use the npm package name without the `eslint-plugin-` prefix. In the preceding example, `eslint-plugin-example` is assigned a namespace of `example`. +::: -```yaml ---- - plugins: - - plugin1 - - eslint-plugin-plugin2 -``` - -**Notes:** +### Configure a Local Plugin -1. Plugins are resolved relative to the config file. In other words, ESLint loads the plugin as a user would obtain by running `require('eslint-plugin-pluginname')` in the config file. -2. Plugins in the base configuration (loaded by `extends` setting) are relative to the derived config file. For example, if `./.eslintrc` has `extends: ["foo"]` and the `eslint-config-foo` has `plugins: ["bar"]`, ESLint finds the `eslint-plugin-bar` from `./node_modules/` (rather than `./node_modules/eslint-config-foo/node_modules/`) or ancestor directories. Thus every plugin in the config file and base configurations is resolved uniquely. +Plugins don't need to be published to npm for use with ESLint. You can also load plugins directly from a file, as in this example: -### Naming convention +```js +// eslint.config.js +import local from "./my-local-plugin.js"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + plugins: { + local, + }, + rules: { + "local/rule1": "warn", + }, + }, +]); +``` -#### Include a plugin +Here, the namespace `local` is used, but you can also use any name you'd like instead. -The `eslint-plugin-` prefix can be omitted for both non-scoped and scoped packages. +### Configure a Virtual Plugin -A non-scoped package: +Plugin definitions can be created virtually directly in your config. For example, suppose you have a rule contained in a file called `my-rule.js` that you'd like to enable in your config. You can define a virtual plugin to do so, as in this example: ```js -{ - // ... - "plugins": [ - "jquery", // means eslint-plugin-jquery - ] - // ... -} +// eslint.config.js +import myRule from "./rules/my-rule.js"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + plugins: { + local: { + rules: { + "my-rule": myRule, + }, + }, + }, + rules: { + "local/my-rule": "warn", + }, + }, +]); ``` -A scoped package: +Here, the namespace `local` is used to define a virtual plugin. The rule `myRule` is then assigned a name of `my-rule` inside of the virtual plugin's `rules` object. (See [Create Plugins](../../extend/plugins) for the complete format of a plugin.) You can then reference the rule as `local/my-rule` to configure it. + +## Use Plugin Rules + +You can use specific rules included in a plugin. To do this, specify the plugin +in a configuration object using the `plugins` key. The value for the `plugin` key +is an object where the name of the plugin is the property name and the value is the plugin object itself. Here's an example: ```js -{ - // ... - "plugins": [ - "@jquery/jquery", // means @jquery/eslint-plugin-jquery - "@foobar" // means @foobar/eslint-plugin - ] - // ... -} +// eslint.config.js +import jsdoc from "eslint-plugin-jsdoc"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js"], + plugins: { + jsdoc: jsdoc, + }, + rules: { + "jsdoc/require-description": "error", + "jsdoc/check-values": "error", + }, + }, +]); ``` -#### Use a plugin +In this configuration, the JSDoc plugin is defined to have the name `jsdoc`. The prefix `jsdoc/` in each rule name indicates that the rule is coming from the plugin with that name rather than from ESLint itself. -Rules, environments, and configurations defined in plugins must be referenced with the following convention: +Because the name of the plugin and the plugin object are both `jsdoc`, you can also shorten the configuration to this: -* `eslint-plugin-foo` → `foo/a-rule` -* `@foo/eslint-plugin` → `@foo/a-config` -* `@foo/eslint-plugin-bar` → `@foo/bar/a-environment` +```js +import jsdoc from "eslint-plugin-jsdoc"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js"], + plugins: { + jsdoc, + }, + rules: { + "jsdoc/require-description": "error", + "jsdoc/check-values": "error", + }, + }, +]); +``` -For example: +While this is the most common convention, you don't need to use the same name that the plugin prescribes. You can specify any prefix that you'd like, such as: ```js -{ - // ... - "plugins": [ - "jquery", // eslint-plugin-jquery - "@foo/foo", // @foo/eslint-plugin-foo - "@bar" // @bar/eslint-plugin - ], - "extends": [ - "plugin:@foo/foo/recommended", - "plugin:@bar/recommended" - ], - "rules": { - "jquery/a-rule": "error", - "@foo/foo/some-rule": "error", - "@bar/another-rule": "error" - }, - "env": { - "jquery/jquery": true, - "@foo/foo/env-foo": true, - "@bar/env-bar": true, - } - // ... -} +import jsdoc from "eslint-plugin-jsdoc"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.js"], + plugins: { + jsd: jsdoc, + }, + rules: { + "jsd/require-description": "error", + "jsd/check-values": "error", + }, + }, +]); ``` -### Specify a Processor +This configuration object uses `jsd` as the prefix plugin instead of `jsdoc`. + +## Specify a Processor Plugins may provide processors. Processors can extract JavaScript code from other kinds of files, then let ESLint lint the JavaScript code. Alternatively, processors can convert JavaScript code during preprocessing. -To specify processors in a configuration file, use the `processor` key with the concatenated string of a plugin name and a processor name by a slash. For example, the following enables the processor `a-processor` that the plugin `a-plugin` provided: +To specify processors in a [configuration file](./configuration-files#configuration-file), use the `processor` key and assign the name of processor in the format `namespace/processor-name`. For example, the following uses the processor from `@eslint/markdown` for `*.md` files. -```json -{ - "plugins": ["a-plugin"], - "processor": "a-plugin/a-processor" -} +```js +// eslint.config.js +import markdown from "@eslint/markdown"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.md"], + plugins: { + markdown, + }, + processor: "markdown/markdown", + }, +]); +``` + +Processors may make named code blocks such as `0.js` and `1.js`. ESLint handles such a named code block as a child file of the original file. You can specify additional configurations for named code blocks with additional config objects. For example, the following disables the `strict` rule for the named code blocks which end with `.js` in markdown files. + +```js +// eslint.config.js +import markdown from "@eslint/markdown"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + // applies to all JavaScript files + { + rules: { + strict: "error", + }, + }, + + // applies to Markdown files + { + files: ["**/*.md"], + plugins: { + markdown, + }, + processor: "markdown/markdown", + }, + + // applies only to JavaScript blocks inside of Markdown files + { + files: ["**/*.md/*.js"], + rules: { + strict: "off", + }, + }, +]); ``` -To specify processors for specific kinds of files, use the combination of the `overrides` key and the `processor` key. For example, the following uses the processor `a-plugin/markdown` for `*.md` files. - -```json -{ - "plugins": ["a-plugin"], - "overrides": [ - { - "files": ["*.md"], - "processor": "a-plugin/markdown" - } - ] -} +ESLint only lints named code blocks when they are JavaScript files or if they match a `files` entry in a config object. Be sure to add a config object with a matching `files` entry if you want to lint non-JavaScript named code blocks. Also note that [global ignores](./ignore) apply to named code blocks as well. + +```js +// eslint.config.js +import markdown from "@eslint/markdown"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + // applies to Markdown files + { + files: ["**/*.md"], + plugins: { + markdown, + }, + processor: "markdown/markdown", + }, + + // applies to all .jsx files, including jsx blocks inside of Markdown files + { + files: ["**/*.jsx"], + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, + + // ignore jsx blocks inside of test.md files + { + ignores: ["**/test.md/*.jsx"], + }, +]); ``` -Processors may make named code blocks such as `0.js` and `1.js`. ESLint handles such a named code block as a child file of the original file. You can specify additional configurations for named code blocks in the `overrides` section of the config. For example, the following disables the `strict` rule for the named code blocks which end with `.js` in markdown files. - -```json -{ - "plugins": ["a-plugin"], - "overrides": [ - { - "files": ["*.md"], - "processor": "a-plugin/markdown" - }, - { - "files": ["**/*.md/*.js"], - "rules": { - "strict": "off" - } - } - ] -} +## Specify a Language + +Plugins may provide languages. Languages allow ESLint to lint programming languages besides JavaScript. To specify a language in a [configuration file](./configuration-files#configuration-file), use the `language` key and assign the name of language in the format `namespace/language-name`. For example, the following uses the `json/jsonc` language from `@eslint/json` for `*.json` files. + +```js +// eslint.config.js +import json from "@eslint/json"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + files: ["**/*.json"], + plugins: { + json, + }, + language: "json/jsonc", + }, +]); ``` -ESLint checks the file path of named code blocks then ignores those if any `overrides` entry didn't match the file path. Be sure to add an `overrides` entry if you want to lint named code blocks other than `*.js`. +::: tip +When you specify a `language` in a config object, `languageOptions` becomes specific to that language. Each language defines its own `languageOptions`, so check the documentation of the plugin to determine which options are available. +::: + +## Common Problems + +- [Plugin rules using the ESLint < v9.0.0 API](../troubleshooting/v9-rule-api-changes) +- [Plugin configurations have not been upgraded to flat config](migration-guide#using-eslintrc-configs-in-flat-config) diff --git a/docs/src/use/configure/rules-deprecated.md b/docs/src/use/configure/rules-deprecated.md new file mode 100644 index 000000000000..ba4f57dfa3fd --- /dev/null +++ b/docs/src/use/configure/rules-deprecated.md @@ -0,0 +1,316 @@ +--- +title: Configure Rules (Deprecated) +--- + +::: warning +This documentation is for configuring rules using the deprecated eslintrc configuration format. [View the updated documentation](rules). +::: + +Rules are the core building block of ESLint. A rule validates if your code meets a certain expectation, and what to do if it does not meet that expectation. Rules can also contain additional configuration options specific to that rule. + +ESLint comes with a large number of [built-in rules](../../rules/) and you can add more rules through plugins. You can modify which rules your project uses with either configuration comments or configuration files. + +## Rule Severities + +To change a rule's severity, set the rule ID equal to one of these values: + +- `"off"` or `0` - turn the rule off +- `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code) +- `"error"` or `2` - turn the rule on as an error (exit code is 1 when triggered) + +Rules are typically set to `"error"` to enforce compliance with the rule during continuous integration testing, pre-commit checks, and pull request merging because doing so causes ESLint to exit with a non-zero exit code. + +If you don't want to enforce compliance with a rule but would still like ESLint to report the rule's violations, set the severity to `"warn"`. This is typically used when introducing a new rule that will eventually be set to `"error"`, when a rule is flagging something other than a potential buildtime or runtime error (such as an unused variable), or when a rule cannot determine with certainty that a problem has been found (when a rule might have false positives and need manual review). + +### Using configuration comments + +To configure rules inside of a file using configuration comments, use a comment in the following format: + +```js +/* eslint eqeqeq: "off", curly: "error" */ +``` + +In this example, [`eqeqeq`](../../rules/eqeqeq) is turned off and [`curly`](../../rules/curly) is turned on as an error. You can also use the numeric equivalent for the rule severity: + +```js +/* eslint eqeqeq: 0, curly: 2 */ +``` + +This example is the same as the last example, only it uses the numeric codes instead of the string values. The `eqeqeq` rule is off and the `curly` rule is set to be an error. + +If a rule has additional options, you can specify them using array literal syntax, such as: + +```js +/* eslint quotes: ["error", "double"], curly: 2 */ +``` + +This comment specifies the "double" option for the [`quotes`](../../rules/quotes) rule. The first item in the array is always the rule severity (number or string). + +#### Configuration Comment Descriptions + +Configuration comments can include descriptions to explain why the comment is necessary. The description must occur after the configuration and is separated from the configuration by two or more consecutive `-` characters. For example: + +```js +/* eslint eqeqeq: "off", curly: "error" -- Here's a description about why this configuration is necessary. */ +``` + +```js +/* eslint eqeqeq: "off", curly: "error" + -------- + Here's a description about why this configuration is necessary. */ +``` + +```js +/* eslint eqeqeq: "off", curly: "error" + * -------- + * This will not work due to the line above starting with a '*' character. + */ +``` + +### Using configuration files + +To configure rules inside of a configuration file, use the `rules` key along with an error level and any options you want to use. For example: + +```json +{ + "rules": { + "eqeqeq": "off", + "curly": "error", + "quotes": ["error", "double"] + } +} +``` + +And in YAML: + +```yaml +--- +rules: + eqeqeq: off + curly: error + quotes: + - error + - double +``` + +## Rules from Plugins + +To configure a rule that is defined within a plugin, prefix the rule ID with the plugin name and `/`. + +In a configuration file, for example: + +```json +{ + "plugins": ["plugin1"], + "rules": { + "eqeqeq": "off", + "curly": "error", + "quotes": ["error", "double"], + "plugin1/rule1": "error" + } +} +``` + +And in YAML: + +```yaml +--- +plugins: + - plugin1 +rules: + eqeqeq: 0 + curly: error + quotes: + - error + - "double" + plugin1/rule1: error +``` + +In these configuration files, the rule `plugin1/rule1` comes from the plugin named `plugin1`, which is contained in an npm package named `eslint-plugin-plugin1`. + +You can also use this format with configuration comments, such as: + +```js +/* eslint "plugin1/rule1": "error" */ +``` + +**Note:** When specifying rules from plugins, make sure to omit `eslint-plugin-`. ESLint uses only the unprefixed name internally to locate rules. + +## Disabling Rules + +### Using configuration comments + +- **Use with Caution.** Disabling ESLint rules inline should be restricted and used only in situations with a clear and + valid reason for doing so. Disabling rules inline should not be the default solution to resolve linting errors. +- **Document the Reason.** Provide a comment explaining the reason for disabling a particular rule after the `--` section of the comment. This + documentation should clarify why the rule is being disabled and why it is necessary in that specific situation. +- **Temporary Solutions.** If a disable comment is added as a temporary measure to address a pressing issue, create a follow-up task to address the underlying problem adequately. This ensures that the + disable comment is revisited and resolved at a later stage. +- **Code Reviews and Pair Programming.** Encourage team members to review each other's code regularly. Code reviews can help + identify the reasons behind disable comments and ensure that they are used appropriately. +- **Configurations.** Whenever possible, prefer using ESLint configuration files over disable comments. Configuration + files allow for consistent and project-wide rule handling. + +To disable rule warnings in a part of a file, use block comments in the following format: + +```js +/* eslint-disable */ + +alert("foo"); + +/* eslint-enable */ +``` + +You can also disable or enable warnings for specific rules: + +```js +/* eslint-disable no-alert, no-console */ + +alert("foo"); +console.log("bar"); + +/* eslint-enable no-alert, no-console */ +``` + +**Note:** `/* eslint-enable */` without any specific rules listed causes all disabled rules to be re-enabled. + +To disable rule warnings in an entire file, put a `/* eslint-disable */` block comment at the top of the file: + +```js +/* eslint-disable */ + +alert("foo"); +``` + +You can also disable or enable specific rules for an entire file: + +```js +/* eslint-disable no-alert */ + +alert("foo"); +``` + +To ensure that a rule is never applied (regardless of any future enable/disable lines): + +```js +/* eslint no-alert: "off" */ + +alert("foo"); +``` + +To disable all rules on a specific line, use a line or block comment in one of the following formats: + +```js +alert("foo"); // eslint-disable-line + +// eslint-disable-next-line +alert("foo"); + +/* eslint-disable-next-line */ +alert("foo"); + +alert("foo"); /* eslint-disable-line */ +``` + +To disable a specific rule on a specific line: + +```js +alert("foo"); // eslint-disable-line no-alert + +// eslint-disable-next-line no-alert +alert("foo"); + +alert("foo"); /* eslint-disable-line no-alert */ + +/* eslint-disable-next-line no-alert */ +alert("foo"); +``` + +To disable multiple rules on a specific line: + +```js +alert("foo"); // eslint-disable-line no-alert, quotes, semi + +// eslint-disable-next-line no-alert, quotes, semi +alert("foo"); + +alert("foo"); /* eslint-disable-line no-alert, quotes, semi */ + +/* eslint-disable-next-line no-alert, quotes, semi */ +alert("foo"); + +/* eslint-disable-next-line + no-alert, + quotes, + semi +*/ +alert("foo"); +``` + +All of the above methods also work for plugin rules. For example, to disable `eslint-plugin-example`'s `rule-name` rule, combine the plugin's name (`example`) and the rule's name (`rule-name`) into `example/rule-name`: + +```js +foo(); // eslint-disable-line example/rule-name +foo(); /* eslint-disable-line example/rule-name */ +``` + +**Note:** Comments that disable warnings for a portion of a file tell ESLint not to report rule violations for the disabled code. ESLint still parses the entire file, however, so disabled code still needs to be syntactically valid JavaScript. + +#### Comment descriptions + +Configuration comments can include descriptions to explain why disabling or re-enabling the rule is necessary. The description must come after the configuration and needs to be separated from the configuration by two or more consecutive `-` characters. For example: + +```js +// eslint-disable-next-line no-console -- Here's a description about why this configuration is necessary. +console.log("hello"); + +/* eslint-disable-next-line no-console -- + * Here's a very long description about why this configuration is necessary + * along with some additional information + **/ +console.log("hello"); +``` + +### Using configuration files + +To disable rules inside of a configuration file for a group of files, use the `overrides` key along with a `files` key. For example: + +```json +{ + "rules": {...}, + "overrides": [ + { + "files": ["*-test.js","*.spec.js"], + "rules": { + "no-unused-expressions": "off" + } + } + ] +} +``` + +### Disabling Inline Comments + +To disable all inline config comments, use the `noInlineConfig` setting in your configuration file. For example: + +```json +{ + "rules": {...}, + "noInlineConfig": true +} +``` + +You can also use the [`--no-inline-config`](../command-line-interface#--no-inline-config) CLI option to disable rule comments, in addition to other in-line configuration. + +#### Report unused `eslint-disable` comments + +To report unused `eslint-disable` comments, use the `reportUnusedDisableDirectives` setting. For example: + +```json +{ + "rules": {...}, + "reportUnusedDisableDirectives": true +} +``` + +This setting is similar to [`--report-unused-disable-directives`](../command-line-interface#--report-unused-disable-directives) CLI option, but doesn't fail linting (reports as `"warn"` severity). diff --git a/docs/src/use/configure/rules.md b/docs/src/use/configure/rules.md index 65277610e54e..251d52cd00bb 100644 --- a/docs/src/use/configure/rules.md +++ b/docs/src/use/configure/rules.md @@ -4,10 +4,13 @@ eleventyNavigation: key: configure rules parent: configure title: Configure Rules - order: 4 - + order: 3 --- +::: tip +This page explains how to configure rules using the flat config format. For the deprecated eslintrc format, [see the deprecated documentation](rules-deprecated). +::: + Rules are the core building block of ESLint. A rule validates if your code meets a certain expectation, and what to do if it does not meet that expectation. Rules can also contain additional configuration options specific to that rule. ESLint comes with a large number of [built-in rules](../../rules/) and you can add more rules through plugins. You can modify which rules your project uses with either configuration comments or configuration files. @@ -16,9 +19,9 @@ ESLint comes with a large number of [built-in rules](../../rules/) and you can a To change a rule's severity, set the rule ID equal to one of these values: -* `"off"` or `0` - turn the rule off -* `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code) -* `"error"` or `2` - turn the rule on as an error (exit code is 1 when triggered) +- `"off"` or `0` - turn the rule off. +- `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code). +- `"error"` or `2` - turn the rule on as an error (exit code is 1 when triggered). Rules are typically set to `"error"` to enforce compliance with the rule during continuous integration testing, pre-commit checks, and pull request merging because doing so causes ESLint to exit with a non-zero exit code. @@ -38,7 +41,7 @@ In this example, [`eqeqeq`](../../rules/eqeqeq) is turned off and [`curly`](../. /* eslint eqeqeq: 0, curly: 2 */ ``` -This example is the same as the last example, only it uses the numeric codes instead of the string values. The `eqeqeq` rule is off and the `curly` rule is set to be an error. +This example is the same as the last example, only it uses the numeric codes instead of the string values. The [`eqeqeq`](../../rules/eqeqeq) rule is off and the [`curly`](../../rules/curly) rule is set to be an error. If a rule has additional options, you can specify them using array literal syntax, such as: @@ -46,7 +49,7 @@ If a rule has additional options, you can specify them using array literal synta /* eslint quotes: ["error", "double"], curly: 2 */ ``` -This comment specifies the "double" option for the [`quotes`](../../rules/quotes) rule. The first item in the array is always the rule severity (number or string). +This comment specifies the `"double"` option for the [`quotes`](../../rules/quotes) rule. The first item in the array is always the rule severity (number or string). #### Configuration Comment Descriptions @@ -69,98 +72,146 @@ Configuration comments can include descriptions to explain why the comment is ne */ ``` -### Using configuration files +#### Report unused `eslint` inline config comments -To configure rules inside of a configuration file, use the `rules` key along with an error level and any options you want to use. For example: +To report unused `eslint` inline config comments (those that don't change anything from what was already configured), use the `reportUnusedInlineConfigs` setting. For example: -```json -{ - "rules": { - "eqeqeq": "off", - "curly": "error", - "quotes": ["error", "double"] - } -} +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + linterOptions: { + reportUnusedInlineConfigs: "error", + }, + }, +]); ``` -And in YAML: +This setting defaults to `"off"`. -```yaml ---- -rules: - eqeqeq: off - curly: error - quotes: - - error - - double +This setting is similar to the [`--report-unused-inline-configs`](../command-line-interface#--report-unused-inline-configs) CLI option. + +### Using Configuration Files + +To configure rules inside of a [configuration file](./configuration-files#configuration-file), use the `rules` key along with an error level and any options you want to use. For example: + +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + rules: { + eqeqeq: "off", + "no-unused-vars": "error", + "prefer-const": ["error", { ignoreReadBeforeAssign: true }], + }, + }, +]); ``` -## Rules from Plugins +When more than one configuration object specifies the same rule, the rule configuration is merged with the later object taking precedence over any previous objects. For example: -To configure a rule that is defined within a plugin, prefix the rule ID with the plugin name and `/`. - -In a configuration file, for example: - -```json -{ - "plugins": [ - "plugin1" - ], - "rules": { - "eqeqeq": "off", - "curly": "error", - "quotes": ["error", "double"], - "plugin1/rule1": "error" - } -} +```js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + rules: { + semi: ["error", "never"], + }, + }, + { + rules: { + semi: ["warn", "always"], + }, + }, +]); ``` -And in YAML: +Using this configuration, the final rule configuration for `semi` is `["warn", "always"]` because it appears last in the array. The array indicates that the configuration is for the severity and any options. You can change just the severity by defining only a string or number, as in this example: -```yaml ---- -plugins: - - plugin1 -rules: - eqeqeq: 0 - curly: error - quotes: - - error - - "double" - plugin1/rule1: error +```js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + rules: { + semi: ["error", "never"], + }, + }, + { + rules: { + semi: "warn", + }, + }, +]); ``` -In these configuration files, the rule `plugin1/rule1` comes from the plugin named `plugin1`, which is contained in an npm package named `eslint-plugin-plugin1`. +Here, the second configuration object only overrides the severity, so the final configuration for `semi` is `["warn", "never"]`. + +::: important +Rules configured via configuration comments have the highest priority and are applied after all configuration files settings. +::: + +## Rules from Plugins + +To configure a rule that is defined within a plugin, prefix the rule ID with the plugin namespace and `/`. + +In a [configuration file](./configuration-files#configuration-file), for example: + +```js +// eslint.config.js +import example from "eslint-plugin-example"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + plugins: { + example, + }, + rules: { + "example/rule1": "warn", + }, + }, +]); +``` + +In this configuration file, the rule `example/rule1` comes from the plugin named `eslint-plugin-example`. You can also use this format with configuration comments, such as: ```js -/* eslint "plugin1/rule1": "error" */ +/* eslint "example/rule1": "error" */ ``` -**Note:** When specifying rules from plugins, make sure to omit `eslint-plugin-`. ESLint uses only the unprefixed name internally to locate rules. +:::important +In order to use plugin rules in configuration comments, your configuration file must load the plugin and specify it in the `plugins` object of your config. Configuration comments can not load plugins on their own. +::: ## Disabling Rules ### Using configuration comments -* **Use with Caution.** Disabling ESLint rules inline should be restricted and used only in situations with a clear and - valid reason for doing so. Disabling rules inline should not be the default solution to resolve linting errors. -* **Document the Reason.** Provide a comment explaining the reason for disabling a particular rule after the `--` section of the comment. This - documentation should clarify why the rule is being disabled and why it is necessary in that specific situation. -* **Temporary Solutions.** If a disable comment is added as a temporary measure to address a pressing issue, create a follow-up task to address the underlying problem adequately. This ensures that the - disable comment is revisited and resolved at a later stage. -* **Code Reviews and Pair Programming.** Encourage team members to review each other's code regularly. Code reviews can help - identify the reasons behind disable comments and ensure that they are used appropriately. -* **Configurations.** Whenever possible, prefer using ESLint configuration files over disable comments. Configuration - files allow for consistent and project-wide rule handling. +- **Use with Caution.** Disabling ESLint rules inline should be restricted and used only in situations with a clear and + valid reason for doing so. Disabling rules inline should not be the default solution to resolve linting errors. +- **Document the Reason.** Provide a comment explaining the reason for disabling a particular rule after the `--` section of the comment. This + documentation should clarify why the rule is being disabled and why it is necessary in that specific situation. +- **Temporary Solutions.** If a disable comment is added as a temporary measure to address a pressing issue, create a follow-up task to address the underlying problem adequately. This ensures that the + disable comment is revisited and resolved at a later stage. +- **Code Reviews and Pair Programming.** Encourage team members to review each other's code regularly. Code reviews can help + identify the reasons behind disable comments and ensure that they are used appropriately. +- **Configurations.** Whenever possible, prefer using ESLint configuration files over disable comments. Configuration + files allow for consistent and project-wide rule handling. To disable rule warnings in a part of a file, use block comments in the following format: ```js /* eslint-disable */ -alert('foo'); +alert("foo"); /* eslint-enable */ ``` @@ -170,20 +221,22 @@ You can also disable or enable warnings for specific rules: ```js /* eslint-disable no-alert, no-console */ -alert('foo'); -console.log('bar'); +alert("foo"); +console.log("bar"); /* eslint-enable no-alert, no-console */ ``` -**Note:** `/* eslint-enable */` without any specific rules listed causes all disabled rules to be re-enabled. +::: warning +`/* eslint-enable */` without any specific rules listed causes all disabled rules to be re-enabled. +::: To disable rule warnings in an entire file, put a `/* eslint-disable */` block comment at the top of the file: ```js /* eslint-disable */ -alert('foo'); +alert("foo"); ``` You can also disable or enable specific rules for an entire file: @@ -191,7 +244,7 @@ You can also disable or enable specific rules for an entire file: ```js /* eslint-disable no-alert */ -alert('foo'); +alert("foo"); ``` To ensure that a rule is never applied (regardless of any future enable/disable lines): @@ -199,56 +252,56 @@ To ensure that a rule is never applied (regardless of any future enable/disable ```js /* eslint no-alert: "off" */ -alert('foo'); +alert("foo"); ``` To disable all rules on a specific line, use a line or block comment in one of the following formats: ```js -alert('foo'); // eslint-disable-line +alert("foo"); // eslint-disable-line // eslint-disable-next-line -alert('foo'); +alert("foo"); /* eslint-disable-next-line */ -alert('foo'); +alert("foo"); -alert('foo'); /* eslint-disable-line */ +alert("foo"); /* eslint-disable-line */ ``` To disable a specific rule on a specific line: ```js -alert('foo'); // eslint-disable-line no-alert +alert("foo"); // eslint-disable-line no-alert // eslint-disable-next-line no-alert -alert('foo'); +alert("foo"); -alert('foo'); /* eslint-disable-line no-alert */ +alert("foo"); /* eslint-disable-line no-alert */ /* eslint-disable-next-line no-alert */ -alert('foo'); +alert("foo"); ``` To disable multiple rules on a specific line: ```js -alert('foo'); // eslint-disable-line no-alert, quotes, semi +alert("foo"); // eslint-disable-line no-alert, quotes, semi // eslint-disable-next-line no-alert, quotes, semi -alert('foo'); +alert("foo"); -alert('foo'); /* eslint-disable-line no-alert, quotes, semi */ +alert("foo"); /* eslint-disable-line no-alert, quotes, semi */ /* eslint-disable-next-line no-alert, quotes, semi */ -alert('foo'); +alert("foo"); /* eslint-disable-next-line no-alert, quotes, semi */ -alert('foo'); +alert("foo"); ``` All of the above methods also work for plugin rules. For example, to disable `eslint-plugin-example`'s `rule-name` rule, combine the plugin's name (`example`) and the rule's name (`rule-name`) into `example/rule-name`: @@ -258,7 +311,9 @@ foo(); // eslint-disable-line example/rule-name foo(); /* eslint-disable-line example/rule-name */ ``` -**Note:** Comments that disable warnings for a portion of a file tell ESLint not to report rule violations for the disabled code. ESLint still parses the entire file, however, so disabled code still needs to be syntactically valid JavaScript. +::: tip +Comments that disable warnings for a portion of a file tell ESLint not to report rule violations for the disabled code. ESLint still parses the entire file, however, so disabled code still needs to be syntactically valid JavaScript. +::: #### Comment descriptions @@ -266,55 +321,77 @@ Configuration comments can include descriptions to explain why disabling or re-e ```js // eslint-disable-next-line no-console -- Here's a description about why this configuration is necessary. -console.log('hello'); +console.log("hello"); /* eslint-disable-next-line no-console -- * Here's a very long description about why this configuration is necessary * along with some additional information -**/ -console.log('hello'); + **/ +console.log("hello"); ``` ### Using configuration files -To disable rules inside of a configuration file for a group of files, use the `overrides` key along with a `files` key. For example: - -```json -{ - "rules": {...}, - "overrides": [ - { - "files": ["*-test.js","*.spec.js"], - "rules": { - "no-unused-expressions": "off" - } - } - ] -} +To disable rules inside of a [configuration file](./configuration-files#configuration-file) for a group of files, use a subsequent config object with a `files` key. For example: + +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + rules: { + "no-unused-expressions": "error", + }, + }, + { + files: ["*-test.js", "*.spec.js"], + rules: { + "no-unused-expressions": "off", + }, + }, +]); ``` ### Disabling Inline Comments To disable all inline config comments, use the `noInlineConfig` setting in your configuration file. For example: -```json -{ - "rules": {...}, - "noInlineConfig": true -} +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + linterOptions: { + noInlineConfig: true, + }, + rules: { + "no-unused-expressions": "error", + }, + }, +]); ``` -You can also use the [--no-inline-config](../command-line-interface#--no-inline-config) CLI option to disable rule comments, in addition to other in-line configuration. +You can also use the [`--no-inline-config`](../command-line-interface#--no-inline-config) CLI option to disable rule comments, in addition to other in-line configuration. #### Report unused `eslint-disable` comments -To report unused `eslint-disable` comments, use the `reportUnusedDisableDirectives` setting. For example: +To report unused `eslint-disable` comments (those that disable rules which would not report on the disabled line), use the `reportUnusedDisableDirectives` setting. For example: -```json -{ - "rules": {...}, - "reportUnusedDisableDirectives": true -} +```js +// eslint.config.js +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + { + linterOptions: { + reportUnusedDisableDirectives: "error", + }, + }, +]); ``` -This setting is similar to [--report-unused-disable-directives](../command-line-interface#--report-unused-disable-directives) and [--report-unused-disable-directives-severity](../command-line-interface#--report-unused-disable-directives-severity) CLI options. +This setting defaults to `"warn"`. + +This setting is similar to [`--report-unused-disable-directives`](../command-line-interface#--report-unused-disable-directives) and [`--report-unused-disable-directives-severity`](../command-line-interface#--report-unused-disable-directives-severity) CLI options. diff --git a/docs/src/use/core-concepts/glossary.md b/docs/src/use/core-concepts/glossary.md new file mode 100644 index 000000000000..400a9ec068e9 --- /dev/null +++ b/docs/src/use/core-concepts/glossary.md @@ -0,0 +1,403 @@ +--- +title: Glossary +eleventyNavigation: + key: glossary + title: Glossary + parent: core concepts +--- + +This page serves as a reference for common terms associated with ESLint. + +## A + +### Abstract Syntax Tree (AST) + +A structured representation of code syntax. + +Each section of source code in an AST is referred to as a [node](#node). +Each node may have any number of properties, including properties that store child nodes. + +The AST format used by ESLint is the [ESTree](#estree) format. + +ESLint [rules](#rule) are given an AST and may produce [violations](#violation) on parts of the AST when they detect a [violation](#violation). + +## C + +### Config File (Configuration File) + +A file containing preferences for how ESLint should parse files and run [rules](#rule). + +ESLint config files are named like `eslint.config.(c|m)js`. +Each config file exports a [config array](#config-array) containing [config objects](#config-object). + +For example, this `eslint.config.js` file enables the `prefer-const` [rule](#rule) at the _error_ [severity](#severity): + +```js +export default [ + { + rules: { + "prefer-const": "error", + }, + }, +]; +``` + +See [Configuration Files](../configure/configuration-files) for more details. + +### Config Array + +An array of [config objects](#config-object) within a [config file](#config-file-configuration-file). + +Each config file exports an array of config objects. +The objects in the array are evaluated in order: later objects may override settings specified in earlier objects. + +See [Configuration Files](../configure/configuration-files) for more details. + +### Config Object + +A [config file](#config-file-configuration-file) entry specifying all of the information ESLint needs to execute on a set of files. + +Each configuration object may include properties describing which files to run on, how to handle different file types, which [plugins](#plugin) to include, and how to run [rules](#rule). + +See [Configuration Files > Configuration Objects](../configure/configuration-files#configuration-objects) for more details. + +## E + +### ESQuery + +The library used by ESLint to parse [selector](#selector) syntax for querying [nodes](#node) in an [AST](#abstract-syntax-tree-ast). + +ESQuery interprets CSS syntax for AST node properties. +Examples of ESQuery selectors include: + +- `BinaryExpression`: selects all nodes of type _BinaryExpression_ +- `BinaryExpression[operator='+']`: selects all _BinaryExpression_ nodes whose _operator_ is `+` +- `BinaryExpression > Literal[value=1]`: selects all _Literal_ nodes with _value_ `1` whose direct parent is a _BinaryExpression_ + +See [github.com/estools/esquery](https://github.com/estools/esquery) for more information on the ESQuery format. + +### ESTree + +The format used by ESLint for how to represent JavaScript syntax as an [AST](#abstract-syntax-tree-ast). + +For example, the ESTree representation of the code `1 + 2;` would be an object roughly like: + +```json +{ + "type": "ExpressionStatement", + "expression": { + "type": "BinaryExpression", + "left": { + "type": "Literal", + "value": 1, + "raw": "1" + }, + "operator": "+", + "right": { + "type": "Literal", + "value": 2, + "raw": "2" + } + } +} +``` + +[Static analysis](#static-analysis) tools such as ESLint typically operate by converting syntax into an AST in the ESTree format. + +See [github.com/estree/estree](https://github.com/estree/estree) for more information on the ESTree specification. + +## F + +### Fix + +An optional augmentation to a [rule](#rule) [violation](#violation) that describes how to automatically correct the violation. + +Fixes are generally "safe" to apply automatically: they shouldn't cause code behavior changes. +ESLint attempts to apply as many fixes as possible in a [report](#report) when run with the `--fix` flag, though there is no guarantee that all fixes will be applied. +Fixes may also be applied by common editor extensions. + +Rule violations may also include file changes that are unsafe and not automatically applied in the form of [suggestions](#suggestion). + +### Flat Config + +The current configuration file format for ESLint. + +Flat config files are named in the format `eslint.config.(c|m)?js`. +"Flat" config files are named as such because all nesting must be done in one configuration file. +In contrast, the ["Legacy" config format](#legacy-config) allowed nesting configuration files in sub-directories within a project. + +You can read more about the motivations behind flat configurations in [ESLint's new config system, Part 2: Introduction to flat config](https://eslint.org/blog/2022/08/new-config-system-part-2). + +### Formatter (Linting) + +A package that presents the [report](#report) generated by ESLint. + +ESLint ships with several built-in reporters, including `stylish` (default), `json`, and `html`. + +For more information, see [Formatters](../formatters). + +### Formatter (Tool) + +A [static analysis](#static-analysis) tool that quickly reformats code without changing its logic or names. + +Formatters generally only modify the "trivia" of code, such as semicolons, spacing, newlines, and whitespace in general. +Trivia changes generally don't modify the [AST](#abstract-syntax-tree-ast) of code. + +Common formatters in the ecosystem include [Prettier](https://prettier.io) and [dprint](https://dprint.dev). + +Note that although ESLint is a [linter](#linter) rather than a formatter, ESLint rules can also apply formatting changes to source code. +See [Formatting (Rule)](#formatting-rule) for more information on formatting rules. + +### Formatting (Rule) + +A rule that solely targets [formatting](#formatter-tool) concerns, such as semicolons and whitespace. +These rules don't change application logic and are a subset of [Stylistic rules](#stylistic-rule). + +ESLint no longer recommends formatting rules and previously deprecated its built-in formatting rules. +ESLint recommends instead using a dedicated formatter such as [Prettier](https://prettier.io) or [dprint](https://dprint.dev). +Alternately, the [ESLint Stylistic project](https://eslint.style) provides formatting-related lint rules. + +For more information, see [Deprecation of formatting rules](https://eslint.org/blog/2023/10/deprecating-formatting-rules). + +## G + +### Global Declaration + +A description to ESLint of a JavaScript [global variable](#global-variable) that should exist at runtime. + +Global declarations inform lint rules that check for proper uses of global variables. +For example, the [`no-undef` rule](../../rules/no-undef) will create a violation for references to global variables not defined in the configured list of globals. + +[Config files](#config-file-configuration-file) have globals defined as JavaScript objects. + +For information about configuring globals, see [Configure Language Options > Specifying Globals](../configure/language-options#specifying-globals). + +### Global Variable + +A runtime variable that exists in the global scope, meaning all modules and scripts have access to it. + +Global variables in JavaScript are declared on the `globalThis` object (generally aliased as `global` in Node.js and `window` in browsers). + +You can let ESLint know which global variables your code uses with [global declarations](#global-declaration). + +## I + +### Inline Config (Configuration Comment) + +A source code comment that configures a rule to a different severity and/or set of options. + +Inline configs use similar syntax as [config files](#config-file-configuration-file) to specify any number of rules by name, their new severity, and optionally new options for the rules. +For example, the following inline config comment simultaneously disables the [`eqeqeq`](../../rules/eqeqeq) rule and sets the [`curly`](../../rules/curly) rule to `"error"`: + +```js +/* eslint eqeqeq: "off", curly: "error" */ +``` + +For documentation on inline config comments, see [Rules > Using configuration comments](../configure/rules#using-configuration-comments). + +## L + +### Legacy Config + +The previous configuration file format for ESLint, now superseded by ["Flat" config](#flat-config). + +Legacy ESLint configurations are named in the format `.eslintrc.*` and allowed to be nested across files within sub-directories in a project. + +You can read more about the lifetime of legacy configurations in [ESLint's new config system, Part 1: Background](https://eslint.org/blog/2022/08/new-config-system-part-1). + +### Linter + +A [static analysis](#static-analysis) tool that can [report](#report) the results from running a set of [rules](#rule) on source code. +Each rule may report any number of [violations](#violation) in the source code. + +ESLint is a commonly used linter for JavaScript and other web technologies. + +Note that a _linter_ is separate from [formatters](#formatter-tool) and [type checkers](#type-checker). + +### Logical Rule + +A [rule](#rule) that inspects how code operates to find problems. + +Many logical rules look for likely crashes (e.g. [`no-undef`](../../rules/no-undef)), unintended behavior (e.g. [`no-sparse-arrays`](../../rules/no-sparse-arrays)), and unused code (e.g [`no-unused-vars`](../../rules/no-unused-vars)). + +You can see the full list of logical rules that ship with ESLint under [Rules > Possible Problems](../../rules/#possible-problems) + +## N + +### Node + +A section of code within an [AST](#abstract-syntax-tree-ast). + +Each node represents a type of syntax found in source code. +For example, the `1 + 2` in the AST for `1 + 2;` is a _BinaryExpression_. + +See [#esquery](#esquery) for the library ESLint uses to parse [selectors](#selector) that allow [rules](#rule) to search for nodes. + +## O + +### Override + +When a [config object](#config-object) or [inline config](#inline-config-configuration-comment) sets a new severity and/or rule options that supersede previously set severity and/or options. + +The following [config file](#config-file-configuration-file) overrides `no-unused-expressions` from `"error"` to `"off"` in `*.test.js` files: + +```js +export default [ + { + rules: { + "no-unused-expressions": "error", + }, + }, + { + files: ["*.test.js"], + rules: { + "no-unused-expressions": "off", + }, + }, +]; +``` + +The following [inline config](#inline-config-configuration-comment) sets `no-unused-expressions` to `"error"`: + +```js +/* eslint no-unused-expressions: "error" */ +``` + +For more information on overrides in legacy configs, see [Configuration Files (Deprecated) > How do overrides work?](../configure/configuration-files-deprecated#how-do-overrides-work). + +## P + +### Parser + +An object containing a method that reads in a string and converts it to a standardized format. + +ESLint uses parsers to convert source code strings into an [AST](#abstract-syntax-tree-ast) shape. +By default, ESLint uses the [Espree](https://github.com/eslint/js/tree/main/packages/espree) parser, which generates an AST compatible with standard JavaScript runtimes and versions. + +Custom parsers let ESLint parse non-standard JavaScript syntax. +Often custom parsers are included as part of shareable configurations or plugins, so you don’t have to use them directly. +For example, [@typescript-eslint/parser](https://www.npmjs.com/package/@typescript-eslint/parser) is a custom parser included in the [typescript-eslint](https://typescript-eslint.io) project that lets ESLint parse TypeScript code. + +For more information on using parsers with ESLint, see [Configure a Parser](../configure/parser). + +### Plugin + +A package that can contain a set of [configurations](#shareable-config-configuration), [processors](#processor), and/or [rules](#rule). + +A popular use case for plugins is to enforce best practices for a framework. +For example, [@angular-eslint/eslint-plugin](https://www.npmjs.com/package/@angular-eslint/eslint-plugin) contains best practices for using the Angular framework. + +For more information, refer to [Configure Plugins](../configure/plugins). + +### Processor + +A part of a plugin that extracts JavaScript code from other kinds of files, then lets ESLint lint the JavaScript code. + +For example, [`@eslint/markdown`](https://github.com/eslint/markdown) includes a processor that converts the text of ``` code blocks in Markdown files into code that can be linted. + +For more information on configuring processor, see [Plugins > Specify a Processor](../configure/plugins#specify-a-processor). + +## R + +### Report + +A collection of [violations](#violation) from a single ESLint run. + +When ESLint runs on source files, it will pass an [AST](#abstract-syntax-tree-ast) for each source file to each configured [rule](#rule). +The collection of violations from each of the rules will be packaged together and passed to a [formatter](#formatter-linting) to be presented to the user. + +### Rule + +Code that checks an [AST](#abstract-syntax-tree-ast) for expected patterns. When a rule's expectation is not met, it creates a [violation](#violation). + +ESLint provides a large collection of rules that check for common JavaScript code issues. +Many more rules may be loaded in by [plugins](#plugin). + +For an overview of rules provided, see [Core Concepts > Rules](../core-concepts/#rules). + +## S + +### Selector + +Syntax describing how to search for [nodes](#node) within an [AST](#abstract-syntax-tree-ast). + +ESLint [rules](#rule) use [ESQuery](#esquery) selectors to find nodes that should be checked. + +### Severity + +What level of reporting a rule is configured to run, if at all. + +ESLint supports three levels of severity: + +- `"off"` (`0`): Do not run the rule. +- `"warn"` (`1`): Run the rule, but don't exit with a non-zero status code based on its violations (excluding the [`--max-warnings` flag](../command-line-interface#--max-warnings)) +- `"error"` (`2`): Run the rule, and exit with a non-zero status code if it produces any violations + +For documentation on configuring rules, see [Configure Rules](../configure/rules). + +### Shareable Config (Configuration) + +A module that provides a predefined [config file](#config-file-configuration-file) configurations. + +Shareable configs can configure all the same information from config files, including [plugins](#plugin) and [rules](#rule). + +Shareable configs are often provided alongside [plugins](#plugin). +Many plugins provide configs with names like _"recommended"_ that enable their suggested starting set of rules. +For example, [`eslint-plugin-solid`](https://github.com/solidjs-community/eslint-plugin-solid) provides a shareable recommended config: + +```js +import js from "@eslint/js"; +import solid from "eslint-plugin-solid/configs/recommended"; + +export default [js.configs.recommended, solid]; +``` + +For information on shareable configs, see [Share Configurations](../../extend/shareable-configs). + +### Static Analysis + +The process of analyzing source code without building or running it. + +[Linters](#linter) such as ESLint, [formatters](#formatter-tool), and [type checkers](#type-checker) are examples of static analysis tools. + +Static analysis is different from _dynamic_ analysis, which is the process of evaluating source code after it is built and executed. +Unit, integration, and end-to-end tests are common examples of dynamic analysis. + +### Stylistic (Rule) + +A rule that enforces a preference rather than a logical issue. +Stylistic areas include [Formatting rules](#formatting-rule), naming conventions, and consistent choices between equivalent syntaxes. + +ESLint's built-in stylistic rules are feature frozen: except for supporting new ECMAScript versions, they won't receive new features. + +For more information, see [Changes to our rules policies](https://eslint.org/blog/2020/05/changes-to-rules-policies) and [Deprecation of formatting rules](https://eslint.org/blog/2023/10/deprecating-formatting-rules). + +### Suggestion + +An optional augmentation to a [rule](#rule) [violation](#violation) that describes how one may manually adjust the code to address the violation. + +Suggestions are not generally safe to apply automatically because they cause code behavior changes. +ESLint does not apply suggestions directly but does provide suggestion to integrations that may choose to apply suggestions (such as an editor extension). + +Rule violations may also include file changes that are safe and may be automatically applied in the form of [fixes](#fix). + +## T + +### Type Checker + +A [static analysis](#static-analysis) tool that builds a full understanding of a project's code constructs and data shapes. + +Type checkers are generally slower and more comprehensive than linters. +Whereas linters traditionally operate only on a single file's or snippet's [AST](#abstract-syntax-tree-ast) at a time, type checkers understand cross-file dependencies and types. + +[TypeScript](https://typescriptlang.org) is the most common type checker for JavaScript. +The [typescript-eslint](https://typescript-eslint.io) project provides integrations that allow using type checker in lint rules. + +## V + +### Violation + +An indication from a [rule](#rule) that an area of code doesn't meet the expectation of the rule. + +Rule violations indicate a range in source code and error message explaining the violation. +Violations may also optionally include a [fix](#fix) and/or [suggestions](#suggestion) that indicate how to improve the violating code. diff --git a/docs/src/use/core-concepts.md b/docs/src/use/core-concepts/index.md similarity index 72% rename from docs/src/use/core-concepts.md rename to docs/src/use/core-concepts/index.md index b60ad3b0b0ed..d768e42d74f6 100644 --- a/docs/src/use/core-concepts.md +++ b/docs/src/use/core-concepts/index.md @@ -17,19 +17,19 @@ ESLint is a configurable JavaScript linter. It helps you find and fix problems i Rules are the core building block of ESLint. A rule validates if your code meets a certain expectation, and what to do if it does not meet that expectation. Rules can also contain additional configuration options specific to that rule. -For example, the [`semi`](../rules/semi) rule lets you specify whether or not JavaScript statements should end with a semicolon (`;`). You can set the rule to either always require semicolons or require that a statement never ends with a semicolon. +For example, the [`semi`](../../rules/semi) rule lets you specify whether or not JavaScript statements should end with a semicolon (`;`). You can set the rule to either always require semicolons or require that a statement never ends with a semicolon. ESLint contains hundreds of built-in rules that you can use. You can also create custom rules or use rules that others have created with [plugins](#plugins). -For more information, refer to [Rules](../rules/). +For more information, refer to [Rules](../../rules/). ### Rule Fixes Rules may optionally provide fixes for violations that they find. Fixes safely correct the violation without changing application logic. -Fixes may be applied automatically with the [`--fix` command line option](https://eslint.org/docs/latest/use/command-line-interface#--fix) and via editor extensions. +Fixes may be applied automatically with the [`--fix` command line option](../command-line-interface#--fix) and via editor extensions. -Rules that may provide fixes are marked with 🔧 in [Rules](../rules/). +Rules that may provide fixes are marked with 🔧 in [Rules](../../rules/). ### Rule Suggestions @@ -38,13 +38,13 @@ Rules may optionally provide suggestions in addition to or instead of providing 1. Suggestions may change application logic and so cannot be automatically applied. 1. Suggestions cannot be applied through the ESLint CLI and are only available through editor integrations. -Rules that may provide suggestions are marked with 💡 in [Rules](../rules/). +Rules that may provide suggestions are marked with 💡 in [Rules](../../rules/). ## Configuration Files An ESLint configuration file is a place where you put the configuration for ESLint in your project. You can include built-in rules, how you want them enforced, plugins with custom rules, shareable configurations, which files you want rules to apply to, and more. -For more information, refer to [Configuration Files](./configure/configuration-files). +For more information, refer to [Configuration Files](../configure/configuration-files). ## Shareable Configurations @@ -52,19 +52,19 @@ Shareable configurations are ESLint configurations that are shared via npm. Often shareable configurations are used to enforce style guides using ESLint's built-in rules. For example the sharable configuration [eslint-config-airbnb-base](https://www.npmjs.com/package/eslint-config-airbnb-base) implements the popular Airbnb JavaScript style guide. -For more information, refer to [Using a shareable configuration package](./configure/configuration-files#using-a-shareable-configuration-package). +For more information, refer to [Using a Shareable Configuration Package](../configure/configuration-files#using-a-shareable-configuration-package). ## Plugins -An ESLint plugin is an npm module that can contain a set of ESLint rules, configurations, processors, and environments. Often plugins include custom rules. Plugins can be used to enforce a style guide and support JavaScript extensions (like TypeScript), libraries (like React), and frameworks (Angular). +An ESLint plugin is an npm module that can contain a set of ESLint rules, configurations, processors, and languages. Often plugins include custom rules. Plugins can be used to enforce a style guide and support JavaScript extensions (like [TypeScript](https://www.typescriptlang.org)), libraries (like [React](https://react.dev)), and frameworks (like [Angular](https://angular.dev)). A popular use case for plugins is to enforce best practices for a framework. For example, [@angular-eslint/eslint-plugin](https://www.npmjs.com/package/@angular-eslint/eslint-plugin) contains best practices for using the Angular framework. -For more information, refer to [Configure Plugins](./configure/plugins). +For more information, refer to [Configure Plugins](../configure/plugins). ## Parsers -An ESLint parser converts code into an abstract syntax tree that ESLint can evaluate. By default, ESLint uses the built-in [Espree](https://github.com/eslint/espree) parser, which is compatible with standard JavaScript runtimes and versions. +An ESLint parser converts code into an abstract syntax tree that ESLint can evaluate. By default, ESLint uses the built-in [Espree](https://github.com/eslint/js/tree/main/packages/espree) parser, which is compatible with standard JavaScript runtimes and versions. Custom parsers let ESLint parse non-standard JavaScript syntax. Often custom parsers are included as part of shareable configurations or plugins, so you don't have to use them directly. @@ -74,19 +74,19 @@ For example, [@typescript-eslint/parser](https://www.npmjs.com/package/@typescri An ESLint processor extracts JavaScript code from other kinds of files, then lets ESLint lint the JavaScript code. Alternatively, you can use a processor to manipulate JavaScript code before parsing it with ESLint. -For example, [eslint-plugin-markdown](https://github.com/eslint/eslint-plugin-markdown) contains a custom processor that lets you lint JavaScript code inside of Markdown code blocks. +For example, [@eslint/markdown](https://github.com/eslint/markdown) contains a custom processor that lets you lint JavaScript code inside of Markdown code blocks. ## Formatters An ESLint formatter controls the appearance of the linting results in the CLI. -For more information, refer to [Formatters](./formatters/). +For more information, refer to [Formatters](../formatters/). ## Integrations One of the things that makes ESLint such a useful tool is the ecosystem of integrations that surrounds it. For example, many code editors have ESLint extensions that show you the ESLint results of your code in the file as you work so that you don't need to use the ESLint CLI to see linting results. -For more information, refer to [Integrations](./integrations). +For more information, refer to [Integrations](../integrations). ## CLI & Node.js API @@ -96,4 +96,4 @@ The ESLint Node.js API lets you use ESLint programmatically from Node.js code. T Unless you are extending ESLint in some way, you should use the CLI. -For more information, refer to [Command Line Interface](./command-line-interface) and [Node.js API](../integrate/nodejs-api). +For more information, refer to [Command Line Interface](../command-line-interface) and [Node.js API](../../integrate/nodejs-api). diff --git a/docs/src/use/formatters/html-formatter-example.html b/docs/src/use/formatters/html-formatter-example.html index 3cb873a3440f..af978fa7e64b 100644 --- a/docs/src/use/formatters/html-formatter-example.html +++ b/docs/src/use/formatters/html-formatter-example.html @@ -118,7 +118,7 @@

ESLint Report

- 9 problems (5 errors, 4 warnings) - Generated on Fri Dec 15 2023 22:54:05 GMT+0000 (Coordinated Universal Time) + 8 problems (4 errors, 4 warnings) - Generated on Fri Jul 11 2025 20:33:47 GMT+0000 (Coordinated Universal Time)
@@ -126,7 +126,7 @@

ESLint Report

@@ -201,15 +201,6 @@

ESLint Report

- - - - - - -
[+] /var/lib/jenkins/workspace/eslint Release/eslint/fullOfProblems.js - 9 problems (5 errors, 4 warnings) + 8 problems (4 errors, 4 warnings)
", "foo.html"); - - assert.strictEqual(report.results[0].messages.length, 0); - assert.strictEqual(report.results[0].output, ""); - }); - - it("should not run in autofix mode when using a processor that does not support autofixing", () => { - engine = new CLIEngine({ - useEslintrc: false, - plugins: ["test-processor"], - rules: { - semi: 2 - }, - extensions: ["js", "txt"], - ignore: false, - fix: true - }, { - preloadedPlugins: { - "test-processor": { - processors: { - ".html": HTML_PROCESSOR - } - } - } - }); - - const report = engine.executeOnText("", "foo.html"); - - assert.strictEqual(report.results[0].messages.length, 1); - assert.isFalse(Object.prototype.hasOwnProperty.call(report.results[0], "output")); - }); - - it("should not run in autofix mode when `fix: true` is not provided, even if the processor supports autofixing", () => { - engine = new CLIEngine({ - useEslintrc: false, - plugins: ["test-processor"], - rules: { - semi: 2 - }, - extensions: ["js", "txt"], - ignore: false - }, { - preloadedPlugins: { - "test-processor": { - processors: { - ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) - } - } - } - }); - - const report = engine.executeOnText("", "foo.html"); - - assert.strictEqual(report.results[0].messages.length, 1); - assert.isFalse(Object.prototype.hasOwnProperty.call(report.results[0], "output")); - }); - }); - }); - - describe("Patterns which match no file should throw errors.", () => { - beforeEach(() => { - engine = new CLIEngine({ - cwd: getFixturePath("cli-engine"), - useEslintrc: false - }); - }); - - it("one file", () => { - assert.throws(() => { - engine.executeOnFiles(["non-exist.js"]); - }, "No files matching 'non-exist.js' were found."); - }); - - it("should throw if the directory exists and is empty", () => { - assert.throws(() => { - engine.executeOnFiles(["empty"]); - }, "No files matching 'empty' were found."); - }); - - it("one glob pattern", () => { - assert.throws(() => { - engine.executeOnFiles(["non-exist/**/*.js"]); - }, "No files matching 'non-exist/**/*.js' were found."); - }); - - it("two files", () => { - assert.throws(() => { - engine.executeOnFiles(["aaa.js", "bbb.js"]); - }, "No files matching 'aaa.js' were found."); - }); - - it("a mix of an existing file and a non-existing file", () => { - assert.throws(() => { - engine.executeOnFiles(["console.js", "non-exist.js"]); - }, "No files matching 'non-exist.js' were found."); - }); - }); - - describe("overrides", () => { - beforeEach(() => { - engine = new CLIEngine({ - cwd: getFixturePath("cli-engine/overrides-with-dot"), - ignore: false - }); - }); - - it("should recognize dotfiles", () => { - const ret = engine.executeOnFiles([".test-target.js"]); - - assert.strictEqual(ret.results.length, 1); - assert.strictEqual(ret.results[0].messages.length, 1); - assert.strictEqual(ret.results[0].messages[0].ruleId, "no-unused-vars"); - assert.strictEqual(ret.results[0].suppressedMessages.length, 0); - }); - }); - - describe("a config file setting should have higher priority than a shareable config file's settings always; https://github.com/eslint/eslint/issues/11510", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: path.join(os.tmpdir(), "cli-engine/11510"), - files: { - "no-console-error-in-overrides.json": { - overrides: [{ - files: ["*.js"], - rules: { "no-console": "error" } - }] - }, - ".eslintrc.json": { - extends: "./no-console-error-in-overrides.json", - rules: { "no-console": "off" } - }, - "a.js": "console.log();" - } - }); - - beforeEach(() => { - engine = new CLIEngine({ - cwd: getPath() - }); - - return prepare(); - }); - - afterEach(cleanup); - - it("should not report 'no-console' error.", () => { - const { results } = engine.executeOnFiles("a.js"); - - assert.strictEqual(results.length, 1); - assert.deepStrictEqual(results[0].messages, []); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - }); - - describe("configs of plugin rules should be validated even if 'plugins' key doesn't exist; https://github.com/eslint/eslint/issues/11559", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: path.join(os.tmpdir(), "cli-engine/11559"), - files: { - "node_modules/eslint-plugin-test/index.js": ` + const examplePluginName = "eslint-plugin-example", + examplePluginNameWithNamespace = "@eslint/eslint-plugin-example", + examplePlugin = { + rules: { + "example-rule": require("../../fixtures/rules/custom-rule"), + "make-syntax-error": require("../../fixtures/rules/make-syntax-error-rule"), + }, + }, + examplePreprocessorName = "eslint-plugin-processor", + originalDir = process.cwd(), + fixtureDir = path.resolve( + fs.realpathSync(os.tmpdir()), + "eslint/fixtures", + ); + + /** @type {import("../../../lib/cli-engine").CLIEngine} */ + let CLIEngine; + + /** @type {import("../../../lib/cli-engine/cli-engine").getCLIEngineInternalSlots} */ + let getCLIEngineInternalSlots; + + /** + * Returns the path inside of the fixture directory. + * @param {...string} args file path segments. + * @returns {string} The path inside the fixture directory. + * @private + */ + function getFixturePath(...args) { + const filepath = path.join(fixtureDir, ...args); + + try { + return fs.realpathSync(filepath); + } catch { + return filepath; + } + } + + /** + * Create the CLIEngine object by mocking some of the plugins + * @param {Object} options options for CLIEngine + * @returns {CLIEngine} engine object + * @private + */ + function cliEngineWithPlugins(options) { + return new CLIEngine(options, { + preloadedPlugins: { + [examplePluginName]: examplePlugin, + [examplePluginNameWithNamespace]: examplePlugin, + [examplePreprocessorName]: require("../../fixtures/processors/custom-processor"), + }, + }); + } + + // copy into clean area so as not to get "infected" by this project's .eslintrc files + before(function () { + /* + * GitHub Actions Windows and macOS runners occasionally exhibit + * extremely slow filesystem operations, during which copying fixtures + * exceeds the default test timeout, so raise it just for this hook. + * Mocha uses `this` to set timeouts on an individual hook level. + */ + this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API + shell.mkdir("-p", fixtureDir); + shell.cp("-r", "./tests/fixtures/.", fixtureDir); + }); + + beforeEach(() => { + ({ + CLIEngine, + getCLIEngineInternalSlots, + } = require("../../../lib/cli-engine/cli-engine")); + }); + + after(() => { + shell.rm("-r", fixtureDir); + }); + + describe("new CLIEngine(options)", () => { + it("the default value of 'options.cwd' should be the current working directory.", () => { + process.chdir(__dirname); + try { + const engine = new CLIEngine(); + const internalSlots = getCLIEngineInternalSlots(engine); + + assert.strictEqual(internalSlots.options.cwd, __dirname); + } finally { + process.chdir(originalDir); + } + }); + + it("should report one fatal message when given a path by --ignore-path that is not a file when ignore is true.", () => { + assert.throws(() => { + // eslint-disable-next-line no-new -- Testing synchronous throwing + new CLIEngine({ ignorePath: fixtureDir }); + }, `Cannot read .eslintignore file: ${fixtureDir}\nError: EISDIR: illegal operation on a directory, read`); + }); + + // https://github.com/eslint/eslint/issues/2380 + it("should not modify baseConfig when format is specified", () => { + const customBaseConfig = { root: true }; + + new CLIEngine({ baseConfig: customBaseConfig, format: "foo" }); // eslint-disable-line no-new -- Test side effects + + assert.deepStrictEqual(customBaseConfig, { root: true }); + }); + }); + + describe("executeOnText()", () => { + let engine; + + describe("when using local cwd .eslintrc", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: path.join(os.tmpdir(), "eslint/multiple-rules-config"), + files: { + ".eslintrc.json": { + root: true, + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2, + }, + env: { + node: true, + }, + }, + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should report the total and per file errors", () => { + engine = new CLIEngine({ cwd: getPath() }); + + const report = engine.executeOnText("var foo = 'bar';"); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.errorCount, 5); + assert.strictEqual(report.warningCount, 0); + assert.strictEqual(report.fatalErrorCount, 0); + assert.strictEqual(report.fixableErrorCount, 3); + assert.strictEqual(report.fixableWarningCount, 0); + assert.strictEqual(report.results[0].messages.length, 5); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "strict", + ); + assert.strictEqual( + report.results[0].messages[1].ruleId, + "no-var", + ); + assert.strictEqual( + report.results[0].messages[2].ruleId, + "no-unused-vars", + ); + assert.strictEqual( + report.results[0].messages[3].ruleId, + "quotes", + ); + assert.strictEqual( + report.results[0].messages[4].ruleId, + "eol-last", + ); + assert.strictEqual(report.results[0].fixableErrorCount, 3); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + it("should report the total and per file warnings", () => { + engine = new CLIEngine({ + cwd: getPath(), + rules: { + quotes: 1, + "no-var": 1, + "eol-last": 1, + strict: 1, + "no-unused-vars": 1, + }, + }); + + const report = engine.executeOnText("var foo = 'bar';"); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.errorCount, 0); + assert.strictEqual(report.warningCount, 5); + assert.strictEqual(report.fixableErrorCount, 0); + assert.strictEqual(report.fixableWarningCount, 3); + assert.strictEqual(report.results[0].messages.length, 5); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "strict", + ); + assert.strictEqual( + report.results[0].messages[1].ruleId, + "no-var", + ); + assert.strictEqual( + report.results[0].messages[2].ruleId, + "no-unused-vars", + ); + assert.strictEqual( + report.results[0].messages[3].ruleId, + "quotes", + ); + assert.strictEqual( + report.results[0].messages[4].ruleId, + "eol-last", + ); + assert.strictEqual(report.results[0].fixableErrorCount, 0); + assert.strictEqual(report.results[0].fixableWarningCount, 3); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + }); + + it("should report one message when using specific config file", () => { + engine = new CLIEngine({ + configFile: "fixtures/configurations/quotes-error.json", + useEslintrc: false, + cwd: getFixturePath(".."), + }); + + const report = engine.executeOnText("var foo = 'bar';"); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.errorCount, 1); + assert.strictEqual(report.warningCount, 0); + assert.strictEqual(report.fixableErrorCount, 1); + assert.strictEqual(report.fixableWarningCount, 0); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); + assert.isUndefined(report.results[0].messages[0].output); + assert.strictEqual(report.results[0].errorCount, 1); + assert.strictEqual(report.results[0].fixableErrorCount, 1); + assert.strictEqual(report.results[0].warningCount, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should report the filename when passed in", () => { + engine = new CLIEngine({ + ignore: false, + cwd: getFixturePath(), + }); + + const report = engine.executeOnText("var foo = 'bar';", "test.js"); + + assert.strictEqual( + report.results[0].filePath, + getFixturePath("test.js"), + ); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is true", () => { + engine = new CLIEngine({ + ignorePath: getFixturePath(".eslintignore"), + cwd: getFixturePath(".."), + }); + + const report = engine.executeOnText( + "var bar = foo;", + "fixtures/passing.js", + true, + ); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.errorCount, 0); + assert.strictEqual(report.warningCount, 1); + assert.strictEqual(report.fatalErrorCount, 0); + assert.strictEqual(report.fixableErrorCount, 0); + assert.strictEqual(report.fixableWarningCount, 0); + assert.strictEqual( + report.results[0].filePath, + getFixturePath("passing.js"), + ); + assert.strictEqual(report.results[0].messages[0].severity, 1); + assert.strictEqual( + report.results[0].messages[0].message, + 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override.', + ); + assert.isUndefined(report.results[0].messages[0].output); + assert.strictEqual(report.results[0].errorCount, 0); + assert.strictEqual(report.results[0].warningCount, 1); + assert.strictEqual(report.results[0].fatalErrorCount, 0); + assert.strictEqual(report.results[0].fixableErrorCount, 0); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should not return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is false", () => { + engine = new CLIEngine({ + ignorePath: getFixturePath(".eslintignore"), + cwd: getFixturePath(".."), + }); + + // intentional parsing error + const report = engine.executeOnText( + "va r bar = foo;", + "fixtures/passing.js", + false, + ); + + // should not report anything because the file is ignored + assert.strictEqual(report.results.length, 0); + }); + + it("should suppress excluded file warnings by default", () => { + engine = new CLIEngine({ + ignorePath: getFixturePath(".eslintignore"), + cwd: getFixturePath(".."), + }); + + const report = engine.executeOnText( + "var bar = foo;", + "fixtures/passing.js", + ); + + // should not report anything because there are no errors + assert.strictEqual(report.results.length, 0); + }); + + it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", () => { + engine = new CLIEngine({ + ignorePath: "fixtures/.eslintignore", + cwd: getFixturePath(".."), + ignore: false, + useEslintrc: false, + rules: { + "no-undef": 2, + }, + }); + + const report = engine.executeOnText( + "var bar = foo;", + "fixtures/passing.js", + ); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual( + report.results[0].filePath, + getFixturePath("passing.js"), + ); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "no-undef", + ); + assert.strictEqual(report.results[0].messages[0].severity, 2); + assert.isUndefined(report.results[0].messages[0].output); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should return a message and fixed text when in fix mode", () => { + engine = new CLIEngine({ + useEslintrc: false, + fix: true, + rules: { + semi: 2, + }, + ignore: false, + cwd: getFixturePath(), + }); + + const report = engine.executeOnText("var bar = foo", "passing.js"); + + assert.deepStrictEqual(report, { + results: [ + { + filePath: getFixturePath("passing.js"), + messages: [], + suppressedMessages: [], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var bar = foo;", + }, + ], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + usedDeprecatedRules: [ + { + ruleId: "semi", + replacedBy: [], + }, + ], + }); + }); + + it("correctly autofixes semicolon-conflicting-fixes", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + }); + const inputPath = getFixturePath( + "autofix/semicolon-conflicting-fixes.js", + ); + const outputPath = getFixturePath( + "autofix/semicolon-conflicting-fixes.expected.js", + ); + const report = engine.executeOnFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(report.results[0].output, expectedOutput); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("correctly autofixes return-conflicting-fixes", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + }); + const inputPath = getFixturePath( + "autofix/return-conflicting-fixes.js", + ); + const outputPath = getFixturePath( + "autofix/return-conflicting-fixes.expected.js", + ); + const report = engine.executeOnFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(report.results[0].output, expectedOutput); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + describe("Fix Types", () => { + it("should throw an error when an invalid fix type is specified", () => { + assert.throws(() => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layou"], + }); + }, /invalid fix type/iu); + }); + + it("should not fix any rules when fixTypes is used without fix", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: false, + fixTypes: ["layout"], + }); + + const inputPath = getFixturePath("fix-types/fix-only-semi.js"); + const report = engine.executeOnFiles([inputPath]); + + assert.isUndefined(report.results[0].output); + }); + + it("should not fix non-style rules when fixTypes has only 'layout'", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"], + }); + const inputPath = getFixturePath("fix-types/fix-only-semi.js"); + const outputPath = getFixturePath( + "fix-types/fix-only-semi.expected.js", + ); + const report = engine.executeOnFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(report.results[0].output, expectedOutput); + }); + + it("should not fix style or problem rules when fixTypes has only 'suggestion'", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["suggestion"], + }); + const inputPath = getFixturePath( + "fix-types/fix-only-prefer-arrow-callback.js", + ); + const outputPath = getFixturePath( + "fix-types/fix-only-prefer-arrow-callback.expected.js", + ); + const report = engine.executeOnFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(report.results[0].output, expectedOutput); + }); + + it("should fix both style and problem rules when fixTypes has 'suggestion' and 'layout'", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["suggestion", "layout"], + }); + const inputPath = getFixturePath( + "fix-types/fix-both-semi-and-prefer-arrow-callback.js", + ); + const outputPath = getFixturePath( + "fix-types/fix-both-semi-and-prefer-arrow-callback.expected.js", + ); + const report = engine.executeOnFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(report.results[0].output, expectedOutput); + }); + + it("should not throw an error when a rule doesn't have a 'meta' property", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"], + rulePaths: [getFixturePath("rules", "fix-types-test")], + }); + + const inputPath = getFixturePath( + "fix-types/ignore-missing-meta.js", + ); + const outputPath = getFixturePath( + "fix-types/ignore-missing-meta.expected.js", + ); + const report = engine.executeOnFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(report.results[0].output, expectedOutput); + }); + + it("should not throw an error when a rule is loaded after initialization with executeOnFiles()", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"], + }); + const internalSlots = getCLIEngineInternalSlots(engine); + + internalSlots.linter.defineRule( + "no-program", + require( + getFixturePath( + "rules", + "fix-types-test", + "no-program.js", + ), + ), + ); + + const inputPath = getFixturePath( + "fix-types/ignore-missing-meta.js", + ); + const outputPath = getFixturePath( + "fix-types/ignore-missing-meta.expected.js", + ); + const report = engine.executeOnFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(report.results[0].output, expectedOutput); + }); + + it("should not throw an error when a rule is loaded after initialization with executeOnText()", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"], + }); + const internalSlots = getCLIEngineInternalSlots(engine); + + internalSlots.linter.defineRule( + "no-program", + require( + getFixturePath( + "rules", + "fix-types-test", + "no-program.js", + ), + ), + ); + + const inputPath = getFixturePath( + "fix-types/ignore-missing-meta.js", + ); + const outputPath = getFixturePath( + "fix-types/ignore-missing-meta.expected.js", + ); + const report = engine.executeOnText( + fs.readFileSync(inputPath, { encoding: "utf8" }), + inputPath, + ); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(report.results[0].output, expectedOutput); + }); + }); + + it("should return a message and omit fixed text when in fix mode and fixes aren't done", () => { + engine = new CLIEngine({ + useEslintrc: false, + fix: true, + rules: { + "no-undef": 2, + }, + ignore: false, + cwd: getFixturePath(), + }); + + const report = engine.executeOnText("var bar = foo", "passing.js"); + + assert.deepStrictEqual(report, { + results: [ + { + filePath: getFixturePath("passing.js"), + messages: [ + { + ruleId: "no-undef", + severity: 2, + messageId: "undef", + message: "'foo' is not defined.", + line: 1, + column: 11, + endLine: 1, + endColumn: 14, + nodeType: "Identifier", + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "var bar = foo", + }, + ], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + usedDeprecatedRules: [], + }); + }); + + it("should not delete code if there is a syntax error after trying to autofix.", () => { + engine = cliEngineWithPlugins({ + useEslintrc: false, + fix: true, + plugins: ["example"], + rules: { + "example/make-syntax-error": "error", + }, + ignore: false, + cwd: getFixturePath(), + }); + + const report = engine.executeOnText("var bar = foo", "test.js"); + + assert.deepStrictEqual(report, { + results: [ + { + filePath: getFixturePath("test.js"), + messages: [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Parsing error: Unexpected token is", + line: 1, + column: 19, + nodeType: null, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var bar = foothis is a syntax error.", + }, + ], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + usedDeprecatedRules: [], + }); + }); + + it("should not crash even if there are any syntax error since the first time.", () => { + engine = new CLIEngine({ + useEslintrc: false, + fix: true, + rules: { + "example/make-syntax-error": "error", + }, + ignore: false, + cwd: getFixturePath(), + }); + + const report = engine.executeOnText("var bar =", "test.js"); + + assert.deepStrictEqual(report, { + results: [ + { + filePath: getFixturePath("test.js"), + messages: [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Parsing error: Unexpected token", + line: 1, + column: 10, + nodeType: null, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "var bar =", + }, + ], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + usedDeprecatedRules: [], + }); + }); + + it("should return source code of file in `source` property when errors are present", () => { + engine = new CLIEngine({ + useEslintrc: false, + rules: { semi: 2 }, + }); + + const report = engine.executeOnText("var foo = 'bar'"); + + assert.strictEqual(report.results[0].source, "var foo = 'bar'"); + }); + + it("should return source code of file in `source` property when warnings are present", () => { + engine = new CLIEngine({ + useEslintrc: false, + rules: { semi: 1 }, + }); + + const report = engine.executeOnText("var foo = 'bar'"); + + assert.strictEqual(report.results[0].source, "var foo = 'bar'"); + }); + + it("should not return a `source` property when no errors or warnings are present", () => { + engine = new CLIEngine({ + useEslintrc: false, + rules: { semi: 2 }, + }); + + const report = engine.executeOnText("var foo = 'bar';"); + + assert.lengthOf(report.results[0].messages, 0); + assert.isUndefined(report.results[0].source); + }); + + it("should not return a `source` property when fixes are applied", () => { + engine = new CLIEngine({ + useEslintrc: false, + fix: true, + rules: { + semi: 2, + "no-unused-vars": 2, + }, + }); + + const report = engine.executeOnText("var msg = 'hi' + foo\n"); + + assert.isUndefined(report.results[0].source); + assert.strictEqual( + report.results[0].output, + "var msg = 'hi' + foo;\n", + ); + }); + + it("should return a `source` property when a parsing error has occurred", () => { + engine = new CLIEngine({ + useEslintrc: false, + rules: { eqeqeq: 2 }, + }); + + const report = engine.executeOnText( + "var bar = foothis is a syntax error.\n return bar;", + ); + + assert.deepStrictEqual(report, { + results: [ + { + filePath: "", + messages: [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Parsing error: Unexpected token is", + line: 1, + column: 19, + nodeType: null, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "var bar = foothis is a syntax error.\n return bar;", + }, + ], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + usedDeprecatedRules: [], + }); + }); + + // https://github.com/eslint/eslint/issues/5547 + it("should respect default ignore rules, even with --no-ignore", () => { + engine = new CLIEngine({ + cwd: getFixturePath(), + ignore: false, + }); + + const report = engine.executeOnText( + "var bar = foo;", + "node_modules/passing.js", + true, + ); + const expectedMsg = + "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; + + assert.strictEqual(report.results.length, 1); + assert.strictEqual( + report.results[0].filePath, + getFixturePath("node_modules/passing.js"), + ); + assert.strictEqual( + report.results[0].messages[0].message, + expectedMsg, + ); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + // @scope for @scope/eslint-plugin + describe("(plugin shorthand)", () => { + const Module = require("node:module"); + let originalFindPath = null; + + /* eslint-disable no-underscore-dangle -- Private Node API overriding */ + before(() => { + originalFindPath = Module._findPath; + Module._findPath = function (id, ...otherArgs) { + if (id === "@scope/eslint-plugin") { + return path.resolve( + __dirname, + "../../fixtures/plugin-shorthand/basic/node_modules/@scope/eslint-plugin/index.js", + ); + } + return originalFindPath.call(this, id, ...otherArgs); + }; + }); + after(() => { + Module._findPath = originalFindPath; + }); + /* eslint-enable no-underscore-dangle -- Private Node API overriding */ + + it("should resolve 'plugins:[\"@scope\"]' to 'node_modules/@scope/eslint-plugin'.", () => { + engine = new CLIEngine({ + cwd: getFixturePath("plugin-shorthand/basic"), + }); + const report = engine.executeOnText("var x = 0", "index.js") + .results[0]; + + assert.strictEqual( + report.filePath, + getFixturePath("plugin-shorthand/basic/index.js"), + ); + assert.strictEqual(report.messages[0].ruleId, "@scope/rule"); + assert.strictEqual(report.messages[0].message, "OK"); + assert.strictEqual(report.suppressedMessages.length, 0); + }); + + it("should resolve 'extends:[\"plugin:@scope/recommended\"]' to 'node_modules/@scope/eslint-plugin'.", () => { + engine = new CLIEngine({ + cwd: getFixturePath("plugin-shorthand/extends"), + }); + const report = engine.executeOnText("var x = 0", "index.js") + .results[0]; + + assert.strictEqual( + report.filePath, + getFixturePath("plugin-shorthand/extends/index.js"), + ); + assert.strictEqual(report.messages[0].ruleId, "@scope/rule"); + assert.strictEqual(report.messages[0].message, "OK"); + assert.strictEqual(report.suppressedMessages.length, 0); + }); + }); + it("should warn when deprecated rules are found in a config", () => { + engine = new CLIEngine({ + cwd: originalDir, + useEslintrc: false, + configFile: + "tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml", + }); + + const report = engine.executeOnText("foo"); + + assert.deepStrictEqual(report.usedDeprecatedRules, [ + { ruleId: "indent-legacy", replacedBy: [] }, + ]); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + }); + + describe("executeOnFiles()", () => { + /** @type {InstanceType} */ + let engine; + + it("should use correct parser when custom parser is specified", () => { + engine = new CLIEngine({ + cwd: originalDir, + ignore: false, + }); + + const filePath = path.resolve( + __dirname, + "../../fixtures/configurations/parser/custom.js", + ); + const report = engine.executeOnFiles([filePath]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual( + report.results[0].messages[0].message, + "Parsing error: Boom!", + ); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should report zero messages when given a config file and a valid file", () => { + engine = new CLIEngine({ + cwd: originalDir, + useEslintrc: false, + ignore: false, + overrideConfigFile: + "tests/fixtures/simple-valid-project/.eslintrc.js", + }); + + const report = engine.executeOnFiles([ + "tests/fixtures/simple-valid-project/**/foo*.js", + ]); + + assert.strictEqual(report.results.length, 2); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[1].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should handle multiple patterns with overlapping files", () => { + engine = new CLIEngine({ + cwd: originalDir, + useEslintrc: false, + ignore: false, + overrideConfigFile: + "tests/fixtures/simple-valid-project/.eslintrc.js", + }); + + const report = engine.executeOnFiles([ + "tests/fixtures/simple-valid-project/**/foo*.js", + "tests/fixtures/simple-valid-project/foo.?s", + "tests/fixtures/simple-valid-project/{foo,src/foobar}.js", + ]); + + assert.strictEqual(report.results.length, 2); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + assert.strictEqual(report.results[1].messages.length, 0); + assert.strictEqual(report.results[1].suppressedMessages.length, 0); + }); + + it("should report zero messages when given a config file and a valid file and espree as parser", () => { + engine = new CLIEngine({ + parser: "espree", + parserOptions: { + ecmaVersion: 2021, + }, + useEslintrc: false, + }); + + const report = engine.executeOnFiles(["lib/cli.js"]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should report zero messages when given a config file and a valid file and esprima as parser", () => { + engine = new CLIEngine({ + parser: "esprima", + useEslintrc: false, + ignore: false, + }); + + const report = engine.executeOnFiles([ + "tests/fixtures/simple-valid-project/foo.js", + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should throw an error when given a config file and a valid file and invalid parser", () => { + engine = new CLIEngine({ + parser: "test11", + useEslintrc: false, + }); + + assert.throws( + () => engine.executeOnFiles(["lib/cli.js"]), + "Cannot find module 'test11'", + ); + }); + + it("should report zero messages when given a directory with a .js2 file", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + extensions: [".js2"], + }); + + const report = engine.executeOnFiles([ + getFixturePath("files/foo.js2"), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should fall back to defaults when extensions is set to an empty array", () => { + engine = new CLIEngine({ + cwd: getFixturePath("configurations"), + configFile: getFixturePath( + "configurations", + "quotes-error.json", + ), + extensions: [], + }); + const report = engine.executeOnFiles([ + getFixturePath("single-quoted.js"), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual(report.errorCount, 1); + assert.strictEqual(report.warningCount, 0); + assert.strictEqual(report.fixableErrorCount, 1); + assert.strictEqual(report.fixableWarningCount, 0); + assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(report.results[0].messages[0].severity, 2); + assert.strictEqual(report.results[0].errorCount, 1); + assert.strictEqual(report.results[0].warningCount, 0); + assert.strictEqual(report.results[0].fixableErrorCount, 1); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should report zero messages when given a directory with a .js and a .js2 file", () => { + engine = new CLIEngine({ + extensions: [".js", ".js2"], + ignore: false, + cwd: getFixturePath(".."), + }); + + const report = engine.executeOnFiles(["fixtures/files/"]); + + assert.strictEqual(report.results.length, 2); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[1].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should report zero messages when given a '**' pattern with a .js and a .js2 file", () => { + engine = new CLIEngine({ + extensions: [".js", ".js2"], + ignore: false, + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnFiles(["fixtures/files/*"]); + + assert.strictEqual(report.results.length, 2); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[1].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should resolve globs when 'globInputPaths' option is true", () => { + engine = new CLIEngine({ + extensions: [".js", ".js2"], + ignore: false, + cwd: getFixturePath(".."), + }); + + const report = engine.executeOnFiles(["fixtures/files/*"]); + + assert.strictEqual(report.results.length, 2); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[1].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should not resolve globs when 'globInputPaths' option is false", () => { + engine = new CLIEngine({ + extensions: [".js", ".js2"], + ignore: false, + cwd: getFixturePath(".."), + globInputPaths: false, + }); + + assert.throws(() => { + engine.executeOnFiles(["fixtures/files/*"]); + }, "No files matching 'fixtures/files/*' were found (glob was disabled)."); + }); + + it("should report on all files passed explicitly, even if ignored by default", () => { + engine = new CLIEngine({ + cwd: getFixturePath("cli-engine"), + }); + + const report = engine.executeOnFiles(["node_modules/foo.js"]); + const expectedMsg = + "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].errorCount, 0); + assert.strictEqual(report.results[0].warningCount, 1); + assert.strictEqual(report.results[0].fatalErrorCount, 0); + assert.strictEqual(report.results[0].fixableErrorCount, 0); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual( + report.results[0].messages[0].message, + expectedMsg, + ); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should report on globs with explicit inclusion of dotfiles, even though ignored by default", () => { + engine = new CLIEngine({ + cwd: getFixturePath("cli-engine"), + rules: { + quotes: [2, "single"], + }, + }); + + const report = engine.executeOnFiles(["hidden/.hiddenfolder/*.js"]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].errorCount, 1); + assert.strictEqual(report.results[0].warningCount, 0); + assert.strictEqual(report.results[0].fixableErrorCount, 1); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should not check default ignored files without --no-ignore flag", () => { + engine = new CLIEngine({ + cwd: getFixturePath("cli-engine"), + }); + + assert.throws(() => { + engine.executeOnFiles(["node_modules"]); + }, "All files matched by 'node_modules' are ignored."); + }); + + // https://github.com/eslint/eslint/issues/5547 + it("should not check node_modules files even with --no-ignore flag", () => { + engine = new CLIEngine({ + cwd: getFixturePath("cli-engine"), + ignore: false, + }); + + assert.throws(() => { + engine.executeOnFiles(["node_modules"]); + }, "All files matched by 'node_modules' are ignored."); + }); + + it("should not check .hidden files if they are passed explicitly without --no-ignore flag", () => { + engine = new CLIEngine({ + cwd: getFixturePath(".."), + useEslintrc: false, + rules: { + quotes: [2, "single"], + }, + }); + + const report = engine.executeOnFiles(["fixtures/files/.bar.js"]); + const expectedMsg = + "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].errorCount, 0); + assert.strictEqual(report.results[0].warningCount, 1); + assert.strictEqual(report.results[0].fatalErrorCount, 0); + assert.strictEqual(report.results[0].fixableErrorCount, 0); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual( + report.results[0].messages[0].message, + expectedMsg, + ); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/12873 + it("should not check files within a .hidden folder if they are passed explicitly without the --no-ignore flag", () => { + engine = new CLIEngine({ + cwd: getFixturePath("cli-engine"), + useEslintrc: false, + rules: { + quotes: [2, "single"], + }, + }); + + const report = engine.executeOnFiles([ + "hidden/.hiddenfolder/double-quotes.js", + ]); + const expectedMsg = + "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].errorCount, 0); + assert.strictEqual(report.results[0].warningCount, 1); + assert.strictEqual(report.results[0].fatalErrorCount, 0); + assert.strictEqual(report.results[0].fixableErrorCount, 0); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual( + report.results[0].messages[0].message, + expectedMsg, + ); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should check .hidden files if they are passed explicitly with --no-ignore flag", () => { + engine = new CLIEngine({ + cwd: getFixturePath(".."), + ignore: false, + useEslintrc: false, + rules: { + quotes: [2, "single"], + }, + }); + + const report = engine.executeOnFiles(["fixtures/files/.bar.js"]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].warningCount, 0); + assert.strictEqual(report.results[0].errorCount, 1); + assert.strictEqual(report.results[0].fixableErrorCount, 1); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should check .hidden files if they are unignored with an --ignore-pattern", () => { + engine = new CLIEngine({ + cwd: getFixturePath("cli-engine"), + ignore: true, + useEslintrc: false, + ignorePattern: "!.hidden*", + rules: { + quotes: [2, "single"], + }, + }); + + const report = engine.executeOnFiles(["hidden/"]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].warningCount, 0); + assert.strictEqual(report.results[0].errorCount, 1); + assert.strictEqual(report.results[0].fixableErrorCount, 1); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should report zero messages when given a pattern with a .js and a .js2 file", () => { + engine = new CLIEngine({ + extensions: [".js", ".js2"], + ignore: false, + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnFiles(["fixtures/files/*.?s*"]); + + assert.strictEqual(report.results.length, 2); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + assert.strictEqual(report.results[1].messages.length, 0); + assert.strictEqual(report.results[1].suppressedMessages.length, 0); + }); + + it("should return one error message when given a config with rules with options and severity level set to error", () => { + engine = new CLIEngine({ + cwd: getFixturePath("configurations"), + configFile: getFixturePath( + "configurations", + "quotes-error.json", + ), + }); + const report = engine.executeOnFiles([ + getFixturePath("single-quoted.js"), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual(report.errorCount, 1); + assert.strictEqual(report.warningCount, 0); + assert.strictEqual(report.fixableErrorCount, 1); + assert.strictEqual(report.fixableWarningCount, 0); + assert.strictEqual(report.results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(report.results[0].messages[0].severity, 2); + assert.strictEqual(report.results[0].errorCount, 1); + assert.strictEqual(report.results[0].warningCount, 0); + assert.strictEqual(report.results[0].fixableErrorCount, 1); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should return 3 messages when given a config file and a directory of 3 valid files", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath("configurations", "semi-error.json"), + }); + + const fixturePath = getFixturePath("formatters"); + const report = engine.executeOnFiles([fixturePath]); + + assert.strictEqual(report.errorCount, 0); + assert.strictEqual(report.warningCount, 0); + assert.strictEqual(report.fixableErrorCount, 0); + assert.strictEqual(report.fixableWarningCount, 0); + assert.strictEqual(report.results.length, 5); + assert.strictEqual( + path.relative(fixturePath, report.results[0].filePath), + "async.js", + ); + assert.strictEqual(report.results[0].errorCount, 0); + assert.strictEqual(report.results[0].warningCount, 0); + assert.strictEqual(report.results[0].fixableErrorCount, 0); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + assert.strictEqual( + path.relative(fixturePath, report.results[1].filePath), + "broken.js", + ); + assert.strictEqual(report.results[1].errorCount, 0); + assert.strictEqual(report.results[1].warningCount, 0); + assert.strictEqual(report.results[1].fixableErrorCount, 0); + assert.strictEqual(report.results[1].fixableWarningCount, 0); + assert.strictEqual(report.results[1].messages.length, 0); + assert.strictEqual(report.results[1].suppressedMessages.length, 0); + assert.strictEqual( + path.relative(fixturePath, report.results[2].filePath), + "cwd.js", + ); + assert.strictEqual(report.results[2].errorCount, 0); + assert.strictEqual(report.results[2].warningCount, 0); + assert.strictEqual(report.results[2].fixableErrorCount, 0); + assert.strictEqual(report.results[2].fixableWarningCount, 0); + assert.strictEqual(report.results[2].messages.length, 0); + assert.strictEqual(report.results[2].suppressedMessages.length, 0); + assert.strictEqual( + path.relative(fixturePath, report.results[3].filePath), + "simple.js", + ); + assert.strictEqual(report.results[3].errorCount, 0); + assert.strictEqual(report.results[3].warningCount, 0); + assert.strictEqual(report.results[3].fixableErrorCount, 0); + assert.strictEqual(report.results[3].fixableWarningCount, 0); + assert.strictEqual(report.results[3].messages.length, 0); + assert.strictEqual(report.results[3].suppressedMessages.length, 0); + assert.strictEqual( + path.relative(fixturePath, report.results[4].filePath), + path.join("test", "simple.js"), + ); + assert.strictEqual(report.results[4].errorCount, 0); + assert.strictEqual(report.results[4].warningCount, 0); + assert.strictEqual(report.results[4].fixableErrorCount, 0); + assert.strictEqual(report.results[4].fixableWarningCount, 0); + assert.strictEqual(report.results[4].messages.length, 0); + assert.strictEqual(report.results[4].suppressedMessages.length, 0); + }); + + it("should return the total number of errors when given multiple files", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath( + "configurations", + "single-quotes-error.json", + ), + }); + + const fixturePath = getFixturePath("formatters"); + const report = engine.executeOnFiles([fixturePath]); + + assert.strictEqual(report.errorCount, 6); + assert.strictEqual(report.warningCount, 0); + assert.strictEqual(report.fixableErrorCount, 6); + assert.strictEqual(report.fixableWarningCount, 0); + assert.strictEqual(report.results.length, 5); + assert.strictEqual( + path.relative(fixturePath, report.results[0].filePath), + "async.js", + ); + assert.strictEqual(report.results[0].errorCount, 0); + assert.strictEqual(report.results[0].warningCount, 0); + assert.strictEqual(report.results[0].fixableErrorCount, 0); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual( + path.relative(fixturePath, report.results[1].filePath), + "broken.js", + ); + assert.strictEqual(report.results[1].errorCount, 0); + assert.strictEqual(report.results[1].warningCount, 0); + assert.strictEqual(report.results[1].fixableErrorCount, 0); + assert.strictEqual(report.results[1].fixableWarningCount, 0); + assert.strictEqual( + path.relative(fixturePath, report.results[2].filePath), + "cwd.js", + ); + assert.strictEqual(report.results[2].errorCount, 0); + assert.strictEqual(report.results[2].warningCount, 0); + assert.strictEqual(report.results[2].fixableErrorCount, 0); + assert.strictEqual(report.results[2].fixableWarningCount, 0); + assert.strictEqual( + path.relative(fixturePath, report.results[3].filePath), + "simple.js", + ); + assert.strictEqual(report.results[3].errorCount, 3); + assert.strictEqual(report.results[3].warningCount, 0); + assert.strictEqual(report.results[3].fixableErrorCount, 3); + assert.strictEqual(report.results[3].fixableWarningCount, 0); + assert.strictEqual( + path.relative(fixturePath, report.results[4].filePath), + path.join("test", "simple.js"), + ); + assert.strictEqual(report.results[4].errorCount, 3); + assert.strictEqual(report.results[4].warningCount, 0); + assert.strictEqual(report.results[4].fixableErrorCount, 3); + assert.strictEqual(report.results[4].fixableWarningCount, 0); + }); + + it("should process when file is given by not specifying extensions", () => { + engine = new CLIEngine({ + ignore: false, + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnFiles(["fixtures/files/foo.js2"]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should return zero messages when given a config with environment set to browser", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath( + "configurations", + "env-browser.json", + ), + }); + + const report = engine.executeOnFiles([ + fs.realpathSync(getFixturePath("globals-browser.js")), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should return zero messages when given an option to set environment to browser", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + envs: ["browser"], + rules: { + "no-alert": 0, + "no-undef": 2, + }, + }); + + const report = engine.executeOnFiles([ + fs.realpathSync(getFixturePath("globals-browser.js")), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should return zero messages when given a config with environment set to Node.js", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath("configurations", "env-node.json"), + }); + + const report = engine.executeOnFiles([ + fs.realpathSync(getFixturePath("globals-node.js")), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should not return results from previous call when calling more than once", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + ignore: false, + rules: { + semi: 2, + }, + }); + + const failFilePath = fs.realpathSync( + getFixturePath("missing-semicolon.js"), + ); + const passFilePath = fs.realpathSync(getFixturePath("passing.js")); + + let report = engine.executeOnFiles([failFilePath]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].filePath, failFilePath); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual(report.results[0].messages[0].ruleId, "semi"); + assert.strictEqual(report.results[0].messages[0].severity, 2); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + + report = engine.executeOnFiles([passFilePath]); + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].filePath, passFilePath); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should throw an error when given a directory with all eslint excluded files in the directory", () => { + engine = new CLIEngine({ + ignorePath: getFixturePath(".eslintignore"), + }); + + assert.throws( + () => { + engine.executeOnFiles([getFixturePath("./cli-engine/")]); + }, + `All files matched by '${getFixturePath("./cli-engine/")}' are ignored.`, + ); + }); + + it("should throw an error when all given files are ignored", () => { + engine = new CLIEngine({ + useEslintrc: false, + ignorePath: getFixturePath(".eslintignore"), + }); + + assert.throws(() => { + engine.executeOnFiles(["tests/fixtures/cli-engine/"]); + }, "All files matched by 'tests/fixtures/cli-engine/' are ignored."); + }); + + it("should throw an error when all given files are ignored even with a `./` prefix", () => { + engine = new CLIEngine({ + useEslintrc: false, + ignorePath: getFixturePath(".eslintignore"), + }); + + assert.throws(() => { + engine.executeOnFiles(["./tests/fixtures/cli-engine/"]); + }, "All files matched by './tests/fixtures/cli-engine/' are ignored."); + }); + + // https://github.com/eslint/eslint/issues/3788 + it("should ignore one-level down node_modules when ignore file has 'node_modules/' in it", () => { + engine = new CLIEngine({ + ignorePath: getFixturePath( + "cli-engine", + "nested_node_modules", + ".eslintignore", + ), + useEslintrc: false, + rules: { + quotes: [2, "double"], + }, + cwd: getFixturePath("cli-engine", "nested_node_modules"), + }); + + const report = engine.executeOnFiles(["."]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].errorCount, 0); + assert.strictEqual(report.results[0].warningCount, 0); + assert.strictEqual(report.results[0].fixableErrorCount, 0); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/3812 + it("should ignore all files and throw an error when tests/fixtures/ is in ignore file", () => { + engine = new CLIEngine({ + ignorePath: getFixturePath("cli-engine/.eslintignore2"), + useEslintrc: false, + rules: { + quotes: [2, "double"], + }, + }); + + assert.throws(() => { + engine.executeOnFiles(["./tests/fixtures/cli-engine/"]); + }, "All files matched by './tests/fixtures/cli-engine/' are ignored."); + }); + + it("should throw an error when all given files are ignored via ignore-pattern", () => { + engine = new CLIEngine({ + useEslintrc: false, + ignorePattern: "tests/fixtures/single-quoted.js", + }); + + assert.throws(() => { + engine.executeOnFiles(["tests/fixtures/*-quoted.js"]); + }, "All files matched by 'tests/fixtures/*-quoted.js' are ignored."); + }); + + it("should return a warning when an explicitly given file is ignored", () => { + engine = new CLIEngine({ + ignorePath: getFixturePath(".eslintignore"), + cwd: getFixturePath(), + }); + + const filePath = getFixturePath("passing.js"); + + const report = engine.executeOnFiles([filePath]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.errorCount, 0); + assert.strictEqual(report.warningCount, 1); + assert.strictEqual(report.fatalErrorCount, 0); + assert.strictEqual(report.fixableErrorCount, 0); + assert.strictEqual(report.fixableWarningCount, 0); + assert.strictEqual(report.results[0].filePath, filePath); + assert.strictEqual(report.results[0].messages[0].severity, 1); + assert.strictEqual( + report.results[0].messages[0].message, + 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override.', + ); + assert.strictEqual(report.results[0].errorCount, 0); + assert.strictEqual(report.results[0].warningCount, 1); + assert.strictEqual(report.results[0].fatalErrorCount, 0); + assert.strictEqual(report.results[0].fixableErrorCount, 0); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should return two messages when given a file in excluded files list while ignore is off", () => { + engine = new CLIEngine({ + ignorePath: getFixturePath(".eslintignore"), + ignore: false, + rules: { + "no-undef": 2, + }, + }); + + const filePath = fs.realpathSync(getFixturePath("undef.js")); + + const report = engine.executeOnFiles([filePath]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].filePath, filePath); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "no-undef", + ); + assert.strictEqual(report.results[0].messages[0].severity, 2); + assert.strictEqual( + report.results[0].messages[1].ruleId, + "no-undef", + ); + assert.strictEqual(report.results[0].messages[1].severity, 2); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should return zero messages when executing a file with a shebang", () => { + engine = new CLIEngine({ + ignore: false, + }); + + const report = engine.executeOnFiles([ + getFixturePath("shebang.js"), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should give a warning when loading a custom rule that doesn't exist", () => { + engine = new CLIEngine({ + ignore: false, + rulesPaths: [getFixturePath("rules", "dir1")], + configFile: getFixturePath("rules", "missing-rule.json"), + }); + const report = engine.executeOnFiles([ + getFixturePath("rules", "test", "test-custom-rule.js"), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "missing-rule", + ); + assert.strictEqual(report.results[0].messages[0].severity, 2); + assert.strictEqual( + report.results[0].messages[0].message, + "Definition for rule 'missing-rule' was not found.", + ); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should throw an error when loading a bad custom rule", () => { + engine = new CLIEngine({ + ignore: false, + rulePaths: [getFixturePath("rules", "wrong")], + configFile: getFixturePath("rules", "eslint.json"), + }); + + assert.throws(() => { + engine.executeOnFiles([ + getFixturePath("rules", "test", "test-custom-rule.js"), + ]); + }, /Error while loading rule 'custom-rule'/u); + }); + + it("should return one message when a custom rule matches a file", () => { + engine = new CLIEngine({ + ignore: false, + useEslintrc: false, + rulePaths: [getFixturePath("rules/")], + configFile: getFixturePath("rules", "eslint.json"), + }); + + const filePath = fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ); + + const report = engine.executeOnFiles([filePath]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].filePath, filePath); + assert.strictEqual(report.results[0].messages.length, 2); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "custom-rule", + ); + assert.strictEqual(report.results[0].messages[0].severity, 1); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should load custom rule from the provided cwd", () => { + const cwd = path.resolve(getFixturePath("rules")); + + engine = new CLIEngine({ + ignore: false, + cwd, + rulePaths: ["./"], + configFile: "eslint.json", + }); + + const filePath = fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ); + + const report = engine.executeOnFiles([filePath]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].filePath, filePath); + assert.strictEqual(report.results[0].messages.length, 2); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "custom-rule", + ); + assert.strictEqual(report.results[0].messages[0].severity, 1); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should return messages when multiple custom rules match a file", () => { + engine = new CLIEngine({ + ignore: false, + rulePaths: [ + getFixturePath("rules", "dir1"), + getFixturePath("rules", "dir2"), + ], + configFile: getFixturePath("rules", "multi-rulesdirs.json"), + }); + + const filePath = fs.realpathSync( + getFixturePath("rules", "test-multi-rulesdirs.js"), + ); + + const report = engine.executeOnFiles([filePath]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].filePath, filePath); + assert.strictEqual(report.results[0].messages.length, 2); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "no-literals", + ); + assert.strictEqual(report.results[0].messages[0].severity, 2); + assert.strictEqual( + report.results[0].messages[1].ruleId, + "no-strings", + ); + assert.strictEqual(report.results[0].messages[1].severity, 2); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should return zero messages when executing without useEslintrc flag", () => { + engine = new CLIEngine({ + ignore: false, + useEslintrc: false, + }); + + const filePath = fs.realpathSync( + getFixturePath("missing-semicolon.js"), + ); + + const report = engine.executeOnFiles([filePath]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].filePath, filePath); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should return zero messages when executing without useEslintrc flag in Node.js environment", () => { + engine = new CLIEngine({ + ignore: false, + useEslintrc: false, + envs: ["node"], + }); + + const filePath = fs.realpathSync(getFixturePath("process-exit.js")); + + const report = engine.executeOnFiles([filePath]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].filePath, filePath); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should return zero messages when executing with base-config flag set to false", () => { + engine = new CLIEngine({ + ignore: false, + baseConfig: false, + useEslintrc: false, + }); + + const filePath = fs.realpathSync( + getFixturePath("missing-semicolon.js"), + ); + + const report = engine.executeOnFiles([filePath]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].filePath, filePath); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should return zero messages and ignore .eslintrc files when executing with no-eslintrc flag", () => { + engine = new CLIEngine({ + ignore: false, + useEslintrc: false, + envs: ["node"], + }); + + const filePath = fs.realpathSync( + getFixturePath("eslintrc", "quotes.js"), + ); + + const report = engine.executeOnFiles([filePath]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].filePath, filePath); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should return zero messages and ignore package.json files when executing with no-eslintrc flag", () => { + engine = new CLIEngine({ + ignore: false, + useEslintrc: false, + envs: ["node"], + }); + + const filePath = fs.realpathSync( + getFixturePath("packagejson", "quotes.js"), + ); + + const report = engine.executeOnFiles([filePath]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].filePath, filePath); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should warn when deprecated rules are configured", () => { + engine = new CLIEngine({ + cwd: originalDir, + useEslintrc: false, + rules: { + "indent-legacy": 1, + "callback-return": 1, + }, + }); + + const report = engine.executeOnFiles(["lib/cli*.js"]); + + assert.sameDeepMembers(report.usedDeprecatedRules, [ + { ruleId: "indent-legacy", replacedBy: [] }, + { ruleId: "callback-return", replacedBy: [] }, + ]); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should not warn when deprecated rules are not configured", () => { + engine = new CLIEngine({ + cwd: originalDir, + useEslintrc: false, + rules: { eqeqeq: 1, "callback-return": 0 }, + }); + + const report = engine.executeOnFiles(["lib/cli*.js"]); + + assert.deepStrictEqual(report.usedDeprecatedRules, []); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + it("should warn when deprecated rules are found in a config", () => { + engine = new CLIEngine({ + cwd: originalDir, + configFile: + "tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml", + useEslintrc: false, + }); + + const report = engine.executeOnFiles(["lib/cli*.js"]); + + assert.deepStrictEqual(report.usedDeprecatedRules, [ + { ruleId: "indent-legacy", replacedBy: [] }, + ]); + assert.strictEqual(report.results[0].suppressedMessages.length, 0); + }); + + describe("Fix Mode", () => { + it("should return fixed text on multiple files when in fix mode", () => { + /** + * Converts CRLF to LF in output. + * This is a workaround for git's autocrlf option on Windows. + * @param {Object} result A result object to convert. + * @returns {void} + */ + function convertCRLF(result) { + if (result && result.output) { + result.output = result.output.replace(/\r\n/gu, "\n"); + } + } + + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + rules: { + semi: 2, + quotes: [2, "double"], + eqeqeq: 2, + "no-undef": 2, + "space-infix-ops": 2, + }, + }); + + const report = engine.executeOnFiles([ + path.resolve(fixtureDir, `${fixtureDir}/fixmode`), + ]); + + report.results.forEach(convertCRLF); + assert.deepStrictEqual(report.results, [ + { + filePath: fs.realpathSync( + path.resolve(fixtureDir, "fixmode/multipass.js"), + ), + messages: [], + suppressedMessages: [], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: 'true ? "yes" : "no";\n', + }, + { + filePath: fs.realpathSync( + path.resolve(fixtureDir, "fixmode/ok.js"), + ), + messages: [], + suppressedMessages: [], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + }, + { + filePath: fs.realpathSync( + path.resolve( + fixtureDir, + "fixmode/quotes-semi-eqeqeq.js", + ), + ), + messages: [ + { + column: 9, + line: 2, + endColumn: 11, + endLine: 2, + message: "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + severity: 2, + suggestions: [ + { + data: { + actualOperator: "==", + expectedOperator: "===", + }, + desc: "Use '===' instead of '=='.", + fix: { + range: [24, 26], + text: "===", + }, + messageId: "replaceOperator", + }, + ], + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: 'var msg = "hi";\nif (msg == "hi") {\n\n}\n', + }, + { + filePath: fs.realpathSync( + path.resolve(fixtureDir, "fixmode/quotes.js"), + ), + messages: [ + { + column: 18, + line: 1, + endColumn: 21, + endLine: 1, + messageId: "undef", + message: "'foo' is not defined.", + nodeType: "Identifier", + ruleId: "no-undef", + severity: 2, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: 'var msg = "hi" + foo;\n', + }, + ]); + assert.strictEqual(report.errorCount, 2); + assert.strictEqual(report.warningCount, 0); + assert.strictEqual(report.fixableErrorCount, 0); + assert.strictEqual(report.fixableWarningCount, 0); + }); + + it("should run autofix even if files are cached without autofix results", () => { + const baseOptions = { + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + rules: { + semi: 2, + quotes: [2, "double"], + eqeqeq: 2, + "no-undef": 2, + "space-infix-ops": 2, + }, + }; + + engine = new CLIEngine( + Object.assign({}, baseOptions, { cache: true, fix: false }), + ); + + // Do initial lint run and populate the cache file + engine.executeOnFiles([ + path.resolve(fixtureDir, `${fixtureDir}/fixmode`), + ]); + + engine = new CLIEngine( + Object.assign({}, baseOptions, { cache: true, fix: true }), + ); + + const report = engine.executeOnFiles([ + path.resolve(fixtureDir, `${fixtureDir}/fixmode`), + ]); + + assert.ok(report.results.some(result => result.output)); + }); + }); + + // These tests have to do with https://github.com/eslint/eslint/issues/963 + + describe("configuration hierarchy", () => { + // Default configuration - blank + it("should return zero messages when executing with no .eslintrc", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // No default configuration rules - conf/environments.js (/*eslint-env node*/) + it("should return zero messages when executing with no .eslintrc in the Node.js environment", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + reset: true, + useEslintrc: false, + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/console-wrong-quotes-node.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Project configuration - first level .eslintrc + it("should return zero messages when executing with .eslintrc in the Node.js environment", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/process-exit.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Project configuration - first level .eslintrc + it("should return zero messages when executing with .eslintrc in the Node.js environment", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/process-exit.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + }); + + // Project configuration - first level .eslintrc + it("should return one message when executing with .eslintrc", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "quotes", + ); + assert.strictEqual(report.results[0].messages[0].severity, 2); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Project configuration - second level .eslintrc + it("should return one message when executing with local .eslintrc that overrides parent .eslintrc", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "no-console", + ); + assert.strictEqual(report.results[0].messages[0].severity, 1); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Project configuration - third level .eslintrc + it("should return one message when executing with local .eslintrc that overrides parent and grandparent .eslintrc", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/subbroken/subsubbroken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "quotes", + ); + assert.strictEqual(report.results[0].messages[0].severity, 1); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Project configuration - first level package.json + it("should return one message when executing with package.json", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/packagejson/subdir/wrong-quotes.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "quotes", + ); + assert.strictEqual(report.results[0].messages[0].severity, 1); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Project configuration - second level package.json + it("should return zero messages when executing with local package.json that overrides parent package.json", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/packagejson/subdir/subsubdir/wrong-quotes.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Project configuration - third level package.json + it("should return one message when executing with local package.json that overrides parent and grandparent package.json", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/packagejson/subdir/subsubdir/subsubsubdir/wrong-quotes.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "quotes", + ); + assert.strictEqual(report.results[0].messages[0].severity, 2); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Project configuration - .eslintrc overrides package.json in same directory + it("should return one message when executing with .eslintrc that overrides a package.json in the same directory", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/packagejson/wrong-quotes.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "quotes", + ); + assert.strictEqual(report.results[0].messages[0].severity, 2); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Command line configuration - --config with first level .eslintrc + it("should return two messages when executing with config file that adds to local .eslintrc", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + configFile: `${fixtureDir}/config-hierarchy/broken/add-conf.yaml`, + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 2); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "quotes", + ); + assert.strictEqual(report.results[0].messages[0].severity, 2); + assert.strictEqual( + report.results[0].messages[1].ruleId, + "semi", + ); + assert.strictEqual(report.results[0].messages[1].severity, 1); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Command line configuration - --config with first level .eslintrc + it("should return no messages when executing with config file that overrides local .eslintrc", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + configFile: `${fixtureDir}/config-hierarchy/broken/override-conf.yaml`, + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Command line configuration - --config with second level .eslintrc + it("should return two messages when executing with config file that adds to local and parent .eslintrc", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + configFile: `${fixtureDir}/config-hierarchy/broken/add-conf.yaml`, + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 2); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "no-console", + ); + assert.strictEqual(report.results[0].messages[0].severity, 1); + assert.strictEqual( + report.results[0].messages[1].ruleId, + "semi", + ); + assert.strictEqual(report.results[0].messages[1].severity, 1); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Command line configuration - --config with second level .eslintrc + it("should return one message when executing with config file that overrides local and parent .eslintrc", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath( + "config-hierarchy/broken/override-conf.yaml", + ), + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "no-console", + ); + assert.strictEqual(report.results[0].messages[0].severity, 1); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Command line configuration - --config with first level .eslintrc + it("should return no messages when executing with config file that overrides local .eslintrc", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + configFile: `${fixtureDir}/config-hierarchy/broken/override-conf.yaml`, + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Command line configuration - --rule with --config and first level .eslintrc + it("should return one message when executing with command line rule and config file that overrides local .eslintrc", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath( + "config-hierarchy/broken/override-conf.yaml", + ), + rules: { + quotes: [1, "double"], + }, + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "quotes", + ); + assert.strictEqual(report.results[0].messages[0].severity, 1); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + // Command line configuration - --rule with --config and first level .eslintrc + it("should return one message when executing with command line rule and config file that overrides local .eslintrc", () => { + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath( + "/config-hierarchy/broken/override-conf.yaml", + ), + rules: { + quotes: [1, "double"], + }, + }); + + const report = engine.executeOnFiles([ + getFixturePath( + "config-hierarchy/broken/console-wrong-quotes.js", + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "quotes", + ); + assert.strictEqual(report.results[0].messages[0].severity, 1); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + }); + + describe("plugins", () => { + it("should return two messages when executing with config file that specifies a plugin", () => { + engine = cliEngineWithPlugins({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath( + "configurations", + "plugins-with-prefix.json", + ), + useEslintrc: false, + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + getFixturePath("rules", "test/test-custom-rule.js"), + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 2); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "example/example-rule", + ); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + it("should return two messages when executing with config file that specifies a plugin with namespace", () => { + engine = cliEngineWithPlugins({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath( + "configurations", + "plugins-with-prefix-and-namespace.json", + ), + useEslintrc: false, + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 2); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "@eslint/example/example-rule", + ); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + it("should return two messages when executing with config file that specifies a plugin without prefix", () => { + engine = cliEngineWithPlugins({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath( + "configurations", + "plugins-without-prefix.json", + ), + useEslintrc: false, + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 2); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "example/example-rule", + ); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + it("should return two messages when executing with config file that specifies a plugin without prefix and with namespace", () => { + engine = cliEngineWithPlugins({ + cwd: path.join(fixtureDir, ".."), + configFile: getFixturePath( + "configurations", + "plugins-without-prefix-with-namespace.json", + ), + useEslintrc: false, + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 2); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "@eslint/example/example-rule", + ); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + it("should return two messages when executing with cli option that specifies a plugin", () => { + engine = cliEngineWithPlugins({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + plugins: ["example"], + rules: { "example/example-rule": 1 }, + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 2); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "example/example-rule", + ); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + it("should return two messages when executing with cli option that specifies preloaded plugin", () => { + engine = new CLIEngine( + { + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + plugins: ["test"], + rules: { "test/example-rule": 1 }, + }, + { + preloadedPlugins: { + "eslint-plugin-test": { + rules: { + "example-rule": require("../../fixtures/rules/custom-rule"), + }, + }, + }, + }, + ); + + const report = engine.executeOnFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 2); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "test/example-rule", + ); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + + it("should load plugins from the `loadPluginsRelativeTo` directory, if specified", () => { + engine = new CLIEngine({ + resolvePluginsRelativeTo: getFixturePath("plugins"), + baseConfig: { + plugins: ["with-rules"], + rules: { "with-rules/rule1": "error" }, + }, + useEslintrc: false, + }); + + const report = engine.executeOnText("foo"); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "with-rules/rule1", + ); + assert.strictEqual( + report.results[0].messages[0].message, + "Rule report from plugin", + ); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + }); + + describe("cache", () => { + /** + * helper method to delete a file without caring about exceptions + * @param {string} filePath The file path + * @returns {void} + */ + function doDelete(filePath) { + try { + fs.unlinkSync(filePath); + } catch { + /* + * we don't care if the file didn't exist, since our + * intention was to remove the file + */ + } + } + + /** + * helper method to delete the cache files created during testing + * @returns {void} + */ + function deleteCache() { + doDelete(path.resolve(".eslintcache")); + doDelete(path.resolve(".cache/custom-cache")); + } + + beforeEach(() => { + deleteCache(); + }); + + afterEach(() => { + sinon.restore(); + deleteCache(); + }); + + describe("when the cacheFile is a directory or looks like a directory", () => { + /** + * helper method to delete the cache files created during testing + * @returns {void} + */ + function deleteCacheDir() { + try { + fs.unlinkSync( + "./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory", + ); + } catch { + /* + * we don't care if the file didn't exist, since our + * intention was to remove the file + */ + } + } + beforeEach(() => { + deleteCacheDir(); + }); + + afterEach(() => { + deleteCacheDir(); + }); + + it("should create the cache file inside the provided directory", () => { + assert.isFalse( + shell.test( + "-d", + path.resolve( + "./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory", + ), + ), + "the cache for eslint does not exist", + ); + + engine = new CLIEngine({ + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheFile: "./tmp/.cacheFileDir/", + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + ignore: false, + }); + + const file = getFixturePath("cache/src", "test-file.js"); + + engine.executeOnFiles([file]); + + assert.isTrue( + shell.test( + "-f", + path.resolve( + `./tmp/.cacheFileDir/.cache_${hash(process.cwd())}`, + ), + ), + "the cache for eslint was created", + ); + + sinon.restore(); + }); + }); + + it("should create the cache file inside the provided directory using the cacheLocation option", () => { + assert.isFalse( + shell.test( + "-d", + path.resolve( + "./tmp/.cacheFileDir/.cache_hashOfCurrentWorkingDirectory", + ), + ), + "the cache for eslint does not exist", + ); + + engine = new CLIEngine({ + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: "./tmp/.cacheFileDir/", + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + ignore: false, + }); + + const file = getFixturePath("cache/src", "test-file.js"); + + engine.executeOnFiles([file]); + + assert.isTrue( + shell.test( + "-f", + path.resolve( + `./tmp/.cacheFileDir/.cache_${hash(process.cwd())}`, + ), + ), + "the cache for eslint was created", + ); + + sinon.restore(); + }); + + it("should create the cache file inside cwd when no cacheLocation provided", () => { + const cwd = path.resolve(getFixturePath("cli-engine")); + + engine = new CLIEngine({ + useEslintrc: false, + cache: true, + cwd, + rules: { + "no-console": 0, + }, + extensions: ["js"], + ignore: false, + }); + + const file = getFixturePath("cli-engine", "console.js"); + + engine.executeOnFiles([file]); + + assert.isTrue( + shell.test("-f", path.resolve(cwd, ".eslintcache")), + "the cache for eslint was created at provided cwd", + ); + }); + + it("should invalidate the cache if the configuration changed between executions", () => { + assert.isFalse( + shell.test("-f", path.resolve(".eslintcache")), + "the cache for eslint does not exist", + ); + + engine = new CLIEngine({ + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + ignore: false, + }); + + let spy = sinon.spy(fs, "readFileSync"); + + let file = getFixturePath("cache/src", "test-file.js"); + + file = fs.realpathSync(file); + + const result = engine.executeOnFiles([file]); + + assert.strictEqual( + result.errorCount + result.warningCount, + 0, + "the file passed without errors or warnings", + ); + assert.strictEqual( + spy.getCall(0).args[0], + file, + "the module read the file because is considered changed", + ); + assert.isTrue( + shell.test("-f", path.resolve(".eslintcache")), + "the cache for eslint was created", + ); + + // destroy the spy + sinon.restore(); + + engine = new CLIEngine({ + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + rules: { + "no-console": 2, + "no-unused-vars": 2, + }, + extensions: ["js"], + ignore: false, + }); + + // create a new spy + spy = sinon.spy(fs, "readFileSync"); + + const cachedResult = engine.executeOnFiles([file]); + + assert.strictEqual( + spy.getCall(0).args[0], + file, + "the module read the file because is considered changed because the config changed", + ); + assert.strictEqual( + cachedResult.errorCount, + 1, + "since configuration changed the cache was not used an one error was reported", + ); + assert.isTrue( + shell.test("-f", path.resolve(".eslintcache")), + "the cache for eslint was created", + ); + }); + + it("should remember the files from a previous run and do not operate on them if not changed", () => { + assert.isFalse( + shell.test("-f", path.resolve(".eslintcache")), + "the cache for eslint does not exist", + ); + + engine = new CLIEngine({ + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + ignore: false, + }); + + let spy = sinon.spy(fs, "readFileSync"); + + let file = getFixturePath("cache/src", "test-file.js"); + + file = fs.realpathSync(file); + + const result = engine.executeOnFiles([file]); + + assert.strictEqual( + spy.getCall(0).args[0], + file, + "the module read the file because is considered changed", + ); + assert.isTrue( + shell.test("-f", path.resolve(".eslintcache")), + "the cache for eslint was created", + ); + + // destroy the spy + sinon.restore(); + + engine = new CLIEngine({ + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + ignore: false, + }); + + // create a new spy + spy = sinon.spy(fs, "readFileSync"); + + const cachedResult = engine.executeOnFiles([file]); + + assert.deepStrictEqual( + result, + cachedResult, + "the result is the same regardless of using cache or not", + ); + + // assert the file was not processed because the cache was used + assert.isFalse( + spy.calledWith(file), + "the file was not loaded because it used the cache", + ); + }); + + it("should remember the files from a previous run and do not operate on then if not changed", () => { + const cacheFile = getFixturePath(".eslintcache"); + const cliEngineOptions = { + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheFile, + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + cwd: path.join(fixtureDir, ".."), + }; + + assert.isFalse( + shell.test("-f", cacheFile), + "the cache for eslint does not exist", + ); + + engine = new CLIEngine(cliEngineOptions); + + let file = getFixturePath("cache/src", "test-file.js"); + + file = fs.realpathSync(file); + + engine.executeOnFiles([file]); + + assert.isTrue( + shell.test("-f", cacheFile), + "the cache for eslint was created", + ); + + cliEngineOptions.cache = false; + engine = new CLIEngine(cliEngineOptions); + + engine.executeOnFiles([file]); + + assert.isFalse( + shell.test("-f", cacheFile), + "the cache for eslint was deleted since last run did not used the cache", + ); + }); + + it("should store in the cache a file that failed the test", () => { + const cacheFile = getFixturePath(".eslintcache"); + + assert.isFalse( + shell.test("-f", cacheFile), + "the cache for eslint does not exist", + ); + + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheFile, + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + }); + + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + + const result = engine.executeOnFiles([badFile, goodFile]); + + assert.isTrue( + shell.test("-f", cacheFile), + "the cache for eslint was created", + ); + + const fileCache = fCache.createFromFile(cacheFile); + const { cache } = fileCache; + + assert.isTrue( + typeof cache.getKey(goodFile) === "object", + "the entry for the good file is in the cache", + ); + + assert.isTrue( + typeof cache.getKey(badFile) === "object", + "the entry for the bad file is in the cache", + ); + + const cachedResult = engine.executeOnFiles([badFile, goodFile]); + + assert.deepStrictEqual( + result, + cachedResult, + "result is the same with or without cache", + ); + }); + + it("should not contain in the cache a file that was deleted", () => { + const cacheFile = getFixturePath(".eslintcache"); + + doDelete(cacheFile); + + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheFile, + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + }); + + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + const toBeDeletedFile = fs.realpathSync( + getFixturePath("cache/src", "file-to-delete.js"), + ); + + engine.executeOnFiles([badFile, goodFile, toBeDeletedFile]); + + const fileCache = fCache.createFromFile(cacheFile); + let { cache } = fileCache; + + assert.isTrue( + typeof cache.getKey(toBeDeletedFile) === "object", + "the entry for the file to be deleted is in the cache", + ); + + // delete the file from the file system + fs.unlinkSync(toBeDeletedFile); + + /* + * file-entry-cache@2.0.0 will remove from the cache deleted files + * even when they were not part of the array of files to be analyzed + */ + engine.executeOnFiles([badFile, goodFile]); + + cache = JSON.parse(fs.readFileSync(cacheFile)); + + assert.isTrue( + typeof cache[toBeDeletedFile] === "undefined", + "the entry for the file to be deleted is not in the cache", + ); + }); + + it("should contain files that were not visited in the cache provided they still exist", () => { + const cacheFile = getFixturePath(".eslintcache"); + + doDelete(cacheFile); + + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheFile, + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + }); + + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + const testFile2 = fs.realpathSync( + getFixturePath("cache/src", "test-file2.js"), + ); + + engine.executeOnFiles([badFile, goodFile, testFile2]); + + let fileCache = fCache.createFromFile(cacheFile); + let { cache } = fileCache; + + assert.isTrue( + typeof cache.getKey(testFile2) === "object", + "the entry for the test-file2 is in the cache", + ); + + /* + * we pass a different set of files minus test-file2 + * previous version of file-entry-cache would remove the non visited + * entries. 2.0.0 version will keep them unless they don't exist + */ + engine.executeOnFiles([badFile, goodFile]); + + fileCache = fCache.createFromFile(cacheFile); + cache = fileCache.cache; + + assert.isTrue( + typeof cache.getKey(testFile2) === "object", + "the entry for the test-file2 is in the cache", + ); + }); + + it("should not delete cache when executing on text", () => { + const cacheFile = getFixturePath(".eslintcache"); + + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + cacheFile, + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + }); + + assert.isTrue( + shell.test("-f", cacheFile), + "the cache for eslint exists", + ); + + engine.executeOnText("var foo = 'bar';"); + + assert.isTrue( + shell.test("-f", cacheFile), + "the cache for eslint still exists", + ); + }); + + it("should not delete cache when executing on text with a provided filename", () => { + const cacheFile = getFixturePath(".eslintcache"); + + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + cacheFile, + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + }); + + assert.isTrue( + shell.test("-f", cacheFile), + "the cache for eslint exists", + ); + + engine.executeOnText("var bar = foo;", "fixtures/passing.js"); + + assert.isTrue( + shell.test("-f", cacheFile), + "the cache for eslint still exists", + ); + }); + + it("should not delete cache when executing on files with --cache flag", () => { + const cacheFile = getFixturePath(".eslintcache"); + + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + cache: true, + cacheFile, + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + }); + + const file = getFixturePath("cli-engine", "console.js"); + + assert.isTrue( + shell.test("-f", cacheFile), + "the cache for eslint exists", + ); + + engine.executeOnFiles([file]); + + assert.isTrue( + shell.test("-f", cacheFile), + "the cache for eslint still exists", + ); + }); + + it("should delete cache when executing on files without --cache flag", () => { + const cacheFile = getFixturePath(".eslintcache"); + + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + cacheFile, + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + }); + + const file = getFixturePath("cli-engine", "console.js"); + + assert.isTrue( + shell.test("-f", cacheFile), + "the cache for eslint exists", + ); + + engine.executeOnFiles([file]); + + assert.isFalse( + shell.test("-f", cacheFile), + "the cache for eslint has been deleted", + ); + }); + + describe("cacheFile", () => { + it("should use the specified cache file", () => { + const customCacheFile = path.resolve(".cache/custom-cache"); + + assert.isFalse( + shell.test("-f", customCacheFile), + "the cache for eslint does not exist", + ); + + engine = new CLIEngine({ + useEslintrc: false, + + // specify a custom cache file + cacheFile: customCacheFile, + + // specifying cache true the cache will be created + cache: true, + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + cwd: path.join(fixtureDir, ".."), + }); + + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + + const result = engine.executeOnFiles([badFile, goodFile]); + + assert.isTrue( + shell.test("-f", customCacheFile), + "the cache for eslint was created", + ); + + const fileCache = fCache.createFromFile(customCacheFile); + const { cache } = fileCache; + + assert.isTrue( + typeof cache.getKey(goodFile) === "object", + "the entry for the good file is in the cache", + ); + + assert.isTrue( + typeof cache.getKey(badFile) === "object", + "the entry for the bad file is in the cache", + ); + + const cachedResult = engine.executeOnFiles([ + badFile, + goodFile, + ]); + + assert.deepStrictEqual( + result, + cachedResult, + "result is the same with or without cache", + ); + }); + }); + + describe("cacheStrategy", () => { + it("should detect changes using a file's modification time when set to 'metadata'", () => { + const cacheFile = getFixturePath(".eslintcache"); + const badFile = getFixturePath("cache/src", "fail-file.js"); + const goodFile = getFixturePath( + "cache/src", + "test-file.js", + ); + + doDelete(cacheFile); + + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheFile, + cacheStrategy: "metadata", + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + }); + + engine.executeOnFiles([badFile, goodFile]); + + let fileCache = fCache.createFromFile(cacheFile, false); + const entries = fileCache.normalizeEntries([ + badFile, + goodFile, + ]); + + entries.forEach(entry => { + assert.isFalse( + entry.changed, + `the entry for ${entry.key} is initially unchanged`, + ); + }); + + // this should result in a changed entry + shell.touch(goodFile); + fileCache = fCache.createFromFile(cacheFile, false); + assert.isFalse( + fileCache.getFileDescriptor(badFile).changed, + `the entry for ${badFile} is unchanged`, + ); + assert.isTrue( + fileCache.getFileDescriptor(goodFile).changed, + `the entry for ${goodFile} is changed`, + ); + }); + + it("should not detect changes using a file's modification time when set to 'content'", () => { + const cacheFile = getFixturePath(".eslintcache"); + const badFile = getFixturePath("cache/src", "fail-file.js"); + const goodFile = getFixturePath( + "cache/src", + "test-file.js", + ); + + doDelete(cacheFile); + + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheFile, + cacheStrategy: "content", + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + }); + + engine.executeOnFiles([badFile, goodFile]); + + let fileCache = fCache.createFromFile(cacheFile, true); + let entries = fileCache.normalizeEntries([ + badFile, + goodFile, + ]); + + entries.forEach(entry => { + assert.isFalse( + entry.changed, + `the entry for ${entry.key} is initially unchanged`, + ); + }); + + // this should NOT result in a changed entry + shell.touch(goodFile); + fileCache = fCache.createFromFile(cacheFile, true); + entries = fileCache.normalizeEntries([badFile, goodFile]); + entries.forEach(entry => { + assert.isFalse( + entry.changed, + `the entry for ${entry.key} remains unchanged`, + ); + }); + }); + + it("should detect changes using a file's contents when set to 'content'", () => { + const cacheFile = getFixturePath(".eslintcache"); + const badFile = getFixturePath("cache/src", "fail-file.js"); + const goodFile = getFixturePath( + "cache/src", + "test-file.js", + ); + const goodFileCopy = path.resolve( + `${path.dirname(goodFile)}`, + "test-file-copy.js", + ); + + shell.cp(goodFile, goodFileCopy); + + doDelete(cacheFile); + + engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheFile, + cacheStrategy: "content", + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + extensions: ["js"], + }); + + engine.executeOnFiles([badFile, goodFileCopy]); + + let fileCache = fCache.createFromFile(cacheFile, true); + const entries = fileCache.normalizeEntries([ + badFile, + goodFileCopy, + ]); + + entries.forEach(entry => { + assert.isFalse( + entry.changed, + `the entry for ${entry.key} is initially unchanged`, + ); + }); + + // this should result in a changed entry + shell.sed("-i", "abc", "xzy", goodFileCopy); + fileCache = fCache.createFromFile(cacheFile, true); + assert.isFalse( + fileCache.getFileDescriptor(badFile).changed, + `the entry for ${badFile} is unchanged`, + ); + assert.isTrue( + fileCache.getFileDescriptor(goodFileCopy).changed, + `the entry for ${goodFileCopy} is changed`, + ); + }); + }); + }); + + describe("processors", () => { + it("should return two messages when executing with config file that specifies a processor", () => { + engine = cliEngineWithPlugins({ + configFile: getFixturePath( + "configurations", + "processors.json", + ), + useEslintrc: false, + extensions: ["js", "txt"], + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnFiles([ + fs.realpathSync( + getFixturePath( + "processors", + "test", + "test-processor.txt", + ), + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 2); + }); + it("should return two messages when executing with config file that specifies preloaded processor", () => { + engine = new CLIEngine( + { + useEslintrc: false, + plugins: ["test-processor"], + rules: { + "no-console": 2, + "no-unused-vars": 2, + }, + extensions: ["js", "txt"], + cwd: path.join(fixtureDir, ".."), + }, + { + preloadedPlugins: { + "test-processor": { + processors: { + ".txt": { + preprocess(text) { + return [text]; + }, + postprocess(messages) { + return messages[0]; + }, + }, + }, + }, + }, + }, + ); + + const report = engine.executeOnFiles([ + fs.realpathSync( + getFixturePath( + "processors", + "test", + "test-processor.txt", + ), + ), + ]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 2); + }); + it("should run processors when calling executeOnFiles with config file that specifies a processor", () => { + engine = cliEngineWithPlugins({ + configFile: getFixturePath( + "configurations", + "processors.json", + ), + useEslintrc: false, + extensions: ["js", "txt"], + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnFiles([ + getFixturePath("processors", "test", "test-processor.txt"), + ]); + + assert.strictEqual( + report.results[0].messages[0].message, + "'b' is defined but never used.", + ); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "post-processed", + ); + }); + it("should run processors when calling executeOnFiles with config file that specifies preloaded processor", () => { + engine = new CLIEngine( + { + useEslintrc: false, + plugins: ["test-processor"], + rules: { + "no-console": 2, + "no-unused-vars": 2, + }, + extensions: ["js", "txt"], + cwd: path.join(fixtureDir, ".."), + }, + { + preloadedPlugins: { + "test-processor": { + processors: { + ".txt": { + preprocess(text) { + return [text.replace("a()", "b()")]; + }, + postprocess(messages) { + messages[0][0].ruleId = + "post-processed"; + return messages[0]; + }, + }, + }, + }, + }, + }, + ); + + const report = engine.executeOnFiles([ + getFixturePath("processors", "test", "test-processor.txt"), + ]); + + assert.strictEqual( + report.results[0].messages[0].message, + "'b' is defined but never used.", + ); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "post-processed", + ); + }); + it("should run processors when calling executeOnText with config file that specifies a processor", () => { + engine = cliEngineWithPlugins({ + configFile: getFixturePath( + "configurations", + "processors.json", + ), + useEslintrc: false, + extensions: ["js", "txt"], + ignore: false, + }); + + const report = engine.executeOnText( + 'function a() {console.log("Test");}', + "tests/fixtures/processors/test/test-processor.txt", + ); + + assert.strictEqual( + report.results[0].messages[0].message, + "'b' is defined but never used.", + ); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "post-processed", + ); + }); + it("should run processors when calling executeOnText with config file that specifies preloaded processor", () => { + engine = new CLIEngine( + { + useEslintrc: false, + plugins: ["test-processor"], + rules: { + "no-console": 2, + "no-unused-vars": 2, + }, + extensions: ["js", "txt"], + ignore: false, + }, + { + preloadedPlugins: { + "test-processor": { + processors: { + ".txt": { + preprocess(text) { + return [text.replace("a()", "b()")]; + }, + postprocess(messages) { + messages[0][0].ruleId = + "post-processed"; + return messages[0]; + }, + }, + }, + }, + }, + }, + ); + + const report = engine.executeOnText( + 'function a() {console.log("Test");}', + "tests/fixtures/processors/test/test-processor.txt", + ); + + assert.strictEqual( + report.results[0].messages[0].message, + "'b' is defined but never used.", + ); + assert.strictEqual( + report.results[0].messages[0].ruleId, + "post-processed", + ); + }); + + describe("autofixing with processors", () => { + const HTML_PROCESSOR = Object.freeze({ + preprocess(text) { + return [ + text + .replace(/^", + "foo.html", + ); + + assert.strictEqual(report.results[0].messages.length, 0); + assert.strictEqual( + report.results[0].output, + "", + ); + }); + + it("should not run in autofix mode when using a processor that does not support autofixing", () => { + engine = new CLIEngine( + { + useEslintrc: false, + plugins: ["test-processor"], + rules: { + semi: 2, + }, + extensions: ["js", "txt"], + ignore: false, + fix: true, + }, + { + preloadedPlugins: { + "test-processor": { + processors: { + ".html": HTML_PROCESSOR, + }, + }, + }, + }, + ); + + const report = engine.executeOnText( + "", + "foo.html", + ); + + assert.strictEqual(report.results[0].messages.length, 1); + assert.isFalse(Object.hasOwn(report.results[0], "output")); + }); + + it("should not run in autofix mode when `fix: true` is not provided, even if the processor supports autofixing", () => { + engine = new CLIEngine( + { + useEslintrc: false, + plugins: ["test-processor"], + rules: { + semi: 2, + }, + extensions: ["js", "txt"], + ignore: false, + }, + { + preloadedPlugins: { + "test-processor": { + processors: { + ".html": Object.assign( + { supportsAutofix: true }, + HTML_PROCESSOR, + ), + }, + }, + }, + }, + ); + + const report = engine.executeOnText( + "", + "foo.html", + ); + + assert.strictEqual(report.results[0].messages.length, 1); + assert.isFalse(Object.hasOwn(report.results[0], "output")); + }); + }); + }); + + describe("Patterns which match no file should throw errors.", () => { + beforeEach(() => { + engine = new CLIEngine({ + cwd: getFixturePath("cli-engine"), + useEslintrc: false, + }); + }); + + it("one file", () => { + assert.throws(() => { + engine.executeOnFiles(["non-exist.js"]); + }, "No files matching 'non-exist.js' were found."); + }); + + it("should throw if the directory exists and is empty", () => { + assert.throws(() => { + engine.executeOnFiles(["empty"]); + }, "No files matching 'empty' were found."); + }); + + it("one glob pattern", () => { + assert.throws(() => { + engine.executeOnFiles(["non-exist/**/*.js"]); + }, "No files matching 'non-exist/**/*.js' were found."); + }); + + it("two files", () => { + assert.throws(() => { + engine.executeOnFiles(["aaa.js", "bbb.js"]); + }, "No files matching 'aaa.js' were found."); + }); + + it("a mix of an existing file and a non-existing file", () => { + assert.throws(() => { + engine.executeOnFiles(["console.js", "non-exist.js"]); + }, "No files matching 'non-exist.js' were found."); + }); + }); + + describe("overrides", () => { + beforeEach(() => { + engine = new CLIEngine({ + cwd: getFixturePath("cli-engine/overrides-with-dot"), + ignore: false, + }); + }); + + it("should recognize dotfiles", () => { + const ret = engine.executeOnFiles([".test-target.js"]); + + assert.strictEqual(ret.results.length, 1); + assert.strictEqual(ret.results[0].messages.length, 1); + assert.strictEqual( + ret.results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(ret.results[0].suppressedMessages.length, 0); + }); + }); + + describe("a config file setting should have higher priority than a shareable config file's settings always; https://github.com/eslint/eslint/issues/11510", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: path.join(os.tmpdir(), "cli-engine/11510"), + files: { + "no-console-error-in-overrides.json": { + overrides: [ + { + files: ["*.js"], + rules: { "no-console": "error" }, + }, + ], + }, + ".eslintrc.json": { + extends: "./no-console-error-in-overrides.json", + rules: { "no-console": "off" }, + }, + "a.js": "console.log();", + }, + }); + + beforeEach(() => { + engine = new CLIEngine({ + cwd: getPath(), + }); + + return prepare(); + }); + + afterEach(cleanup); + + it("should not report 'no-console' error.", () => { + const { results } = engine.executeOnFiles("a.js"); + + assert.strictEqual(results.length, 1); + assert.deepStrictEqual(results[0].messages, []); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + }); + + describe("configs of plugin rules should be validated even if 'plugins' key doesn't exist; https://github.com/eslint/eslint/issues/11559", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: path.join(os.tmpdir(), "cli-engine/11559"), + files: { + "node_modules/eslint-plugin-test/index.js": ` exports.configs = { recommended: { plugins: ["test"] } }; @@ -3369,41 +4183,39 @@ describe("CLIEngine", () => { } }; `, - ".eslintrc.json": { - - // Import via the recommended config. - extends: "plugin:test/recommended", - - // Has invalid option. - rules: { "test/foo": ["error", "invalid-option"] } - }, - "a.js": "console.log();" - } - }); - - beforeEach(() => { - engine = new CLIEngine({ - cwd: getPath() - }); - - return prepare(); - }); - - afterEach(cleanup); - - it("should throw fatal error.", () => { - assert.throws(() => { - engine.executeOnFiles("a.js"); - }, /invalid-option/u); - }); - }); - - describe("'--fix-type' should not crash even if plugin rules exist; https://github.com/eslint/eslint/issues/11586", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: path.join(os.tmpdir(), "cli-engine/11586"), - files: { - "node_modules/eslint-plugin-test/index.js": ` + ".eslintrc.json": { + // Import via the recommended config. + extends: "plugin:test/recommended", + + // Has invalid option. + rules: { "test/foo": ["error", "invalid-option"] }, + }, + "a.js": "console.log();", + }, + }); + + beforeEach(() => { + engine = new CLIEngine({ + cwd: getPath(), + }); + + return prepare(); + }); + + afterEach(cleanup); + + it("should throw fatal error.", () => { + assert.throws(() => { + engine.executeOnFiles("a.js"); + }, /invalid-option/u); + }); + }); + + describe("'--fix-type' should not crash even if plugin rules exist; https://github.com/eslint/eslint/issues/11586", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: path.join(os.tmpdir(), "cli-engine/11586"), + files: { + "node_modules/eslint-plugin-test/index.js": ` exports.rules = { "no-example": { meta: { type: "problem", fixable: "code" }, @@ -3423,45 +4235,49 @@ describe("CLIEngine", () => { } }; `, - ".eslintrc.json": { - plugins: ["test"], - rules: { "test/no-example": "error" } - }, - "a.js": "example;" - } - }); - - beforeEach(() => { - engine = new CLIEngine({ - cwd: getPath(), - fix: true, - fixTypes: ["problem"] - }); - - return prepare(); - }); - - afterEach(cleanup); - - - it("should not crash.", () => { - const { results } = engine.executeOnFiles("a.js"); - - assert.strictEqual(results.length, 1); - assert.deepStrictEqual(results[0].messages, []); - assert.deepStrictEqual(results[0].output, "fixed;"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - }); - - describe("multiple processors", () => { - const root = path.join(os.tmpdir(), "eslint/cli-engine/multiple-processors"); - const commonFiles = { - "node_modules/pattern-processor/index.js": fs.readFileSync( - require.resolve("../../fixtures/processors/pattern-processor"), - "utf8" - ), - "node_modules/eslint-plugin-markdown/index.js": ` + ".eslintrc.json": { + plugins: ["test"], + rules: { "test/no-example": "error" }, + }, + "a.js": "example;", + }, + }); + + beforeEach(() => { + engine = new CLIEngine({ + cwd: getPath(), + fix: true, + fixTypes: ["problem"], + }); + + return prepare(); + }); + + afterEach(cleanup); + + it("should not crash.", () => { + const { results } = engine.executeOnFiles("a.js"); + + assert.strictEqual(results.length, 1); + assert.deepStrictEqual(results[0].messages, []); + assert.deepStrictEqual(results[0].output, "fixed;"); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + }); + + describe("multiple processors", () => { + const root = path.join( + os.tmpdir(), + "eslint/cli-engine/multiple-processors", + ); + const commonFiles = { + "node_modules/pattern-processor/index.js": fs.readFileSync( + require.resolve( + "../../fixtures/processors/pattern-processor", + ), + "utf8", + ), + "node_modules/eslint-plugin-markdown/index.js": ` const { defineProcessor } = require("pattern-processor"); const processor = defineProcessor(${/```(\w+)\n([\s\S]+?)\n```/gu}); exports.processors = { @@ -3469,7 +4285,7 @@ describe("CLIEngine", () => { "non-fixable": processor }; `, - "node_modules/eslint-plugin-html/index.js": ` + "node_modules/eslint-plugin-html/index.js": ` const { defineProcessor } = require("pattern-processor"); const processor = defineProcessor(${/ \`\`\` - ` - }; - - let cleanup; - - beforeEach(() => { - cleanup = () => {}; - }); - - afterEach(() => cleanup()); - - it("should lint only JavaScript blocks if '--ext' was not given.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" } - } - } - }); - - cleanup = teardown.cleanup; - await teardown.prepare(); - engine = new CLIEngine({ cwd: teardown.getPath() }); - - const { results } = engine.executeOnFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].messages[0].line, 2); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should fix only JavaScript blocks if '--ext' was not given.", async () => { - - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" } - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - engine = new CLIEngine({ - cwd: teardown.getPath(), - fix: true - }); - - const { results } = engine.executeOnFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[0].output, unIndent` + `, + }; + + let cleanup; + + beforeEach(() => { + cleanup = () => {}; + }); + + afterEach(() => cleanup()); + + it("should lint only JavaScript blocks if '--ext' was not given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + }, + }, + }); + + cleanup = teardown.cleanup; + await teardown.prepare(); + engine = new CLIEngine({ cwd: teardown.getPath() }); + + const { results } = engine.executeOnFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should fix only JavaScript blocks if '--ext' was not given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + engine = new CLIEngine({ + cwd: teardown.getPath(), + fix: true, + }); + + const { results } = engine.executeOnFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + results[0].output, + unIndent` \`\`\`js - console.log("hello");${/* ← fixed */""} + console.log("hello");${/* ← fixed */ ""} \`\`\` \`\`\`html
Hello
\`\`\` - `); - }); - - it("should lint HTML blocks as well with multiple processors if '--ext' option was given.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" } - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - engine = new CLIEngine({ - cwd: teardown.getPath(), - extensions: ["js", "html"] - }); - - const { results } = engine.executeOnFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block - assert.strictEqual(results[0].messages[0].line, 2); - assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block - assert.strictEqual(results[0].messages[1].line, 7); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should fix HTML blocks as well with multiple processors if '--ext' option was given.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" } - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - engine = new CLIEngine({ - cwd: teardown.getPath(), - extensions: ["js", "html"], - fix: true - }); - - const { results } = engine.executeOnFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[0].output, unIndent` + `, + ); + }); + + it("should lint HTML blocks as well with multiple processors if '--ext' option was given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + engine = new CLIEngine({ + cwd: teardown.getPath(), + extensions: ["js", "html"], + }); + + const { results } = engine.executeOnFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block + assert.strictEqual(results[0].messages[1].line, 7); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should fix HTML blocks as well with multiple processors if '--ext' option was given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + engine = new CLIEngine({ + cwd: teardown.getPath(), + extensions: ["js", "html"], + fix: true, + }); + + const { results } = engine.executeOnFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + results[0].output, + unIndent` \`\`\`js - console.log("hello");${/* ← fixed */""} + console.log("hello");${/* ← fixed */ ""} \`\`\` \`\`\`html
Hello
\`\`\` - `); - }); - - it("should use overridden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", async () => { - - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" }, - overrides: [ - { - files: "*.html", - processor: "html/non-fixable" // supportsAutofix: false - } - ] - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - engine = new CLIEngine({ - cwd: teardown.getPath(), - extensions: ["js", "html"], - fix: true - }); - - const { results } = engine.executeOnFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS Block in HTML Block - assert.strictEqual(results[0].messages[0].line, 7); - assert.strictEqual(results[0].messages[0].fix, void 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[0].output, unIndent` + `, + ); + }); + + it("should use overridden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + overrides: [ + { + files: "*.html", + processor: "html/non-fixable", // supportsAutofix: false + }, + ], + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + engine = new CLIEngine({ + cwd: teardown.getPath(), + extensions: ["js", "html"], + fix: true, + }); + + const { results } = engine.executeOnFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS Block in HTML Block + assert.strictEqual(results[0].messages[0].line, 7); + assert.strictEqual(results[0].messages[0].fix, void 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + results[0].output, + unIndent` \`\`\`js - console.log("hello");${/* ← fixed */""} + console.log("hello");${/* ← fixed */ ""} \`\`\` \`\`\`html
Hello
\`\`\` - `); - }); - - it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", async () => { - - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" }, - overrides: [ - { - files: "*.html", - - // this rules are not used because ESLint re-resolve configs if a code block had a different file extension. - rules: { - semi: "error", - "no-console": "off" - } - }, - { - files: "**/*.html/*.js", - rules: { - semi: "off", - "no-console": "error" - } - } - ] - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - engine = new CLIEngine({ - cwd: teardown.getPath(), - extensions: ["js", "html"] - }); - - const { results } = engine.executeOnFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].messages[0].line, 2); - assert.strictEqual(results[0].messages[1].ruleId, "no-console"); - assert.strictEqual(results[0].messages[1].line, 7); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should use the same config as one which has 'processor' property in order to lint blocks in HTML if the processor was legacy style.", async () => { - - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" }, - overrides: [ - { - files: "*.html", - processor: "html/legacy", // this processor returns strings rather than `{text, filename}` - rules: { - semi: "off", - "no-console": "error" - } - }, - { - files: "**/*.html/*.js", - rules: { - semi: "error", - "no-console": "off" - } - } - ] - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - engine = new CLIEngine({ - cwd: teardown.getPath(), - extensions: ["js", "html"] - }); - - const { results } = engine.executeOnFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 3); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].messages[0].line, 2); - assert.strictEqual(results[0].messages[1].ruleId, "no-console"); - assert.strictEqual(results[0].messages[1].line, 7); - assert.strictEqual(results[0].messages[2].ruleId, "no-console"); - assert.strictEqual(results[0].messages[2].line, 10); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should throw an error if invalid processor was specified.", async () => { - - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - processor: "markdown/unknown" - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - engine = new CLIEngine({ - cwd: teardown.getPath() - }); - - assert.throws(() => { - engine.executeOnFiles(["test.md"]); - }, /ESLint configuration of processor in '\.eslintrc\.json' is invalid: 'markdown\/unknown' was not found\./u); - }); - - it("should lint HTML blocks as well with multiple processors if 'overrides[].files' is present.", async () => { - - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" }, - overrides: [ - { - files: "*.html", - processor: "html/.html" + `, + ); + }); + + it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + overrides: [ + { + files: "*.html", + + // this rules are not used because ESLint re-resolve configs if a code block had a different file extension. + rules: { + semi: "error", + "no-console": "off", + }, + }, + { + files: "**/*.html/*.js", + rules: { + semi: "off", + "no-console": "error", + }, + }, + ], + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + engine = new CLIEngine({ + cwd: teardown.getPath(), + extensions: ["js", "html"], + }); + + const { results } = engine.executeOnFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-console"); + assert.strictEqual(results[0].messages[1].line, 7); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should use the same config as one which has 'processor' property in order to lint blocks in HTML if the processor was legacy style.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + overrides: [ + { + files: "*.html", + processor: "html/legacy", // this processor returns strings rather than `{text, filename}` + rules: { + semi: "off", + "no-console": "error", + }, + }, + { + files: "**/*.html/*.js", + rules: { + semi: "error", + "no-console": "off", + }, + }, + ], + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + engine = new CLIEngine({ + cwd: teardown.getPath(), + extensions: ["js", "html"], + }); + + const { results } = engine.executeOnFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 3); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-console"); + assert.strictEqual(results[0].messages[1].line, 7); + assert.strictEqual(results[0].messages[2].ruleId, "no-console"); + assert.strictEqual(results[0].messages[2].line, 10); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should throw an error if invalid processor was specified.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + processor: "markdown/unknown", + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + engine = new CLIEngine({ + cwd: teardown.getPath(), + }); + + assert.throws(() => { + engine.executeOnFiles(["test.md"]); + }, /ESLint configuration of processor in '\.eslintrc\.json' is invalid: 'markdown\/unknown' was not found\./u); + }); + + it("should lint HTML blocks as well with multiple processors if 'overrides[].files' is present.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + overrides: [ + { + files: "*.html", + processor: "html/.html", + }, + { + files: "*.md", + processor: "markdown/.md", + }, + ], + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + engine = new CLIEngine({ + cwd: teardown.getPath(), + }); + + const { results } = engine.executeOnFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block + assert.strictEqual(results[0].messages[1].line, 7); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + }); + + describe("MODULE_NOT_FOUND error handling", () => { + const cwd = getFixturePath("module-not-found"); + + beforeEach(() => { + engine = new CLIEngine({ cwd }); + }); + + it("should throw an error with a message template when 'extends' property has a non-existence JavaScript config.", () => { + try { + engine.executeOnText("test", "extends-js/test.js"); + } catch (err) { + assert.strictEqual( + err.messageTemplate, + "extend-config-missing", + ); + assert.deepStrictEqual(err.messageData, { + configName: "nonexistent-config", + importerName: getFixturePath( + "module-not-found", + "extends-js", + ".eslintrc.yml", + ), + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with a message template when 'extends' property has a non-existence plugin config.", () => { + try { + engine.executeOnText("test", "extends-plugin/test.js"); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, "plugin-missing"); + assert.deepStrictEqual(err.messageData, { + importerName: `extends-plugin${path.sep}.eslintrc.yml`, + pluginName: "eslint-plugin-nonexistent-plugin", + resolvePluginsRelativeTo: path.join( + cwd, + "extends-plugin", + ), // the directory of the config file. + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with a message template when 'plugins' property has a non-existence plugin.", () => { + try { + engine.executeOnText("test", "plugins/test.js"); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, "plugin-missing"); + assert.deepStrictEqual(err.messageData, { + importerName: `plugins${path.sep}.eslintrc.yml`, + pluginName: "eslint-plugin-nonexistent-plugin", + resolvePluginsRelativeTo: path.join(cwd, "plugins"), // the directory of the config file. + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when a JavaScript config threw a 'MODULE_NOT_FOUND' error.", () => { + try { + engine.executeOnText( + "test", + "throw-in-config-itself/test.js", + ); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'extends' property has a JavaScript config that throws a 'MODULE_NOT_FOUND' error.", () => { + try { + engine.executeOnText("test", "throw-in-extends-js/test.js"); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'extends' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", () => { + try { + engine.executeOnText( + "test", + "throw-in-extends-plugin/test.js", + ); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'plugins' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", () => { + try { + engine.executeOnText("test", "throw-in-plugins/test.js"); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + }); + + describe("with '--rulesdir' option", () => { + const rootPath = getFixturePath("cli-engine/with-rulesdir"); + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: rootPath, + files: { + "internal-rules/test.js": ` + module.exports = { + create(context) { + return { + ExpressionStatement(node) { + context.report({ node, message: "ok" }); + }, + }; }, - { - files: "*.md", - processor: "markdown/.md" - } - ] - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - engine = new CLIEngine({ - cwd: teardown.getPath() - }); - - const { results } = engine.executeOnFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block - assert.strictEqual(results[0].messages[0].line, 2); - assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block - assert.strictEqual(results[0].messages[1].line, 7); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - }); - - describe("MODULE_NOT_FOUND error handling", () => { - const cwd = getFixturePath("module-not-found"); - - beforeEach(() => { - engine = new CLIEngine({ cwd }); - }); - - it("should throw an error with a message template when 'extends' property has a non-existence JavaScript config.", () => { - try { - engine.executeOnText("test", "extends-js/test.js"); - } catch (err) { - assert.strictEqual(err.messageTemplate, "extend-config-missing"); - assert.deepStrictEqual(err.messageData, { - configName: "nonexistent-config", - importerName: getFixturePath("module-not-found", "extends-js", ".eslintrc.yml") - }); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with a message template when 'extends' property has a non-existence plugin config.", () => { - try { - engine.executeOnText("test", "extends-plugin/test.js"); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, "plugin-missing"); - assert.deepStrictEqual(err.messageData, { - importerName: `extends-plugin${path.sep}.eslintrc.yml`, - pluginName: "eslint-plugin-nonexistent-plugin", - resolvePluginsRelativeTo: path.join(cwd, "extends-plugin") // the directory of the config file. - }); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with a message template when 'plugins' property has a non-existence plugin.", () => { - try { - engine.executeOnText("test", "plugins/test.js"); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, "plugin-missing"); - assert.deepStrictEqual(err.messageData, { - importerName: `plugins${path.sep}.eslintrc.yml`, - pluginName: "eslint-plugin-nonexistent-plugin", - resolvePluginsRelativeTo: path.join(cwd, "plugins") // the directory of the config file. - }); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with no message template when a JavaScript config threw a 'MODULE_NOT_FOUND' error.", () => { - try { - engine.executeOnText("test", "throw-in-config-itself/test.js"); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, void 0); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with no message template when 'extends' property has a JavaScript config that throws a 'MODULE_NOT_FOUND' error.", () => { - try { - engine.executeOnText("test", "throw-in-extends-js/test.js"); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, void 0); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with no message template when 'extends' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", () => { - try { - engine.executeOnText("test", "throw-in-extends-plugin/test.js"); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, void 0); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with no message template when 'plugins' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", () => { - try { - engine.executeOnText("test", "throw-in-plugins/test.js"); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, void 0); - return; - } - assert.fail("Expected to throw an error"); - }); - }); - - describe("with '--rulesdir' option", () => { - - const rootPath = getFixturePath("cli-engine/with-rulesdir"); - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: rootPath, - files: { - "internal-rules/test.js": ` - module.exports = context => ({ - ExpressionStatement(node) { - context.report({ node, message: "ok" }) - } - }) + }; `, - ".eslintrc.json": { - root: true, - rules: { test: "error" } - }, - "test.js": "console.log('hello')" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - - it("should use the configured rules which are defined by '--rulesdir' option.", () => { - - engine = new CLIEngine({ - cwd: getPath(), - rulePaths: ["internal-rules"] - }); - const report = engine.executeOnFiles(["test.js"]); - - assert.strictEqual(report.results.length, 1); - assert.strictEqual(report.results[0].messages.length, 1); - assert.strictEqual(report.results[0].messages[0].message, "ok"); - assert.strictEqual(report.results[0].suppressedMessages.length, 0); - }); - }); - - describe("glob pattern '[ab].js'", () => { - const root = getFixturePath("cli-engine/unmatched-glob"); - - let cleanup; - - beforeEach(() => { - cleanup = () => { }; - }); - - afterEach(() => cleanup()); - - it("should match '[ab].js' if existed.", async () => { - - const teardown = createCustomTeardown({ - cwd: root, - files: { - "a.js": "", - "b.js": "", - "ab.js": "", - "[ab].js": "", - ".eslintrc.yml": "root: true" - } - }); - - engine = new CLIEngine({ cwd: teardown.getPath() }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - const { results } = engine.executeOnFiles(["[ab].js"]); - const filenames = results.map(r => path.basename(r.filePath)); - - assert.deepStrictEqual(filenames, ["[ab].js"]); - }); - - it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", async () => { - - const teardown = createCustomTeardown({ - cwd: root, - files: { - "a.js": "", - "b.js": "", - "ab.js": "", - ".eslintrc.yml": "root: true" - } - }); - - engine = new CLIEngine({ cwd: teardown.getPath() }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - const { results } = engine.executeOnFiles(["[ab].js"]); - const filenames = results.map(r => path.basename(r.filePath)); - - assert.deepStrictEqual(filenames, ["a.js", "b.js"]); - }); - }); - - describe("with 'noInlineConfig' setting", () => { - const root = getFixturePath("cli-engine/noInlineConfig"); - - let cleanup; - - beforeEach(() => { - cleanup = () => { }; - }); - - afterEach(() => cleanup()); - - it("should warn directive comments if 'noInlineConfig' was given.", async () => { - - const teardown = createCustomTeardown({ - cwd: root, - files: { - "test.js": "/* globals foo */", - ".eslintrc.yml": "noInlineConfig: true" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - engine = new CLIEngine({ cwd: teardown.getPath() }); - - const { results } = engine.executeOnFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml)."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should show the config file what the 'noInlineConfig' came from.", async () => { - - const teardown = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-config-foo/index.js": "module.exports = {noInlineConfig: true}", - "test.js": "/* globals foo */", - ".eslintrc.yml": "extends: foo" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - engine = new CLIEngine({ cwd: teardown.getPath() }); - - const { results } = engine.executeOnFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml Âģ eslint-config-foo)."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - }); - - describe("with 'reportUnusedDisableDirectives' setting", () => { - const root = getFixturePath("cli-engine/reportUnusedDisableDirectives"); - - let cleanup; - - beforeEach(() => { - cleanup = () => { }; - }); - - afterEach(() => cleanup()); - - it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives' was given.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "test.js": "/* eslint-disable eqeqeq */", - ".eslintrc.yml": "reportUnusedDisableDirectives: true" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - engine = new CLIEngine({ cwd: teardown.getPath() }); - - const { results } = engine.executeOnFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - describe("the runtime option overrides config files.", () => { - it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "test.js": "/* eslint-disable eqeqeq */", - ".eslintrc.yml": "reportUnusedDisableDirectives: true" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - engine = new CLIEngine({ - cwd: teardown.getPath(), - reportUnusedDisableDirectives: "off" - }); - - const { results } = engine.executeOnFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "test.js": "/* eslint-disable eqeqeq */", - ".eslintrc.yml": "reportUnusedDisableDirectives: true" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - engine = new CLIEngine({ - cwd: teardown.getPath(), - reportUnusedDisableDirectives: "error" - }); - - const { results } = engine.executeOnFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - }); - }); - - describe("with 'overrides[*].extends' setting on deep locations", () => { - const root = getFixturePath("cli-engine/deeply-overrides-i-extends"); - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - overrides: [{ files: ["*test*"], extends: "two" }] - })}`, - "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({ - overrides: [{ files: ["*.js"], extends: "three" }] - })}`, - "node_modules/eslint-config-three/index.js": `module.exports = ${JSON.stringify({ - rules: { "no-console": "error" } - })}`, - "test.js": "console.log('hello')", - ".eslintrc.yml": "extends: one" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("should not throw.", () => { - engine = new CLIEngine({ cwd: getPath() }); - - const { results } = engine.executeOnFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - }); - - describe("don't ignore the entry directory.", () => { - const root = getFixturePath("cli-engine/dont-ignore-entry-dir"); - - let cleanup; - - beforeEach(() => { - cleanup = () => {}; - }); - - afterEach(async () => { - await cleanup(); - - const configFilePath = path.resolve(root, "../.eslintrc.json"); - - if (shell.test("-e", configFilePath)) { - shell.rm(configFilePath); - } - }); - - it("'executeOnFiles(\".\")' should not load config files from outside of \".\".", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "../.eslintrc.json": "BROKEN FILE", - ".eslintrc.json": JSON.stringify({ root: true }), - "index.js": "console.log(\"hello\")" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - engine = new CLIEngine({ cwd: teardown.getPath() }); - - // Don't throw "failed to load config file" error. - engine.executeOnFiles("."); - }); - - it("'executeOnFiles(\".\")' should not ignore '.' even if 'ignorePatterns' contains it.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "../.eslintrc.json": { ignorePatterns: ["/dont-ignore-entry-dir"] }, - ".eslintrc.json": { root: true }, - "index.js": "console.log(\"hello\")" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - engine = new CLIEngine({ cwd: teardown.getPath() }); - - - // Don't throw "file not found" error. - engine.executeOnFiles("."); - }); - - it("'executeOnFiles(\"subdir\")' should not ignore './subdir' even if 'ignorePatterns' contains it.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": { ignorePatterns: ["/subdir"] }, - "subdir/.eslintrc.json": { root: true }, - "subdir/index.js": "console.log(\"hello\")" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - engine = new CLIEngine({ cwd: teardown.getPath() }); - - // Don't throw "file not found" error. - engine.executeOnFiles("subdir"); - }); - }); - }); - - describe("getConfigForFile", () => { - - it("should return the info from Config#getConfig when called", () => { - const options = { - configFile: getFixturePath("configurations", "quotes-error.json") - }; - const engine = new CLIEngine(options); - const filePath = getFixturePath("single-quoted.js"); - - const actualConfig = engine.getConfigForFile(filePath); - const expectedConfig = new CascadingConfigArrayFactory({ specificConfigPath: options.configFile }) - .getConfigArrayForFile(filePath) - .extractConfig(filePath) - .toCompatibleObjectAsConfigFileContent(); - - assert.deepStrictEqual(actualConfig, expectedConfig); - }); - - - it("should return the config when run from within a subdir", () => { - const options = { - cwd: getFixturePath("config-hierarchy", "root-true", "parent", "root", "subdir") - }; - const engine = new CLIEngine(options); - const filePath = getFixturePath("config-hierarchy", "root-true", "parent", "root", ".eslintrc"); - - const actualConfig = engine.getConfigForFile("./.eslintrc"); - const expectedConfig = new CascadingConfigArrayFactory(options) - .getConfigArrayForFile(filePath) - .extractConfig(filePath) - .toCompatibleObjectAsConfigFileContent(); - - assert.deepStrictEqual(actualConfig, expectedConfig); - }); - - it("should throw an error if a directory path was given.", () => { - const engine = new CLIEngine(); - - try { - engine.getConfigForFile("."); - } catch (error) { - assert.strictEqual(error.messageTemplate, "print-config-with-directory-path"); - return; - } - assert.fail("should throw an error"); - }); - }); - - describe("isPathIgnored", () => { - it("should check if the given path is ignored", () => { - const engine = new CLIEngine({ - ignorePath: getFixturePath(".eslintignore2"), - cwd: getFixturePath() - }); - - assert.isTrue(engine.isPathIgnored("undef.js")); - assert.isFalse(engine.isPathIgnored("passing.js")); - }); - - it("should return false if ignoring is disabled", () => { - const engine = new CLIEngine({ - ignore: false, - ignorePath: getFixturePath(".eslintignore2"), - cwd: getFixturePath() - }); - - assert.isFalse(engine.isPathIgnored("undef.js")); - }); - - // https://github.com/eslint/eslint/issues/5547 - it("should return true for default ignores even if ignoring is disabled", () => { - const engine = new CLIEngine({ - ignore: false, - cwd: getFixturePath("cli-engine") - }); - - assert.isTrue(engine.isPathIgnored("node_modules/foo.js")); - }); - - describe("about the default ignore patterns", () => { - it("should always apply defaultPatterns if ignore option is true", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); - }); - - it("should still apply defaultPatterns if ignore option is is false", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ ignore: false, cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); - }); - - it("should allow subfolders of defaultPatterns to be unignored by ignorePattern", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ cwd, ignorePattern: "!/node_modules/package" }); - - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); - }); - - it("should allow subfolders of defaultPatterns to be unignored by ignorePath", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ cwd, ignorePath: getFixturePath("ignored-paths", ".eslintignoreWithUnignoredDefaults") }); - - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); - }); - - it("should ignore dotfiles", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", ".foo"))); - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar"))); - }); - - it("should ignore directories beginning with a dot", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", ".foo/bar"))); - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar/baz"))); - }); - - it("should still ignore dotfiles when ignore option disabled", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ ignore: false, cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", ".foo"))); - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar"))); - }); - - it("should still ignore directories beginning with a dot when ignore option disabled", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ ignore: false, cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", ".foo/bar"))); - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar/baz"))); - }); - - it("should not ignore absolute paths containing '..'", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ cwd }); - - assert(!engine.isPathIgnored(`${getFixturePath("ignored-paths", "foo")}/../unignored.js`)); - }); - - it("should ignore /node_modules/ relative to .eslintignore when loaded", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ ignorePath: getFixturePath("ignored-paths", ".eslintignore"), cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "existing.js"))); - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo", "node_modules", "existing.js"))); - }); - - it("should ignore /node_modules/ relative to cwd without an .eslintignore", () => { - const cwd = getFixturePath("ignored-paths", "no-ignore-file"); - const engine = new CLIEngine({ cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "node_modules", "existing.js"))); - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "foo", "node_modules", "existing.js"))); - }); - }); - - describe("with no .eslintignore file", () => { - it("should not travel to parent directories to find .eslintignore when it's missing and cwd is provided", () => { - const cwd = getFixturePath("ignored-paths", "configurations"); - const engine = new CLIEngine({ cwd }); - - // an .eslintignore in parent directories includes `*.js`, but don't load it. - assert(!engine.isPathIgnored("foo.js")); - assert(engine.isPathIgnored("node_modules/foo.js")); - }); - - it("should return false for files outside of the cwd (with no ignore file provided)", () => { - - // Default ignore patterns should not inadvertently ignore files in parent directories - const engine = new CLIEngine({ cwd: getFixturePath("ignored-paths", "no-ignore-file") }); - - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - }); - - describe("with .eslintignore file or package.json file", () => { - it("should load .eslintignore from cwd when explicitly passed", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ cwd }); - - // `${cwd}/.eslintignore` includes `sampleignorepattern`. - assert(engine.isPathIgnored("sampleignorepattern")); - }); - - it("should use package.json's eslintIgnore files if no specified .eslintignore file", () => { - const cwd = getFixturePath("ignored-paths", "package-json-ignore"); - const engine = new CLIEngine({ cwd }); - - assert(engine.isPathIgnored("hello.js")); - assert(engine.isPathIgnored("world.js")); - }); - - it("should use correct message template if failed to parse package.json", () => { - const cwd = getFixturePath("ignored-paths", "broken-package-json"); - - assert.throw(() => { - try { - // eslint-disable-next-line no-new -- Check for throwing - new CLIEngine({ cwd }); - } catch (error) { - assert.strictEqual(error.messageTemplate, "failed-to-read-json"); - throw error; - } - }); - }); - - it("should not use package.json's eslintIgnore files if specified .eslintignore file", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ cwd }); - - /* - * package.json includes `hello.js` and `world.js`. - * .eslintignore includes `sampleignorepattern`. - */ - assert(!engine.isPathIgnored("hello.js")); - assert(!engine.isPathIgnored("world.js")); - assert(engine.isPathIgnored("sampleignorepattern")); - }); - - it("should error if package.json's eslintIgnore is not an array of file paths", () => { - const cwd = getFixturePath("ignored-paths", "bad-package-json-ignore"); - - assert.throws(() => { - // eslint-disable-next-line no-new -- Check for throwing - new CLIEngine({ cwd }); - }, "Package.json eslintIgnore property requires an array of paths"); - }); - }); - - describe("with --ignore-pattern option", () => { - it("should accept a string for options.ignorePattern", () => { - const cwd = getFixturePath("ignored-paths", "ignore-pattern"); - const engine = new CLIEngine({ - ignorePattern: "ignore-me.txt", - cwd - }); - - assert(engine.isPathIgnored("ignore-me.txt")); - }); - - it("should accept an array for options.ignorePattern", () => { - const engine = new CLIEngine({ - ignorePattern: ["a", "b"], - useEslintrc: false - }); - - assert(engine.isPathIgnored("a")); - assert(engine.isPathIgnored("b")); - assert(!engine.isPathIgnored("c")); - }); - - it("should return true for files which match an ignorePattern even if they do not exist on the filesystem", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ - ignorePattern: "not-a-file", - cwd - }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "not-a-file"))); - }); - - it("should return true for file matching an ignore pattern exactly", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ ignorePattern: "undef.js", cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - - it("should return false for file matching an invalid ignore pattern with leading './'", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ ignorePattern: "./undef.js", cwd }); - - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - - it("should return false for file in subfolder of cwd matching an ignore pattern with leading '/'", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ ignorePattern: "/undef.js", cwd }); - - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "subdir", "undef.js"))); - }); - - it("should return true for file matching a child of an ignore pattern", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ ignorePattern: "ignore-pattern", cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "ignore-me.txt"))); - }); - - it("should return true for file matching a grandchild of an ignore pattern", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ ignorePattern: "ignore-pattern", cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "subdir", "ignore-me.txt"))); - }); - - it("should return false for file not matching any ignore pattern", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ ignorePattern: "failing.js", cwd }); - - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "unignored.js"))); - }); - - it("two globstar '**' ignore pattern should ignore files in nested directories", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ ignorePattern: "**/*.js", cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js"))); - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.js"))); - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.js"))); - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "foo.j2"))); - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.j2"))); - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.j2"))); - }); - }); - - describe("with --ignore-path option", () => { - it("should load empty array with ignorePath set to false", () => { - const cwd = getFixturePath("ignored-paths", "no-ignore-file"); - const engine = new CLIEngine({ ignorePath: false, cwd }); - - // an .eslintignore in parent directories includes `*.js`, but don't load it. - assert(!engine.isPathIgnored("foo.js")); - assert(engine.isPathIgnored("node_modules/foo.js")); - }); - - it("initialization with ignorePath should work when cwd is a parent directory", () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new CLIEngine({ ignorePath, cwd }); - - assert(engine.isPathIgnored("custom-name/foo.js")); - }); - - it("initialization with ignorePath should work when the file is in the cwd", () => { - const cwd = getFixturePath("ignored-paths", "custom-name"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new CLIEngine({ ignorePath, cwd }); - - assert(engine.isPathIgnored("foo.js")); - }); - - it("initialization with ignorePath should work when cwd is a subdirectory", () => { - const cwd = getFixturePath("ignored-paths", "custom-name", "subdirectory"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new CLIEngine({ ignorePath, cwd }); - - assert(engine.isPathIgnored("../custom-name/foo.js")); - }); - - it("initialization with invalid file should throw error", () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "not-a-directory", ".foobaz"); - - assert.throws(() => { - // eslint-disable-next-line no-new -- Check for throwing - new CLIEngine({ ignorePath, cwd }); - }, "Cannot read .eslintignore file"); - }); - - it("should return false for files outside of ignorePath's directory", () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new CLIEngine({ ignorePath, cwd }); - - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - - it("should resolve relative paths from CWD", () => { - const cwd = getFixturePath("ignored-paths", "subdir"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreForDifferentCwd"); - const engine = new CLIEngine({ ignorePath, cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/undef.js"))); - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - - it("should resolve relative paths from CWD when it's in a child directory", () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new CLIEngine({ ignorePath, cwd }); - - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/undef.js"))); - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js"))); - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/foo.js"))); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/bar.js"))); - }); - - it("should resolve relative paths from CWD when it contains negated globs", () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new CLIEngine({ ignorePath, cwd }); - - assert(engine.isPathIgnored("subdir/blah.txt")); - assert(engine.isPathIgnored("blah.txt")); - assert(engine.isPathIgnored("subdir/bar.txt")); - assert(!engine.isPathIgnored("bar.txt")); - assert(!engine.isPathIgnored("subdir/baz.txt")); - assert(!engine.isPathIgnored("baz.txt")); - }); - - it("should resolve default ignore patterns from the CWD even when the ignorePath is in a subdirectory", () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new CLIEngine({ ignorePath, cwd }); - - assert(engine.isPathIgnored("node_modules/blah.js")); - }); - - it("should resolve default ignore patterns from the CWD even when the ignorePath is in a parent directory", () => { - const cwd = getFixturePath("ignored-paths", "subdir"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreForDifferentCwd"); - const engine = new CLIEngine({ ignorePath, cwd }); - - assert(engine.isPathIgnored("node_modules/blah.js")); - }); - - it("should handle .eslintignore which contains CRLF correctly.", () => { - const ignoreFileContent = fs.readFileSync(getFixturePath("ignored-paths", "crlf/.eslintignore"), "utf8"); - - assert(ignoreFileContent.includes("\r"), "crlf/.eslintignore should contains CR."); - - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "crlf/.eslintignore"); - const engine = new CLIEngine({ ignorePath, cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide1/a.js"))); - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide2/a.js"))); - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide3/a.js"))); - }); - - it("should not include comments in ignore rules", () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithComments"); - const engine = new CLIEngine({ ignorePath, cwd }); - - assert(!engine.isPathIgnored("# should be ignored")); - assert(engine.isPathIgnored("this_one_not")); - }); - - it("should ignore a non-negated pattern", () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithNegation"); - const engine = new CLIEngine({ ignorePath, cwd }); - - assert(engine.isPathIgnored(getFixturePath("ignored-paths", "negation", "ignore.js"))); - }); - - it("should not ignore a negated pattern", () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithNegation"); - const engine = new CLIEngine({ ignorePath, cwd }); - - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "negation", "unignore.js"))); - }); - }); - - describe("with --ignore-path option and --ignore-pattern option", () => { - it("should return false for ignored file when unignored with ignore pattern", () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new CLIEngine({ - ignorePath: getFixturePath("ignored-paths", ".eslintignore"), - ignorePattern: "!sampleignorepattern", - cwd - }); - - assert(!engine.isPathIgnored(getFixturePath("ignored-paths", "sampleignorepattern"))); - }); - }); - }); - - describe("getFormatter()", () => { - - it("should return a function when a bundled formatter is requested", () => { - const engine = new CLIEngine(), - formatter = engine.getFormatter("compact"); - - assert.isFunction(formatter); - }); - - it("should return a function when no argument is passed", () => { - const engine = new CLIEngine(), - formatter = engine.getFormatter(); - - assert.isFunction(formatter); - }); - - it("should return a function when a custom formatter is requested", () => { - const engine = new CLIEngine(), - formatter = engine.getFormatter(getFixturePath("formatters", "simple.js")); - - assert.isFunction(formatter); - }); - - it("should return a function when a custom formatter is requested, also if the path has backslashes", () => { - const engine = new CLIEngine({ - cwd: path.join(fixtureDir, "..") - }), - formatter = engine.getFormatter(".\\fixtures\\formatters\\simple.js"); - - assert.isFunction(formatter); - }); - - it("should return a function when a formatter prefixed with eslint-formatter is requested", () => { - const engine = new CLIEngine({ - cwd: getFixturePath("cli-engine") - }), - formatter = engine.getFormatter("bar"); - - assert.isFunction(formatter); - }); - - it("should return a function when a formatter is requested, also when the eslint-formatter prefix is included in the format argument", () => { - const engine = new CLIEngine({ - cwd: getFixturePath("cli-engine") - }), - formatter = engine.getFormatter("eslint-formatter-bar"); - - assert.isFunction(formatter); - }); - - it("should return a function when a formatter is requested within a scoped npm package", () => { - const engine = new CLIEngine({ - cwd: getFixturePath("cli-engine") - }), - formatter = engine.getFormatter("@somenamespace/foo"); - - assert.isFunction(formatter); - }); - - it("should return a function when a formatter is requested within a scoped npm package, also when the eslint-formatter prefix is included in the format argument", () => { - const engine = new CLIEngine({ - cwd: getFixturePath("cli-engine") - }), - formatter = engine.getFormatter("@somenamespace/eslint-formatter-foo"); - - assert.isFunction(formatter); - }); - - it("should return null when a custom formatter doesn't exist", () => { - const engine = new CLIEngine(), - formatterPath = getFixturePath("formatters", "doesntexist.js"), - fullFormatterPath = path.resolve(formatterPath); - - assert.throws(() => { - engine.getFormatter(formatterPath); - }, `There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`); - }); - - it("should return null when a built-in formatter doesn't exist", () => { - const engine = new CLIEngine(); - const fullFormatterPath = path.resolve(__dirname, "../../../lib/cli-engine/formatters/special"); - - assert.throws(() => { - engine.getFormatter("special"); - }, `There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`); - }); - - it("should throw when a built-in formatter no longer exists", () => { - const engine = new CLIEngine(); - - assert.throws(() => { - engine.getFormatter("table"); - }, "The table formatter is no longer part of core ESLint. Install it manually with `npm install -D eslint-formatter-table`"); - - assert.throws(() => { - engine.getFormatter("codeframe"); - }, "The codeframe formatter is no longer part of core ESLint. Install it manually with `npm install -D eslint-formatter-codeframe`"); - }); - - it("should throw if the required formatter exists but has an error", () => { - const engine = new CLIEngine(), - formatterPath = getFixturePath("formatters", "broken.js"); - - assert.throws(() => { - engine.getFormatter(formatterPath); - }, `There was a problem loading formatter: ${formatterPath}\nError: Cannot find module 'this-module-does-not-exist'`); - }); - - it("should return null when a non-string formatter name is passed", () => { - const engine = new CLIEngine(), - formatter = engine.getFormatter(5); - - assert.isNull(formatter); - }); - - it("should return a function when called as a static function on CLIEngine", () => { - const formatter = CLIEngine.getFormatter(); - - assert.isFunction(formatter); - }); - - it("should return a function when called as a static function on CLIEngine and a custom formatter is requested", () => { - const formatter = CLIEngine.getFormatter(getFixturePath("formatters", "simple.js")); - - assert.isFunction(formatter); - }); - - }); - - describe("getErrorResults()", () => { - it("should report 5 error messages when looking for errors only", () => { - - process.chdir(originalDir); - const engine = new CLIEngine({ - useEslintrc: false, - baseConfig: { - rules: { - quotes: 2, - "no-var": 2, - "eol-last": 2, - strict: [2, "global"], - "no-unused-vars": 2 - }, - env: { - node: true - } - } - }); - - const report = engine.executeOnText("var foo = 'bar';"); - const errorResults = CLIEngine.getErrorResults(report.results); - - assert.lengthOf(errorResults[0].messages, 5); - assert.strictEqual(errorResults[0].errorCount, 5); - assert.strictEqual(errorResults[0].fixableErrorCount, 3); - assert.strictEqual(errorResults[0].fixableWarningCount, 0); - assert.strictEqual(errorResults[0].messages[0].ruleId, "strict"); - assert.strictEqual(errorResults[0].messages[0].severity, 2); - assert.strictEqual(errorResults[0].messages[1].ruleId, "no-var"); - assert.strictEqual(errorResults[0].messages[1].severity, 2); - assert.strictEqual(errorResults[0].messages[2].ruleId, "no-unused-vars"); - assert.strictEqual(errorResults[0].messages[2].severity, 2); - assert.strictEqual(errorResults[0].messages[3].ruleId, "quotes"); - assert.strictEqual(errorResults[0].messages[3].severity, 2); - assert.strictEqual(errorResults[0].messages[4].ruleId, "eol-last"); - assert.strictEqual(errorResults[0].messages[4].severity, 2); - assert.lengthOf(errorResults[0].suppressedMessages, 0); - }); - - it("should report no error messages when looking for errors only", () => { - process.chdir(originalDir); - const engine = new CLIEngine({ - useEslintrc: false, - baseConfig: { - rules: { - quotes: 2, - "no-var": 2, - "eol-last": 2, - strict: [2, "global"], - "no-unused-vars": 2 - }, - env: { - node: true - } - } - }); - - const report = engine.executeOnText("var foo = 'bar'; // eslint-disable-line strict, no-var, no-unused-vars, quotes, eol-last -- justification"); - const errorResults = CLIEngine.getErrorResults(report.results); - - assert.lengthOf(errorResults, 0); - }); - - it("should not mutate passed report.results parameter", () => { - process.chdir(originalDir); - const engine = new CLIEngine({ - useEslintrc: false, - rules: { - quotes: [1, "double"], - "no-var": 2 - } - }); - - const report = engine.executeOnText("var foo = 'bar';"); - const reportResultsLength = report.results[0].messages.length; - - assert.strictEqual(report.results[0].messages.length, 2); - - CLIEngine.getErrorResults(report.results); - - assert.lengthOf(report.results[0].messages, reportResultsLength); - }); - - it("should report no suppressed error messages when looking for errors only", () => { - process.chdir(originalDir); - const engine = new CLIEngine({ - useEslintrc: false, - rules: { - quotes: 1, - "no-var": 2, - "eol-last": 2, - strict: [2, "global"], - "no-unused-vars": 2 - }, - env: { - node: true - } - }); - - const report = engine.executeOnText("var foo = 'bar'; // eslint-disable-line quotes -- justification\n"); - const errorResults = CLIEngine.getErrorResults(report.results); - - assert.lengthOf(report.results[0].messages, 3); - assert.lengthOf(report.results[0].suppressedMessages, 1); - assert.lengthOf(errorResults[0].messages, 3); - assert.lengthOf(errorResults[0].suppressedMessages, 0); - }); - - it("should report a warningCount of 0 when looking for errors only", () => { - - process.chdir(originalDir); - const engine = new CLIEngine({ - useEslintrc: false, - baseConfig: { - rules: { - quotes: 2, - "no-var": 2, - "eol-last": 2, - strict: [2, "global"], - "no-unused-vars": 2 - }, - env: { - node: true - } - } - }); - - const report = engine.executeOnText("var foo = 'bar';"); - const errorResults = CLIEngine.getErrorResults(report.results); - - assert.strictEqual(errorResults[0].warningCount, 0); - assert.strictEqual(errorResults[0].fixableWarningCount, 0); - }); - - it("should return 0 error or warning messages even when the file has warnings", () => { - const engine = new CLIEngine({ - ignorePath: path.join(fixtureDir, ".eslintignore"), - cwd: path.join(fixtureDir, "..") - }); - - const report = engine.executeOnText("var bar = foo;", "fixtures/passing.js", true); - const errorReport = CLIEngine.getErrorResults(report.results); - - assert.lengthOf(errorReport, 0); - assert.lengthOf(report.results, 1); - assert.strictEqual(report.errorCount, 0); - assert.strictEqual(report.warningCount, 1); - assert.strictEqual(report.fatalErrorCount, 0); - assert.strictEqual(report.fixableErrorCount, 0); - assert.strictEqual(report.fixableWarningCount, 0); - assert.strictEqual(report.results[0].errorCount, 0); - assert.strictEqual(report.results[0].warningCount, 1); - assert.strictEqual(report.results[0].fatalErrorCount, 0); - assert.strictEqual(report.results[0].fixableErrorCount, 0); - assert.strictEqual(report.results[0].fixableWarningCount, 0); - }); - - it("should return source code of file in the `source` property", () => { - process.chdir(originalDir); - const engine = new CLIEngine({ - useEslintrc: false, - rules: { quotes: [2, "double"] } - }); - - - const report = engine.executeOnText("var foo = 'bar';"); - const errorResults = CLIEngine.getErrorResults(report.results); - - assert.lengthOf(errorResults[0].messages, 1); - assert.strictEqual(errorResults[0].source, "var foo = 'bar';"); - }); - - it("should contain `output` property after fixes", () => { - process.chdir(originalDir); - const engine = new CLIEngine({ - useEslintrc: false, - fix: true, - rules: { - semi: 2, - "no-console": 2 - } - }); - - const report = engine.executeOnText("console.log('foo')"); - const errorResults = CLIEngine.getErrorResults(report.results); - - assert.lengthOf(errorResults[0].messages, 1); - assert.strictEqual(errorResults[0].output, "console.log('foo');"); - }); - }); - - describe("outputFixes()", () => { - afterEach(() => { - sinon.verifyAndRestore(); - }); - - it("should call fs.writeFileSync() for each result with output", () => { - const fakeFS = { - writeFileSync() {} - }, - localCLIEngine = proxyquire("../../../lib/cli-engine/cli-engine", { - fs: fakeFS - }).CLIEngine, - report = { - results: [ - { - filePath: "foo.js", - output: "bar" - }, - { - filePath: "bar.js", - output: "baz" - } - ] - }; - - const spy = sinon.spy(fakeFS, "writeFileSync"); - - localCLIEngine.outputFixes(report); - - assert.strictEqual(spy.callCount, 2); - assert.isTrue(spy.firstCall.calledWithExactly("foo.js", "bar"), "First call was incorrect."); - assert.isTrue(spy.secondCall.calledWithExactly("bar.js", "baz"), "Second call was incorrect."); - - }); - - it("should call fs.writeFileSync() for each result with output and not at all for a result without output", () => { - const fakeFS = { - writeFileSync() {} - }, - localCLIEngine = proxyquire("../../../lib/cli-engine/cli-engine", { - fs: fakeFS - }).CLIEngine, - report = { - results: [ - { - filePath: "foo.js", - output: "bar" - }, - { - filePath: "abc.js" - }, - { - filePath: "bar.js", - output: "baz" - } - ] - }; - - const spy = sinon.spy(fakeFS, "writeFileSync"); - - localCLIEngine.outputFixes(report); - - assert.strictEqual(spy.callCount, 2); - assert.isTrue(spy.firstCall.calledWithExactly("foo.js", "bar"), "First call was incorrect."); - assert.isTrue(spy.secondCall.calledWithExactly("bar.js", "baz"), "Second call was incorrect."); - - }); - - }); - - describe("getRules()", () => { - it("should expose the list of rules", () => { - const engine = new CLIEngine(); - - assert(engine.getRules().has("no-eval"), "no-eval is present"); - }); - - it("should expose the list of plugin rules", () => { - const engine = new CLIEngine({ plugins: ["internal-rules"] }); - - assert(engine.getRules().has("internal-rules/no-invalid-meta"), "internal-rules/no-invalid-meta is present"); - }); - - it("should expose the list of rules from a preloaded plugin", () => { - const engine = new CLIEngine({ - plugins: ["foo"] - }, { - preloadedPlugins: { - foo: require("eslint-plugin-internal-rules") - } - }); - - assert(engine.getRules().has("foo/no-invalid-meta"), "foo/no-invalid-meta is present"); - }); - }); - - describe("resolveFileGlobPatterns", () => { - - [ - [".", ["**/*.{js}"]], - ["./", ["**/*.{js}"]], - ["../", ["../**/*.{js}"]], - ["", []] - ].forEach(([input, expected]) => { - - it(`should correctly resolve ${input} to ${expected}`, () => { - const engine = new CLIEngine(); - - const result = engine.resolveFileGlobPatterns([input]); - - assert.deepStrictEqual(result, expected); - - }); - }); - - it("should convert a directory name with no provided extensions into a glob pattern", () => { - const patterns = ["one-js-file"]; - const opts = { - cwd: getFixturePath("glob-util") - }; - const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); - - assert.deepStrictEqual(result, ["one-js-file/**/*.{js}"]); - }); - - it("should not convert path with globInputPaths option false", () => { - const patterns = ["one-js-file"]; - const opts = { - cwd: getFixturePath("glob-util"), - globInputPaths: false - }; - const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); - - assert.deepStrictEqual(result, ["one-js-file"]); - }); - - it("should convert an absolute directory name with no provided extensions into a posix glob pattern", () => { - const patterns = [getFixturePath("glob-util", "one-js-file")]; - const opts = { - cwd: getFixturePath("glob-util") - }; - const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); - const expected = [`${getFixturePath("glob-util", "one-js-file").replace(/\\/gu, "/")}/**/*.{js}`]; - - assert.deepStrictEqual(result, expected); - }); - - it("should convert a directory name with a single provided extension into a glob pattern", () => { - const patterns = ["one-js-file"]; - const opts = { - cwd: getFixturePath("glob-util"), - extensions: [".jsx"] - }; - const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); - - assert.deepStrictEqual(result, ["one-js-file/**/*.{jsx}"]); - }); - - it("should convert a directory name with multiple provided extensions into a glob pattern", () => { - const patterns = ["one-js-file"]; - const opts = { - cwd: getFixturePath("glob-util"), - extensions: [".jsx", ".js"] - }; - const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); - - assert.deepStrictEqual(result, ["one-js-file/**/*.{jsx,js}"]); - }); - - it("should convert multiple directory names into glob patterns", () => { - const patterns = ["one-js-file", "two-js-files"]; - const opts = { - cwd: getFixturePath("glob-util") - }; - const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); - - assert.deepStrictEqual(result, ["one-js-file/**/*.{js}", "two-js-files/**/*.{js}"]); - }); - - it("should remove leading './' from glob patterns", () => { - const patterns = ["./one-js-file"]; - const opts = { - cwd: getFixturePath("glob-util") - }; - const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); - - assert.deepStrictEqual(result, ["one-js-file/**/*.{js}"]); - }); - - it("should convert a directory name with a trailing '/' into a glob pattern", () => { - const patterns = ["one-js-file/"]; - const opts = { - cwd: getFixturePath("glob-util") - }; - const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); - - assert.deepStrictEqual(result, ["one-js-file/**/*.{js}"]); - }); - - it("should return filenames as they are", () => { - const patterns = ["some-file.js"]; - const opts = { - cwd: getFixturePath("glob-util") - }; - const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); - - assert.deepStrictEqual(result, ["some-file.js"]); - }); - - it("should convert backslashes into forward slashes", () => { - const patterns = ["one-js-file\\example.js"]; - const opts = { - cwd: getFixturePath() - }; - const result = new CLIEngine(opts).resolveFileGlobPatterns(patterns); - - assert.deepStrictEqual(result, ["one-js-file/example.js"]); - }); - }); - - describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { - - it("should report a violation for disabling rules", () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert" - ].join("\n"); - const config = { - envs: ["browser"], - ignore: true, - useEslintrc: false, - allowInlineConfig: false, - rules: { - "eol-last": 0, - "no-alert": 1, - "no-trailing-spaces": 0, - strict: 0, - quotes: 0 - } - }; - - const eslintCLI = new CLIEngine(config); - - const report = eslintCLI.executeOnText(code); - const { messages, suppressedMessages } = report.results[0]; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report a violation by default", () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert" - ].join("\n"); - const config = { - envs: ["browser"], - ignore: true, - useEslintrc: false, - - // allowInlineConfig: true is the default - rules: { - "eol-last": 0, - "no-alert": 1, - "no-trailing-spaces": 0, - strict: 0, - quotes: 0 - } - }; - - const eslintCLI = new CLIEngine(config); - - const report = eslintCLI.executeOnText(code); - const { messages, suppressedMessages } = report.results[0]; - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - }); - - describe("when evaluating code when reportUnusedDisableDirectives is enabled", () => { - it("should report problems for unused eslint-disable directives", () => { - const cliEngine = new CLIEngine({ useEslintrc: false, reportUnusedDisableDirectives: true }); - - assert.deepStrictEqual( - cliEngine.executeOnText("/* eslint-disable */"), - { - results: [ - { - filePath: "", - messages: [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 2, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 1, - fixableWarningCount: 0, - source: "/* eslint-disable */" - } - ], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 1, - fixableWarningCount: 0, - usedDeprecatedRules: [] - } - ); - }); - }); - - describe("when retrieving version number", () => { - it("should return current version number", () => { - const eslintCLI = require("../../../lib/cli-engine").CLIEngine; - const version = eslintCLI.version; - - assert.isString(version); - assert.isTrue(parseInt(version[0], 10) >= 3); - }); - }); - - describe("mutability", () => { - describe("plugins", () => { - it("Loading plugin in one instance doesn't mutate to another instance", () => { - const filePath = getFixturePath("single-quoted.js"); - const engine1 = cliEngineWithPlugins({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - plugins: ["example"], - rules: { "example/example-rule": 1 } - }); - const engine2 = new CLIEngine({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false - }); - const fileConfig1 = engine1.getConfigForFile(filePath); - const fileConfig2 = engine2.getConfigForFile(filePath); - - // plugin - assert.deepStrictEqual(fileConfig1.plugins, ["example"], "Plugin is present for engine 1"); - assert.deepStrictEqual(fileConfig2.plugins, [], "Plugin is not present for engine 2"); - }); - }); - - describe("rules", () => { - it("Loading rules in one instance doesn't mutate to another instance", () => { - const filePath = getFixturePath("single-quoted.js"); - const engine1 = new CLIEngine({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - rules: { "example/example-rule": 1 } - }); - const engine2 = new CLIEngine({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false - }); - const fileConfig1 = engine1.getConfigForFile(filePath); - const fileConfig2 = engine2.getConfigForFile(filePath); - - // plugin - assert.deepStrictEqual(fileConfig1.rules["example/example-rule"], [1], "example is present for engine 1"); - assert.isUndefined(fileConfig2.rules["example/example-rule"], "example is not present for engine 2"); - }); - }); - }); - - describe("with ignorePatterns config", () => { - const root = getFixturePath("cli-engine/ignore-patterns"); - - describe("ignorePatterns can add an ignore pattern ('foo.js').", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": { - ignorePatterns: "foo.js" - }, - "foo.js": "", - "bar.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("foo.js"), true); - assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("bar.js"), false); - assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), false); - }); - - it("'executeOnFiles()' should not verify 'foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js"), - path.join(root, "subdir/bar.js") - ]); - }); - }); - - describe("ignorePatterns can add ignore patterns ('foo.js', '/bar.js').", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": { - ignorePatterns: ["foo.js", "/bar.js"] - }, - "foo.js": "", - "bar.js": "", - "baz.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "", - "subdir/baz.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("foo.js"), true); - assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true); - }); - - it("'isPathIgnored()' should return 'true' for '/bar.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("bar.js"), true); - assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), false); - }); - - it("'executeOnFiles()' should not verify 'foo.js' and '/bar.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "baz.js"), - path.join(root, "subdir/bar.js"), - path.join(root, "subdir/baz.js") - ]); - }); - }); - - describe("ignorePatterns can unignore '/node_modules/foo'.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": { - ignorePatterns: "!/node_modules/foo" - }, - "node_modules/foo/index.js": "", - "node_modules/foo/.dot.js": "", - "node_modules/bar/index.js": "", - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("node_modules/foo/index.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for 'node_modules/foo/.dot.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("node_modules/foo/.dot.js"), true); - }); - - it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("node_modules/bar/index.js"), true); - }); - - it("'executeOnFiles()' should verify 'node_modules/foo/index.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "foo.js"), - path.join(root, "node_modules/foo/index.js") - ]); - }); - }); - - describe("ignorePatterns can unignore '.eslintrc.js'.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "!.eslintrc.js" - })}`, - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for '.eslintrc.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored(".eslintrc.js"), false); - }); - - it("'executeOnFiles()' should verify '.eslintrc.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, ".eslintrc.js"), - path.join(root, "foo.js") - ]); - }); - }); - - describe(".eslintignore can re-ignore files that are unignored by ignorePatterns.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "!.*" - })}`, - ".eslintignore": ".foo*", - ".foo.js": "", - ".bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored(".foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored(".bar.js"), false); - }); - - it("'executeOnFiles()' should not verify re-ignored '.foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, ".bar.js"), - path.join(root, ".eslintrc.js") - ]); - }); - }); - - describe(".eslintignore can unignore files that are ignored by ignorePatterns.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "*.js" - })}`, - ".eslintignore": "!foo.js", - "foo.js": "", - "bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("foo.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("bar.js"), true); - }); - - it("'executeOnFiles()' should verify unignored 'foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "foo.js") - ]); - }); - }); - - describe("ignorePatterns in the config file in a child directory affects to only in the directory.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({ - ignorePatterns: "foo.js" - }), - "subdir/.eslintrc.json": JSON.stringify({ - ignorePatterns: "bar.js" - }), - "foo.js": "", - "bar.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "", - "subdir/subsubdir/foo.js": "", - "subdir/subsubdir/bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("foo.js"), true); - assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true); - assert.strictEqual(engine.isPathIgnored("subdir/subsubdir/foo.js"), true); - }); - - it("'isPathIgnored()' should return 'true' for 'bar.js' in 'subdir'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true); - assert.strictEqual(engine.isPathIgnored("subdir/subsubdir/bar.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'bar.js' in the outside of 'subdir'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("bar.js"), false); - }); - - it("'executeOnFiles()' should verify 'bar.js' in the outside of 'subdir'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js") - ]); - }); - }); - - describe("ignorePatterns in the config file in a child directory can unignore the ignored files in the parent directory's config.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({ - ignorePatterns: "foo.js" - }), - "subdir/.eslintrc.json": JSON.stringify({ - ignorePatterns: "!foo.js" - }), - "foo.js": "", - "subdir/foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false); - }); - - it("'executeOnFiles()' should verify 'foo.js' in the child directory.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "subdir/foo.js") - ]); - }); - }); - - describe(".eslintignore can unignore files that are ignored by ignorePatterns in the config file in the child directory.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": {}, - "subdir/.eslintrc.json": { - ignorePatterns: "*.js" - }, - ".eslintignore": "!foo.js", - "foo.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("foo.js"), false); - assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true); - }); - - it("'executeOnFiles()' should verify unignored 'foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "foo.js"), - path.join(root, "subdir/foo.js") - ]); - }); - }); - - describe("if the config in a child directory has 'root:true', ignorePatterns in the config file in the parent directory should not be used.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": { - ignorePatterns: "foo.js" - }, - "subdir/.eslintrc.json": { - root: true, - ignorePatterns: "bar.js" - }, - "foo.js": "", - "bar.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("bar.js"), false); - }); - - it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true); - }); - - it("'executeOnFiles()' should verify 'bar.js' in the root directory and 'foo.js' in the child directory.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js"), - path.join(root, "subdir/foo.js") - ]); - }); - }); - - describe("even if the config in a child directory has 'root:true', .eslintignore should be used.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({}), - "subdir/.eslintrc.json": JSON.stringify({ - root: true, - ignorePatterns: "bar.js" - }), - ".eslintignore": "foo.js", - "foo.js": "", - "bar.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("foo.js"), true); - assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("bar.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true); - }); - - it("'executeOnFiles()' should verify 'bar.js' in the root directory.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js") - ]); - }); - }); - - describe("ignorePatterns in the shareable config should be used.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "foo.js" - })}`, - ".eslintrc.json": JSON.stringify({ - extends: "one" - }), - "foo.js": "", - "bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("bar.js"), false); - }); - - it("'executeOnFiles()' should verify 'bar.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js") - ]); - }); - }); - - describe("ignorePatterns in the shareable config should be relative to the entry config file.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "/foo.js" - })}`, - ".eslintrc.json": JSON.stringify({ - extends: "one" - }), - "foo.js": "", - "subdir/foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'subdir/foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), false); - }); - - it("'executeOnFiles()' should verify 'subdir/foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "subdir/foo.js") - ]); - }); - }); - - describe("ignorePatterns in a config file can unignore the files which are ignored by ignorePatterns in the shareable config.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "*.js" - })}`, - ".eslintrc.json": JSON.stringify({ - extends: "one", - ignorePatterns: "!bar.js" - }), - "foo.js": "", - "bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assert.strictEqual(engine.isPathIgnored("bar.js"), false); - }); - - it("'executeOnFiles()' should verify 'bar.js'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js") - ]); - }); - }); - - describe("ignorePatterns in a config file should not be used if --no-ignore option was given.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({ - ignorePatterns: "*.js" - }), - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for 'foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath(), ignore: false }); - - assert.strictEqual(engine.isPathIgnored("foo.js"), false); - }); - - it("'executeOnFiles()' should verify 'foo.js'.", () => { - const engine = new CLIEngine({ cwd: getPath(), ignore: false }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "foo.js") - ]); - }); - }); - - describe("ignorePatterns in overrides section is not allowed.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.js": `module.exports = ${JSON.stringify({ - overrides: [ - { - files: "*.js", - ignorePatterns: "foo.js" - } - ] - })}`, - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("should throw a configuration error.", () => { - assert.throws(() => { - const engine = new CLIEngine({ cwd: getPath() }); - - engine.executeOnFiles("*.js"); - }, "Unexpected top-level property \"overrides[0].ignorePatterns\""); - }); - }); - - }); - - describe("'overrides[].files' adds lint targets", () => { - const root = getFixturePath("cli-engine/additional-lint-targets"); - - describe("if { files: 'foo/*.txt', excludedFiles: '**/ignore.txt' } is present,", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({ - overrides: [ - { - files: "foo/*.txt", - excludedFiles: "**/ignore.txt" - } - ] - }), - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "foo/ignore.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "bar/ignore.txt": "", - "test.js": "", - "test.txt": "", - "ignore.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' with a directory path should contain 'foo/test.txt'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles(".") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/test.js"), - path.join(root, "foo/test.txt"), - path.join(root, "test.js") - ]); - }); - - it("'executeOnFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/test.js"), - path.join(root, "test.js") - ]); - }); - }); - - describe("if { files: 'foo/**/*.txt' } is present,", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({ - overrides: [ - { - files: "foo/**/*.txt" - } - ] - }), - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "test.js": "", - "test.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles(".") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/nested/test.txt"), - path.join(root, "foo/test.js"), - path.join(root, "foo/test.txt"), - path.join(root, "test.js") - ]); - }); - }); - - describe("if { files: 'foo/**/*' } is present,", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({ - overrides: [ - { - files: "foo/**/*" - } - ] - }), - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "test.js": "", - "test.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles(".") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/test.js"), - path.join(root, "test.js") - ]); - }); - }); - - describe("if { files: 'foo/**/*.txt' } is present in a shareable config,", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-config-foo/index.js": `module.exports = ${JSON.stringify({ - overrides: [ - { - files: "foo/**/*.txt" - } - ] - })}`, - ".eslintrc.json": JSON.stringify({ - extends: "foo" - }), - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "test.js": "", - "test.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles(".") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/nested/test.txt"), - path.join(root, "foo/test.js"), - path.join(root, "foo/test.txt"), - path.join(root, "test.js") - ]); - }); - }); - - describe("if { files: 'foo/**/*.txt' } is present in a plugin config,", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-plugin-foo/index.js": `exports.configs = ${JSON.stringify({ - bar: { - overrides: [ - { - files: "foo/**/*.txt" - } - ] - } - })}`, - ".eslintrc.json": JSON.stringify({ - extends: "plugin:foo/bar" - }), - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "test.js": "", - "test.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const filePaths = engine.executeOnFiles(".") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/nested/test.txt"), - path.join(root, "foo/test.js"), - path.join(root, "foo/test.txt"), - path.join(root, "test.js") - ]); - }); - }); - }); - - describe("'ignorePatterns', 'overrides[].files', and 'overrides[].excludedFiles' of the configuration that the '--config' option provided should be resolved from CWD.", () => { - const root = getFixturePath("cli-engine/config-and-overrides-files"); - - describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/myconf/.eslintrc.json": JSON.stringify({ - overrides: [ - { - files: "foo/*.js", - rules: { - eqeqeq: "error" - } - } - ] - }), - "node_modules/myconf/foo/test.js": "a == b", - "foo/test.js": "a == b" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' with 'foo/test.js' should use the override entry.", () => { - const engine = new CLIEngine({ - configFile: "node_modules/myconf/.eslintrc.json", - cwd: getPath(), - ignore: false, - useEslintrc: false - }); - const { results } = engine.executeOnFiles("foo/test.js"); - - // Expected to be an 'eqeqeq' error because the file matches to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - errorCount: 1, - filePath: path.join(root, "foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [ - { - column: 3, - endColumn: 5, - endLine: 1, - line: 1, - message: "Expected '===' and instead saw '=='.", - messageId: "unexpected", - nodeType: "BinaryExpression", - ruleId: "eqeqeq", - severity: 2 - } - ], - suppressedMessages: [], - source: "a == b", - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - - it("'executeOnFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the override entry.", () => { - const engine = new CLIEngine({ - configFile: "node_modules/myconf/.eslintrc.json", - cwd: getPath(), - ignore: false, - useEslintrc: false - }); - const { results } = engine.executeOnFiles("node_modules/myconf/foo/test.js"); - - // Expected to be no errors because the file doesn't match to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - errorCount: 0, - filePath: path.join(root, "node_modules/myconf/foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [], - suppressedMessages: [], - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - }); - - describe("if { files: '*', excludedFiles: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/myconf/.eslintrc.json": JSON.stringify({ - overrides: [ - { - files: "*", - excludedFiles: "foo/*.js", - rules: { - eqeqeq: "error" - } - } - ] - }), - "node_modules/myconf/foo/test.js": "a == b", - "foo/test.js": "a == b" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' with 'foo/test.js' should NOT use the override entry.", () => { - const engine = new CLIEngine({ - configFile: "node_modules/myconf/.eslintrc.json", - cwd: getPath(), - ignore: false, - useEslintrc: false - }); - const { results } = engine.executeOnFiles("foo/test.js"); - - // Expected to be no errors because the file matches to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - errorCount: 0, - filePath: path.join(root, "foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [], - suppressedMessages: [], - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - - it("'executeOnFiles()' with 'node_modules/myconf/foo/test.js' should use the override entry.", () => { - const engine = new CLIEngine({ - configFile: "node_modules/myconf/.eslintrc.json", - cwd: getPath(), - ignore: false, - useEslintrc: false - }); - const { results } = engine.executeOnFiles("node_modules/myconf/foo/test.js"); - - // Expected to be an 'eqeqeq' error because the file doesn't match to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - errorCount: 1, - filePath: path.join(root, "node_modules/myconf/foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [ - { - column: 3, - endColumn: 5, - endLine: 1, - line: 1, - message: "Expected '===' and instead saw '=='.", - messageId: "unexpected", - nodeType: "BinaryExpression", - ruleId: "eqeqeq", - severity: 2 - } - ], - suppressedMessages: [], - source: "a == b", - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - }); - - describe("if { ignorePatterns: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/myconf/.eslintrc.json": JSON.stringify({ - ignorePatterns: ["!/node_modules/myconf", "foo/*.js"], - rules: { - eqeqeq: "error" - } - }), - "node_modules/myconf/foo/test.js": "a == b", - "foo/test.js": "a == b" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' with '**/*.js' should iterate 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", () => { - const engine = new CLIEngine({ - configFile: "node_modules/myconf/.eslintrc.json", - cwd: getPath(), - useEslintrc: false - }); - const files = engine.executeOnFiles("**/*.js") - .results - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(files, [ - path.join(root, "node_modules/myconf/foo/test.js") - ]); - }); - }); - }); - - describe("plugin conflicts", () => { - let uid = 0; - const root = getFixturePath("cli-engine/plugin-conflicts-"); - - /** - * Verify thrown errors. - * @param {() => void} f The function to run and throw. - * @param {Record} props The properties to verify. - * @returns {void} - */ - function assertThrows(f, props) { - try { - f(); - } catch (error) { - for (const [key, value] of Object.entries(props)) { - assert.deepStrictEqual(error[key], value, key); - } - return; - } - - assert.fail("Function should throw an error, but not."); - } - - describe("between a config file and linear extendees.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - extends: ["two"], - plugins: ["foo"] - })}`, - "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({ - plugins: ["foo"] - })}`, - ".eslintrc.json": JSON.stringify({ - extends: ["one"], - plugins: ["foo"] - }), - "test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - engine.executeOnFiles("test.js"); - }); - }); - - describe("between a config file and same-depth extendees.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - plugins: ["foo"] - })}`, - "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({ - plugins: ["foo"] - })}`, - ".eslintrc.json": JSON.stringify({ - extends: ["one", "two"], - plugins: ["foo"] - }), - "test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - engine.executeOnFiles("test.js"); - }); - }); - - describe("between two config files in different directories, with single node_modules.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - engine.executeOnFiles("subdir/test.js"); - }); - }); - - describe("between two config files in different directories, with multiple node_modules.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/node_modules/eslint-plugin-foo/index.js": "", - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", () => { - const engine = new CLIEngine({ cwd: getPath() }); - - assertThrows( - () => engine.executeOnFiles("subdir/test.js"), - { - message: `Plugin "foo" was conflicted between "subdir${path.sep}.eslintrc.json" and ".eslintrc.json".`, - messageTemplate: "plugin-conflict", - messageData: { - pluginId: "foo", - plugins: [ - { - filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"), - importerName: `subdir${path.sep}.eslintrc.json` - }, - { - filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"), - importerName: ".eslintrc.json" - } - ] - } - } - ); - }); - }); - - describe("between '--config' option and a regular config file, with single node_modules.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "node_modules/mine/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", () => { - const engine = new CLIEngine({ - cwd: getPath(), - configFile: "node_modules/mine/.eslintrc.json" - }); - - engine.executeOnFiles("test.js"); - }); - }); - - describe("between '--config' option and a regular config file, with multiple node_modules.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "node_modules/mine/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/mine/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", () => { - const engine = new CLIEngine({ - cwd: getPath(), - configFile: "node_modules/mine/.eslintrc.json" - }); - - assertThrows( - () => engine.executeOnFiles("test.js"), - { - message: "Plugin \"foo\" was conflicted between \"--config\" and \".eslintrc.json\".", - messageTemplate: "plugin-conflict", - messageData: { - pluginId: "foo", - plugins: [ - { - filePath: path.join(getPath(), "node_modules/mine/node_modules/eslint-plugin-foo/index.js"), - importerName: "--config" - }, - { - filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"), - importerName: ".eslintrc.json" - } - ] - } - } - ); - }); - }); - - describe("between '--plugin' option and a regular config file, with single node_modules.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file, but node_modules directory is unique.)", () => { - const engine = new CLIEngine({ - cwd: getPath(), - plugins: ["foo"] - }); - - engine.executeOnFiles("subdir/test.js"); - }); - }); - - describe("between '--plugin' option and a regular config file, with multiple node_modules.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "subdir/node_modules/eslint-plugin-foo/index.js": "", - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file.)", () => { - const engine = new CLIEngine({ - cwd: getPath(), - plugins: ["foo"] - }); - - assertThrows( - () => engine.executeOnFiles("subdir/test.js"), - { - message: `Plugin "foo" was conflicted between "CLIOptions" and "subdir${path.sep}.eslintrc.json".`, - messageTemplate: "plugin-conflict", - messageData: { - pluginId: "foo", - plugins: [ - { - filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"), - importerName: "CLIOptions" - }, - { - filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"), - importerName: `subdir${path.sep}.eslintrc.json` - } - ] - } - } - ); - }); - }); - - describe("'--resolve-plugins-relative-to' option overrides the location that ESLint load plugins from.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/node_modules/eslint-plugin-foo/index.js": "", - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from '--resolve-plugins-relative-to'.)", () => { - const engine = new CLIEngine({ - cwd: getPath(), - resolvePluginsRelativeTo: getPath() - }); - - engine.executeOnFiles("subdir/test.js"); - }); - }); - - describe("between two config files with different target files.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "one/node_modules/eslint-plugin-foo/index.js": "", - "one/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "one/test.js": "", - "two/node_modules/eslint-plugin-foo/index.js": "", - "two/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "two/test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file for each target file. Not related to each other.)", () => { - const engine = new CLIEngine({ cwd: getPath() }); - const { results } = engine.executeOnFiles("*/test.js"); - - assert.strictEqual(results.length, 2); - }); - }); - }); + ".eslintrc.json": { + root: true, + rules: { test: "error" }, + }, + "test.js": "console.log('hello')", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should use the configured rules which are defined by '--rulesdir' option.", () => { + engine = new CLIEngine({ + cwd: getPath(), + rulePaths: ["internal-rules"], + }); + const report = engine.executeOnFiles(["test.js"]); + + assert.strictEqual(report.results.length, 1); + assert.strictEqual(report.results[0].messages.length, 1); + assert.strictEqual(report.results[0].messages[0].message, "ok"); + assert.strictEqual( + report.results[0].suppressedMessages.length, + 0, + ); + }); + }); + + describe("glob pattern '[ab].js'", () => { + const root = getFixturePath("cli-engine/unmatched-glob"); + + let cleanup; + + beforeEach(() => { + cleanup = () => {}; + }); + + afterEach(() => cleanup()); + + it("should match '[ab].js' if existed.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "a.js": "", + "b.js": "", + "ab.js": "", + "[ab].js": "", + ".eslintrc.yml": "root: true", + }, + }); + + engine = new CLIEngine({ cwd: teardown.getPath() }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + const { results } = engine.executeOnFiles(["[ab].js"]); + const filenames = results.map(r => path.basename(r.filePath)); + + assert.deepStrictEqual(filenames, ["[ab].js"]); + }); + + it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "a.js": "", + "b.js": "", + "ab.js": "", + ".eslintrc.yml": "root: true", + }, + }); + + engine = new CLIEngine({ cwd: teardown.getPath() }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + const { results } = engine.executeOnFiles(["[ab].js"]); + const filenames = results.map(r => path.basename(r.filePath)); + + assert.deepStrictEqual(filenames, ["a.js", "b.js"]); + }); + }); + + describe("with 'noInlineConfig' setting", () => { + const root = getFixturePath("cli-engine/noInlineConfig"); + + let cleanup; + + beforeEach(() => { + cleanup = () => {}; + }); + + afterEach(() => cleanup()); + + it("should warn directive comments if 'noInlineConfig' was given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "test.js": "/* globals foo */", + ".eslintrc.yml": "noInlineConfig: true", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + engine = new CLIEngine({ cwd: teardown.getPath() }); + + const { results } = engine.executeOnFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml).", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should show the config file what the 'noInlineConfig' came from.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-foo/index.js": + "module.exports = {noInlineConfig: true}", + "test.js": "/* globals foo */", + ".eslintrc.yml": "extends: foo", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + engine = new CLIEngine({ cwd: teardown.getPath() }); + + const { results } = engine.executeOnFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml Âģ eslint-config-foo).", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + }); + + describe("with 'reportUnusedDisableDirectives' setting", () => { + const root = getFixturePath( + "cli-engine/reportUnusedDisableDirectives", + ); + + let cleanup; + + beforeEach(() => { + cleanup = () => {}; + }); + + afterEach(() => cleanup()); + + it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives' was given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "test.js": "/* eslint-disable eqeqeq */", + ".eslintrc.yml": "reportUnusedDisableDirectives: true", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + engine = new CLIEngine({ cwd: teardown.getPath() }); + + const { results } = engine.executeOnFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual( + messages[0].message, + "Unused eslint-disable directive (no problems were reported from 'eqeqeq').", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + describe("the runtime option overrides config files.", () => { + it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "test.js": "/* eslint-disable eqeqeq */", + ".eslintrc.yml": + "reportUnusedDisableDirectives: true", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + engine = new CLIEngine({ + cwd: teardown.getPath(), + reportUnusedDisableDirectives: "off", + }); + + const { results } = engine.executeOnFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "test.js": "/* eslint-disable eqeqeq */", + ".eslintrc.yml": + "reportUnusedDisableDirectives: true", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + engine = new CLIEngine({ + cwd: teardown.getPath(), + reportUnusedDisableDirectives: "error", + }); + + const { results } = engine.executeOnFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + "Unused eslint-disable directive (no problems were reported from 'eqeqeq').", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + }); + }); + + describe("with 'overrides[*].extends' setting on deep locations", () => { + const root = getFixturePath( + "cli-engine/deeply-overrides-i-extends", + ); + + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify( + { + overrides: [{ files: ["*test*"], extends: "two" }], + }, + )}`, + "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify( + { + overrides: [{ files: ["*.js"], extends: "three" }], + }, + )}`, + "node_modules/eslint-config-three/index.js": `module.exports = ${JSON.stringify( + { + rules: { "no-console": "error" }, + }, + )}`, + "test.js": "console.log('hello')", + ".eslintrc.yml": "extends: one", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should not throw.", () => { + engine = new CLIEngine({ cwd: getPath() }); + + const { results } = engine.executeOnFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + }); + + describe("don't ignore the entry directory.", () => { + const root = getFixturePath("cli-engine/dont-ignore-entry-dir"); + + let cleanup; + + beforeEach(() => { + cleanup = () => {}; + }); + + afterEach(async () => { + await cleanup(); + + const configFilePath = path.resolve(root, "../.eslintrc.json"); + + if (shell.test("-e", configFilePath)) { + shell.rm(configFilePath); + } + }); + + it('\'executeOnFiles(".")\' should not load config files from outside of ".".', async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "../.eslintrc.json": "BROKEN FILE", + ".eslintrc.json": JSON.stringify({ root: true }), + "index.js": 'console.log("hello")', + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + engine = new CLIEngine({ cwd: teardown.getPath() }); + + // Don't throw "failed to load config file" error. + engine.executeOnFiles("."); + }); + + it("'executeOnFiles(\".\")' should not ignore '.' even if 'ignorePatterns' contains it.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "../.eslintrc.json": { + ignorePatterns: ["/dont-ignore-entry-dir"], + }, + ".eslintrc.json": { root: true }, + "index.js": 'console.log("hello")', + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + engine = new CLIEngine({ cwd: teardown.getPath() }); + + // Don't throw "file not found" error. + engine.executeOnFiles("."); + }); + + it("'executeOnFiles(\"subdir\")' should not ignore './subdir' even if 'ignorePatterns' contains it.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": { ignorePatterns: ["/subdir"] }, + "subdir/.eslintrc.json": { root: true }, + "subdir/index.js": 'console.log("hello")', + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + engine = new CLIEngine({ cwd: teardown.getPath() }); + + // Don't throw "file not found" error. + engine.executeOnFiles("subdir"); + }); + }); + }); + + describe("getConfigForFile", () => { + it("should return the info from Config#getConfig when called", () => { + const options = { + configFile: getFixturePath( + "configurations", + "quotes-error.json", + ), + }; + const engine = new CLIEngine(options); + const filePath = getFixturePath("single-quoted.js"); + + const actualConfig = engine.getConfigForFile(filePath); + const expectedConfig = new CascadingConfigArrayFactory({ + specificConfigPath: options.configFile, + }) + .getConfigArrayForFile(filePath) + .extractConfig(filePath) + .toCompatibleObjectAsConfigFileContent(); + + assert.deepStrictEqual(actualConfig, expectedConfig); + }); + + it("should return the config when run from within a subdir", () => { + const options = { + cwd: getFixturePath( + "config-hierarchy", + "root-true", + "parent", + "root", + "subdir", + ), + }; + const engine = new CLIEngine(options); + const filePath = getFixturePath( + "config-hierarchy", + "root-true", + "parent", + "root", + ".eslintrc", + ); + + const actualConfig = engine.getConfigForFile("./.eslintrc"); + const expectedConfig = new CascadingConfigArrayFactory(options) + .getConfigArrayForFile(filePath) + .extractConfig(filePath) + .toCompatibleObjectAsConfigFileContent(); + + assert.deepStrictEqual(actualConfig, expectedConfig); + }); + + it("should throw an error if a directory path was given.", () => { + const engine = new CLIEngine(); + + try { + engine.getConfigForFile("."); + } catch (error) { + assert.strictEqual( + error.messageTemplate, + "print-config-with-directory-path", + ); + return; + } + assert.fail("should throw an error"); + }); + }); + + describe("isPathIgnored", () => { + it("should check if the given path is ignored", () => { + const engine = new CLIEngine({ + ignorePath: getFixturePath(".eslintignore2"), + cwd: getFixturePath(), + }); + + assert.isTrue(engine.isPathIgnored("undef.js")); + assert.isFalse(engine.isPathIgnored("passing.js")); + }); + + it("should return false if ignoring is disabled", () => { + const engine = new CLIEngine({ + ignore: false, + ignorePath: getFixturePath(".eslintignore2"), + cwd: getFixturePath(), + }); + + assert.isFalse(engine.isPathIgnored("undef.js")); + }); + + // https://github.com/eslint/eslint/issues/5547 + it("should return true for default ignores even if ignoring is disabled", () => { + const engine = new CLIEngine({ + ignore: false, + cwd: getFixturePath("cli-engine"), + }); + + assert.isTrue(engine.isPathIgnored("node_modules/foo.js")); + }); + + describe("about the default ignore patterns", () => { + it("should always apply defaultPatterns if ignore option is true", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ cwd }); + + assert( + engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "node_modules/package/file.js", + ), + ), + ); + assert( + engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "subdir/node_modules/package/file.js", + ), + ), + ); + }); + + it("should still apply defaultPatterns if ignore option is false", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ ignore: false, cwd }); + + assert( + engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "node_modules/package/file.js", + ), + ), + ); + assert( + engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "subdir/node_modules/package/file.js", + ), + ), + ); + }); + + it("should allow subfolders of defaultPatterns to be unignored by ignorePattern", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ + cwd, + ignorePattern: "!/node_modules/package", + }); + + assert( + !engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "node_modules", + "package", + "file.js", + ), + ), + ); + }); + + it("should allow subfolders of defaultPatterns to be unignored by ignorePath", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ + cwd, + ignorePath: getFixturePath( + "ignored-paths", + ".eslintignoreWithUnignoredDefaults", + ), + }); + + assert( + !engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "node_modules", + "package", + "file.js", + ), + ), + ); + }); + + it("should ignore dotfiles", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ cwd }); + + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", ".foo"), + ), + ); + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/.bar"), + ), + ); + }); + + it("should ignore directories beginning with a dot", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ cwd }); + + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", ".foo/bar"), + ), + ); + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/.bar/baz"), + ), + ); + }); + + it("should still ignore dotfiles when ignore option disabled", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ ignore: false, cwd }); + + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", ".foo"), + ), + ); + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/.bar"), + ), + ); + }); + + it("should still ignore directories beginning with a dot when ignore option disabled", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ ignore: false, cwd }); + + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", ".foo/bar"), + ), + ); + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/.bar/baz"), + ), + ); + }); + + it("should not ignore absolute paths containing '..'", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ cwd }); + + assert( + !engine.isPathIgnored( + `${getFixturePath("ignored-paths", "foo")}/../unignored.js`, + ), + ); + }); + + it("should ignore /node_modules/ relative to .eslintignore when loaded", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ + ignorePath: getFixturePath( + "ignored-paths", + ".eslintignore", + ), + cwd, + }); + + assert( + engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "node_modules", + "existing.js", + ), + ), + ); + assert( + engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "foo", + "node_modules", + "existing.js", + ), + ), + ); + }); + + it("should ignore /node_modules/ relative to cwd without an .eslintignore", () => { + const cwd = getFixturePath("ignored-paths", "no-ignore-file"); + const engine = new CLIEngine({ cwd }); + + assert( + engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "no-ignore-file", + "node_modules", + "existing.js", + ), + ), + ); + assert( + engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "no-ignore-file", + "foo", + "node_modules", + "existing.js", + ), + ), + ); + }); + }); + + describe("with no .eslintignore file", () => { + it("should not travel to parent directories to find .eslintignore when it's missing and cwd is provided", () => { + const cwd = getFixturePath("ignored-paths", "configurations"); + const engine = new CLIEngine({ cwd }); + + // an .eslintignore in parent directories includes `*.js`, but don't load it. + assert(!engine.isPathIgnored("foo.js")); + assert(engine.isPathIgnored("node_modules/foo.js")); + }); + + it("should return false for files outside of the cwd (with no ignore file provided)", () => { + // Default ignore patterns should not inadvertently ignore files in parent directories + const engine = new CLIEngine({ + cwd: getFixturePath("ignored-paths", "no-ignore-file"), + }); + + assert( + !engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + ), + ); + }); + }); + + describe("with .eslintignore file or package.json file", () => { + it("should load .eslintignore from cwd when explicitly passed", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ cwd }); + + // `${cwd}/.eslintignore` includes `sampleignorepattern`. + assert(engine.isPathIgnored("sampleignorepattern")); + }); + + it("should use package.json's eslintIgnore files if no specified .eslintignore file", () => { + const cwd = getFixturePath( + "ignored-paths", + "package-json-ignore", + ); + const engine = new CLIEngine({ cwd }); + + assert(engine.isPathIgnored("hello.js")); + assert(engine.isPathIgnored("world.js")); + }); + + it("should use correct message template if failed to parse package.json", () => { + const cwd = getFixturePath( + "ignored-paths", + "broken-package-json", + ); + + assert.throw(() => { + try { + // eslint-disable-next-line no-new -- Check for throwing + new CLIEngine({ cwd }); + } catch (error) { + assert.strictEqual( + error.messageTemplate, + "failed-to-read-json", + ); + throw error; + } + }); + }); + + it("should not use package.json's eslintIgnore files if specified .eslintignore file", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ cwd }); + + /* + * package.json includes `hello.js` and `world.js`. + * .eslintignore includes `sampleignorepattern`. + */ + assert(!engine.isPathIgnored("hello.js")); + assert(!engine.isPathIgnored("world.js")); + assert(engine.isPathIgnored("sampleignorepattern")); + }); + + it("should error if package.json's eslintIgnore is not an array of file paths", () => { + const cwd = getFixturePath( + "ignored-paths", + "bad-package-json-ignore", + ); + + assert.throws(() => { + // eslint-disable-next-line no-new -- Check for throwing + new CLIEngine({ cwd }); + }, "Package.json eslintIgnore property requires an array of paths"); + }); + }); + + describe("with --ignore-pattern option", () => { + it("should accept a string for options.ignorePattern", () => { + const cwd = getFixturePath("ignored-paths", "ignore-pattern"); + const engine = new CLIEngine({ + ignorePattern: "ignore-me.txt", + cwd, + }); + + assert(engine.isPathIgnored("ignore-me.txt")); + }); + + it("should accept an array for options.ignorePattern", () => { + const engine = new CLIEngine({ + ignorePattern: ["a", "b"], + useEslintrc: false, + }); + + assert(engine.isPathIgnored("a")); + assert(engine.isPathIgnored("b")); + assert(!engine.isPathIgnored("c")); + }); + + it("should return true for files which match an ignorePattern even if they do not exist on the filesystem", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ + ignorePattern: "not-a-file", + cwd, + }); + + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "not-a-file"), + ), + ); + }); + + it("should return true for file matching an ignore pattern exactly", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ + ignorePattern: "undef.js", + cwd, + }); + + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + ), + ); + }); + + it("should return false for file matching an invalid ignore pattern with leading './'", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ + ignorePattern: "./undef.js", + cwd, + }); + + assert( + !engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + ), + ); + }); + + it("should return false for file in subfolder of cwd matching an ignore pattern with leading '/'", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ + ignorePattern: "/undef.js", + cwd, + }); + + assert( + !engine.isPathIgnored( + getFixturePath("ignored-paths", "subdir", "undef.js"), + ), + ); + }); + + it("should return true for file matching a child of an ignore pattern", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ + ignorePattern: "ignore-pattern", + cwd, + }); + + assert( + engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "ignore-pattern", + "ignore-me.txt", + ), + ), + ); + }); + + it("should return true for file matching a grandchild of an ignore pattern", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ + ignorePattern: "ignore-pattern", + cwd, + }); + + assert( + engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "ignore-pattern", + "subdir", + "ignore-me.txt", + ), + ), + ); + }); + + it("should return false for file not matching any ignore pattern", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ + ignorePattern: "failing.js", + cwd, + }); + + assert( + !engine.isPathIgnored( + getFixturePath("ignored-paths", "unignored.js"), + ), + ); + }); + + it("two globstar '**' ignore pattern should ignore files in nested directories", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ ignorePattern: "**/*.js", cwd }); + + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "foo.js"), + ), + ); + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/bar.js"), + ), + ); + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/bar/baz.js"), + ), + ); + assert( + !engine.isPathIgnored( + getFixturePath("ignored-paths", "foo.j2"), + ), + ); + assert( + !engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/bar.j2"), + ), + ); + assert( + !engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/bar/baz.j2"), + ), + ); + }); + }); + + describe("with --ignore-path option", () => { + it("should load empty array with ignorePath set to false", () => { + const cwd = getFixturePath("ignored-paths", "no-ignore-file"); + const engine = new CLIEngine({ ignorePath: false, cwd }); + + // an .eslintignore in parent directories includes `*.js`, but don't load it. + assert(!engine.isPathIgnored("foo.js")); + assert(engine.isPathIgnored("node_modules/foo.js")); + }); + + it("initialization with ignorePath should work when cwd is a parent directory", () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + "custom-name", + "ignore-file", + ); + const engine = new CLIEngine({ ignorePath, cwd }); + + assert(engine.isPathIgnored("custom-name/foo.js")); + }); + + it("initialization with ignorePath should work when the file is in the cwd", () => { + const cwd = getFixturePath("ignored-paths", "custom-name"); + const ignorePath = getFixturePath( + "ignored-paths", + "custom-name", + "ignore-file", + ); + const engine = new CLIEngine({ ignorePath, cwd }); + + assert(engine.isPathIgnored("foo.js")); + }); + + it("initialization with ignorePath should work when cwd is a subdirectory", () => { + const cwd = getFixturePath( + "ignored-paths", + "custom-name", + "subdirectory", + ); + const ignorePath = getFixturePath( + "ignored-paths", + "custom-name", + "ignore-file", + ); + const engine = new CLIEngine({ ignorePath, cwd }); + + assert(engine.isPathIgnored("../custom-name/foo.js")); + }); + + it("initialization with invalid file should throw error", () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + "not-a-directory", + ".foobaz", + ); + + assert.throws(() => { + // eslint-disable-next-line no-new -- Check for throwing + new CLIEngine({ ignorePath, cwd }); + }, "Cannot read .eslintignore file"); + }); + + it("should return false for files outside of ignorePath's directory", () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + "custom-name", + "ignore-file", + ); + const engine = new CLIEngine({ ignorePath, cwd }); + + assert( + !engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + ), + ); + }); + + it("should resolve relative paths from CWD", () => { + const cwd = getFixturePath("ignored-paths", "subdir"); + const ignorePath = getFixturePath( + "ignored-paths", + ".eslintignoreForDifferentCwd", + ); + const engine = new CLIEngine({ ignorePath, cwd }); + + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "subdir/undef.js"), + ), + ); + assert( + !engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + ), + ); + }); + + it("should resolve relative paths from CWD when it's in a child directory", () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + "subdir/.eslintignoreInChildDir", + ); + const engine = new CLIEngine({ ignorePath, cwd }); + + assert( + !engine.isPathIgnored( + getFixturePath("ignored-paths", "subdir/undef.js"), + ), + ); + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + ), + ); + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "foo.js"), + ), + ); + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "subdir/foo.js"), + ), + ); + + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "node_modules/bar.js"), + ), + ); + }); + + it("should resolve relative paths from CWD when it contains negated globs", () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + "subdir/.eslintignoreInChildDir", + ); + const engine = new CLIEngine({ ignorePath, cwd }); + + assert(engine.isPathIgnored("subdir/blah.txt")); + assert(engine.isPathIgnored("blah.txt")); + assert(engine.isPathIgnored("subdir/bar.txt")); + assert(!engine.isPathIgnored("bar.txt")); + assert(!engine.isPathIgnored("subdir/baz.txt")); + assert(!engine.isPathIgnored("baz.txt")); + }); + + it("should resolve default ignore patterns from the CWD even when the ignorePath is in a subdirectory", () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + "subdir/.eslintignoreInChildDir", + ); + const engine = new CLIEngine({ ignorePath, cwd }); + + assert(engine.isPathIgnored("node_modules/blah.js")); + }); + + it("should resolve default ignore patterns from the CWD even when the ignorePath is in a parent directory", () => { + const cwd = getFixturePath("ignored-paths", "subdir"); + const ignorePath = getFixturePath( + "ignored-paths", + ".eslintignoreForDifferentCwd", + ); + const engine = new CLIEngine({ ignorePath, cwd }); + + assert(engine.isPathIgnored("node_modules/blah.js")); + }); + + it("should handle .eslintignore which contains CRLF correctly.", () => { + const ignoreFileContent = fs.readFileSync( + getFixturePath("ignored-paths", "crlf/.eslintignore"), + "utf8", + ); + + assert( + ignoreFileContent.includes("\r"), + "crlf/.eslintignore should contains CR.", + ); + + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + "crlf/.eslintignore", + ); + const engine = new CLIEngine({ ignorePath, cwd }); + + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "crlf/hide1/a.js"), + ), + ); + assert( + engine.isPathIgnored( + getFixturePath("ignored-paths", "crlf/hide2/a.js"), + ), + ); + assert( + !engine.isPathIgnored( + getFixturePath("ignored-paths", "crlf/hide3/a.js"), + ), + ); + }); + + it("should not include comments in ignore rules", () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + ".eslintignoreWithComments", + ); + const engine = new CLIEngine({ ignorePath, cwd }); + + assert(!engine.isPathIgnored("# should be ignored")); + assert(engine.isPathIgnored("this_one_not")); + }); + + it("should ignore a non-negated pattern", () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + ".eslintignoreWithNegation", + ); + const engine = new CLIEngine({ ignorePath, cwd }); + + assert( + engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "negation", + "ignore.js", + ), + ), + ); + }); + + it("should not ignore a negated pattern", () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + ".eslintignoreWithNegation", + ); + const engine = new CLIEngine({ ignorePath, cwd }); + + assert( + !engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "negation", + "unignore.js", + ), + ), + ); + }); + }); + + describe("with --ignore-path option and --ignore-pattern option", () => { + it("should return false for ignored file when unignored with ignore pattern", () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new CLIEngine({ + ignorePath: getFixturePath( + "ignored-paths", + ".eslintignore", + ), + ignorePattern: "!sampleignorepattern", + cwd, + }); + + assert( + !engine.isPathIgnored( + getFixturePath("ignored-paths", "sampleignorepattern"), + ), + ); + }); + }); + }); + + describe("getFormatter()", () => { + it("should return a function when a bundled formatter is requested", () => { + const engine = new CLIEngine(), + formatter = engine.getFormatter("json"); + + assert.isFunction(formatter); + }); + + it("should return a function when no argument is passed", () => { + const engine = new CLIEngine(), + formatter = engine.getFormatter(); + + assert.isFunction(formatter); + }); + + it("should return a function when a custom formatter is requested", () => { + const engine = new CLIEngine(), + formatter = engine.getFormatter( + getFixturePath("formatters", "simple.js"), + ); + + assert.isFunction(formatter); + }); + + it("should return a function when a custom formatter is requested, also if the path has backslashes", () => { + const engine = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + }), + formatter = engine.getFormatter( + ".\\fixtures\\formatters\\simple.js", + ); + + assert.isFunction(formatter); + }); + + it("should return a function when a formatter prefixed with eslint-formatter is requested", () => { + const engine = new CLIEngine({ + cwd: getFixturePath("cli-engine"), + }), + formatter = engine.getFormatter("bar"); + + assert.isFunction(formatter); + }); + + it("should return a function when a formatter is requested, also when the eslint-formatter prefix is included in the format argument", () => { + const engine = new CLIEngine({ + cwd: getFixturePath("cli-engine"), + }), + formatter = engine.getFormatter("eslint-formatter-bar"); + + assert.isFunction(formatter); + }); + + it("should return a function when a formatter is requested within a scoped npm package", () => { + const engine = new CLIEngine({ + cwd: getFixturePath("cli-engine"), + }), + formatter = engine.getFormatter("@somenamespace/foo"); + + assert.isFunction(formatter); + }); + + it("should return a function when a formatter is requested within a scoped npm package, also when the eslint-formatter prefix is included in the format argument", () => { + const engine = new CLIEngine({ + cwd: getFixturePath("cli-engine"), + }), + formatter = engine.getFormatter( + "@somenamespace/eslint-formatter-foo", + ); + + assert.isFunction(formatter); + }); + + it("should return null when a custom formatter doesn't exist", () => { + const engine = new CLIEngine(), + formatterPath = getFixturePath("formatters", "doesntexist.js"), + fullFormatterPath = path.resolve(formatterPath); + + assert.throws(() => { + engine.getFormatter(formatterPath); + }, `There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`); + }); + + it("should return null when a built-in formatter doesn't exist", () => { + const engine = new CLIEngine(); + const fullFormatterPath = path.resolve( + __dirname, + "../../../lib/cli-engine/formatters/special", + ); + + assert.throws(() => { + engine.getFormatter("special"); + }, `There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`); + }); + + it("should throw when a built-in formatter no longer exists", () => { + const engine = new CLIEngine(); + + assert.throws(() => { + engine.getFormatter("table"); + }, "The table formatter is no longer part of core ESLint. Install it manually with `npm install -D eslint-formatter-table`"); + + assert.throws(() => { + engine.getFormatter("codeframe"); + }, "The codeframe formatter is no longer part of core ESLint. Install it manually with `npm install -D eslint-formatter-codeframe`"); + }); + + it("should throw if the required formatter exists but has an error", () => { + const engine = new CLIEngine(), + formatterPath = getFixturePath("formatters", "broken.js"); + + assert.throws(() => { + engine.getFormatter(formatterPath); + }, `There was a problem loading formatter: ${formatterPath}\nError: Cannot find module 'this-module-does-not-exist'`); + }); + + it("should return null when a non-string formatter name is passed", () => { + const engine = new CLIEngine(), + formatter = engine.getFormatter(5); + + assert.isNull(formatter); + }); + + it("should return a function when called as a static function on CLIEngine", () => { + const formatter = CLIEngine.getFormatter(); + + assert.isFunction(formatter); + }); + + it("should return a function when called as a static function on CLIEngine and a custom formatter is requested", () => { + const formatter = CLIEngine.getFormatter( + getFixturePath("formatters", "simple.js"), + ); + + assert.isFunction(formatter); + }); + }); + + describe("getErrorResults()", () => { + it("should report 5 error messages when looking for errors only", () => { + process.chdir(originalDir); + const engine = new CLIEngine({ + useEslintrc: false, + baseConfig: { + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2, + }, + env: { + node: true, + }, + }, + }); + + const report = engine.executeOnText("var foo = 'bar';"); + const errorResults = CLIEngine.getErrorResults(report.results); + + assert.lengthOf(errorResults[0].messages, 5); + assert.strictEqual(errorResults[0].errorCount, 5); + assert.strictEqual(errorResults[0].fixableErrorCount, 3); + assert.strictEqual(errorResults[0].fixableWarningCount, 0); + assert.strictEqual(errorResults[0].messages[0].ruleId, "strict"); + assert.strictEqual(errorResults[0].messages[0].severity, 2); + assert.strictEqual(errorResults[0].messages[1].ruleId, "no-var"); + assert.strictEqual(errorResults[0].messages[1].severity, 2); + assert.strictEqual( + errorResults[0].messages[2].ruleId, + "no-unused-vars", + ); + assert.strictEqual(errorResults[0].messages[2].severity, 2); + assert.strictEqual(errorResults[0].messages[3].ruleId, "quotes"); + assert.strictEqual(errorResults[0].messages[3].severity, 2); + assert.strictEqual(errorResults[0].messages[4].ruleId, "eol-last"); + assert.strictEqual(errorResults[0].messages[4].severity, 2); + assert.lengthOf(errorResults[0].suppressedMessages, 0); + }); + + it("should report no error messages when looking for errors only", () => { + process.chdir(originalDir); + const engine = new CLIEngine({ + useEslintrc: false, + baseConfig: { + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2, + }, + env: { + node: true, + }, + }, + }); + + const report = engine.executeOnText( + "var foo = 'bar'; // eslint-disable-line strict, no-var, no-unused-vars, quotes, eol-last -- justification", + ); + const errorResults = CLIEngine.getErrorResults(report.results); + + assert.lengthOf(errorResults, 0); + }); + + it("should not mutate passed report.results parameter", () => { + process.chdir(originalDir); + const engine = new CLIEngine({ + useEslintrc: false, + rules: { + quotes: [1, "double"], + "no-var": 2, + }, + }); + + const report = engine.executeOnText("var foo = 'bar';"); + const reportResultsLength = report.results[0].messages.length; + + assert.strictEqual(report.results[0].messages.length, 2); + + CLIEngine.getErrorResults(report.results); + + assert.lengthOf(report.results[0].messages, reportResultsLength); + }); + + it("should report no suppressed error messages when looking for errors only", () => { + process.chdir(originalDir); + const engine = new CLIEngine({ + useEslintrc: false, + rules: { + quotes: 1, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2, + }, + env: { + node: true, + }, + }); + + const report = engine.executeOnText( + "var foo = 'bar'; // eslint-disable-line quotes -- justification\n", + ); + const errorResults = CLIEngine.getErrorResults(report.results); + + assert.lengthOf(report.results[0].messages, 3); + assert.lengthOf(report.results[0].suppressedMessages, 1); + assert.lengthOf(errorResults[0].messages, 3); + assert.lengthOf(errorResults[0].suppressedMessages, 0); + }); + + it("should report a warningCount of 0 when looking for errors only", () => { + process.chdir(originalDir); + const engine = new CLIEngine({ + useEslintrc: false, + baseConfig: { + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2, + }, + env: { + node: true, + }, + }, + }); + + const report = engine.executeOnText("var foo = 'bar';"); + const errorResults = CLIEngine.getErrorResults(report.results); + + assert.strictEqual(errorResults[0].warningCount, 0); + assert.strictEqual(errorResults[0].fixableWarningCount, 0); + }); + + it("should return 0 error or warning messages even when the file has warnings", () => { + const engine = new CLIEngine({ + ignorePath: path.join(fixtureDir, ".eslintignore"), + cwd: path.join(fixtureDir, ".."), + }); + + const report = engine.executeOnText( + "var bar = foo;", + "fixtures/passing.js", + true, + ); + const errorReport = CLIEngine.getErrorResults(report.results); + + assert.lengthOf(errorReport, 0); + assert.lengthOf(report.results, 1); + assert.strictEqual(report.errorCount, 0); + assert.strictEqual(report.warningCount, 1); + assert.strictEqual(report.fatalErrorCount, 0); + assert.strictEqual(report.fixableErrorCount, 0); + assert.strictEqual(report.fixableWarningCount, 0); + assert.strictEqual(report.results[0].errorCount, 0); + assert.strictEqual(report.results[0].warningCount, 1); + assert.strictEqual(report.results[0].fatalErrorCount, 0); + assert.strictEqual(report.results[0].fixableErrorCount, 0); + assert.strictEqual(report.results[0].fixableWarningCount, 0); + }); + + it("should return source code of file in the `source` property", () => { + process.chdir(originalDir); + const engine = new CLIEngine({ + useEslintrc: false, + rules: { quotes: [2, "double"] }, + }); + + const report = engine.executeOnText("var foo = 'bar';"); + const errorResults = CLIEngine.getErrorResults(report.results); + + assert.lengthOf(errorResults[0].messages, 1); + assert.strictEqual(errorResults[0].source, "var foo = 'bar';"); + }); + + it("should contain `output` property after fixes", () => { + process.chdir(originalDir); + const engine = new CLIEngine({ + useEslintrc: false, + fix: true, + rules: { + semi: 2, + "no-console": 2, + }, + }); + + const report = engine.executeOnText("console.log('foo')"); + const errorResults = CLIEngine.getErrorResults(report.results); + + assert.lengthOf(errorResults[0].messages, 1); + assert.strictEqual(errorResults[0].output, "console.log('foo');"); + }); + }); + + describe("outputFixes()", () => { + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it("should call fs.writeFileSync() for each result with output", () => { + const fakeFS = { + writeFileSync() {}, + }, + localCLIEngine = proxyquire( + "../../../lib/cli-engine/cli-engine", + { + "node:fs": fakeFS, + }, + ).CLIEngine, + report = { + results: [ + { + filePath: "foo.js", + output: "bar", + }, + { + filePath: "bar.js", + output: "baz", + }, + ], + }; + + const spy = sinon.spy(fakeFS, "writeFileSync"); + + localCLIEngine.outputFixes(report); + + assert.strictEqual(spy.callCount, 2); + assert.isTrue( + spy.firstCall.calledWithExactly("foo.js", "bar"), + "First call was incorrect.", + ); + assert.isTrue( + spy.secondCall.calledWithExactly("bar.js", "baz"), + "Second call was incorrect.", + ); + }); + + it("should call fs.writeFileSync() for each result with output and not at all for a result without output", () => { + const fakeFS = { + writeFileSync() {}, + }, + localCLIEngine = proxyquire( + "../../../lib/cli-engine/cli-engine", + { + "node:fs": fakeFS, + }, + ).CLIEngine, + report = { + results: [ + { + filePath: "foo.js", + output: "bar", + }, + { + filePath: "abc.js", + }, + { + filePath: "bar.js", + output: "baz", + }, + ], + }; + + const spy = sinon.spy(fakeFS, "writeFileSync"); + + localCLIEngine.outputFixes(report); + + assert.strictEqual(spy.callCount, 2); + assert.isTrue( + spy.firstCall.calledWithExactly("foo.js", "bar"), + "First call was incorrect.", + ); + assert.isTrue( + spy.secondCall.calledWithExactly("bar.js", "baz"), + "Second call was incorrect.", + ); + }); + }); + + describe("getRules()", () => { + it("should expose the list of rules", () => { + const engine = new CLIEngine(); + + assert(engine.getRules().has("no-eval"), "no-eval is present"); + }); + + it("should expose the list of plugin rules", () => { + const engine = new CLIEngine({ + plugins: ["eslint-plugin-eslint-plugin"], + }); + + assert( + engine.getRules().has("eslint-plugin/require-meta-schema"), + "eslint-plugin/require-meta-schema is present", + ); + }); + + it("should expose the list of rules from a preloaded plugin", () => { + const engine = new CLIEngine( + { + plugins: ["foo"], + }, + { + preloadedPlugins: { + foo: require("../../../tools/internal-rules"), + }, + }, + ); + + assert( + engine.getRules().has("foo/no-invalid-meta"), + "foo/no-invalid-meta is present", + ); + }); + }); + + describe("resolveFileGlobPatterns", () => { + [ + [".", ["**/*.{js}"]], + ["./", ["**/*.{js}"]], + ["../", ["../**/*.{js}"]], + ["", []], + ].forEach(([input, expected]) => { + it(`should correctly resolve ${input} to ${expected}`, () => { + const engine = new CLIEngine(); + + const result = engine.resolveFileGlobPatterns([input]); + + assert.deepStrictEqual(result, expected); + }); + }); + + it("should convert a directory name with no provided extensions into a glob pattern", () => { + const patterns = ["one-js-file"]; + const opts = { + cwd: getFixturePath("glob-util"), + }; + const result = new CLIEngine(opts).resolveFileGlobPatterns( + patterns, + ); + + assert.deepStrictEqual(result, ["one-js-file/**/*.{js}"]); + }); + + it("should not convert path with globInputPaths option false", () => { + const patterns = ["one-js-file"]; + const opts = { + cwd: getFixturePath("glob-util"), + globInputPaths: false, + }; + const result = new CLIEngine(opts).resolveFileGlobPatterns( + patterns, + ); + + assert.deepStrictEqual(result, ["one-js-file"]); + }); + + it("should convert an absolute directory name with no provided extensions into a posix glob pattern", () => { + const patterns = [getFixturePath("glob-util", "one-js-file")]; + const opts = { + cwd: getFixturePath("glob-util"), + }; + const result = new CLIEngine(opts).resolveFileGlobPatterns( + patterns, + ); + const expected = [ + `${getFixturePath("glob-util", "one-js-file").replace(/\\/gu, "/")}/**/*.{js}`, + ]; + + assert.deepStrictEqual(result, expected); + }); + + it("should convert a directory name with a single provided extension into a glob pattern", () => { + const patterns = ["one-js-file"]; + const opts = { + cwd: getFixturePath("glob-util"), + extensions: [".jsx"], + }; + const result = new CLIEngine(opts).resolveFileGlobPatterns( + patterns, + ); + + assert.deepStrictEqual(result, ["one-js-file/**/*.{jsx}"]); + }); + + it("should convert a directory name with multiple provided extensions into a glob pattern", () => { + const patterns = ["one-js-file"]; + const opts = { + cwd: getFixturePath("glob-util"), + extensions: [".jsx", ".js"], + }; + const result = new CLIEngine(opts).resolveFileGlobPatterns( + patterns, + ); + + assert.deepStrictEqual(result, ["one-js-file/**/*.{jsx,js}"]); + }); + + it("should convert multiple directory names into glob patterns", () => { + const patterns = ["one-js-file", "two-js-files"]; + const opts = { + cwd: getFixturePath("glob-util"), + }; + const result = new CLIEngine(opts).resolveFileGlobPatterns( + patterns, + ); + + assert.deepStrictEqual(result, [ + "one-js-file/**/*.{js}", + "two-js-files/**/*.{js}", + ]); + }); + + it("should remove leading './' from glob patterns", () => { + const patterns = ["./one-js-file"]; + const opts = { + cwd: getFixturePath("glob-util"), + }; + const result = new CLIEngine(opts).resolveFileGlobPatterns( + patterns, + ); + + assert.deepStrictEqual(result, ["one-js-file/**/*.{js}"]); + }); + + it("should convert a directory name with a trailing '/' into a glob pattern", () => { + const patterns = ["one-js-file/"]; + const opts = { + cwd: getFixturePath("glob-util"), + }; + const result = new CLIEngine(opts).resolveFileGlobPatterns( + patterns, + ); + + assert.deepStrictEqual(result, ["one-js-file/**/*.{js}"]); + }); + + it("should return filenames as they are", () => { + const patterns = ["some-file.js"]; + const opts = { + cwd: getFixturePath("glob-util"), + }; + const result = new CLIEngine(opts).resolveFileGlobPatterns( + patterns, + ); + + assert.deepStrictEqual(result, ["some-file.js"]); + }); + + it("should convert backslashes into forward slashes", () => { + const patterns = ["one-js-file\\example.js"]; + const opts = { + cwd: getFixturePath(), + }; + const result = new CLIEngine(opts).resolveFileGlobPatterns( + patterns, + ); + + assert.deepStrictEqual(result, ["one-js-file/example.js"]); + }); + }); + + describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { + it("should report a violation for disabling rules", () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + ].join("\n"); + const config = { + envs: ["browser"], + ignore: true, + useEslintrc: false, + allowInlineConfig: false, + rules: { + "eol-last": 0, + "no-alert": 1, + "no-trailing-spaces": 0, + strict: 0, + quotes: 0, + }, + }; + + const eslintCLI = new CLIEngine(config); + + const report = eslintCLI.executeOnText(code); + const { messages, suppressedMessages } = report.results[0]; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report a violation by default", () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + ].join("\n"); + const config = { + envs: ["browser"], + ignore: true, + useEslintrc: false, + + // allowInlineConfig: true is the default + rules: { + "eol-last": 0, + "no-alert": 1, + "no-trailing-spaces": 0, + strict: 0, + quotes: 0, + }, + }; + + const eslintCLI = new CLIEngine(config); + + const report = eslintCLI.executeOnText(code); + const { messages, suppressedMessages } = report.results[0]; + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + }); + }); + + describe("when evaluating code when reportUnusedDisableDirectives is enabled", () => { + it("should report problems for unused eslint-disable directives", () => { + const cliEngine = new CLIEngine({ + useEslintrc: false, + reportUnusedDisableDirectives: true, + }); + + assert.deepStrictEqual( + cliEngine.executeOnText("/* eslint-disable */"), + { + results: [ + { + filePath: "", + messages: [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 0, + source: "/* eslint-disable */", + }, + ], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 0, + usedDeprecatedRules: [], + }, + ); + }); + }); + + describe("when retrieving version number", () => { + it("should return current version number", () => { + const eslintCLI = require("../../../lib/cli-engine").CLIEngine; + const version = eslintCLI.version; + + assert.isString(version); + assert.isTrue(parseInt(version[0], 10) >= 3); + }); + }); + + describe("mutability", () => { + describe("plugins", () => { + it("Loading plugin in one instance doesn't mutate to another instance", () => { + const filePath = getFixturePath("single-quoted.js"); + const engine1 = cliEngineWithPlugins({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + plugins: ["example"], + rules: { "example/example-rule": 1 }, + }); + const engine2 = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + }); + const fileConfig1 = engine1.getConfigForFile(filePath); + const fileConfig2 = engine2.getConfigForFile(filePath); + + // plugin + assert.deepStrictEqual( + fileConfig1.plugins, + ["example"], + "Plugin is present for engine 1", + ); + assert.deepStrictEqual( + fileConfig2.plugins, + [], + "Plugin is not present for engine 2", + ); + }); + }); + + describe("rules", () => { + it("Loading rules in one instance doesn't mutate to another instance", () => { + const filePath = getFixturePath("single-quoted.js"); + const engine1 = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + rules: { "example/example-rule": 1 }, + }); + const engine2 = new CLIEngine({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + }); + const fileConfig1 = engine1.getConfigForFile(filePath); + const fileConfig2 = engine2.getConfigForFile(filePath); + + // plugin + assert.deepStrictEqual( + fileConfig1.rules["example/example-rule"], + [1], + "example is present for engine 1", + ); + assert.isUndefined( + fileConfig2.rules["example/example-rule"], + "example is not present for engine 2", + ); + }); + }); + }); + + describe("with ignorePatterns config", () => { + const root = getFixturePath("cli-engine/ignore-patterns"); + + describe("ignorePatterns can add an ignore pattern ('foo.js').", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": { + ignorePatterns: "foo.js", + }, + "foo.js": "", + "bar.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("foo.js"), true); + assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("bar.js"), false); + assert.strictEqual( + engine.isPathIgnored("subdir/bar.js"), + false, + ); + }); + + it("'executeOnFiles()' should not verify 'foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar.js"), + path.join(root, "subdir/bar.js"), + ]); + }); + }); + + describe("ignorePatterns can add ignore patterns ('foo.js', '/bar.js').", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": { + ignorePatterns: ["foo.js", "/bar.js"], + }, + "foo.js": "", + "bar.js": "", + "baz.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + "subdir/baz.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("foo.js"), true); + assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true); + }); + + it("'isPathIgnored()' should return 'true' for '/bar.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("bar.js"), true); + assert.strictEqual( + engine.isPathIgnored("subdir/bar.js"), + false, + ); + }); + + it("'executeOnFiles()' should not verify 'foo.js' and '/bar.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "baz.js"), + path.join(root, "subdir/bar.js"), + path.join(root, "subdir/baz.js"), + ]); + }); + }); + + describe("ignorePatterns can unignore '/node_modules/foo'.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": { + ignorePatterns: "!/node_modules/foo", + }, + "node_modules/foo/index.js": "", + "node_modules/foo/.dot.js": "", + "node_modules/bar/index.js": "", + "foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual( + engine.isPathIgnored("node_modules/foo/index.js"), + false, + ); + }); + + it("'isPathIgnored()' should return 'true' for 'node_modules/foo/.dot.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual( + engine.isPathIgnored("node_modules/foo/.dot.js"), + true, + ); + }); + + it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual( + engine.isPathIgnored("node_modules/bar/index.js"), + true, + ); + }); + + it("'executeOnFiles()' should verify 'node_modules/foo/index.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "foo.js"), + path.join(root, "node_modules/foo/index.js"), + ]); + }); + }); + + describe("ignorePatterns can unignore '.eslintrc.js'.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.js": `module.exports = ${JSON.stringify({ + ignorePatterns: "!.eslintrc.js", + })}`, + "foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for '.eslintrc.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored(".eslintrc.js"), false); + }); + + it("'executeOnFiles()' should verify '.eslintrc.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, ".eslintrc.js"), + path.join(root, "foo.js"), + ]); + }); + }); + + describe(".eslintignore can re-ignore files that are unignored by ignorePatterns.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.js": `module.exports = ${JSON.stringify({ + ignorePatterns: "!.*", + })}`, + ".eslintignore": ".foo*", + ".foo.js": "", + ".bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored(".foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored(".bar.js"), false); + }); + + it("'executeOnFiles()' should not verify re-ignored '.foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, ".bar.js"), + path.join(root, ".eslintrc.js"), + ]); + }); + }); + + describe(".eslintignore can unignore files that are ignored by ignorePatterns.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.js": `module.exports = ${JSON.stringify({ + ignorePatterns: "*.js", + })}`, + ".eslintignore": "!foo.js", + "foo.js": "", + "bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("foo.js"), false); + }); + + it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("bar.js"), true); + }); + + it("'executeOnFiles()' should verify unignored 'foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [path.join(root, "foo.js")]); + }); + }); + + describe("ignorePatterns in the config file in a child directory affects to only in the directory.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + ignorePatterns: "foo.js", + }), + "subdir/.eslintrc.json": JSON.stringify({ + ignorePatterns: "bar.js", + }), + "foo.js": "", + "bar.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + "subdir/subsubdir/foo.js": "", + "subdir/subsubdir/bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("foo.js"), true); + assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true); + assert.strictEqual( + engine.isPathIgnored("subdir/subsubdir/foo.js"), + true, + ); + }); + + it("'isPathIgnored()' should return 'true' for 'bar.js' in 'subdir'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true); + assert.strictEqual( + engine.isPathIgnored("subdir/subsubdir/bar.js"), + true, + ); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js' in the outside of 'subdir'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("bar.js"), false); + }); + + it("'executeOnFiles()' should verify 'bar.js' in the outside of 'subdir'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [path.join(root, "bar.js")]); + }); + }); + + describe("ignorePatterns in the config file in a child directory can unignore the ignored files in the parent directory's config.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + ignorePatterns: "foo.js", + }), + "subdir/.eslintrc.json": JSON.stringify({ + ignorePatterns: "!foo.js", + }), + "foo.js": "", + "subdir/foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual( + engine.isPathIgnored("subdir/foo.js"), + false, + ); + }); + + it("'executeOnFiles()' should verify 'foo.js' in the child directory.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "subdir/foo.js"), + ]); + }); + }); + + describe(".eslintignore can unignore files that are ignored by ignorePatterns in the config file in the child directory.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": {}, + "subdir/.eslintrc.json": { + ignorePatterns: "*.js", + }, + ".eslintignore": "!foo.js", + "foo.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("foo.js"), false); + assert.strictEqual( + engine.isPathIgnored("subdir/foo.js"), + false, + ); + }); + + it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true); + }); + + it("'executeOnFiles()' should verify unignored 'foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "foo.js"), + path.join(root, "subdir/foo.js"), + ]); + }); + }); + + describe("if the config in a child directory has 'root:true', ignorePatterns in the config file in the parent directory should not be used.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": { + ignorePatterns: "foo.js", + }, + "subdir/.eslintrc.json": { + root: true, + ignorePatterns: "bar.js", + }, + "foo.js": "", + "bar.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("bar.js"), false); + }); + + it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual( + engine.isPathIgnored("subdir/foo.js"), + false, + ); + }); + + it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true); + }); + + it("'executeOnFiles()' should verify 'bar.js' in the root directory and 'foo.js' in the child directory.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar.js"), + path.join(root, "subdir/foo.js"), + ]); + }); + }); + + describe("even if the config in a child directory has 'root:true', .eslintignore should be used.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({}), + "subdir/.eslintrc.json": JSON.stringify({ + root: true, + ignorePatterns: "bar.js", + }), + ".eslintignore": "foo.js", + "foo.js": "", + "bar.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("foo.js"), true); + assert.strictEqual(engine.isPathIgnored("subdir/foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("bar.js"), false); + }); + + it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("subdir/bar.js"), true); + }); + + it("'executeOnFiles()' should verify 'bar.js' in the root directory.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [path.join(root, "bar.js")]); + }); + }); + + describe("ignorePatterns in the shareable config should be used.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify( + { + ignorePatterns: "foo.js", + }, + )}`, + ".eslintrc.json": JSON.stringify({ + extends: "one", + }), + "foo.js": "", + "bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("bar.js"), false); + }); + + it("'executeOnFiles()' should verify 'bar.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [path.join(root, "bar.js")]); + }); + }); + + describe("ignorePatterns in the shareable config should be relative to the entry config file.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify( + { + ignorePatterns: "/foo.js", + }, + )}`, + ".eslintrc.json": JSON.stringify({ + extends: "one", + }), + "foo.js": "", + "subdir/foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'subdir/foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual( + engine.isPathIgnored("subdir/foo.js"), + false, + ); + }); + + it("'executeOnFiles()' should verify 'subdir/foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "subdir/foo.js"), + ]); + }); + }); + + describe("ignorePatterns in a config file can unignore the files which are ignored by ignorePatterns in the shareable config.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify( + { + ignorePatterns: "*.js", + }, + )}`, + ".eslintrc.json": JSON.stringify({ + extends: "one", + ignorePatterns: "!bar.js", + }), + "foo.js": "", + "bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assert.strictEqual(engine.isPathIgnored("bar.js"), false); + }); + + it("'executeOnFiles()' should verify 'bar.js'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [path.join(root, "bar.js")]); + }); + }); + + describe("ignorePatterns in a config file should not be used if --no-ignore option was given.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + ignorePatterns: "*.js", + }), + "foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for 'foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath(), ignore: false }); + + assert.strictEqual(engine.isPathIgnored("foo.js"), false); + }); + + it("'executeOnFiles()' should verify 'foo.js'.", () => { + const engine = new CLIEngine({ cwd: getPath(), ignore: false }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [path.join(root, "foo.js")]); + }); + }); + + describe("ignorePatterns in overrides section is not allowed.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.js": `module.exports = ${JSON.stringify({ + overrides: [ + { + files: "*.js", + ignorePatterns: "foo.js", + }, + ], + })}`, + "foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should throw a configuration error.", () => { + assert.throws(() => { + const engine = new CLIEngine({ cwd: getPath() }); + + engine.executeOnFiles("*.js"); + }, 'Unexpected top-level property "overrides[0].ignorePatterns"'); + }); + }); + }); + + describe("'overrides[].files' adds lint targets", () => { + const root = getFixturePath("cli-engine/additional-lint-targets"); + + describe("if { files: 'foo/*.txt', excludedFiles: '**/ignore.txt' } is present,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + overrides: [ + { + files: "foo/*.txt", + excludedFiles: "**/ignore.txt", + }, + ], + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "foo/ignore.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "bar/ignore.txt": "", + "test.js": "", + "test.txt": "", + "ignore.txt": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' with a directory path should contain 'foo/test.txt'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles(".") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/test.js"), + path.join(root, "foo/test.txt"), + path.join(root, "test.js"), + ]); + }); + + it("'executeOnFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/test.js"), + path.join(root, "test.js"), + ]); + }); + }); + + describe("if { files: 'foo/**/*.txt' } is present,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + overrides: [ + { + files: "foo/**/*.txt", + }, + ], + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "test.js": "", + "test.txt": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles(".") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/nested/test.txt"), + path.join(root, "foo/test.js"), + path.join(root, "foo/test.txt"), + path.join(root, "test.js"), + ]); + }); + }); + + describe("if { files: 'foo/**/*' } is present,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + overrides: [ + { + files: "foo/**/*", + }, + ], + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "test.js": "", + "test.txt": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles(".") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/test.js"), + path.join(root, "test.js"), + ]); + }); + }); + + describe("if { files: 'foo/**/*.txt' } is present in a shareable config,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-foo/index.js": `module.exports = ${JSON.stringify( + { + overrides: [ + { + files: "foo/**/*.txt", + }, + ], + }, + )}`, + ".eslintrc.json": JSON.stringify({ + extends: "foo", + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "test.js": "", + "test.txt": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles(".") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/nested/test.txt"), + path.join(root, "foo/test.js"), + path.join(root, "foo/test.txt"), + path.join(root, "test.js"), + ]); + }); + }); + + describe("if { files: 'foo/**/*.txt' } is present in a plugin config,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-plugin-foo/index.js": `exports.configs = ${JSON.stringify( + { + bar: { + overrides: [ + { + files: "foo/**/*.txt", + }, + ], + }, + }, + )}`, + ".eslintrc.json": JSON.stringify({ + extends: "plugin:foo/bar", + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "test.js": "", + "test.txt": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const filePaths = engine + .executeOnFiles(".") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/nested/test.txt"), + path.join(root, "foo/test.js"), + path.join(root, "foo/test.txt"), + path.join(root, "test.js"), + ]); + }); + }); + }); + + describe("'ignorePatterns', 'overrides[].files', and 'overrides[].excludedFiles' of the configuration that the '--config' option provided should be resolved from CWD.", () => { + const root = getFixturePath("cli-engine/config-and-overrides-files"); + + describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/myconf/.eslintrc.json": JSON.stringify({ + overrides: [ + { + files: "foo/*.js", + rules: { + eqeqeq: "error", + }, + }, + ], + }), + "node_modules/myconf/foo/test.js": "a == b", + "foo/test.js": "a == b", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' with 'foo/test.js' should use the override entry.", () => { + const engine = new CLIEngine({ + configFile: "node_modules/myconf/.eslintrc.json", + cwd: getPath(), + ignore: false, + useEslintrc: false, + }); + const { results } = engine.executeOnFiles("foo/test.js"); + + // Expected to be an 'eqeqeq' error because the file matches to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + errorCount: 1, + filePath: path.join(root, "foo/test.js"), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [ + { + column: 3, + endColumn: 5, + endLine: 1, + line: 1, + message: "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + suggestions: [ + { + data: { + actualOperator: "==", + expectedOperator: "===", + }, + desc: "Use '===' instead of '=='.", + fix: { + range: [2, 4], + text: "===", + }, + messageId: "replaceOperator", + }, + ], + severity: 2, + }, + ], + suppressedMessages: [], + source: "a == b", + warningCount: 0, + fatalErrorCount: 0, + }, + ]); + }); + + it("'executeOnFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the override entry.", () => { + const engine = new CLIEngine({ + configFile: "node_modules/myconf/.eslintrc.json", + cwd: getPath(), + ignore: false, + useEslintrc: false, + }); + const { results } = engine.executeOnFiles( + "node_modules/myconf/foo/test.js", + ); + + // Expected to be no errors because the file doesn't match to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + errorCount: 0, + filePath: path.join( + root, + "node_modules/myconf/foo/test.js", + ), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [], + suppressedMessages: [], + warningCount: 0, + fatalErrorCount: 0, + }, + ]); + }); + }); + + describe("if { files: '*', excludedFiles: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/myconf/.eslintrc.json": JSON.stringify({ + overrides: [ + { + files: "*", + excludedFiles: "foo/*.js", + rules: { + eqeqeq: "error", + }, + }, + ], + }), + "node_modules/myconf/foo/test.js": "a == b", + "foo/test.js": "a == b", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' with 'foo/test.js' should NOT use the override entry.", () => { + const engine = new CLIEngine({ + configFile: "node_modules/myconf/.eslintrc.json", + cwd: getPath(), + ignore: false, + useEslintrc: false, + }); + const { results } = engine.executeOnFiles("foo/test.js"); + + // Expected to be no errors because the file matches to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + errorCount: 0, + filePath: path.join(root, "foo/test.js"), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [], + suppressedMessages: [], + warningCount: 0, + fatalErrorCount: 0, + }, + ]); + }); + + it("'executeOnFiles()' with 'node_modules/myconf/foo/test.js' should use the override entry.", () => { + const engine = new CLIEngine({ + configFile: "node_modules/myconf/.eslintrc.json", + cwd: getPath(), + ignore: false, + useEslintrc: false, + }); + const { results } = engine.executeOnFiles( + "node_modules/myconf/foo/test.js", + ); + + // Expected to be an 'eqeqeq' error because the file doesn't match to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + errorCount: 1, + filePath: path.join( + root, + "node_modules/myconf/foo/test.js", + ), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [ + { + column: 3, + endColumn: 5, + endLine: 1, + line: 1, + message: "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + severity: 2, + suggestions: [ + { + data: { + actualOperator: "==", + expectedOperator: "===", + }, + desc: "Use '===' instead of '=='.", + fix: { + range: [2, 4], + text: "===", + }, + messageId: "replaceOperator", + }, + ], + }, + ], + suppressedMessages: [], + source: "a == b", + warningCount: 0, + fatalErrorCount: 0, + }, + ]); + }); + }); + + describe("if { ignorePatterns: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/myconf/.eslintrc.json": JSON.stringify({ + ignorePatterns: ["!/node_modules/myconf", "foo/*.js"], + rules: { + eqeqeq: "error", + }, + }), + "node_modules/myconf/foo/test.js": "a == b", + "foo/test.js": "a == b", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' with '**/*.js' should iterate 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", () => { + const engine = new CLIEngine({ + configFile: "node_modules/myconf/.eslintrc.json", + cwd: getPath(), + useEslintrc: false, + }); + const files = engine + .executeOnFiles("**/*.js") + .results.map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(files, [ + path.join(root, "node_modules/myconf/foo/test.js"), + ]); + }); + }); + }); + + describe("plugin conflicts", () => { + let uid = 0; + const root = getFixturePath("cli-engine/plugin-conflicts-"); + + /** + * Verify thrown errors. + * @param {() => void} f The function to run and throw. + * @param {Record} props The properties to verify. + * @returns {void} + */ + function assertThrows(f, props) { + try { + f(); + } catch (error) { + for (const [key, value] of Object.entries(props)) { + assert.deepStrictEqual(error[key], value, key); + } + return; + } + + assert.fail("Function should throw an error, but not."); + } + + describe("between a config file and linear extendees.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": + "", + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify( + { + extends: ["two"], + plugins: ["foo"], + }, + )}`, + "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": + "", + "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify( + { + plugins: ["foo"], + }, + )}`, + ".eslintrc.json": JSON.stringify({ + extends: ["one"], + plugins: ["foo"], + }), + "test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + engine.executeOnFiles("test.js"); + }); + }); + + describe("between a config file and same-depth extendees.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": + "", + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify( + { + plugins: ["foo"], + }, + )}`, + "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": + "", + "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify( + { + plugins: ["foo"], + }, + )}`, + ".eslintrc.json": JSON.stringify({ + extends: ["one", "two"], + plugins: ["foo"], + }), + "test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + engine.executeOnFiles("test.js"); + }); + }); + + describe("between two config files in different directories, with single node_modules.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + engine.executeOnFiles("subdir/test.js"); + }); + }); + + describe("between two config files in different directories, with multiple node_modules.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/node_modules/eslint-plugin-foo/index.js": "", + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", () => { + const engine = new CLIEngine({ cwd: getPath() }); + + assertThrows(() => engine.executeOnFiles("subdir/test.js"), { + message: `Plugin "foo" was conflicted between "subdir${path.sep}.eslintrc.json" and ".eslintrc.json".`, + messageTemplate: "plugin-conflict", + messageData: { + pluginId: "foo", + plugins: [ + { + filePath: path.join( + getPath(), + "subdir/node_modules/eslint-plugin-foo/index.js", + ), + importerName: `subdir${path.sep}.eslintrc.json`, + }, + { + filePath: path.join( + getPath(), + "node_modules/eslint-plugin-foo/index.js", + ), + importerName: ".eslintrc.json", + }, + ], + }, + }); + }); + }); + + describe("between '--config' option and a regular config file, with single node_modules.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "node_modules/mine/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", () => { + const engine = new CLIEngine({ + cwd: getPath(), + configFile: "node_modules/mine/.eslintrc.json", + }); + + engine.executeOnFiles("test.js"); + }); + }); + + describe("between '--config' option and a regular config file, with multiple node_modules.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "node_modules/mine/node_modules/eslint-plugin-foo/index.js": + "", + "node_modules/mine/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", () => { + const engine = new CLIEngine({ + cwd: getPath(), + configFile: "node_modules/mine/.eslintrc.json", + }); + + assertThrows(() => engine.executeOnFiles("test.js"), { + message: + 'Plugin "foo" was conflicted between "--config" and ".eslintrc.json".', + messageTemplate: "plugin-conflict", + messageData: { + pluginId: "foo", + plugins: [ + { + filePath: path.join( + getPath(), + "node_modules/mine/node_modules/eslint-plugin-foo/index.js", + ), + importerName: "--config", + }, + { + filePath: path.join( + getPath(), + "node_modules/eslint-plugin-foo/index.js", + ), + importerName: ".eslintrc.json", + }, + ], + }, + }); + }); + }); + + describe("between '--plugin' option and a regular config file, with single node_modules.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file, but node_modules directory is unique.)", () => { + const engine = new CLIEngine({ + cwd: getPath(), + plugins: ["foo"], + }); + + engine.executeOnFiles("subdir/test.js"); + }); + }); + + describe("between '--plugin' option and a regular config file, with multiple node_modules.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "subdir/node_modules/eslint-plugin-foo/index.js": "", + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' should throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file.)", () => { + const engine = new CLIEngine({ + cwd: getPath(), + plugins: ["foo"], + }); + + assertThrows(() => engine.executeOnFiles("subdir/test.js"), { + message: `Plugin "foo" was conflicted between "CLIOptions" and "subdir${path.sep}.eslintrc.json".`, + messageTemplate: "plugin-conflict", + messageData: { + pluginId: "foo", + plugins: [ + { + filePath: path.join( + getPath(), + "node_modules/eslint-plugin-foo/index.js", + ), + importerName: "CLIOptions", + }, + { + filePath: path.join( + getPath(), + "subdir/node_modules/eslint-plugin-foo/index.js", + ), + importerName: `subdir${path.sep}.eslintrc.json`, + }, + ], + }, + }); + }); + }); + + describe("'--resolve-plugins-relative-to' option overrides the location that ESLint load plugins from.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/node_modules/eslint-plugin-foo/index.js": "", + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from '--resolve-plugins-relative-to'.)", () => { + const engine = new CLIEngine({ + cwd: getPath(), + resolvePluginsRelativeTo: getPath(), + }); + + engine.executeOnFiles("subdir/test.js"); + }); + }); + + describe("between two config files with different target files.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "one/node_modules/eslint-plugin-foo/index.js": "", + "one/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "one/test.js": "", + "two/node_modules/eslint-plugin-foo/index.js": "", + "two/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "two/test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'executeOnFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file for each target file. Not related to each other.)", () => { + const engine = new CLIEngine({ cwd: getPath() }); + const { results } = engine.executeOnFiles("*/test.js"); + + assert.strictEqual(results.length, 2); + }); + }); + }); }); diff --git a/tests/lib/cli-engine/file-enumerator.js b/tests/lib/cli-engine/file-enumerator.js index 3a96cf5eb84c..725ba7bb3eb0 100644 --- a/tests/lib/cli-engine/file-enumerator.js +++ b/tests/lib/cli-engine/file-enumerator.js @@ -8,16 +8,14 @@ // Requirements //------------------------------------------------------------------------------ -const fs = require("fs"); -const path = require("path"); -const os = require("os"); +const fs = require("node:fs"); +const path = require("node:path"); +const os = require("node:os"); const { assert } = require("chai"); const sh = require("shelljs"); const sinon = require("sinon"); const { - Legacy: { - CascadingConfigArrayFactory - } + Legacy: { CascadingConfigArrayFactory }, } = require("@eslint/eslintrc"); const { createCustomTeardown } = require("../../_utils"); const { FileEnumerator } = require("../../../lib/cli-engine/file-enumerator"); @@ -27,615 +25,832 @@ const { FileEnumerator } = require("../../../lib/cli-engine/file-enumerator"); //------------------------------------------------------------------------------ describe("FileEnumerator", () => { - describe("'iterateFiles(patterns)' method should iterate files and configs.", () => { - describe("with three directories ('lib', 'lib/nested', 'test') that contains 'one.js' and 'two.js'", () => { - const root = path.join(os.tmpdir(), "eslint/file-enumerator"); - const files = { - "lib/nested/one.js": "", - "lib/nested/two.js": "", - "lib/nested/parser.js": "", - "lib/nested/.eslintrc.yml": "parser: './parser'", - "lib/one.js": "", - "lib/two.js": "", - "test/one.js": "", - "test/two.js": "", - "test/.eslintrc.yml": "env: { mocha: true }", - ".eslintignore": "/lib/nested/parser.js", - ".eslintrc.json": JSON.stringify({ - rules: { - "no-undef": "error", - "no-unused-vars": "error" - } - }) - }; - const { prepare, cleanup, getPath } = createCustomTeardown({ cwd: root, files }); - - /** @type {FileEnumerator} */ - let enumerator; - - beforeEach(async () => { - await prepare(); - enumerator = new FileEnumerator({ cwd: getPath() }); - }); - - afterEach(cleanup); - - it("should ignore empty strings.", () => { - Array.from(enumerator.iterateFiles(["lib/*.js", ""])); // don't throw "file not found" error. - }); - - describe("if 'lib/*.js' was given,", () => { - - /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */ - let list; - - beforeEach(() => { - list = [...enumerator.iterateFiles("lib/*.js")]; - }); - - it("should list two files.", () => { - assert.strictEqual(list.length, 2); - }); - - it("should list 'lib/one.js' and 'lib/two.js'.", () => { - assert.deepStrictEqual( - list.map(entry => entry.filePath), - [ - path.join(root, "lib/one.js"), - path.join(root, "lib/two.js") - ] - ); - }); - - it("should use the config '.eslintrc.json' for both files.", () => { - assert.strictEqual(list[0].config, list[1].config); - assert.strictEqual(list[0].config.length, 3); - assert.strictEqual(list[0].config[0].name, "DefaultIgnorePattern"); - assert.strictEqual(list[0].config[1].filePath, path.join(root, ".eslintrc.json")); - assert.strictEqual(list[0].config[2].filePath, path.join(root, ".eslintignore")); - }); - }); - - describe("if 'lib/**/*.js' was given,", () => { - - /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */ - let list; - - beforeEach(() => { - list = [...enumerator.iterateFiles("lib/**/*.js")]; - }); - - it("should list four files.", () => { - assert.strictEqual(list.length, 4); - }); - - it("should list 'lib/nested/one.js', 'lib/nested/two.js', 'lib/one.js', 'lib/two.js'.", () => { - assert.deepStrictEqual( - list.map(entry => entry.filePath), - [ - path.join(root, "lib/nested/one.js"), - path.join(root, "lib/nested/two.js"), - path.join(root, "lib/one.js"), - path.join(root, "lib/two.js") - ] - ); - }); - - it("should use the merged config of '.eslintrc.json' and 'lib/nested/.eslintrc.yml' for 'lib/nested/one.js' and 'lib/nested/two.js'.", () => { - assert.strictEqual(list[0].config, list[1].config); - assert.strictEqual(list[0].config.length, 4); - assert.strictEqual(list[0].config[0].name, "DefaultIgnorePattern"); - assert.strictEqual(list[0].config[1].filePath, path.join(root, ".eslintrc.json")); - assert.strictEqual(list[0].config[2].filePath, path.join(root, "lib/nested/.eslintrc.yml")); - assert.strictEqual(list[0].config[3].filePath, path.join(root, ".eslintignore")); - }); - - it("should use the config '.eslintrc.json' for 'lib/one.js' and 'lib/two.js'.", () => { - assert.strictEqual(list[2].config, list[3].config); - assert.strictEqual(list[2].config.length, 3); - assert.strictEqual(list[2].config[0].name, "DefaultIgnorePattern"); - assert.strictEqual(list[2].config[1].filePath, path.join(root, ".eslintrc.json")); - assert.strictEqual(list[2].config[2].filePath, path.join(root, ".eslintignore")); - }); - }); - - describe("if 'lib/*.js' and 'test/*.js' were given,", () => { - - /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */ - let list; - - beforeEach(() => { - list = [...enumerator.iterateFiles(["lib/*.js", "test/*.js"])]; - }); - - it("should list four files.", () => { - assert.strictEqual(list.length, 4); - }); - - it("should list 'lib/one.js', 'lib/two.js', 'test/one.js', 'test/two.js'.", () => { - assert.deepStrictEqual( - list.map(entry => entry.filePath), - [ - path.join(root, "lib/one.js"), - path.join(root, "lib/two.js"), - path.join(root, "test/one.js"), - path.join(root, "test/two.js") - ] - ); - }); - - it("should use the config '.eslintrc.json' for 'lib/one.js' and 'lib/two.js'.", () => { - assert.strictEqual(list[0].config, list[1].config); - assert.strictEqual(list[0].config.length, 3); - assert.strictEqual(list[0].config[0].name, "DefaultIgnorePattern"); - assert.strictEqual(list[0].config[1].filePath, path.join(root, ".eslintrc.json")); - assert.strictEqual(list[0].config[2].filePath, path.join(root, ".eslintignore")); - }); - - it("should use the merged config of '.eslintrc.json' and 'test/.eslintrc.yml' for 'test/one.js' and 'test/two.js'.", () => { - assert.strictEqual(list[2].config, list[3].config); - assert.strictEqual(list[2].config.length, 4); - assert.strictEqual(list[2].config[0].name, "DefaultIgnorePattern"); - assert.strictEqual(list[2].config[1].filePath, path.join(root, ".eslintrc.json")); - assert.strictEqual(list[2].config[2].filePath, path.join(root, "test/.eslintrc.yml")); - assert.strictEqual(list[2].config[3].filePath, path.join(root, ".eslintignore")); - }); - }); - }); - - // https://github.com/eslint/eslint/issues/14742 - describe("with 5 directories ('{lib}', '{lib}/client', '{lib}/client/src', '{lib}/server', '{lib}/server/src') that contains two files '{lib}/client/src/one.js' and '{lib}/server/src/two.js'", () => { - const root = path.join(os.tmpdir(), "eslint/file-enumerator"); - const files = { - "{lib}/client/src/one.js": "console.log('one.js');", - "{lib}/server/src/two.js": "console.log('two.js');", - "{lib}/client/.eslintrc.json": JSON.stringify({ - rules: { - "no-console": "error" - }, - env: { - mocha: true - } - }), - "{lib}/server/.eslintrc.json": JSON.stringify({ - rules: { - "no-console": "off" - }, - env: { - mocha: true - } - }) - }; - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files - }); - - /** @type {FileEnumerator} */ - let enumerator; - - beforeEach(async () => { - await prepare(); - enumerator = new FileEnumerator({ - cwd: path.resolve(getPath("{lib}/server")) - }); - }); - - afterEach(cleanup); - - describe("when running eslint in the server directory", () => { - it("should use the config '{lib}/server/.eslintrc.json' for '{lib}/server/src/two.js'.", () => { - const spy = sinon.spy(fs, "readdirSync"); - - const list = [ - ...enumerator.iterateFiles(["src/**/*.{js,json}"]) - ]; - - // should enter the directory '{lib}/server/src' directly - assert.strictEqual(spy.getCall(0).firstArg, path.join(root, "{lib}/server/src")); - assert.strictEqual(list.length, 1); - assert.strictEqual(list[0].config.length, 2); - assert.strictEqual(list[0].config[0].name, "DefaultIgnorePattern"); - assert.strictEqual(list[0].config[1].filePath, getPath("{lib}/server/.eslintrc.json")); - assert.deepStrictEqual( - list.map(entry => entry.filePath), - [ - path.join(root, "{lib}/server/src/two.js") - ] - ); - - // destroy the spy - sinon.restore(); - }); - }); - }); - - // This group moved from 'tests/lib/util/glob-utils.js' when refactoring to keep the cumulated test cases. - describe("with 'tests/fixtures/glob-utils' files", () => { - let fixtureDir; - - /** - * Returns the path inside of the fixture directory. - * @param {...string} args file path segments. - * @returns {string} The path inside the fixture directory. - * @private - */ - function getFixturePath(...args) { - return path.join(fs.realpathSync(fixtureDir), ...args); - } - - /** - * List files as a compatible shape with glob-utils. - * @param {string|string[]} patterns The patterns to list files. - * @param {Object} options The option for FileEnumerator. - * @returns {{filename:string,ignored:boolean}[]} The listed files. - */ - function listFiles(patterns, options) { - return Array.from( - new FileEnumerator({ - ...options, - configArrayFactory: new CascadingConfigArrayFactory({ - ...options, - - // Disable "No Configuration Found" error. - useEslintrc: false - }) - }).iterateFiles(patterns), - ({ filePath, ignored }) => ({ filename: filePath, ignored }) - ); - } - - before(function() { - - /* - * GitHub Actions Windows and macOS runners occasionally - * exhibit extremely slow filesystem operations, during which - * copying fixtures exceeds the default test timeout, so raise - * it just for this hook. Mocha uses `this` to set timeouts on - * an individual hook level. - */ - this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API - fixtureDir = `${os.tmpdir()}/eslint/tests/fixtures/`; - sh.mkdir("-p", fixtureDir); - sh.cp("-r", "./tests/fixtures/*", fixtureDir); - }); - - after(() => { - sh.rm("-r", fixtureDir); - }); - - describe("listFilesToProcess()", () => { - it("should return an array with a resolved (absolute) filename", () => { - const patterns = [getFixturePath("glob-util", "one-js-file", "**/*.js")]; - const result = listFiles(patterns, { - cwd: getFixturePath() - }); - - const file1 = getFixturePath("glob-util", "one-js-file", "baz.js"); - - assert.isArray(result); - assert.deepStrictEqual(result, [{ filename: file1, ignored: false }]); - }); - - it("should return all files matching a glob pattern", () => { - const patterns = [getFixturePath("glob-util", "two-js-files", "**/*.js")]; - const result = listFiles(patterns, { - cwd: getFixturePath() - }); - - const file1 = getFixturePath("glob-util", "two-js-files", "bar.js"); - const file2 = getFixturePath("glob-util", "two-js-files", "foo.js"); - - assert.strictEqual(result.length, 2); - assert.deepStrictEqual(result, [ - { filename: file1, ignored: false }, - { filename: file2, ignored: false } - ]); - }); - - it("should return all files matching multiple glob patterns", () => { - const patterns = [ - getFixturePath("glob-util", "two-js-files", "**/*.js"), - getFixturePath("glob-util", "one-js-file", "**/*.js") - ]; - const result = listFiles(patterns, { - cwd: getFixturePath() - }); - - const file1 = getFixturePath("glob-util", "two-js-files", "bar.js"); - const file2 = getFixturePath("glob-util", "two-js-files", "foo.js"); - const file3 = getFixturePath("glob-util", "one-js-file", "baz.js"); - - assert.strictEqual(result.length, 3); - assert.deepStrictEqual(result, [ - { filename: file1, ignored: false }, - { filename: file2, ignored: false }, - { filename: file3, ignored: false } - ]); - }); - - it("should ignore hidden files for standard glob patterns", () => { - const patterns = [getFixturePath("glob-util", "hidden", "**/*.js")]; - - assert.throws(() => { - listFiles(patterns, { - cwd: getFixturePath() - }); - }, `All files matched by '${patterns[0]}' are ignored.`); - }); - - it("should return hidden files if included in glob pattern", () => { - const patterns = [getFixturePath("glob-util", "hidden", "**/.*.js")]; - const result = listFiles(patterns, { - cwd: getFixturePath() - }); - - const file1 = getFixturePath("glob-util", "hidden", ".foo.js"); - - assert.strictEqual(result.length, 1); - assert.deepStrictEqual(result, [ - { filename: file1, ignored: false } - ]); - }); - - it("should ignore default ignored files if not passed explicitly", () => { - const directory = getFixturePath("glob-util", "hidden"); - const patterns = [directory]; - - assert.throws(() => { - listFiles(patterns, { - cwd: getFixturePath() - }); - }, `All files matched by '${directory}' are ignored.`); - }); - - it("should ignore and warn for default ignored files when passed explicitly", () => { - const filename = getFixturePath("glob-util", "hidden", ".foo.js"); - const patterns = [filename]; - const result = listFiles(patterns, { - cwd: getFixturePath() - }); - - assert.strictEqual(result.length, 1); - assert.deepStrictEqual(result[0], { filename, ignored: true }); - }); - - it("should ignore default ignored files if not passed explicitly even if ignore is false", () => { - const directory = getFixturePath("glob-util", "hidden"); - const patterns = [directory]; - - assert.throws(() => { - listFiles(patterns, { - cwd: getFixturePath(), - ignore: false - }); - }, `All files matched by '${directory}' are ignored.`); - }); - - it("should not ignore default ignored files when passed explicitly if ignore is false", () => { - const filename = getFixturePath("glob-util", "hidden", ".foo.js"); - const patterns = [filename]; - const result = listFiles(patterns, { - cwd: getFixturePath(), - ignore: false - }); - - assert.strictEqual(result.length, 1); - assert.deepStrictEqual(result[0], { filename, ignored: false }); - }); - - it("should throw an error for a file which does not exist", () => { - const filename = getFixturePath("glob-util", "hidden", "bar.js"); - const patterns = [filename]; - - assert.throws(() => { - listFiles(patterns, { - cwd: getFixturePath(), - allowMissingGlobs: true - }); - }, `No files matching '${filename}' were found.`); - }); - - it("should throw if a folder that does not have any applicable files is linted", () => { - const filename = getFixturePath("glob-util", "empty"); - const patterns = [filename]; - - assert.throws(() => { - listFiles(patterns, { - cwd: getFixturePath() - }); - }, `No files matching '${filename}' were found.`); - }); - - it("should throw if only ignored files match a glob", () => { - const pattern = getFixturePath("glob-util", "ignored"); - const options = { ignore: true, ignorePath: getFixturePath("glob-util", "ignored", ".eslintignore") }; - - assert.throws(() => { - listFiles([pattern], options); - }, `All files matched by '${pattern}' are ignored.`); - }); - - it("should throw an error if no files match a glob", () => { - - // Relying here on the .eslintignore from the repo root - const patterns = ["tests/fixtures/glob-util/ignored/**/*.js"]; - - assert.throws(() => { - listFiles(patterns); - }, `All files matched by '${patterns[0]}' are ignored.`); - }); - - it("should return an ignored file, if ignore option is turned off", () => { - const options = { ignore: false }; - const patterns = [getFixturePath("glob-util", "ignored", "**/*.js")]; - const result = listFiles(patterns, options); - - assert.strictEqual(result.length, 1); - }); - - it("should ignore a file from a glob if it matches a pattern in an ignore file", () => { - const options = { ignore: true, ignorePath: getFixturePath("glob-util", "ignored", ".eslintignore") }; - const patterns = [getFixturePath("glob-util", "ignored", "**/*.js")]; - - assert.throws(() => { - listFiles(patterns, options); - }, `All files matched by '${patterns[0]}' are ignored.`); - }); - - it("should ignore a file from a glob if matching a specified ignore pattern", () => { - const options = { ignore: true, cliConfig: { ignorePatterns: ["foo.js"] }, cwd: getFixturePath() }; - const patterns = [getFixturePath("glob-util", "ignored", "**/*.js")]; - - assert.throws(() => { - listFiles(patterns, options); - }, `All files matched by '${patterns[0]}' are ignored.`); - }); - - it("should return a file only once if listed in more than 1 pattern", () => { - const patterns = [ - getFixturePath("glob-util", "one-js-file", "**/*.js"), - getFixturePath("glob-util", "one-js-file", "baz.js") - ]; - const result = listFiles(patterns, { - cwd: path.join(fixtureDir, "..") - }); - - const file1 = getFixturePath("glob-util", "one-js-file", "baz.js"); - - assert.isArray(result); - assert.deepStrictEqual(result, [ - { filename: file1, ignored: false } - ]); - }); - - it("should set 'ignored: true' for files that are explicitly specified but ignored", () => { - const options = { ignore: true, cliConfig: { ignorePatterns: ["foo.js"] }, cwd: getFixturePath() }; - const filename = getFixturePath("glob-util", "ignored", "foo.js"); - const patterns = [filename]; - const result = listFiles(patterns, options); - - assert.strictEqual(result.length, 1); - assert.deepStrictEqual(result, [ - { filename, ignored: true } - ]); - }); - - it("should not return files from default ignored folders", () => { - const options = { cwd: getFixturePath("glob-util") }; - const glob = getFixturePath("glob-util", "**/*.js"); - const patterns = [glob]; - const result = listFiles(patterns, options); - const resultFilenames = result.map(resultObj => resultObj.filename); - - assert.notInclude(resultFilenames, getFixturePath("glob-util", "node_modules", "dependency.js")); - }); - - it("should return unignored files from default ignored folders", () => { - const options = { cliConfig: { ignorePatterns: ["!/node_modules/dependency.js"] }, cwd: getFixturePath("glob-util") }; - const glob = getFixturePath("glob-util", "**/*.js"); - const patterns = [glob]; - const result = listFiles(patterns, options); - const unignoredFilename = getFixturePath("glob-util", "node_modules", "dependency.js"); - - assert.includeDeepMembers(result, [{ filename: unignoredFilename, ignored: false }]); - }); - - it("should return unignored files from folders unignored in .eslintignore", () => { - const options = { cwd: getFixturePath("glob-util", "unignored"), ignore: true }; - const glob = getFixturePath("glob-util", "unignored", "**/*.js"); - const patterns = [glob]; - const result = listFiles(patterns, options); - - const filename = getFixturePath("glob-util", "unignored", "dir", "foo.js"); - - assert.strictEqual(result.length, 1); - assert.deepStrictEqual(result, [{ filename, ignored: false }]); - }); - - it("should return unignored files from folders unignored in .eslintignore for explicitly specified folder", () => { - const options = { cwd: getFixturePath("glob-util", "unignored"), ignore: true }; - const dir = getFixturePath("glob-util", "unignored", "dir"); - const patterns = [dir]; - const result = listFiles(patterns, options); - - const filename = getFixturePath("glob-util", "unignored", "dir", "foo.js"); - - assert.strictEqual(result.length, 1); - assert.deepStrictEqual(result, [{ filename, ignored: false }]); - }); - }); - }); - - describe("if contains symbolic links", async () => { - const root = path.join(os.tmpdir(), "eslint/file-enumerator"); - const files = { - "dir1/1.js": "", - "dir1/2.js": "", - "top-level.js": "", - ".eslintrc.json": JSON.stringify({ rules: {} }) - }; - const dir2 = path.join(root, "dir2"); - const { prepare, cleanup } = createCustomTeardown({ cwd: root, files }); - - beforeEach(async () => { - await prepare(); - fs.mkdirSync(dir2); - fs.symlinkSync(path.join(root, "top-level.js"), path.join(dir2, "top.js"), "file"); - fs.symlinkSync(path.join(root, "dir1"), path.join(dir2, "nested"), "dir"); - }); - - afterEach(cleanup); - - it("should resolve", () => { - const enumerator = new FileEnumerator({ cwd: root }); - const list = Array.from(enumerator.iterateFiles(["dir2/**/*.js"])).map(({ filePath }) => filePath); - - assert.deepStrictEqual(list, [ - path.join(dir2, "nested", "1.js"), - path.join(dir2, "nested", "2.js"), - path.join(dir2, "top.js") - ]); - }); - - it("should ignore broken links", () => { - fs.unlinkSync(path.join(root, "top-level.js")); - - const enumerator = new FileEnumerator({ cwd: root }); - const list = Array.from(enumerator.iterateFiles(["dir2/**/*.js"])).map(({ filePath }) => filePath); - - assert.deepStrictEqual(list, [ - path.join(dir2, "nested", "1.js"), - path.join(dir2, "nested", "2.js") - ]); - }); - }); - }); - - // https://github.com/eslint/eslint/issues/13789 - describe("constructor default values when config extends eslint:recommended", () => { - const root = path.join(os.tmpdir(), "eslint/file-enumerator"); - const files = { - "file.js": "", - ".eslintrc.json": JSON.stringify({ - extends: ["eslint:recommended", "eslint:all"] - }) - }; - const { prepare, cleanup, getPath } = createCustomTeardown({ cwd: root, files }); - - - /** @type {FileEnumerator} */ - let enumerator; - - beforeEach(async () => { - await prepare(); - enumerator = new FileEnumerator({ cwd: getPath() }); - }); - - afterEach(cleanup); - - it("should not throw an exception iterating files", () => { - Array.from(enumerator.iterateFiles(["."])); - }); - }); + describe("'iterateFiles(patterns)' method should iterate files and configs.", () => { + describe("with three directories ('lib', 'lib/nested', 'test') that contains 'one.js' and 'two.js'", () => { + const root = path.join(os.tmpdir(), "eslint/file-enumerator"); + const files = { + "lib/nested/one.js": "", + "lib/nested/two.js": "", + "lib/nested/parser.js": "", + "lib/nested/.eslintrc.yml": "parser: './parser'", + "lib/one.js": "", + "lib/two.js": "", + "test/one.js": "", + "test/two.js": "", + "test/.eslintrc.yml": "env: { mocha: true }", + ".eslintignore": "/lib/nested/parser.js", + ".eslintrc.json": JSON.stringify({ + rules: { + "no-undef": "error", + "no-unused-vars": "error", + }, + }), + }; + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files, + }); + + /** @type {FileEnumerator} */ + let enumerator; + + beforeEach(async () => { + await prepare(); + enumerator = new FileEnumerator({ cwd: getPath() }); + }); + + afterEach(cleanup); + + it("should ignore empty strings.", () => { + Array.from(enumerator.iterateFiles(["lib/*.js", ""])); // don't throw "file not found" error. + }); + + describe("if 'lib/*.js' was given,", () => { + /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */ + let list; + + beforeEach(() => { + list = [...enumerator.iterateFiles("lib/*.js")]; + }); + + it("should list two files.", () => { + assert.strictEqual(list.length, 2); + }); + + it("should list 'lib/one.js' and 'lib/two.js'.", () => { + assert.deepStrictEqual( + list.map(entry => entry.filePath), + [ + path.join(root, "lib/one.js"), + path.join(root, "lib/two.js"), + ], + ); + }); + + it("should use the config '.eslintrc.json' for both files.", () => { + assert.strictEqual(list[0].config, list[1].config); + assert.strictEqual(list[0].config.length, 3); + assert.strictEqual( + list[0].config[0].name, + "DefaultIgnorePattern", + ); + assert.strictEqual( + list[0].config[1].filePath, + path.join(root, ".eslintrc.json"), + ); + assert.strictEqual( + list[0].config[2].filePath, + path.join(root, ".eslintignore"), + ); + }); + }); + + describe("if 'lib/**/*.js' was given,", () => { + /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */ + let list; + + beforeEach(() => { + list = [...enumerator.iterateFiles("lib/**/*.js")]; + }); + + it("should list four files.", () => { + assert.strictEqual(list.length, 4); + }); + + it("should list 'lib/nested/one.js', 'lib/nested/two.js', 'lib/one.js', 'lib/two.js'.", () => { + assert.deepStrictEqual( + list.map(entry => entry.filePath), + [ + path.join(root, "lib/nested/one.js"), + path.join(root, "lib/nested/two.js"), + path.join(root, "lib/one.js"), + path.join(root, "lib/two.js"), + ], + ); + }); + + it("should use the merged config of '.eslintrc.json' and 'lib/nested/.eslintrc.yml' for 'lib/nested/one.js' and 'lib/nested/two.js'.", () => { + assert.strictEqual(list[0].config, list[1].config); + assert.strictEqual(list[0].config.length, 4); + assert.strictEqual( + list[0].config[0].name, + "DefaultIgnorePattern", + ); + assert.strictEqual( + list[0].config[1].filePath, + path.join(root, ".eslintrc.json"), + ); + assert.strictEqual( + list[0].config[2].filePath, + path.join(root, "lib/nested/.eslintrc.yml"), + ); + assert.strictEqual( + list[0].config[3].filePath, + path.join(root, ".eslintignore"), + ); + }); + + it("should use the config '.eslintrc.json' for 'lib/one.js' and 'lib/two.js'.", () => { + assert.strictEqual(list[2].config, list[3].config); + assert.strictEqual(list[2].config.length, 3); + assert.strictEqual( + list[2].config[0].name, + "DefaultIgnorePattern", + ); + assert.strictEqual( + list[2].config[1].filePath, + path.join(root, ".eslintrc.json"), + ); + assert.strictEqual( + list[2].config[2].filePath, + path.join(root, ".eslintignore"), + ); + }); + }); + + describe("if 'lib/*.js' and 'test/*.js' were given,", () => { + /** @type {Array<{config:(typeof import('../../../lib/cli-engine')).ConfigArray, filePath:string, ignored:boolean}>} */ + let list; + + beforeEach(() => { + list = [ + ...enumerator.iterateFiles(["lib/*.js", "test/*.js"]), + ]; + }); + + it("should list four files.", () => { + assert.strictEqual(list.length, 4); + }); + + it("should list 'lib/one.js', 'lib/two.js', 'test/one.js', 'test/two.js'.", () => { + assert.deepStrictEqual( + list.map(entry => entry.filePath), + [ + path.join(root, "lib/one.js"), + path.join(root, "lib/two.js"), + path.join(root, "test/one.js"), + path.join(root, "test/two.js"), + ], + ); + }); + + it("should use the config '.eslintrc.json' for 'lib/one.js' and 'lib/two.js'.", () => { + assert.strictEqual(list[0].config, list[1].config); + assert.strictEqual(list[0].config.length, 3); + assert.strictEqual( + list[0].config[0].name, + "DefaultIgnorePattern", + ); + assert.strictEqual( + list[0].config[1].filePath, + path.join(root, ".eslintrc.json"), + ); + assert.strictEqual( + list[0].config[2].filePath, + path.join(root, ".eslintignore"), + ); + }); + + it("should use the merged config of '.eslintrc.json' and 'test/.eslintrc.yml' for 'test/one.js' and 'test/two.js'.", () => { + assert.strictEqual(list[2].config, list[3].config); + assert.strictEqual(list[2].config.length, 4); + assert.strictEqual( + list[2].config[0].name, + "DefaultIgnorePattern", + ); + assert.strictEqual( + list[2].config[1].filePath, + path.join(root, ".eslintrc.json"), + ); + assert.strictEqual( + list[2].config[2].filePath, + path.join(root, "test/.eslintrc.yml"), + ); + assert.strictEqual( + list[2].config[3].filePath, + path.join(root, ".eslintignore"), + ); + }); + }); + }); + + // https://github.com/eslint/eslint/issues/14742 + describe("with 5 directories ('{lib}', '{lib}/client', '{lib}/client/src', '{lib}/server', '{lib}/server/src') that contains two files '{lib}/client/src/one.js' and '{lib}/server/src/two.js'", () => { + const root = path.join(os.tmpdir(), "eslint/file-enumerator"); + const files = { + "{lib}/client/src/one.js": "console.log('one.js');", + "{lib}/server/src/two.js": "console.log('two.js');", + "{lib}/client/.eslintrc.json": JSON.stringify({ + rules: { + "no-console": "error", + }, + env: { + mocha: true, + }, + }), + "{lib}/server/.eslintrc.json": JSON.stringify({ + rules: { + "no-console": "off", + }, + env: { + mocha: true, + }, + }), + }; + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files, + }); + + /** @type {FileEnumerator} */ + let enumerator; + + beforeEach(async () => { + await prepare(); + enumerator = new FileEnumerator({ + cwd: path.resolve(getPath("{lib}/server")), + }); + }); + + afterEach(cleanup); + + describe("when running eslint in the server directory", () => { + it("should use the config '{lib}/server/.eslintrc.json' for '{lib}/server/src/two.js'.", () => { + const spy = sinon.spy(fs, "readdirSync"); + + const list = [ + ...enumerator.iterateFiles(["src/**/*.{js,json}"]), + ]; + + // should enter the directory '{lib}/server/src' directly + assert.strictEqual( + spy.getCall(0).firstArg, + path.join(root, "{lib}/server/src"), + ); + assert.strictEqual(list.length, 1); + assert.strictEqual(list[0].config.length, 2); + assert.strictEqual( + list[0].config[0].name, + "DefaultIgnorePattern", + ); + assert.strictEqual( + list[0].config[1].filePath, + getPath("{lib}/server/.eslintrc.json"), + ); + assert.deepStrictEqual( + list.map(entry => entry.filePath), + [path.join(root, "{lib}/server/src/two.js")], + ); + + // destroy the spy + sinon.restore(); + }); + }); + }); + + // This group moved from 'tests/lib/util/glob-utils.js' when refactoring to keep the cumulated test cases. + describe("with 'tests/fixtures/glob-utils' files", () => { + let fixtureDir; + + /** + * Returns the path inside of the fixture directory. + * @param {...string} args file path segments. + * @returns {string} The path inside the fixture directory. + * @private + */ + function getFixturePath(...args) { + return path.join(fs.realpathSync(fixtureDir), ...args); + } + + /** + * List files as a compatible shape with glob-utils. + * @param {string|string[]} patterns The patterns to list files. + * @param {Object} options The option for FileEnumerator. + * @returns {{filename:string,ignored:boolean}[]} The listed files. + */ + function listFiles(patterns, options) { + return Array.from( + new FileEnumerator({ + ...options, + configArrayFactory: new CascadingConfigArrayFactory({ + ...options, + + // Disable "No Configuration Found" error. + useEslintrc: false, + }), + }).iterateFiles(patterns), + ({ filePath, ignored }) => ({ + filename: filePath, + ignored, + }), + ); + } + + before(function () { + /* + * GitHub Actions Windows and macOS runners occasionally + * exhibit extremely slow filesystem operations, during which + * copying fixtures exceeds the default test timeout, so raise + * it just for this hook. Mocha uses `this` to set timeouts on + * an individual hook level. + */ + this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API + fixtureDir = `${os.tmpdir()}/eslint/tests/fixtures/`; + sh.mkdir("-p", fixtureDir); + sh.cp("-r", "./tests/fixtures/*", fixtureDir); + }); + + after(() => { + sh.rm("-r", fixtureDir); + }); + + describe("listFilesToProcess()", () => { + it("should return an array with a resolved (absolute) filename", () => { + const patterns = [ + getFixturePath("glob-util", "one-js-file", "**/*.js"), + ]; + const result = listFiles(patterns, { + cwd: getFixturePath(), + }); + + const file1 = getFixturePath( + "glob-util", + "one-js-file", + "baz.js", + ); + + assert.isArray(result); + assert.deepStrictEqual(result, [ + { filename: file1, ignored: false }, + ]); + }); + + it("should return all files matching a glob pattern", () => { + const patterns = [ + getFixturePath("glob-util", "two-js-files", "**/*.js"), + ]; + const result = listFiles(patterns, { + cwd: getFixturePath(), + }); + + const file1 = getFixturePath( + "glob-util", + "two-js-files", + "bar.js", + ); + const file2 = getFixturePath( + "glob-util", + "two-js-files", + "foo.js", + ); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result, [ + { filename: file1, ignored: false }, + { filename: file2, ignored: false }, + ]); + }); + + it("should return all files matching multiple glob patterns", () => { + const patterns = [ + getFixturePath("glob-util", "two-js-files", "**/*.js"), + getFixturePath("glob-util", "one-js-file", "**/*.js"), + ]; + const result = listFiles(patterns, { + cwd: getFixturePath(), + }); + + const file1 = getFixturePath( + "glob-util", + "two-js-files", + "bar.js", + ); + const file2 = getFixturePath( + "glob-util", + "two-js-files", + "foo.js", + ); + const file3 = getFixturePath( + "glob-util", + "one-js-file", + "baz.js", + ); + + assert.strictEqual(result.length, 3); + assert.deepStrictEqual(result, [ + { filename: file1, ignored: false }, + { filename: file2, ignored: false }, + { filename: file3, ignored: false }, + ]); + }); + + it("should ignore hidden files for standard glob patterns", () => { + const patterns = [ + getFixturePath("glob-util", "hidden", "**/*.js"), + ]; + + assert.throws(() => { + listFiles(patterns, { + cwd: getFixturePath(), + }); + }, `All files matched by '${patterns[0]}' are ignored.`); + }); + + it("should return hidden files if included in glob pattern", () => { + const patterns = [ + getFixturePath("glob-util", "hidden", "**/.*.js"), + ]; + const result = listFiles(patterns, { + cwd: getFixturePath(), + }); + + const file1 = getFixturePath( + "glob-util", + "hidden", + ".foo.js", + ); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result, [ + { filename: file1, ignored: false }, + ]); + }); + + it("should ignore default ignored files if not passed explicitly", () => { + const directory = getFixturePath("glob-util", "hidden"); + const patterns = [directory]; + + assert.throws(() => { + listFiles(patterns, { + cwd: getFixturePath(), + }); + }, `All files matched by '${directory}' are ignored.`); + }); + + it("should ignore and warn for default ignored files when passed explicitly", () => { + const filename = getFixturePath( + "glob-util", + "hidden", + ".foo.js", + ); + const patterns = [filename]; + const result = listFiles(patterns, { + cwd: getFixturePath(), + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + filename, + ignored: true, + }); + }); + + it("should ignore default ignored files if not passed explicitly even if ignore is false", () => { + const directory = getFixturePath("glob-util", "hidden"); + const patterns = [directory]; + + assert.throws(() => { + listFiles(patterns, { + cwd: getFixturePath(), + ignore: false, + }); + }, `All files matched by '${directory}' are ignored.`); + }); + + it("should not ignore default ignored files when passed explicitly if ignore is false", () => { + const filename = getFixturePath( + "glob-util", + "hidden", + ".foo.js", + ); + const patterns = [filename]; + const result = listFiles(patterns, { + cwd: getFixturePath(), + ignore: false, + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + filename, + ignored: false, + }); + }); + + it("should throw an error for a file which does not exist", () => { + const filename = getFixturePath( + "glob-util", + "hidden", + "bar.js", + ); + const patterns = [filename]; + + assert.throws(() => { + listFiles(patterns, { + cwd: getFixturePath(), + allowMissingGlobs: true, + }); + }, `No files matching '${filename}' were found.`); + }); + + it("should throw if a folder that does not have any applicable files is linted", () => { + const filename = getFixturePath("glob-util", "empty"); + const patterns = [filename]; + + assert.throws(() => { + listFiles(patterns, { + cwd: getFixturePath(), + }); + }, `No files matching '${filename}' were found.`); + }); + + it("should throw if only ignored files match a glob", () => { + const pattern = getFixturePath("glob-util", "ignored"); + const options = { + ignore: true, + ignorePath: getFixturePath( + "glob-util", + "ignored", + ".eslintignore", + ), + }; + + assert.throws(() => { + listFiles([pattern], options); + }, `All files matched by '${pattern}' are ignored.`); + }); + + it("should throw an error if no files match a glob", () => { + const patterns = ["dir-does-not-exist/**/*.js"]; + + assert.throws(() => { + listFiles(patterns, { + cwd: getFixturePath("ignored-paths"), + }); + }, `No files matching '${patterns[0]}' were found.`); + }); + + it("should return an ignored file, if ignore option is turned off", () => { + const options = { ignore: false }; + const patterns = [ + getFixturePath("glob-util", "ignored", "**/*.js"), + ]; + const result = listFiles(patterns, options); + + assert.strictEqual(result.length, 1); + }); + + it("should ignore a file from a glob if it matches a pattern in an ignore file", () => { + const options = { + ignore: true, + ignorePath: getFixturePath( + "glob-util", + "ignored", + ".eslintignore", + ), + }; + const patterns = [ + getFixturePath("glob-util", "ignored", "**/*.js"), + ]; + + assert.throws(() => { + listFiles(patterns, options); + }, `All files matched by '${patterns[0]}' are ignored.`); + }); + + it("should ignore a file from a glob if matching a specified ignore pattern", () => { + const options = { + ignore: true, + cliConfig: { ignorePatterns: ["foo.js"] }, + cwd: getFixturePath(), + }; + const patterns = [ + getFixturePath("glob-util", "ignored", "**/*.js"), + ]; + + assert.throws(() => { + listFiles(patterns, options); + }, `All files matched by '${patterns[0]}' are ignored.`); + }); + + it("should return a file only once if listed in more than 1 pattern", () => { + const patterns = [ + getFixturePath("glob-util", "one-js-file", "**/*.js"), + getFixturePath("glob-util", "one-js-file", "baz.js"), + ]; + const result = listFiles(patterns, { + cwd: path.join(fixtureDir, ".."), + }); + + const file1 = getFixturePath( + "glob-util", + "one-js-file", + "baz.js", + ); + + assert.isArray(result); + assert.deepStrictEqual(result, [ + { filename: file1, ignored: false }, + ]); + }); + + it("should set 'ignored: true' for files that are explicitly specified but ignored", () => { + const options = { + ignore: true, + cliConfig: { ignorePatterns: ["foo.js"] }, + cwd: getFixturePath(), + }; + const filename = getFixturePath( + "glob-util", + "ignored", + "foo.js", + ); + const patterns = [filename]; + const result = listFiles(patterns, options); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result, [ + { filename, ignored: true }, + ]); + }); + + it("should not return files from default ignored folders", () => { + const options = { cwd: getFixturePath("glob-util") }; + const glob = getFixturePath("glob-util", "**/*.js"); + const patterns = [glob]; + const result = listFiles(patterns, options); + const resultFilenames = result.map( + resultObj => resultObj.filename, + ); + + assert.notInclude( + resultFilenames, + getFixturePath( + "glob-util", + "node_modules", + "dependency.js", + ), + ); + }); + + it("should return unignored files from default ignored folders", () => { + const options = { + cliConfig: { + ignorePatterns: ["!/node_modules/dependency.js"], + }, + cwd: getFixturePath("glob-util"), + }; + const glob = getFixturePath("glob-util", "**/*.js"); + const patterns = [glob]; + const result = listFiles(patterns, options); + const unignoredFilename = getFixturePath( + "glob-util", + "node_modules", + "dependency.js", + ); + + assert.includeDeepMembers(result, [ + { filename: unignoredFilename, ignored: false }, + ]); + }); + + it("should return unignored files from folders unignored in .eslintignore", () => { + const options = { + cwd: getFixturePath("glob-util", "unignored"), + ignore: true, + }; + const glob = getFixturePath( + "glob-util", + "unignored", + "**/*.js", + ); + const patterns = [glob]; + const result = listFiles(patterns, options); + + const filename = getFixturePath( + "glob-util", + "unignored", + "dir", + "foo.js", + ); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result, [ + { filename, ignored: false }, + ]); + }); + + it("should return unignored files from folders unignored in .eslintignore for explicitly specified folder", () => { + const options = { + cwd: getFixturePath("glob-util", "unignored"), + ignore: true, + }; + const dir = getFixturePath("glob-util", "unignored", "dir"); + const patterns = [dir]; + const result = listFiles(patterns, options); + + const filename = getFixturePath( + "glob-util", + "unignored", + "dir", + "foo.js", + ); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result, [ + { filename, ignored: false }, + ]); + }); + }); + }); + + if (process.platform !== "win32" || process.env.CI === "true") { + describe("if contains symbolic links", async () => { + const root = path.join(os.tmpdir(), "eslint/file-enumerator"); + const files = { + "dir1/1.js": "", + "dir1/2.js": "", + "top-level.js": "", + ".eslintrc.json": JSON.stringify({ rules: {} }), + }; + const dir2 = path.join(root, "dir2"); + const { prepare, cleanup } = createCustomTeardown({ + cwd: root, + files, + }); + + beforeEach(async () => { + await prepare(); + fs.mkdirSync(dir2); + fs.symlinkSync( + path.join(root, "top-level.js"), + path.join(dir2, "top.js"), + "file", + ); + fs.symlinkSync( + path.join(root, "dir1"), + path.join(dir2, "nested"), + "dir", + ); + }); + + afterEach(cleanup); + + it("should resolve", () => { + const enumerator = new FileEnumerator({ cwd: root }); + const list = Array.from( + enumerator.iterateFiles(["dir2/**/*.js"]), + ).map(({ filePath }) => filePath); + + assert.deepStrictEqual(list, [ + path.join(dir2, "nested", "1.js"), + path.join(dir2, "nested", "2.js"), + path.join(dir2, "top.js"), + ]); + }); + + it("should ignore broken links", () => { + fs.unlinkSync(path.join(root, "top-level.js")); + + const enumerator = new FileEnumerator({ cwd: root }); + const list = Array.from( + enumerator.iterateFiles(["dir2/**/*.js"]), + ).map(({ filePath }) => filePath); + + assert.deepStrictEqual(list, [ + path.join(dir2, "nested", "1.js"), + path.join(dir2, "nested", "2.js"), + ]); + }); + }); + } + }); + + // https://github.com/eslint/eslint/issues/13789 + describe("constructor default values when config extends eslint:recommended", () => { + const root = path.join(os.tmpdir(), "eslint/file-enumerator"); + const files = { + "file.js": "", + ".eslintrc.json": JSON.stringify({ + extends: ["eslint:recommended", "eslint:all"], + }), + }; + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files, + }); + + /** @type {FileEnumerator} */ + let enumerator; + + beforeEach(async () => { + await prepare(); + enumerator = new FileEnumerator({ cwd: getPath() }); + }); + + afterEach(cleanup); + + it("should not throw an exception iterating files", () => { + Array.from(enumerator.iterateFiles(["."])); + }); + }); }); diff --git a/tests/lib/cli-engine/formatters/checkstyle.js b/tests/lib/cli-engine/formatters/checkstyle.js deleted file mode 100644 index 218b15310abc..000000000000 --- a/tests/lib/cli-engine/formatters/checkstyle.js +++ /dev/null @@ -1,171 +0,0 @@ -/** - * @fileoverview Tests for checkstyle reporter. - * @author Ian Christian Myers - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert, - formatter = require("../../../../lib/cli-engine/formatters/checkstyle"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("formatter:checkstyle", () => { - describe("when passed a single message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a string in the format filename: line x, col y, Error - z for errors", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - - it("should return a string in the format filename: line x, col y, Warning - z for warnings", () => { - code[0].messages[0].severity = 1; - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passed a message with XML control characters", () => { - const code = [{ - filePath: "<>&\"'.js", - messages: [{ - fatal: true, - message: "Unexpected <>&\"'\b\t\n\f\rቛé€ŧ.", - line: "<", - column: ">", - ruleId: "foo" - }] - }]; - - it("should return a string in the format filename: line x, col y, Error - z", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passed a fatal error message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - fatal: true, - message: "Unexpected foo.", - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a string in the format filename: line x, col y, Error - z", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passed multiple messages", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }, { - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }] - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passed multiple files with 1 message each", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }, { - filePath: "bar.js", - messages: [{ - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }] - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passing single message without rule id", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10 - }] - }]; - - it("should return a string in the format filename: line x, col y, Error - z for errors", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passing single message without line and column", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - ruleId: "foo" - }] - }]; - - it("should return line and column as 0 instead of undefined", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); -}); diff --git a/tests/lib/cli-engine/formatters/compact.js b/tests/lib/cli-engine/formatters/compact.js deleted file mode 100644 index 1a179d9f5d5b..000000000000 --- a/tests/lib/cli-engine/formatters/compact.js +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @fileoverview Tests for options. - * @author Nicholas C. Zakas - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert, - formatter = require("../../../../lib/cli-engine/formatters/compact"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("formatter:compact", () => { - describe("when passed no messages", () => { - const code = [{ - filePath: "foo.js", - messages: [] - }]; - - it("should return nothing", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passed a single message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a string in the format filename: line x, col y, Error - z for errors", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js: line 5, col 10, Error - Unexpected foo. (foo)\n\n1 problem"); - }); - - it("should return a string in the format filename: line x, col y, Warning - z for warnings", () => { - code[0].messages[0].severity = 1; - const result = formatter(code); - - assert.strictEqual(result, "foo.js: line 5, col 10, Warning - Unexpected foo. (foo)\n\n1 problem"); - }); - }); - - describe("when passed a fatal error message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - fatal: true, - message: "Unexpected foo.", - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a string in the format filename: line x, col y, Error - z", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js: line 5, col 10, Error - Unexpected foo. (foo)\n\n1 problem"); - }); - }); - - describe("when passed multiple messages", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }, { - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }] - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js: line 5, col 10, Error - Unexpected foo. (foo)\nfoo.js: line 6, col 11, Warning - Unexpected bar. (bar)\n\n2 problems"); - }); - }); - - describe("when passed multiple files with 1 message each", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }, { - filePath: "bar.js", - messages: [{ - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }] - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js: line 5, col 10, Error - Unexpected foo. (foo)\nbar.js: line 6, col 11, Warning - Unexpected bar. (bar)\n\n2 problems"); - }); - }); - - describe("when passed one file not found message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - fatal: true, - message: "Couldn't find foo.js." - }] - }]; - - it("should return a string without line and column", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js: line 0, col 0, Error - Couldn't find foo.js.\n\n1 problem"); - }); - }); -}); diff --git a/tests/lib/cli-engine/formatters/html.js b/tests/lib/cli-engine/formatters/html.js index 9d61cd6bb18e..57cf13ceb031 100644 --- a/tests/lib/cli-engine/formatters/html.js +++ b/tests/lib/cli-engine/formatters/html.js @@ -24,8 +24,12 @@ const cheerio = require("cheerio"); * @returns {void} */ function checkOverview($, args) { - assert($("#overview").hasClass(args.bgColor), "Check if color is correct"); - assert.strictEqual($("#overview span").text(), args.problems, "Check if correct problem totals"); + assert($("#overview").hasClass(args.bgColor), "Check if color is correct"); + assert.strictEqual( + $("#overview span").text(), + args.problems, + "Check if correct problem totals", + ); } /** @@ -36,12 +40,32 @@ function checkOverview($, args) { * @returns {void} */ function checkHeaderRow($, rowObject, args) { - const row = $(rowObject); - - assert(row.hasClass(args.bgColor), "Check that background color is correct"); - assert.strictEqual(row.attr("data-group"), args.group, "Check that header group is correct"); - assert.strictEqual(row.find("th span").text(), args.problems, "Check if correct totals"); - assert.strictEqual(row.find("th").html().trim().match(/ [^<]*/u)[0].trim(), args.file, "Check if correctly displays filePath"); + const row = $(rowObject); + + assert( + row.hasClass(args.bgColor), + "Check that background color is correct", + ); + assert.strictEqual( + row.attr("data-group"), + args.group, + "Check that header group is correct", + ); + assert.strictEqual( + row.find("th span").text(), + args.problems, + "Check if correct totals", + ); + assert.strictEqual( + row + .find("th") + .html() + .trim() + .match(/ [^<]*/u)[0] + .trim(), + args.file, + "Check if correctly displays filePath", + ); } /** @@ -52,13 +76,28 @@ function checkHeaderRow($, rowObject, args) { * @returns {void} */ function checkContentRow($, rowObject, args) { - const row = $(rowObject); - - assert(row.hasClass(args.group), "Check that linked to correct header"); - assert.strictEqual($(row.find("td")[0]).text(), args.lineCol, "Check that line:column is correct"); - assert($(row.find("td")[1]).hasClass(args.color), "Check that severity color is correct"); - assert.strictEqual($(row.find("td")[2]).html(), args.message, "Check that message is correct"); - assert.strictEqual($(row.find("td")[3]).find("a").text(), args.ruleId, "Check that ruleId is correct"); + const row = $(rowObject); + + assert(row.hasClass(args.group), "Check that linked to correct header"); + assert.strictEqual( + $(row.find("td")[0]).text(), + args.lineCol, + "Check that line:column is correct", + ); + assert( + $(row.find("td")[1]).hasClass(args.color), + "Check that severity color is correct", + ); + assert.strictEqual( + $(row.find("td")[2]).html(), + args.message, + "Check that message is correct", + ); + assert.strictEqual( + $(row.find("td")[3]).find("a").text(), + args.ruleId, + "Check that ruleId is correct", + ); } //------------------------------------------------------------------------------ @@ -66,528 +105,782 @@ function checkContentRow($, rowObject, args) { //------------------------------------------------------------------------------ describe("formatter:html", () => { - describe("when passed a single error message", () => { - const rulesMeta = { - foo: { - type: "problem", - - docs: { - description: "This is rule 'foo'", - category: "error", - recommended: true, - url: "https://eslint.org/docs/rules/foo" - }, - - fixable: "code", - - messages: { - message1: "This is a message for rule 'foo'." - } - } - }; - const code = { - results: [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 0, - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo", - source: "foo" - }] - }], - rulesMeta - }; - - it("should return a string in HTML format with 1 issue in 1 file and styled accordingly", () => { - const result = formatter(code.results, { rulesMeta }); - const $ = cheerio.load(result); - - // Check overview - checkOverview($, { bgColor: "bg-2", problems: "1 problem (1 error, 0 warnings)" }); - - // Check rows - assert.strictEqual($("tr").length, 2, "Check that there are two (1 header, 1 content)"); - assert.strictEqual($("tr[data-group|=\"f\"]").length, 1, "Check that is 1 header row (implying 1 content row)"); - checkHeaderRow($, $("tr")[0], { bgColor: "bg-2", group: "f-0", file: "foo.js", problems: "1 problem (1 error, 0 warnings)" }); - checkContentRow($, $("tr")[1], { group: "f-0", lineCol: "5:10", color: "clr-2", message: "Unexpected foo.", ruleId: "foo" }); - }); - - it("should not fail if metadata is not available", () => { - const result = formatter(code.results); - - const $ = cheerio.load(result); - - // Check overview - checkOverview($, { bgColor: "bg-2", problems: "1 problem (1 error, 0 warnings)" }); - - // Check rows - assert.strictEqual($("tr").length, 2, "Check that there are two (1 header, 1 content)"); - assert.strictEqual($("tr[data-group|=\"f\"]").length, 1, "Check that is 1 header row (implying 1 content row)"); - checkHeaderRow($, $("tr")[0], { bgColor: "bg-2", group: "f-0", file: "foo.js", problems: "1 problem (1 error, 0 warnings)" }); - checkContentRow($, $("tr")[1], { group: "f-0", lineCol: "5:10", color: "clr-2", message: "Unexpected foo.", ruleId: "foo" }); - }); - }); - - describe("when passed a single warning message", () => { - const rulesMeta = { - foo: { - type: "problem", - - docs: { - description: "This is rule 'foo'", - category: "error", - recommended: true, - url: "https://eslint.org/docs/rules/foo" - }, - - fixable: "code", - - messages: { - message1: "This is a message for rule 'foo'." - } - } - }; - const code = { - results: [{ - filePath: "foo.js", - errorCount: 0, - warningCount: 1, - messages: [{ - message: "Unexpected foo.", - severity: 1, - line: 5, - column: 10, - ruleId: "foo", - source: "foo" - }] - }], - rulesMeta - }; - - it("should return a string in HTML format with 1 issue in 1 file and styled accordingly", () => { - const result = formatter(code.results, { rulesMeta }); - const $ = cheerio.load(result); - - // Check overview - checkOverview($, { bgColor: "bg-1", problems: "1 problem (0 errors, 1 warning)" }); - - // Check rows - assert.strictEqual($("tr").length, 2, "Check that there are two (1 header, 1 content)"); - assert.strictEqual($("tr[data-group|=\"f\"]").length, 1, "Check that is 1 header row (implying 1 content row)"); - checkHeaderRow($, $("tr")[0], { bgColor: "bg-1", group: "f-0", file: "foo.js", problems: "1 problem (0 errors, 1 warning)" }); - checkContentRow($, $("tr")[1], { group: "f-0", lineCol: "5:10", color: "clr-1", message: "Unexpected foo.", ruleId: "foo" }); - }); - }); - - describe("when passed a single error message", () => { - const rulesMeta = { - foo: { - type: "problem", - - docs: { - description: "This is rule 'foo'", - category: "error", - recommended: true, - url: "https://eslint.org/docs/rules/foo" - }, - - fixable: "code", - - messages: { - message1: "This is a message for rule 'foo'." - } - } - }; - const code = { - results: [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 0, - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo", - source: "foo" - }] - }], - rulesMeta - }; - - it("should return a string in HTML format with 1 issue in 1 file and styled accordingly", () => { - const result = formatter(code.results, { rulesMeta }); - const $ = cheerio.load(result); - - // Check overview - checkOverview($, { bgColor: "bg-2", problems: "1 problem (1 error, 0 warnings)" }); - - // Check rows - assert.strictEqual($("tr").length, 2, "Check that there are two (1 header, 1 content)"); - assert.strictEqual($("tr[data-group|=\"f\"]").length, 1, "Check that is 1 header row (implying 1 content row)"); - checkHeaderRow($, $("tr")[0], { bgColor: "bg-2", group: "f-0", file: "foo.js", problems: "1 problem (1 error, 0 warnings)" }); - checkContentRow($, $("tr")[1], { group: "f-0", lineCol: "5:10", color: "clr-2", message: "Unexpected foo.", ruleId: "foo" }); - }); - }); - - describe("when passed no error/warning messages", () => { - const code = { - results: [{ - filePath: "foo.js", - errorCount: 0, - warningCount: 0, - messages: [] - }] - }; - - it("should return a string in HTML format with 0 issues in 1 file and styled accordingly", () => { - const result = formatter(code.results, {}); - const $ = cheerio.load(result); - - // Check overview - checkOverview($, { bgColor: "bg-0", problems: "0 problems" }); - - // Check rows - assert.strictEqual($("tr").length, 1, "Check that there is 1 row (header)"); - checkHeaderRow($, $("tr")[0], { bgColor: "bg-0", group: "f-0", file: "foo.js", problems: "0 problems" }); - }); - }); - - describe("when passed multiple messages", () => { - const rulesMeta = { - foo: { - type: "problem", - - docs: { - description: "This is rule 'foo'", - category: "error", - recommended: true, - url: "https://eslint.org/docs/rules/foo" - }, - - fixable: "code", - - messages: { - message1: "This is a message for rule 'foo'." - } - }, - bar: { - type: "suggestion", - - docs: { - description: "This is rule 'bar'", - category: "error", - recommended: false - }, - - messages: { - message1: "This is a message for rule 'bar'." - } - } - }; - const code = { - results: [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 1, - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo", - source: "foo" - }, { - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar", - source: "bar" - }] - }], - rulesMeta - }; - - it("should return a string in HTML format with 2 issues in 1 file and styled accordingly", () => { - const result = formatter(code.results, { rulesMeta }); - const $ = cheerio.load(result); - - // Check overview - checkOverview($, { bgColor: "bg-2", problems: "2 problems (1 error, 1 warning)" }); - - // Check rows - assert.strictEqual($("tr").length, 3, "Check that there are two (1 header, 2 content)"); - assert.strictEqual($("tr[data-group|=\"f\"]").length, 1, "Check that is 1 header row (implying 2 content row)"); - checkHeaderRow($, $("tr")[0], { bgColor: "bg-2", group: "f-0", file: "foo.js", problems: "2 problems (1 error, 1 warning)" }); - checkContentRow($, $("tr")[1], { group: "f-0", lineCol: "5:10", color: "clr-2", message: "Unexpected foo.", ruleId: "foo" }); - checkContentRow($, $("tr")[2], { group: "f-0", lineCol: "6:11", color: "clr-1", message: "Unexpected bar.", ruleId: "bar" }); - }); - }); - - describe("when passed multiple files with 1 error & warning message respectively", () => { - const rulesMeta = { - foo: { - type: "problem", - - docs: { - description: "This is rule 'foo'", - category: "error", - recommended: true, - url: "https://eslint.org/docs/rules/foo" - }, - - fixable: "code", - - messages: { - message1: "This is a message for rule 'foo'." - } - }, - bar: { - type: "suggestion", - - docs: { - description: "This is rule 'bar'", - category: "error", - recommended: false - }, - - messages: { - message1: "This is a message for rule 'bar'." - } - } - }; - const code = { - results: [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 0, - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo", - source: "foo" - }] - }, { - filePath: "bar.js", - errorCount: 0, - warningCount: 1, - messages: [{ - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar", - source: "bar" - }] - }], - rulesMeta - }; - - it("should return a string in HTML format with 2 issues in 2 files and styled accordingly", () => { - const result = formatter(code.results, { rulesMeta }); - const $ = cheerio.load(result); - - // Check overview - checkOverview($, { bgColor: "bg-2", problems: "2 problems (1 error, 1 warning)" }); - - // Check rows - assert.strictEqual($("tr").length, 4, "Check that there are two (2 header, 2 content)"); - assert.strictEqual($("tr[data-group|=\"f\"]").length, 2, "Check that is 2 header row (implying 2 content row)"); - checkHeaderRow($, $("tr")[0], { bgColor: "bg-2", group: "f-0", file: "foo.js", problems: "1 problem (1 error, 0 warnings)" }); - checkContentRow($, $("tr")[1], { group: "f-0", lineCol: "5:10", color: "clr-2", message: "Unexpected foo.", ruleId: "foo" }); - checkHeaderRow($, $("tr")[2], { bgColor: "bg-1", group: "f-1", file: "bar.js", problems: "1 problem (0 errors, 1 warning)" }); - checkContentRow($, $("tr")[3], { group: "f-1", lineCol: "6:11", color: "clr-1", message: "Unexpected bar.", ruleId: "bar" }); - }); - }); - - describe("when passed multiple files with 1 warning message each", () => { - const rulesMeta = { - foo: { - type: "problem", - - docs: { - description: "This is rule 'foo'", - category: "error", - recommended: true, - url: "https://eslint.org/docs/rules/foo" - }, - - fixable: "code", - - messages: { - message1: "This is a message for rule 'foo'." - } - }, - bar: { - type: "suggestion", - - docs: { - description: "This is rule 'bar'", - category: "error", - recommended: false - }, - - messages: { - message1: "This is a message for rule 'bar'." - } - } - }; - const code = { - results: [{ - filePath: "foo.js", - errorCount: 0, - warningCount: 1, - messages: [{ - message: "Unexpected foo.", - severity: 1, - line: 5, - column: 10, - ruleId: "foo", - source: "foo" - }] - }, { - filePath: "bar.js", - errorCount: 0, - warningCount: 1, - messages: [{ - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar", - source: "bar" - }] - }], - rulesMeta - }; - - it("should return a string in HTML format with 2 issues in 2 files and styled accordingly", () => { - const result = formatter(code.results, { rulesMeta }); - const $ = cheerio.load(result); - - // Check overview - checkOverview($, { bgColor: "bg-1", problems: "2 problems (0 errors, 2 warnings)" }); - - // Check rows - assert.strictEqual($("tr").length, 4, "Check that there are two (2 header, 2 content)"); - assert.strictEqual($("tr[data-group|=\"f\"]").length, 2, "Check that is 2 header row (implying 2 content row)"); - checkHeaderRow($, $("tr")[0], { bgColor: "bg-1", group: "f-0", file: "foo.js", problems: "1 problem (0 errors, 1 warning)" }); - checkContentRow($, $("tr")[1], { group: "f-0", lineCol: "5:10", color: "clr-1", message: "Unexpected foo.", ruleId: "foo" }); - checkHeaderRow($, $("tr")[2], { bgColor: "bg-1", group: "f-1", file: "bar.js", problems: "1 problem (0 errors, 1 warning)" }); - checkContentRow($, $("tr")[3], { group: "f-1", lineCol: "6:11", color: "clr-1", message: "Unexpected bar.", ruleId: "bar" }); - }); - }); - - describe("when passing a single message with illegal characters", () => { - const rulesMeta = { - foo: { - type: "problem", - - docs: { - description: "This is rule 'foo'", - category: "error", - recommended: true, - url: "https://eslint.org/docs/rules/foo" - }, - - fixable: "code", - - messages: { - message1: "This is a message for rule 'foo'." - } - } - }; - const code = { - results: [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 0, - messages: [{ - message: "Unexpected <&\"'> foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo", - source: "foo" - }] - }], - rulesMeta - }; - - it("should return a string in HTML format with 1 issue in 1 file", () => { - const result = formatter(code.results, { rulesMeta }); - const $ = cheerio.load(result); - - checkContentRow($, $("tr")[1], { group: "f-0", lineCol: "5:10", color: "clr-2", message: "Unexpected <&"'> foo.", ruleId: "foo" }); - }); - }); - - describe("when passing a single message with no rule id or message", () => { - const code = [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 0, - messages: [{ - severity: 2, - line: 5, - column: 10 - }] - }]; - - it("should return a string in HTML format with 1 issue in 1 file", () => { - const result = formatter(code, {}); - const $ = cheerio.load(result); - - checkContentRow($, $("tr")[1], { group: "f-0", lineCol: "5:10", color: "clr-2", message: "", ruleId: "" }); - }); - }); - - describe("when passed a single message with no line or column", () => { - const rulesMeta = { - foo: { - type: "problem", - - docs: { - description: "This is rule 'foo'", - category: "error", - recommended: true, - url: "https://eslint.org/docs/rules/foo" - }, - - fixable: "code", - - messages: { - message1: "This is a message for rule 'foo'." - } - } - }; - const code = { - results: [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 0, - messages: [{ - message: "Unexpected foo.", - severity: 2, - ruleId: "foo", - source: "foo" - }] - }], - rulesMeta - }; - - it("should return a string in HTML format with 1 issue in 1 file and styled accordingly", () => { - const result = formatter(code.results, { rulesMeta }); - const $ = cheerio.load(result); - - checkContentRow($, $("tr")[1], { group: "f-0", lineCol: "0:0", color: "clr-2", message: "Unexpected foo.", ruleId: "foo" }); - }); - }); + describe("when passed a single error message", () => { + const rulesMeta = { + foo: { + type: "problem", + + docs: { + description: "This is rule 'foo'", + category: "error", + recommended: true, + url: "https://eslint.org/docs/rules/foo", + }, + + fixable: "code", + + messages: { + message1: "This is a message for rule 'foo'.", + }, + }, + }; + const code = { + results: [ + { + filePath: "foo.js", + errorCount: 1, + warningCount: 0, + messages: [ + { + message: "Unexpected foo.", + severity: 2, + line: 5, + column: 10, + ruleId: "foo", + source: "foo", + }, + ], + }, + ], + rulesMeta, + }; + + it("should return a string in HTML format with 1 issue in 1 file and styled accordingly", () => { + const result = formatter(code.results, { rulesMeta }); + const $ = cheerio.load(result); + + // Check overview + checkOverview($, { + bgColor: "bg-2", + problems: "1 problem (1 error, 0 warnings)", + }); + + // Check rows + assert.strictEqual( + $("tr").length, + 2, + "Check that there are two (1 header, 1 content)", + ); + assert.strictEqual( + $('tr[data-group|="f"]').length, + 1, + "Check that is 1 header row (implying 1 content row)", + ); + checkHeaderRow($, $("tr")[0], { + bgColor: "bg-2", + group: "f-0", + file: "foo.js", + problems: "1 problem (1 error, 0 warnings)", + }); + checkContentRow($, $("tr")[1], { + group: "f-0", + lineCol: "5:10", + color: "clr-2", + message: "Unexpected foo.", + ruleId: "foo", + }); + }); + + it("should not fail if metadata is not available", () => { + const result = formatter(code.results); + + const $ = cheerio.load(result); + + // Check overview + checkOverview($, { + bgColor: "bg-2", + problems: "1 problem (1 error, 0 warnings)", + }); + + // Check rows + assert.strictEqual( + $("tr").length, + 2, + "Check that there are two (1 header, 1 content)", + ); + assert.strictEqual( + $('tr[data-group|="f"]').length, + 1, + "Check that is 1 header row (implying 1 content row)", + ); + checkHeaderRow($, $("tr")[0], { + bgColor: "bg-2", + group: "f-0", + file: "foo.js", + problems: "1 problem (1 error, 0 warnings)", + }); + checkContentRow($, $("tr")[1], { + group: "f-0", + lineCol: "5:10", + color: "clr-2", + message: "Unexpected foo.", + ruleId: "foo", + }); + }); + }); + + describe("when passed a single warning message", () => { + const rulesMeta = { + foo: { + type: "problem", + + docs: { + description: "This is rule 'foo'", + category: "error", + recommended: true, + url: "https://eslint.org/docs/rules/foo", + }, + + fixable: "code", + + messages: { + message1: "This is a message for rule 'foo'.", + }, + }, + }; + const code = { + results: [ + { + filePath: "foo.js", + errorCount: 0, + warningCount: 1, + messages: [ + { + message: "Unexpected foo.", + severity: 1, + line: 5, + column: 10, + ruleId: "foo", + source: "foo", + }, + ], + }, + ], + rulesMeta, + }; + + it("should return a string in HTML format with 1 issue in 1 file and styled accordingly", () => { + const result = formatter(code.results, { rulesMeta }); + const $ = cheerio.load(result); + + // Check overview + checkOverview($, { + bgColor: "bg-1", + problems: "1 problem (0 errors, 1 warning)", + }); + + // Check rows + assert.strictEqual( + $("tr").length, + 2, + "Check that there are two (1 header, 1 content)", + ); + assert.strictEqual( + $('tr[data-group|="f"]').length, + 1, + "Check that is 1 header row (implying 1 content row)", + ); + checkHeaderRow($, $("tr")[0], { + bgColor: "bg-1", + group: "f-0", + file: "foo.js", + problems: "1 problem (0 errors, 1 warning)", + }); + checkContentRow($, $("tr")[1], { + group: "f-0", + lineCol: "5:10", + color: "clr-1", + message: "Unexpected foo.", + ruleId: "foo", + }); + }); + }); + + describe("when passed a single error message", () => { + const rulesMeta = { + foo: { + type: "problem", + + docs: { + description: "This is rule 'foo'", + category: "error", + recommended: true, + url: "https://eslint.org/docs/rules/foo", + }, + + fixable: "code", + + messages: { + message1: "This is a message for rule 'foo'.", + }, + }, + }; + const code = { + results: [ + { + filePath: "foo.js", + errorCount: 1, + warningCount: 0, + messages: [ + { + message: "Unexpected foo.", + severity: 2, + line: 5, + column: 10, + ruleId: "foo", + source: "foo", + }, + ], + }, + ], + rulesMeta, + }; + + it("should return a string in HTML format with 1 issue in 1 file and styled accordingly", () => { + const result = formatter(code.results, { rulesMeta }); + const $ = cheerio.load(result); + + // Check overview + checkOverview($, { + bgColor: "bg-2", + problems: "1 problem (1 error, 0 warnings)", + }); + + // Check rows + assert.strictEqual( + $("tr").length, + 2, + "Check that there are two (1 header, 1 content)", + ); + assert.strictEqual( + $('tr[data-group|="f"]').length, + 1, + "Check that is 1 header row (implying 1 content row)", + ); + checkHeaderRow($, $("tr")[0], { + bgColor: "bg-2", + group: "f-0", + file: "foo.js", + problems: "1 problem (1 error, 0 warnings)", + }); + checkContentRow($, $("tr")[1], { + group: "f-0", + lineCol: "5:10", + color: "clr-2", + message: "Unexpected foo.", + ruleId: "foo", + }); + }); + }); + + describe("when passed no error/warning messages", () => { + const code = { + results: [ + { + filePath: "foo.js", + errorCount: 0, + warningCount: 0, + messages: [], + }, + ], + }; + + it("should return a string in HTML format with 0 issues in 1 file and styled accordingly", () => { + const result = formatter(code.results, {}); + const $ = cheerio.load(result); + + // Check overview + checkOverview($, { bgColor: "bg-0", problems: "0 problems" }); + + // Check rows + assert.strictEqual( + $("tr").length, + 1, + "Check that there is 1 row (header)", + ); + checkHeaderRow($, $("tr")[0], { + bgColor: "bg-0", + group: "f-0", + file: "foo.js", + problems: "0 problems", + }); + }); + }); + + describe("when passed multiple messages", () => { + const rulesMeta = { + foo: { + type: "problem", + + docs: { + description: "This is rule 'foo'", + category: "error", + recommended: true, + url: "https://eslint.org/docs/rules/foo", + }, + + fixable: "code", + + messages: { + message1: "This is a message for rule 'foo'.", + }, + }, + bar: { + type: "suggestion", + + docs: { + description: "This is rule 'bar'", + category: "error", + recommended: false, + }, + + messages: { + message1: "This is a message for rule 'bar'.", + }, + }, + }; + const code = { + results: [ + { + filePath: "foo.js", + errorCount: 1, + warningCount: 1, + messages: [ + { + message: "Unexpected foo.", + severity: 2, + line: 5, + column: 10, + ruleId: "foo", + source: "foo", + }, + { + message: "Unexpected bar.", + severity: 1, + line: 6, + column: 11, + ruleId: "bar", + source: "bar", + }, + ], + }, + ], + rulesMeta, + }; + + it("should return a string in HTML format with 2 issues in 1 file and styled accordingly", () => { + const result = formatter(code.results, { rulesMeta }); + const $ = cheerio.load(result); + + // Check overview + checkOverview($, { + bgColor: "bg-2", + problems: "2 problems (1 error, 1 warning)", + }); + + // Check rows + assert.strictEqual( + $("tr").length, + 3, + "Check that there are two (1 header, 2 content)", + ); + assert.strictEqual( + $('tr[data-group|="f"]').length, + 1, + "Check that is 1 header row (implying 2 content row)", + ); + checkHeaderRow($, $("tr")[0], { + bgColor: "bg-2", + group: "f-0", + file: "foo.js", + problems: "2 problems (1 error, 1 warning)", + }); + checkContentRow($, $("tr")[1], { + group: "f-0", + lineCol: "5:10", + color: "clr-2", + message: "Unexpected foo.", + ruleId: "foo", + }); + checkContentRow($, $("tr")[2], { + group: "f-0", + lineCol: "6:11", + color: "clr-1", + message: "Unexpected bar.", + ruleId: "bar", + }); + }); + }); + + describe("when passed multiple files with 1 error & warning message respectively", () => { + const rulesMeta = { + foo: { + type: "problem", + + docs: { + description: "This is rule 'foo'", + category: "error", + recommended: true, + url: "https://eslint.org/docs/rules/foo", + }, + + fixable: "code", + + messages: { + message1: "This is a message for rule 'foo'.", + }, + }, + bar: { + type: "suggestion", + + docs: { + description: "This is rule 'bar'", + category: "error", + recommended: false, + }, + + messages: { + message1: "This is a message for rule 'bar'.", + }, + }, + }; + const code = { + results: [ + { + filePath: "foo.js", + errorCount: 1, + warningCount: 0, + messages: [ + { + message: "Unexpected foo.", + severity: 2, + line: 5, + column: 10, + ruleId: "foo", + source: "foo", + }, + ], + }, + { + filePath: "bar.js", + errorCount: 0, + warningCount: 1, + messages: [ + { + message: "Unexpected bar.", + severity: 1, + line: 6, + column: 11, + ruleId: "bar", + source: "bar", + }, + ], + }, + ], + rulesMeta, + }; + + it("should return a string in HTML format with 2 issues in 2 files and styled accordingly", () => { + const result = formatter(code.results, { rulesMeta }); + const $ = cheerio.load(result); + + // Check overview + checkOverview($, { + bgColor: "bg-2", + problems: "2 problems (1 error, 1 warning)", + }); + + // Check rows + assert.strictEqual( + $("tr").length, + 4, + "Check that there are two (2 header, 2 content)", + ); + assert.strictEqual( + $('tr[data-group|="f"]').length, + 2, + "Check that is 2 header row (implying 2 content row)", + ); + checkHeaderRow($, $("tr")[0], { + bgColor: "bg-2", + group: "f-0", + file: "foo.js", + problems: "1 problem (1 error, 0 warnings)", + }); + checkContentRow($, $("tr")[1], { + group: "f-0", + lineCol: "5:10", + color: "clr-2", + message: "Unexpected foo.", + ruleId: "foo", + }); + checkHeaderRow($, $("tr")[2], { + bgColor: "bg-1", + group: "f-1", + file: "bar.js", + problems: "1 problem (0 errors, 1 warning)", + }); + checkContentRow($, $("tr")[3], { + group: "f-1", + lineCol: "6:11", + color: "clr-1", + message: "Unexpected bar.", + ruleId: "bar", + }); + }); + }); + + describe("when passed multiple files with 1 warning message each", () => { + const rulesMeta = { + foo: { + type: "problem", + + docs: { + description: "This is rule 'foo'", + category: "error", + recommended: true, + url: "https://eslint.org/docs/rules/foo", + }, + + fixable: "code", + + messages: { + message1: "This is a message for rule 'foo'.", + }, + }, + bar: { + type: "suggestion", + + docs: { + description: "This is rule 'bar'", + category: "error", + recommended: false, + }, + + messages: { + message1: "This is a message for rule 'bar'.", + }, + }, + }; + const code = { + results: [ + { + filePath: "foo.js", + errorCount: 0, + warningCount: 1, + messages: [ + { + message: "Unexpected foo.", + severity: 1, + line: 5, + column: 10, + ruleId: "foo", + source: "foo", + }, + ], + }, + { + filePath: "bar.js", + errorCount: 0, + warningCount: 1, + messages: [ + { + message: "Unexpected bar.", + severity: 1, + line: 6, + column: 11, + ruleId: "bar", + source: "bar", + }, + ], + }, + ], + rulesMeta, + }; + + it("should return a string in HTML format with 2 issues in 2 files and styled accordingly", () => { + const result = formatter(code.results, { rulesMeta }); + const $ = cheerio.load(result); + + // Check overview + checkOverview($, { + bgColor: "bg-1", + problems: "2 problems (0 errors, 2 warnings)", + }); + + // Check rows + assert.strictEqual( + $("tr").length, + 4, + "Check that there are two (2 header, 2 content)", + ); + assert.strictEqual( + $('tr[data-group|="f"]').length, + 2, + "Check that is 2 header row (implying 2 content row)", + ); + checkHeaderRow($, $("tr")[0], { + bgColor: "bg-1", + group: "f-0", + file: "foo.js", + problems: "1 problem (0 errors, 1 warning)", + }); + checkContentRow($, $("tr")[1], { + group: "f-0", + lineCol: "5:10", + color: "clr-1", + message: "Unexpected foo.", + ruleId: "foo", + }); + checkHeaderRow($, $("tr")[2], { + bgColor: "bg-1", + group: "f-1", + file: "bar.js", + problems: "1 problem (0 errors, 1 warning)", + }); + checkContentRow($, $("tr")[3], { + group: "f-1", + lineCol: "6:11", + color: "clr-1", + message: "Unexpected bar.", + ruleId: "bar", + }); + }); + }); + + describe("when passing a single message with illegal characters", () => { + const rulesMeta = { + foo: { + type: "problem", + + docs: { + description: "This is rule 'foo'", + category: "error", + recommended: true, + url: "https://eslint.org/docs/rules/foo", + }, + + fixable: "code", + + messages: { + message1: "This is a message for rule 'foo'.", + }, + }, + }; + const code = { + results: [ + { + filePath: "foo.js", + errorCount: 1, + warningCount: 0, + messages: [ + { + message: "Unexpected <&\"'> foo.", + severity: 2, + line: 5, + column: 10, + ruleId: "foo", + source: "foo", + }, + ], + }, + ], + rulesMeta, + }; + + it("should return a string in HTML format with 1 issue in 1 file", () => { + const result = formatter(code.results, { rulesMeta }); + const $ = cheerio.load(result); + + checkContentRow($, $("tr")[1], { + group: "f-0", + lineCol: "5:10", + color: "clr-2", + message: "Unexpected <&"'> foo.", + ruleId: "foo", + }); + }); + }); + + describe("when passing a single message with no rule id or message", () => { + const code = [ + { + filePath: "foo.js", + errorCount: 1, + warningCount: 0, + messages: [ + { + severity: 2, + line: 5, + column: 10, + }, + ], + }, + ]; + + it("should return a string in HTML format with 1 issue in 1 file", () => { + const result = formatter(code, {}); + const $ = cheerio.load(result); + + checkContentRow($, $("tr")[1], { + group: "f-0", + lineCol: "5:10", + color: "clr-2", + message: "", + ruleId: "", + }); + }); + }); + + describe("when passed a single message with no line or column", () => { + const rulesMeta = { + foo: { + type: "problem", + + docs: { + description: "This is rule 'foo'", + category: "error", + recommended: true, + url: "https://eslint.org/docs/rules/foo", + }, + + fixable: "code", + + messages: { + message1: "This is a message for rule 'foo'.", + }, + }, + }; + const code = { + results: [ + { + filePath: "foo.js", + errorCount: 1, + warningCount: 0, + messages: [ + { + message: "Unexpected foo.", + severity: 2, + ruleId: "foo", + source: "foo", + }, + ], + }, + ], + rulesMeta, + }; + + it("should return a string in HTML format with 1 issue in 1 file and styled accordingly", () => { + const result = formatter(code.results, { rulesMeta }); + const $ = cheerio.load(result); + + checkContentRow($, $("tr")[1], { + group: "f-0", + lineCol: "0:0", + color: "clr-2", + message: "Unexpected foo.", + ruleId: "foo", + }); + }); + }); }); diff --git a/tests/lib/cli-engine/formatters/jslint-xml.js b/tests/lib/cli-engine/formatters/jslint-xml.js deleted file mode 100644 index eca587a42d4c..000000000000 --- a/tests/lib/cli-engine/formatters/jslint-xml.js +++ /dev/null @@ -1,176 +0,0 @@ -/** - * @fileoverview Tests for JSLint XML reporter. - * @author Ian Christian Myers - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert, - formatter = require("../../../../lib/cli-engine/formatters/jslint-xml"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("formatter:jslint-xml", () => { - describe("when passed a single message", () => { - - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo", - source: "foo" - }] - }]; - - it("should return a string in JSLint XML format with 1 issue in 1 file", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passed a fatal error message", () => { - - const code = [{ - filePath: "foo.js", - messages: [{ - fatal: true, - message: "Unexpected foo.", - line: 5, - column: 10, - ruleId: "foo", - source: "foo" - }] - }]; - - it("should return a string in JSLint XML format with 1 issue in 1 file", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passed multiple messages", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo", - source: "foo" - }, { - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar", - source: "bar" - }] - }]; - - it("should return a string in JSLint XML format with 2 issues in 1 file", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passed multiple files with 1 message each", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo", - source: "foo" - }] - }, { - filePath: "bar.js", - messages: [{ - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar", - source: "bar" - }] - }]; - - it("should return a string in JSLint XML format with 2 issues in 2 files", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passing a single message with illegal characters", () => { - - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected <&\"'>\b\t\n\f\rቛé€ŧ foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo", - source: "foo" - }] - }]; - - it("should return a string in JSLint XML format with 1 issue in 1 file", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passing a single message with no source", () => { - - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a string in JSLint XML format with 1 issue in 1 file", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passing a single message without rule id", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - severity: 2, - line: 5, - column: 10 - }] - }]; - - it("should return a string in JSLint XML format with 1 issue in 1 file", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); -}); diff --git a/tests/lib/cli-engine/formatters/json-with-metadata.js b/tests/lib/cli-engine/formatters/json-with-metadata.js index 82cb20884b47..bf7575297107 100644 --- a/tests/lib/cli-engine/formatters/json-with-metadata.js +++ b/tests/lib/cli-engine/formatters/json-with-metadata.js @@ -10,72 +10,79 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - formatter = require("../../../../lib/cli-engine/formatters/json-with-metadata"); + formatter = require("../../../../lib/cli-engine/formatters/json-with-metadata"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("formatter:json", () => { - const rulesMeta = { - foo: { - type: "problem", + const rulesMeta = { + foo: { + type: "problem", - docs: { - description: "This is rule 'foo'", - category: "error", - recommended: true, - url: "https://eslint.org/docs/rules/foo" - }, + docs: { + description: "This is rule 'foo'", + category: "error", + recommended: true, + url: "https://eslint.org/docs/rules/foo", + }, - fixable: "code", + fixable: "code", - messages: { - message1: "This is a message for rule 'foo'." - } - }, - bar: { - type: "suggestion", + messages: { + message1: "This is a message for rule 'foo'.", + }, + }, + bar: { + type: "suggestion", - docs: { - description: "This is rule 'bar'", - category: "error", - recommended: false - }, + docs: { + description: "This is rule 'bar'", + category: "error", + recommended: false, + }, - messages: { - message1: "This is a message for rule 'bar'." - } - } - }; - const code = { - results: [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }, { - filePath: "bar.js", - messages: [{ - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }] - }], - metadata: { - rulesMeta - } - }; + messages: { + message1: "This is a message for rule 'bar'.", + }, + }, + }; + const code = { + results: [ + { + filePath: "foo.js", + messages: [ + { + message: "Unexpected foo.", + severity: 2, + line: 5, + column: 10, + ruleId: "foo", + }, + ], + }, + { + filePath: "bar.js", + messages: [ + { + message: "Unexpected bar.", + severity: 1, + line: 6, + column: 11, + ruleId: "bar", + }, + ], + }, + ], + metadata: { + rulesMeta, + }, + }; - it("should return passed results and data as a JSON string without any modification", () => { - const result = JSON.parse(formatter(code.results, code.metadata)); + it("should return passed results and data as a JSON string without any modification", () => { + const result = JSON.parse(formatter(code.results, code.metadata)); - assert.deepStrictEqual(result, code); - }); + assert.deepStrictEqual(result, code); + }); }); diff --git a/tests/lib/cli-engine/formatters/json.js b/tests/lib/cli-engine/formatters/json.js index b0bb5ffca077..d4c3db92708e 100644 --- a/tests/lib/cli-engine/formatters/json.js +++ b/tests/lib/cli-engine/formatters/json.js @@ -10,36 +10,43 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - formatter = require("../../../../lib/cli-engine/formatters/json"); + formatter = require("../../../../lib/cli-engine/formatters/json"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("formatter:json", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }, { - filePath: "bar.js", - messages: [{ - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }] - }]; + const code = [ + { + filePath: "foo.js", + messages: [ + { + message: "Unexpected foo.", + severity: 2, + line: 5, + column: 10, + ruleId: "foo", + }, + ], + }, + { + filePath: "bar.js", + messages: [ + { + message: "Unexpected bar.", + severity: 1, + line: 6, + column: 11, + ruleId: "bar", + }, + ], + }, + ]; - it("should return passed results as a JSON string without any modification", () => { - const result = JSON.parse(formatter(code)); + it("should return passed results as a JSON string without any modification", () => { + const result = JSON.parse(formatter(code)); - assert.deepStrictEqual(result, code); - }); + assert.deepStrictEqual(result, code); + }); }); diff --git a/tests/lib/cli-engine/formatters/junit.js b/tests/lib/cli-engine/formatters/junit.js deleted file mode 100644 index 26a2445b3b39..000000000000 --- a/tests/lib/cli-engine/formatters/junit.js +++ /dev/null @@ -1,216 +0,0 @@ -/** - * @fileoverview Tests for jUnit Formatter. - * @author Jamund Ferguson - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert, - formatter = require("../../../../lib/cli-engine/formatters/junit"), - process = require("process"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -const suppliedFilePath = (process.platform === "win32") ? "C:\\path\\to\\foo.js" : "/path/to/foo.js"; -const expectedClassName = (process.platform === "win32") ? "C:\\path\\to\\foo" : "/path/to/foo"; - -describe("formatter:junit", () => { - describe("when there are no problems", () => { - const code = []; - - it("should not complain about anything", () => { - const result = formatter(code); - - assert.strictEqual(result.replace(/\n/gu, ""), ""); - }); - }); - - describe("when passed a single message", () => { - const code = [{ - filePath: suppliedFilePath, - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a single with a message and the line and col number in the body (error)", () => { - const result = formatter(code); - - assert.strictEqual(result.replace(/\n/gu, ""), ``); - }); - - it("should return a single with a message and the line and col number in the body (warning)", () => { - code[0].messages[0].severity = 1; - const result = formatter(code); - - assert.strictEqual(result.replace(/\n/gu, ""), ``); - }); - }); - - describe("when passed a fatal error message", () => { - const code = [{ - filePath: suppliedFilePath, - messages: [{ - fatal: true, - message: "Unexpected foo.", - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a single and an ", () => { - const result = formatter(code); - - assert.strictEqual(result.replace(/\n/gu, ""), ``); - }); - }); - - describe("when passed a fatal error message with no line or column", () => { - const code = [{ - filePath: suppliedFilePath, - messages: [{ - fatal: true, - message: "Unexpected foo." - }] - }]; - - it("should return a single and an ", () => { - const result = formatter(code); - - assert.strictEqual(result.replace(/\n/gu, ""), ``); - }); - }); - - describe("when passed a fatal error message with no line, column, or message text", () => { - const code = [{ - filePath: suppliedFilePath, - messages: [{ - fatal: true - }] - }]; - - it("should return a single and an ", () => { - const result = formatter(code); - - assert.strictEqual(result.replace(/\n/gu, ""), ``); - }); - }); - - describe("when passed multiple messages", () => { - const code = [{ - filePath: suppliedFilePath, - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }, { - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }] - }]; - - it("should return a multiple 's", () => { - const result = formatter(code); - - assert.strictEqual(result.replace(/\n/gu, ""), ``); - }); - }); - - describe("when passed special characters", () => { - const code = [{ - filePath: suppliedFilePath, - messages: [{ - message: "Unexpected \b\t\n\f\rቛé€ŧ.", - severity: 1, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should make them go away", () => { - const result = formatter(code); - - assert.strictEqual(result.replace(/\n/gu, ""), ``); - }); - }); - - describe("when passed multiple files with 1 message each", () => { - const code = [{ - filePath: suppliedFilePath, - messages: [{ - message: "Unexpected foo.", - severity: 1, - line: 5, - column: 10, - ruleId: "foo" - }] - }, { - filePath: "bar.js", - messages: [{ - message: "Unexpected bar.", - severity: 2, - line: 6, - column: 11, - ruleId: "bar" - }] - }]; - - it("should return 2 's", () => { - const result = formatter(code); - - assert.strictEqual(result.replace(/\n/gu, ""), ``); - }); - }); - - describe("when passed multiple files should print even if no errors", () => { - const code = [{ - filePath: suppliedFilePath, - messages: [{ - message: "Unexpected foo.", - severity: 1, - line: 5, - column: 10, - ruleId: "foo" - }] - }, { - filePath: "bar.js", - messages: [] - }]; - - it("should return 2 ", () => { - const result = formatter(code); - - assert.strictEqual(result.replace(/\n/gu, ""), ``); - }); - }); - - describe("when passed a file with no errors", () => { - const code = [{ - filePath: suppliedFilePath, - messages: [] - }]; - - it("should print a passing ", () => { - const result = formatter(code); - - assert.strictEqual(result.replace(/\n/gu, ""), ``); - }); - }); -}); diff --git a/tests/lib/cli-engine/formatters/stylish.js b/tests/lib/cli-engine/formatters/stylish.js index 24f4be9f32b4..5c8820439bd1 100644 --- a/tests/lib/cli-engine/formatters/stylish.js +++ b/tests/lib/cli-engine/formatters/stylish.js @@ -10,9 +10,9 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - chalk = require("chalk"), - proxyquire = require("proxyquire"), - sinon = require("sinon"); + chalk = require("chalk"), + proxyquire = require("proxyquire"), + sinon = require("sinon"); //----------------------------------------------------------------------------- // Helpers @@ -23,400 +23,495 @@ const assert = require("chai").assert, * for Sinon to work. */ const chalkStub = Object.create(chalk, { - reset: { - value(str) { - return chalk.reset(str); - }, - writable: true - }, - yellow: { - value(str) { - return chalk.yellow(str); - }, - writable: true - }, - red: { - value(str) { - return chalk.red(str); - }, - writable: true - } + reset: { + value(str) { + return chalk.reset(str); + }, + writable: true, + }, + yellow: { + value(str) { + return chalk.yellow(str); + }, + writable: true, + }, + red: { + value(str) { + return chalk.red(str); + }, + writable: true, + }, }); chalkStub.yellow.bold = chalk.yellow.bold; chalkStub.red.bold = chalk.red.bold; -const formatter = proxyquire("../../../../lib/cli-engine/formatters/stylish", { chalk: chalkStub }); +const formatter = proxyquire("../../../../lib/cli-engine/formatters/stylish", { + chalk: chalkStub, +}); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("formatter:stylish", () => { - const originalColorLevel = chalk.level; - - beforeEach(() => { - chalk.level = 0; - sinon.spy(chalkStub, "reset"); - sinon.spy(chalkStub.yellow, "bold"); - sinon.spy(chalkStub.red, "bold"); - }); - - afterEach(() => { - sinon.verifyAndRestore(); - chalk.level = originalColorLevel; - }); - - describe("when passed no messages", () => { - const code = [{ - filePath: "foo.js", - messages: [], - errorCount: 0, - warningCount: 0 - }]; - - it("should not return message", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - assert.strictEqual(chalkStub.reset.callCount, 0); - assert.strictEqual(chalkStub.yellow.bold.callCount, 0); - assert.strictEqual(chalkStub.red.bold.callCount, 0); - }); - }); - - describe("when passed a single error message", () => { - const code = [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a string in the correct format", () => { - const result = formatter(code); - - assert.strictEqual(result, "\nfoo.js\n 5:10 error Unexpected foo foo\n\n\u2716 1 problem (1 error, 0 warnings)\n"); - assert.strictEqual(chalkStub.reset.callCount, 1); - assert.strictEqual(chalkStub.yellow.bold.callCount, 0); - assert.strictEqual(chalkStub.red.bold.callCount, 1); - - }); - - describe("when the error is fixable", () => { - beforeEach(() => { - code[0].fixableErrorCount = 1; - }); - - it("should return a string in the correct format", () => { - const result = formatter(code); - - assert.strictEqual(result, "\nfoo.js\n 5:10 error Unexpected foo foo\n\n\u2716 1 problem (1 error, 0 warnings)\n 1 error and 0 warnings potentially fixable with the `--fix` option.\n"); - assert.strictEqual(chalkStub.reset.callCount, 1); - assert.strictEqual(chalkStub.yellow.bold.callCount, 0); - assert.strictEqual(chalkStub.red.bold.callCount, 2); - }); - }); - }); - - describe("when passed a single warning message", () => { - const code = [{ - filePath: "foo.js", - errorCount: 0, - warningCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [{ - message: "Unexpected foo.", - severity: 1, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a string in the correct format", () => { - const result = formatter(code); - - assert.strictEqual(result, "\nfoo.js\n 5:10 warning Unexpected foo foo\n\n\u2716 1 problem (0 errors, 1 warning)\n"); - assert.strictEqual(chalkStub.reset.callCount, 1); - assert.strictEqual(chalkStub.yellow.bold.callCount, 1); - assert.strictEqual(chalkStub.red.bold.callCount, 0); - }); - - describe("when the error is fixable", () => { - beforeEach(() => { - code[0].fixableWarningCount = 1; - }); - - it("should return a string in the correct format", () => { - const result = formatter(code); - - assert.strictEqual(result, "\nfoo.js\n 5:10 warning Unexpected foo foo\n\n\u2716 1 problem (0 errors, 1 warning)\n 0 errors and 1 warning potentially fixable with the `--fix` option.\n"); - assert.strictEqual(chalkStub.reset.callCount, 1); - assert.strictEqual(chalkStub.yellow.bold.callCount, 2); - assert.strictEqual(chalkStub.red.bold.callCount, 0); - }); - - }); - }); - - describe("when passed a message that ends with ' .'", () => { - const code = [{ - filePath: "foo.js", - errorCount: 0, - warningCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [{ - message: "Unexpected .", - severity: 1, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a string in the correct format (retaining the ' .')", () => { - const result = formatter(code); - - assert.strictEqual(result, "\nfoo.js\n 5:10 warning Unexpected . foo\n\n\u2716 1 problem (0 errors, 1 warning)\n"); - assert.strictEqual(chalkStub.reset.callCount, 1); - assert.strictEqual(chalkStub.yellow.bold.callCount, 1); - assert.strictEqual(chalkStub.red.bold.callCount, 0); - }); - }); - - describe("when passed a fatal error message", () => { - const code = [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 0, - messages: [{ - fatal: true, - message: "Unexpected foo.", - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a string in the correct format", () => { - const result = formatter(code); - - assert.strictEqual(result, "\nfoo.js\n 5:10 error Unexpected foo foo\n\n\u2716 1 problem (1 error, 0 warnings)\n"); - assert.strictEqual(chalkStub.reset.callCount, 1); - assert.strictEqual(chalkStub.yellow.bold.callCount, 0); - assert.strictEqual(chalkStub.red.bold.callCount, 1); - }); - }); - - describe("when passed multiple messages", () => { - const code = [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 1, - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }, { - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }] - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.strictEqual(result, "\nfoo.js\n 5:10 error Unexpected foo foo\n 6:11 warning Unexpected bar bar\n\n\u2716 2 problems (1 error, 1 warning)\n"); - assert.strictEqual(chalkStub.reset.callCount, 1); - assert.strictEqual(chalkStub.yellow.bold.callCount, 0); - assert.strictEqual(chalkStub.red.bold.callCount, 1); - }); - }); - - describe("when passed multiple files with 1 message each", () => { - const code = [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 0, - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }, { - errorCount: 0, - warningCount: 1, - filePath: "bar.js", - messages: [{ - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }] - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.strictEqual(result, "\nfoo.js\n 5:10 error Unexpected foo foo\n\nbar.js\n 6:11 warning Unexpected bar bar\n\n\u2716 2 problems (1 error, 1 warning)\n"); - assert.strictEqual(chalkStub.reset.callCount, 1); - assert.strictEqual(chalkStub.yellow.bold.callCount, 0); - assert.strictEqual(chalkStub.red.bold.callCount, 1); - }); - - it("should add errorCount", () => { - code.forEach(c => { - c.errorCount = 1; - c.warningCount = 0; - }); - - const result = formatter(code); - - assert.strictEqual(result, "\nfoo.js\n 5:10 error Unexpected foo foo\n\nbar.js\n 6:11 warning Unexpected bar bar\n\n\u2716 2 problems (2 errors, 0 warnings)\n"); - assert.strictEqual(chalkStub.reset.callCount, 1); - assert.strictEqual(chalkStub.yellow.bold.callCount, 0); - assert.strictEqual(chalkStub.red.bold.callCount, 1); - }); - - it("should add warningCount", () => { - code.forEach(c => { - c.errorCount = 0; - c.warningCount = 1; - }); - - const result = formatter(code); - - assert.strictEqual(result, "\nfoo.js\n 5:10 error Unexpected foo foo\n\nbar.js\n 6:11 warning Unexpected bar bar\n\n\u2716 2 problems (0 errors, 2 warnings)\n"); - assert.strictEqual(chalkStub.reset.callCount, 1); - assert.strictEqual(chalkStub.yellow.bold.callCount, 0); - assert.strictEqual(chalkStub.red.bold.callCount, 1); - }); - }); - - describe("when passed one file not found message", () => { - const code = [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 0, - messages: [{ - fatal: true, - message: "Couldn't find foo.js." - }] - }]; - - it("should return a string without line and column", () => { - const result = formatter(code); - - assert.strictEqual(result, "\nfoo.js\n 0:0 error Couldn't find foo.js\n\n\u2716 1 problem (1 error, 0 warnings)\n"); - assert.strictEqual(chalkStub.reset.callCount, 1); - assert.strictEqual(chalkStub.yellow.bold.callCount, 0); - assert.strictEqual(chalkStub.red.bold.callCount, 1); - }); - }); - - describe("fixable problems", () => { - it("should not output fixable problems message when no errors or warnings are fixable", () => { - const code = [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - const result = formatter(code); - - assert.notInclude(result, "potentially fixable"); - }); - - it("should output the fixable problems message when errors are fixable", () => { - const code = [{ - filePath: "foo.js", - errorCount: 1, - warningCount: 0, - fixableErrorCount: 1, - fixableWarningCount: 0, - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - const result = formatter(code); - - assert.include(result, " 1 error and 0 warnings potentially fixable with the `--fix` option.\n"); - }); - - it("should output fixable problems message when warnings are fixable", () => { - const code = [{ - filePath: "foo.js", - errorCount: 0, - warningCount: 3, - fixableErrorCount: 0, - fixableWarningCount: 2, - messages: [{ - message: "Unexpected foo." - }] - }]; - - const result = formatter(code); - - assert.include(result, " 0 errors and 2 warnings potentially fixable with the `--fix` option.\n"); - }); - - it("should output the total number of fixable errors and warnings", () => { - const code = [{ - filePath: "foo.js", - errorCount: 5, - warningCount: 3, - fixableErrorCount: 5, - fixableWarningCount: 2, - messages: [{ - message: "Unexpected foo." - }] - }, { - filePath: "bar.js", - errorCount: 4, - warningCount: 2, - fixableErrorCount: 4, - fixableWarningCount: 1, - messages: [{ - message: "Unexpected bar." - }] - }]; - - const result = formatter(code); - - assert.include(result, " 9 errors and 3 warnings potentially fixable with the `--fix` option.\n"); - }); - }); + const originalColorLevel = chalk.level; + + beforeEach(() => { + chalk.level = 0; + sinon.spy(chalkStub, "reset"); + sinon.spy(chalkStub.yellow, "bold"); + sinon.spy(chalkStub.red, "bold"); + }); + + afterEach(() => { + sinon.verifyAndRestore(); + chalk.level = originalColorLevel; + }); + + describe("when passed no messages", () => { + const code = [ + { + filePath: "foo.js", + messages: [], + errorCount: 0, + warningCount: 0, + }, + ]; + + it("should not return message", () => { + const result = formatter(code); + + assert.strictEqual(result, ""); + assert.strictEqual(chalkStub.reset.callCount, 0); + assert.strictEqual(chalkStub.yellow.bold.callCount, 0); + assert.strictEqual(chalkStub.red.bold.callCount, 0); + }); + }); + + describe("when passed a single error message", () => { + const code = [ + { + filePath: "foo.js", + errorCount: 1, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [ + { + message: "Unexpected foo.", + severity: 2, + line: 5, + column: 10, + ruleId: "foo", + }, + ], + }, + ]; + + it("should return a string in the correct format", () => { + const result = formatter(code); + + assert.strictEqual( + result, + "\nfoo.js\n 5:10 error Unexpected foo foo\n\n\u2716 1 problem (1 error, 0 warnings)\n", + ); + assert.strictEqual(chalkStub.reset.callCount, 1); + assert.strictEqual(chalkStub.yellow.bold.callCount, 0); + assert.strictEqual(chalkStub.red.bold.callCount, 1); + }); + + describe("when the error is fixable", () => { + beforeEach(() => { + code[0].fixableErrorCount = 1; + }); + + it("should return a string in the correct format", () => { + const result = formatter(code); + + assert.strictEqual( + result, + "\nfoo.js\n 5:10 error Unexpected foo foo\n\n\u2716 1 problem (1 error, 0 warnings)\n 1 error and 0 warnings potentially fixable with the `--fix` option.\n", + ); + assert.strictEqual(chalkStub.reset.callCount, 1); + assert.strictEqual(chalkStub.yellow.bold.callCount, 0); + assert.strictEqual(chalkStub.red.bold.callCount, 2); + }); + }); + }); + + describe("when passed a single warning message", () => { + const code = [ + { + filePath: "foo.js", + errorCount: 0, + warningCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [ + { + message: "Unexpected foo.", + severity: 1, + line: 5, + column: 10, + ruleId: "foo", + }, + ], + }, + ]; + + it("should return a string in the correct format", () => { + const result = formatter(code); + + assert.strictEqual( + result, + "\nfoo.js\n 5:10 warning Unexpected foo foo\n\n\u2716 1 problem (0 errors, 1 warning)\n", + ); + assert.strictEqual(chalkStub.reset.callCount, 1); + assert.strictEqual(chalkStub.yellow.bold.callCount, 1); + assert.strictEqual(chalkStub.red.bold.callCount, 0); + }); + + describe("when the error is fixable", () => { + beforeEach(() => { + code[0].fixableWarningCount = 1; + }); + + it("should return a string in the correct format", () => { + const result = formatter(code); + + assert.strictEqual( + result, + "\nfoo.js\n 5:10 warning Unexpected foo foo\n\n\u2716 1 problem (0 errors, 1 warning)\n 0 errors and 1 warning potentially fixable with the `--fix` option.\n", + ); + assert.strictEqual(chalkStub.reset.callCount, 1); + assert.strictEqual(chalkStub.yellow.bold.callCount, 2); + assert.strictEqual(chalkStub.red.bold.callCount, 0); + }); + }); + }); + + describe("when passed a message that ends with ' .'", () => { + const code = [ + { + filePath: "foo.js", + errorCount: 0, + warningCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [ + { + message: "Unexpected .", + severity: 1, + line: 5, + column: 10, + ruleId: "foo", + }, + ], + }, + ]; + + it("should return a string in the correct format (retaining the ' .')", () => { + const result = formatter(code); + + assert.strictEqual( + result, + "\nfoo.js\n 5:10 warning Unexpected . foo\n\n\u2716 1 problem (0 errors, 1 warning)\n", + ); + assert.strictEqual(chalkStub.reset.callCount, 1); + assert.strictEqual(chalkStub.yellow.bold.callCount, 1); + assert.strictEqual(chalkStub.red.bold.callCount, 0); + }); + }); + + describe("when passed a fatal error message", () => { + const code = [ + { + filePath: "foo.js", + errorCount: 1, + warningCount: 0, + messages: [ + { + fatal: true, + message: "Unexpected foo.", + line: 5, + column: 10, + ruleId: "foo", + }, + ], + }, + ]; + + it("should return a string in the correct format", () => { + const result = formatter(code); + + assert.strictEqual( + result, + "\nfoo.js\n 5:10 error Unexpected foo foo\n\n\u2716 1 problem (1 error, 0 warnings)\n", + ); + assert.strictEqual(chalkStub.reset.callCount, 1); + assert.strictEqual(chalkStub.yellow.bold.callCount, 0); + assert.strictEqual(chalkStub.red.bold.callCount, 1); + }); + }); + + describe("when passed multiple messages", () => { + const code = [ + { + filePath: "foo.js", + errorCount: 1, + warningCount: 1, + messages: [ + { + message: "Unexpected foo.", + severity: 2, + line: 5, + column: 10, + ruleId: "foo", + }, + { + message: "Unexpected bar.", + severity: 1, + line: 6, + column: 11, + ruleId: "bar", + }, + ], + }, + ]; + + it("should return a string with multiple entries", () => { + const result = formatter(code); + + assert.strictEqual( + result, + "\nfoo.js\n 5:10 error Unexpected foo foo\n 6:11 warning Unexpected bar bar\n\n\u2716 2 problems (1 error, 1 warning)\n", + ); + assert.strictEqual(chalkStub.reset.callCount, 1); + assert.strictEqual(chalkStub.yellow.bold.callCount, 0); + assert.strictEqual(chalkStub.red.bold.callCount, 1); + }); + }); + + describe("when passed multiple files with 1 message each", () => { + const code = [ + { + filePath: "foo.js", + errorCount: 1, + warningCount: 0, + messages: [ + { + message: "Unexpected foo.", + severity: 2, + line: 5, + column: 10, + ruleId: "foo", + }, + ], + }, + { + errorCount: 0, + warningCount: 1, + filePath: "bar.js", + messages: [ + { + message: "Unexpected bar.", + severity: 1, + line: 6, + column: 11, + ruleId: "bar", + }, + ], + }, + ]; + + it("should return a string with multiple entries", () => { + const result = formatter(code); + + assert.strictEqual( + result, + "\nfoo.js\n 5:10 error Unexpected foo foo\n\nbar.js\n 6:11 warning Unexpected bar bar\n\n\u2716 2 problems (1 error, 1 warning)\n", + ); + assert.strictEqual(chalkStub.reset.callCount, 1); + assert.strictEqual(chalkStub.yellow.bold.callCount, 0); + assert.strictEqual(chalkStub.red.bold.callCount, 1); + }); + + it("should add errorCount", () => { + code.forEach(c => { + c.errorCount = 1; + c.warningCount = 0; + }); + + const result = formatter(code); + + assert.strictEqual( + result, + "\nfoo.js\n 5:10 error Unexpected foo foo\n\nbar.js\n 6:11 warning Unexpected bar bar\n\n\u2716 2 problems (2 errors, 0 warnings)\n", + ); + assert.strictEqual(chalkStub.reset.callCount, 1); + assert.strictEqual(chalkStub.yellow.bold.callCount, 0); + assert.strictEqual(chalkStub.red.bold.callCount, 1); + }); + + it("should add warningCount", () => { + code.forEach(c => { + c.errorCount = 0; + c.warningCount = 1; + }); + + const result = formatter(code); + + assert.strictEqual( + result, + "\nfoo.js\n 5:10 error Unexpected foo foo\n\nbar.js\n 6:11 warning Unexpected bar bar\n\n\u2716 2 problems (0 errors, 2 warnings)\n", + ); + assert.strictEqual(chalkStub.reset.callCount, 1); + assert.strictEqual(chalkStub.yellow.bold.callCount, 0); + assert.strictEqual(chalkStub.red.bold.callCount, 1); + }); + }); + + describe("when passed one file not found message", () => { + const code = [ + { + filePath: "foo.js", + errorCount: 1, + warningCount: 0, + messages: [ + { + fatal: true, + message: "Couldn't find foo.js.", + }, + ], + }, + ]; + + it("should return a string without line and column", () => { + const result = formatter(code); + + assert.strictEqual( + result, + "\nfoo.js\n 0:0 error Couldn't find foo.js\n\n\u2716 1 problem (1 error, 0 warnings)\n", + ); + assert.strictEqual(chalkStub.reset.callCount, 1); + assert.strictEqual(chalkStub.yellow.bold.callCount, 0); + assert.strictEqual(chalkStub.red.bold.callCount, 1); + }); + }); + + describe("fixable problems", () => { + it("should not output fixable problems message when no errors or warnings are fixable", () => { + const code = [ + { + filePath: "foo.js", + errorCount: 1, + warningCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [ + { + message: "Unexpected foo.", + severity: 2, + line: 5, + column: 10, + ruleId: "foo", + }, + ], + }, + ]; + + const result = formatter(code); + + assert.notInclude(result, "potentially fixable"); + }); + + it("should output the fixable problems message when errors are fixable", () => { + const code = [ + { + filePath: "foo.js", + errorCount: 1, + warningCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 0, + messages: [ + { + message: "Unexpected foo.", + severity: 2, + line: 5, + column: 10, + ruleId: "foo", + }, + ], + }, + ]; + + const result = formatter(code); + + assert.include( + result, + " 1 error and 0 warnings potentially fixable with the `--fix` option.\n", + ); + }); + + it("should output fixable problems message when warnings are fixable", () => { + const code = [ + { + filePath: "foo.js", + errorCount: 0, + warningCount: 3, + fixableErrorCount: 0, + fixableWarningCount: 2, + messages: [ + { + message: "Unexpected foo.", + }, + ], + }, + ]; + + const result = formatter(code); + + assert.include( + result, + " 0 errors and 2 warnings potentially fixable with the `--fix` option.\n", + ); + }); + + it("should output the total number of fixable errors and warnings", () => { + const code = [ + { + filePath: "foo.js", + errorCount: 5, + warningCount: 3, + fixableErrorCount: 5, + fixableWarningCount: 2, + messages: [ + { + message: "Unexpected foo.", + }, + ], + }, + { + filePath: "bar.js", + errorCount: 4, + warningCount: 2, + fixableErrorCount: 4, + fixableWarningCount: 1, + messages: [ + { + message: "Unexpected bar.", + }, + ], + }, + ]; + + const result = formatter(code); + + assert.include( + result, + " 9 errors and 3 warnings potentially fixable with the `--fix` option.\n", + ); + }); + }); }); diff --git a/tests/lib/cli-engine/formatters/tap.js b/tests/lib/cli-engine/formatters/tap.js deleted file mode 100644 index 8852ee72d60a..000000000000 --- a/tests/lib/cli-engine/formatters/tap.js +++ /dev/null @@ -1,235 +0,0 @@ -/** - * @fileoverview Tests for options. - * @author Jonathan Kingston - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert, - formatter = require("../../../../lib/cli-engine/formatters/tap"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("formatter:tap", () => { - describe("when passed no messages", () => { - const code = [{ - filePath: "foo.js", - messages: [] - }]; - - it("should return nothing", () => { - const result = formatter(code); - - assert.strictEqual(result, "TAP version 13\n1..1\nok 1 - foo.js\n"); - }); - }); - - describe("when passed a single message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a string with YAML severity, line and column", () => { - const result = formatter(code); - - assert.strictEqual(result, "TAP version 13\n1..1\nnot ok 1 - foo.js\n ---\n message: Unexpected foo.\n severity: error\n data:\n line: 5\n column: 10\n ruleId: foo\n ...\n"); - }); - - it("should return a string with line: x, column: y, severity: warning for warnings", () => { - code[0].messages[0].severity = 1; - const result = formatter(code); - - assert.include(result, "line: 5"); - assert.include(result, "column: 10"); - assert.include(result, "ruleId: foo"); - assert.include(result, "severity: warning"); - assert.include(result, "1..1"); - }); - }); - - describe("when passed a fatal error message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - fatal: true, - message: "Unexpected foo.", - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return an error string", () => { - const result = formatter(code); - - assert.include(result, "not ok"); - assert.include(result, "error"); - }); - }); - - describe("when passed a message with a severity of 1", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 1, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a warning string", () => { - const result = formatter(code); - - assert.include(result, "ok"); - assert.notInclude(result, "not ok"); - assert.include(result, "warning"); - }); - }); - - describe("when passed multiple messages with a severity of 1", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Foo.", - severity: 1, - line: 5, - column: 10, - ruleId: "foo" - }, { - message: "Bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }, { - message: "Baz.", - severity: 1, - line: 7, - column: 12, - ruleId: "baz" - }] - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.include(result, "ok"); - assert.notInclude(result, "not ok"); - assert.include(result, "messages"); - assert.include(result, "Foo."); - assert.include(result, "line: 5"); - assert.include(result, "column: 10"); - assert.include(result, "Bar."); - assert.include(result, "line: 6"); - assert.include(result, "column: 11"); - assert.include(result, "Baz."); - assert.include(result, "line: 7"); - assert.include(result, "column: 12"); - }); - }); - - describe("when passed multiple messages with different error severity", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }, { - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }, { - message: "Unexpected baz.", - severity: 1, - line: 7, - column: 12, - ruleId: "baz" - }] - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.include(result, "not ok"); - assert.include(result, "messages"); - assert.include(result, "Unexpected foo."); - assert.include(result, "line: 5"); - assert.include(result, "column: 10"); - assert.include(result, "Unexpected bar."); - assert.include(result, "line: 6"); - assert.include(result, "column: 11"); - assert.include(result, "Unexpected baz."); - assert.include(result, "line: 7"); - assert.include(result, "column: 12"); - }); - }); - - describe("when passed multiple files with 1 message each", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }, { - filePath: "bar.js", - messages: [{ - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }] - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.include(result, "not ok 1"); - assert.include(result, "ok 2"); - assert.notInclude(result, "not ok 2"); - }); - }); - - describe("when passed one file not found message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - fatal: true, - message: "Couldn't find foo.js." - }] - }]; - - it("should return a string without line and column", () => { - const result = formatter(code); - - assert.include(result, "line: 0"); - assert.include(result, "column: 0"); - assert.include(result, "severity: error"); - assert.include(result, "1..1"); - }); - }); -}); diff --git a/tests/lib/cli-engine/formatters/unix.js b/tests/lib/cli-engine/formatters/unix.js deleted file mode 100644 index 5d9a320dc659..000000000000 --- a/tests/lib/cli-engine/formatters/unix.js +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @fileoverview Tests for unix-style formatter. - * @author oshi-shinobu - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert, - formatter = require("../../../../lib/cli-engine/formatters/unix"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("formatter:compact", () => { - describe("when passed no messages", () => { - const code = [{ - filePath: "foo.js", - messages: [] - }]; - - it("should return nothing", () => { - const result = formatter(code); - - assert.strictEqual(result, ""); - }); - }); - - describe("when passed a single message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a string in the format filename:line:column: error [Error/rule_id]", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js:5:10: Unexpected foo. [Error/foo]\n\n1 problem"); - }); - - it("should return a string in the format filename:line:column: warning [Warning/rule_id]", () => { - code[0].messages[0].severity = 1; - const result = formatter(code); - - assert.strictEqual(result, "foo.js:5:10: Unexpected foo. [Warning/foo]\n\n1 problem"); - }); - }); - - describe("when passed a fatal error message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - fatal: true, - message: "Unexpected foo.", - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a string in the format filename:line:column: error [Error/rule_id]", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js:5:10: Unexpected foo. [Error/foo]\n\n1 problem"); - }); - }); - - describe("when passed multiple messages", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }, { - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }] - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js:5:10: Unexpected foo. [Error/foo]\nfoo.js:6:11: Unexpected bar. [Warning/bar]\n\n2 problems"); - }); - }); - - describe("when passed multiple files with 1 message each", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }, { - filePath: "bar.js", - messages: [{ - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }] - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js:5:10: Unexpected foo. [Error/foo]\nbar.js:6:11: Unexpected bar. [Warning/bar]\n\n2 problems"); - }); - }); - - describe("when passed one file not found message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - fatal: true, - message: "Couldn't find foo.js." - }] - }]; - - it("should return a string without line and column", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js:0:0: Couldn't find foo.js. [Error]\n\n1 problem"); - }); - }); -}); diff --git a/tests/lib/cli-engine/formatters/visualstudio.js b/tests/lib/cli-engine/formatters/visualstudio.js deleted file mode 100644 index c4ce358f0397..000000000000 --- a/tests/lib/cli-engine/formatters/visualstudio.js +++ /dev/null @@ -1,146 +0,0 @@ -/** - * @fileoverview Tests for VisualStudio format. - * @author Ronald Pijnacker - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert, - formatter = require("../../../../lib/cli-engine/formatters/visualstudio"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("formatter:visualstudio", () => { - describe("when passed no messages", () => { - const code = [{ - filePath: "foo.js", - messages: [] - }]; - - it("should return nothing", () => { - const result = formatter(code); - - assert.strictEqual(result, "no problems"); - }); - }); - - describe("when passed a single message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a string in the format filename(x,y): error z for errors", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js(5,10): error foo : Unexpected foo.\n\n1 problem"); - }); - - it("should return a string in the format filename(x,y): warning z for warnings", () => { - code[0].messages[0].severity = 1; - const result = formatter(code); - - assert.strictEqual(result, "foo.js(5,10): warning foo : Unexpected foo.\n\n1 problem"); - }); - }); - - describe("when passed a fatal error message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - fatal: true, - message: "Unexpected foo.", - line: 5, - column: 10, - ruleId: "foo" - }] - }]; - - it("should return a string in the format filename(x,y): error z", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js(5,10): error foo : Unexpected foo.\n\n1 problem"); - }); - }); - - describe("when passed multiple messages", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }, { - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }] - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js(5,10): error foo : Unexpected foo.\nfoo.js(6,11): warning bar : Unexpected bar.\n\n2 problems"); - }); - }); - - describe("when passed multiple files with 1 message each", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - message: "Unexpected foo.", - severity: 2, - line: 5, - column: 10, - ruleId: "foo" - }] - }, { - filePath: "bar.js", - messages: [{ - message: "Unexpected bar.", - severity: 1, - line: 6, - column: 11, - ruleId: "bar" - }] - }]; - - it("should return a string with multiple entries", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js(5,10): error foo : Unexpected foo.\nbar.js(6,11): warning bar : Unexpected bar.\n\n2 problems"); - }); - }); - - describe("when passed one file not found message", () => { - const code = [{ - filePath: "foo.js", - messages: [{ - fatal: true, - message: "Couldn't find foo.js." - }] - }]; - - it("should return a string without line and column", () => { - const result = formatter(code); - - assert.strictEqual(result, "foo.js(0): error : Couldn't find foo.js.\n\n1 problem"); - }); - }); -}); diff --git a/tests/lib/cli-engine/lint-result-cache.js b/tests/lib/cli-engine/lint-result-cache.js index 41c72308f427..1be8b7eb92cb 100644 --- a/tests/lib/cli-engine/lint-result-cache.js +++ b/tests/lib/cli-engine/lint-result-cache.js @@ -9,373 +9,422 @@ //----------------------------------------------------------------------------- const assert = require("chai").assert, - { CLIEngine } = require("../../../lib/cli-engine"), - fs = require("fs"), - path = require("path"), - proxyquire = require("proxyquire"), - sinon = require("sinon"); + { CLIEngine } = require("../../../lib/cli-engine"), + fs = require("node:fs"), + path = require("node:path"), + proxyquire = require("proxyquire"), + sinon = require("sinon"); //----------------------------------------------------------------------------- // Tests //----------------------------------------------------------------------------- describe("LintResultCache", () => { - const fixturePath = path.resolve( - __dirname, - "../../fixtures/lint-result-cache" - ); - const cacheFileLocation = path.join(fixturePath, ".eslintcache"); - const fileEntryCacheStubs = {}; - - let LintResultCache, - hashStub, - sandbox, - fakeConfig, - fakeErrorResults, - fakeErrorResultsAutofix; - - before(() => { - sandbox = sinon.createSandbox(); - hashStub = sandbox.stub(); - - let shouldFix = false; - - // Get lint results for test fixtures - const cliEngine = new CLIEngine({ - cache: false, - ignore: false, - globInputPaths: false, - fix: () => shouldFix - }); - - // Get results without autofixing... - fakeErrorResults = cliEngine.executeOnFiles([ - path.join(fixturePath, "test-with-errors.js") - ]).results[0]; - - // ...and with autofixing - shouldFix = true; - fakeErrorResultsAutofix = cliEngine.executeOnFiles([ - path.join(fixturePath, "test-with-errors.js") - ]).results[0]; - - // Set up LintResultCache with fake fileEntryCache module - LintResultCache = proxyquire( - "../../../lib/cli-engine/lint-result-cache.js", - { - "file-entry-cache": fileEntryCacheStubs, - "./hash": hashStub - } - ); - }); - - afterEach(done => { - sandbox.reset(); - - fs.unlink(cacheFileLocation, err => { - if (err && err.code !== "ENOENT") { - return done(err); - } - - return done(); - }); - }); - - describe("constructor", () => { - it("should throw an error if cache file path is not provided", () => { - assert.throws( - () => new LintResultCache(), - /Cache file location is required/u - ); - }); - - it("should throw an error if cacheStrategy is not provided", () => { - assert.throws( - () => new LintResultCache(cacheFileLocation), - /Cache strategy is required/u - ); - }); - - it("should throw an error if cacheStrategy is an invalid value", () => { - assert.throws( - () => new LintResultCache(cacheFileLocation, "foo"), - /Cache strategy must be one of/u - ); - }); - - it("should successfully create an instance if cache file location and cache strategy provided", () => { - const instance = new LintResultCache(cacheFileLocation, "metadata"); - - assert.ok(instance, "Instance should have been created successfully"); - }); - }); - - describe("getCachedLintResults", () => { - const filePath = path.join(fixturePath, "test-with-errors.js"); - const hashOfConfig = "hashOfConfig"; - - let cacheEntry, getFileDescriptorStub, lintResultsCache; - - before(() => { - getFileDescriptorStub = sandbox.stub(); - - fileEntryCacheStubs.create = () => ({ - getFileDescriptor: getFileDescriptorStub - }); - }); - - after(() => { - delete fileEntryCacheStubs.create; - }); - - beforeEach(() => { - cacheEntry = { - meta: { - - // Serialized results will have null source - results: Object.assign({}, fakeErrorResults, { source: null }), - - hashOfConfig - } - }; - - getFileDescriptorStub.withArgs(filePath).returns(cacheEntry); - - fakeConfig = {}; - - lintResultsCache = new LintResultCache(cacheFileLocation, "metadata"); - }); - - describe("when calculating the hashing", () => { - it("contains eslint version during hashing", () => { - const version = "eslint-=-version"; - const NewLintResultCache = proxyquire("../../../lib/cli-engine/lint-result-cache.js", { - "../../package.json": { version }, - "./hash": hashStub - }); - const newLintResultCache = new NewLintResultCache(cacheFileLocation, "metadata"); - - newLintResultCache.getCachedLintResults(filePath, fakeConfig); - assert.ok(hashStub.calledOnce); - assert.ok(hashStub.calledWithMatch(version)); - }); - - it("contains node version during hashing", () => { - const version = "node-=-version"; - - sandbox.stub(process, "version").value(version); - const NewLintResultCache = proxyquire("../../../lib/cli-engine/lint-result-cache.js", { - "./hash": hashStub - }); - const newLintResultCache = new NewLintResultCache(cacheFileLocation, "metadata"); - - newLintResultCache.getCachedLintResults(filePath, fakeConfig); - - assert.ok(hashStub.calledOnce); - assert.ok(hashStub.calledWithMatch(version)); - }); - }); - - describe("When file is changed", () => { - beforeEach(() => { - hashStub.returns(hashOfConfig); - cacheEntry.changed = true; - }); - - it("should return null", () => { - const result = lintResultsCache.getCachedLintResults( - filePath, - fakeConfig - ); - - assert.ok(getFileDescriptorStub.calledOnce); - assert.isNull(result); - }); - }); - - describe("When config hash is changed", () => { - beforeEach(() => { - hashStub.returns("differentHash"); - }); - - it("should return null", () => { - const result = lintResultsCache.getCachedLintResults( - filePath, - fakeConfig - ); - - assert.ok(getFileDescriptorStub.calledOnce); - assert.isNull(result); - }); - }); - - describe("When file is not found on filesystem", () => { - beforeEach(() => { - cacheEntry.notFound = true; - hashStub.returns(hashOfConfig); - }); - - it("should return null", () => { - const result = lintResultsCache.getCachedLintResults( - filePath, - fakeConfig - ); - - assert.ok(getFileDescriptorStub.calledOnce); - assert.isNull(result); - }); - }); - - describe("When file is present and unchanged and config is unchanged", () => { - beforeEach(() => { - hashStub.returns(hashOfConfig); - }); - - it("should return expected results", () => { - const result = lintResultsCache.getCachedLintResults( - filePath, - fakeConfig - ); - - assert.deepStrictEqual(result, fakeErrorResults); - assert.ok( - result.source, - "source property should be hydrated from filesystem" - ); - }); - }); - }); - - describe("setCachedLintResults", () => { - const filePath = path.join(fixturePath, "test-with-errors.js"); - const hashOfConfig = "hashOfConfig"; - - let cacheEntry, getFileDescriptorStub, lintResultsCache; - - before(() => { - getFileDescriptorStub = sandbox.stub(); - - fileEntryCacheStubs.create = () => ({ - getFileDescriptor: getFileDescriptorStub - }); - }); - - after(() => { - delete fileEntryCacheStubs.create; - }); - - beforeEach(() => { - cacheEntry = { - meta: {} - }; - - getFileDescriptorStub.withArgs(filePath).returns(cacheEntry); - - fakeConfig = {}; - - hashStub.returns(hashOfConfig); - - lintResultsCache = new LintResultCache(cacheFileLocation, "metadata"); - }); - - describe("When lint result has output property", () => { - it("does not modify file entry", () => { - lintResultsCache.setCachedLintResults( - filePath, - fakeConfig, - fakeErrorResultsAutofix - ); - - assert.notProperty(cacheEntry.meta, "results"); - assert.notProperty(cacheEntry.meta, "hashOfConfig"); - }); - }); - - describe("When file is not found on filesystem", () => { - beforeEach(() => { - cacheEntry.notFound = true; - }); - - it("does not modify file entry", () => { - lintResultsCache.setCachedLintResults( - filePath, - fakeConfig, - fakeErrorResults - ); - - assert.notProperty(cacheEntry.meta, "results"); - assert.notProperty(cacheEntry.meta, "hashOfConfig"); - }); - }); - - describe("When file is found on filesystem", () => { - beforeEach(() => { - lintResultsCache.setCachedLintResults( - filePath, - fakeConfig, - fakeErrorResults - ); - }); - - it("stores hash of config in file entry", () => { - assert.strictEqual(cacheEntry.meta.hashOfConfig, hashOfConfig); - }); - - it("stores results (except source) in file entry", () => { - const expectedCachedResults = Object.assign({}, fakeErrorResults, { - source: null - }); - - assert.deepStrictEqual(cacheEntry.meta.results, expectedCachedResults); - }); - }); - - describe("When file is found and empty", () => { - beforeEach(() => { - lintResultsCache.setCachedLintResults( - filePath, - fakeConfig, - Object.assign({}, fakeErrorResults, { source: "" }) - ); - }); - - it("stores hash of config in file entry", () => { - assert.strictEqual(cacheEntry.meta.hashOfConfig, hashOfConfig); - }); - - it("stores results (except source) in file entry", () => { - const expectedCachedResults = Object.assign({}, fakeErrorResults, { - source: null - }); - - assert.deepStrictEqual(cacheEntry.meta.results, expectedCachedResults); - }); - }); - }); - - describe("reconcile", () => { - let reconcileStub, lintResultsCache; - - before(() => { - reconcileStub = sandbox.stub(); - - fileEntryCacheStubs.create = () => ({ - reconcile: reconcileStub - }); - }); - - after(() => { - delete fileEntryCacheStubs.create; - }); - - beforeEach(() => { - lintResultsCache = new LintResultCache(cacheFileLocation, "metadata"); - }); - - it("calls reconcile on the underlying cache", () => { - lintResultsCache.reconcile(); - - assert.isTrue(reconcileStub.calledOnce); - }); - }); + const fixturePath = path.resolve( + __dirname, + "../../fixtures/lint-result-cache", + ); + const cacheFileLocation = path.join(fixturePath, ".eslintcache"); + const fileEntryCacheStubs = {}; + + let LintResultCache, + hashStub, + sandbox, + fakeConfig, + fakeErrorResults, + fakeErrorResultsAutofix; + + before(() => { + sandbox = sinon.createSandbox(); + hashStub = sandbox.stub(); + + let shouldFix = false; + + // Get lint results for test fixtures + const cliEngine = new CLIEngine({ + cache: false, + ignore: false, + globInputPaths: false, + fix: () => shouldFix, + }); + + // Get results without autofixing... + fakeErrorResults = cliEngine.executeOnFiles([ + path.join(fixturePath, "test-with-errors.js"), + ]).results[0]; + + // ...and with autofixing + shouldFix = true; + fakeErrorResultsAutofix = cliEngine.executeOnFiles([ + path.join(fixturePath, "test-with-errors.js"), + ]).results[0]; + + // Set up LintResultCache with fake fileEntryCache module + LintResultCache = proxyquire( + "../../../lib/cli-engine/lint-result-cache.js", + { + "file-entry-cache": fileEntryCacheStubs, + "./hash": hashStub, + }, + ); + }); + + afterEach(done => { + sandbox.reset(); + + fs.unlink(cacheFileLocation, err => { + if (err && err.code !== "ENOENT") { + return done(err); + } + + return done(); + }); + }); + + describe("constructor", () => { + it("should throw an error if cache file path is not provided", () => { + assert.throws( + () => new LintResultCache(), + /Cache file location is required/u, + ); + }); + + it("should throw an error if cacheStrategy is not provided", () => { + assert.throws( + () => new LintResultCache(cacheFileLocation), + /Cache strategy is required/u, + ); + }); + + it("should throw an error if cacheStrategy is an invalid value", () => { + assert.throws( + () => new LintResultCache(cacheFileLocation, "foo"), + /Cache strategy must be one of/u, + ); + }); + + it("should successfully create an instance if cache file location and cache strategy provided", () => { + const instance = new LintResultCache(cacheFileLocation, "metadata"); + + assert.ok( + instance, + "Instance should have been created successfully", + ); + }); + }); + + describe("getCachedLintResults", () => { + const filePath = path.join(fixturePath, "test-with-errors.js"); + const hashOfConfig = "hashOfConfig"; + + let cacheEntry, getFileDescriptorStub, lintResultsCache; + + before(() => { + getFileDescriptorStub = sandbox.stub(); + + fileEntryCacheStubs.create = () => ({ + getFileDescriptor: getFileDescriptorStub, + }); + }); + + after(() => { + delete fileEntryCacheStubs.create; + }); + + beforeEach(() => { + cacheEntry = { + meta: { + // Serialized results will have null source + results: Object.assign({}, fakeErrorResults, { + source: null, + }), + + hashOfConfig, + }, + }; + + getFileDescriptorStub.withArgs(filePath).returns(cacheEntry); + + fakeConfig = {}; + + lintResultsCache = new LintResultCache( + cacheFileLocation, + "metadata", + ); + }); + + describe("when calculating the hashing", () => { + it("contains eslint version during hashing", () => { + const version = "eslint-=-version"; + const NewLintResultCache = proxyquire( + "../../../lib/cli-engine/lint-result-cache.js", + { + "../../package.json": { version }, + "./hash": hashStub, + }, + ); + const newLintResultCache = new NewLintResultCache( + cacheFileLocation, + "metadata", + ); + + newLintResultCache.getCachedLintResults(filePath, fakeConfig); + assert.ok(hashStub.calledOnce); + assert.ok(hashStub.calledWithMatch(version)); + }); + + it("contains node version during hashing", () => { + const version = "node-=-version"; + + const versionStub = sandbox + .stub(process, "version") + .value(version); + + try { + const NewLintResultCache = proxyquire( + "../../../lib/cli-engine/lint-result-cache.js", + { + "./hash": hashStub, + }, + ); + const newLintResultCache = new NewLintResultCache( + cacheFileLocation, + "metadata", + ); + + newLintResultCache.getCachedLintResults( + filePath, + fakeConfig, + ); + + assert.ok(hashStub.calledOnce); + assert.ok(hashStub.calledWithMatch(version)); + } finally { + versionStub.restore(); + } + }); + }); + + describe("When file is changed", () => { + beforeEach(() => { + hashStub.returns(hashOfConfig); + cacheEntry.changed = true; + }); + + it("should return null", () => { + const result = lintResultsCache.getCachedLintResults( + filePath, + fakeConfig, + ); + + assert.ok(getFileDescriptorStub.calledOnce); + assert.isNull(result); + }); + }); + + describe("When config hash is changed", () => { + beforeEach(() => { + hashStub.returns("differentHash"); + }); + + it("should return null", () => { + const result = lintResultsCache.getCachedLintResults( + filePath, + fakeConfig, + ); + + assert.ok(getFileDescriptorStub.calledOnce); + assert.isNull(result); + }); + }); + + describe("When file is not found on filesystem", () => { + beforeEach(() => { + cacheEntry.notFound = true; + hashStub.returns(hashOfConfig); + }); + + it("should return null", () => { + const result = lintResultsCache.getCachedLintResults( + filePath, + fakeConfig, + ); + + assert.ok(getFileDescriptorStub.calledOnce); + assert.isNull(result); + }); + }); + + describe("When file is present and unchanged and config is unchanged", () => { + beforeEach(() => { + hashStub.returns(hashOfConfig); + }); + + it("should return expected results", () => { + const result = lintResultsCache.getCachedLintResults( + filePath, + fakeConfig, + ); + + assert.deepStrictEqual(result, fakeErrorResults); + assert.ok( + result.source, + "source property should be hydrated from filesystem", + ); + }); + }); + }); + + describe("setCachedLintResults", () => { + const filePath = path.join(fixturePath, "test-with-errors.js"); + const hashOfConfig = "hashOfConfig"; + + let cacheEntry, getFileDescriptorStub, lintResultsCache; + + before(() => { + getFileDescriptorStub = sandbox.stub(); + + fileEntryCacheStubs.create = () => ({ + getFileDescriptor: getFileDescriptorStub, + }); + }); + + after(() => { + delete fileEntryCacheStubs.create; + }); + + beforeEach(() => { + cacheEntry = { + meta: {}, + }; + + getFileDescriptorStub.withArgs(filePath).returns(cacheEntry); + + fakeConfig = {}; + + hashStub.returns(hashOfConfig); + + lintResultsCache = new LintResultCache( + cacheFileLocation, + "metadata", + ); + }); + + describe("When lint result has output property", () => { + it("does not modify file entry", () => { + lintResultsCache.setCachedLintResults( + filePath, + fakeConfig, + fakeErrorResultsAutofix, + ); + + assert.notProperty(cacheEntry.meta, "results"); + assert.notProperty(cacheEntry.meta, "hashOfConfig"); + }); + }); + + describe("When file is not found on filesystem", () => { + beforeEach(() => { + cacheEntry.notFound = true; + }); + + it("does not modify file entry", () => { + lintResultsCache.setCachedLintResults( + filePath, + fakeConfig, + fakeErrorResults, + ); + + assert.notProperty(cacheEntry.meta, "results"); + assert.notProperty(cacheEntry.meta, "hashOfConfig"); + }); + }); + + describe("When file is found on filesystem", () => { + beforeEach(() => { + lintResultsCache.setCachedLintResults( + filePath, + fakeConfig, + fakeErrorResults, + ); + }); + + it("stores hash of config in file entry", () => { + assert.strictEqual(cacheEntry.meta.hashOfConfig, hashOfConfig); + }); + + it("stores results (except source) in file entry", () => { + const expectedCachedResults = Object.assign( + {}, + fakeErrorResults, + { + source: null, + }, + ); + + assert.deepStrictEqual( + cacheEntry.meta.results, + expectedCachedResults, + ); + }); + }); + + describe("When file is found and empty", () => { + beforeEach(() => { + lintResultsCache.setCachedLintResults( + filePath, + fakeConfig, + Object.assign({}, fakeErrorResults, { source: "" }), + ); + }); + + it("stores hash of config in file entry", () => { + assert.strictEqual(cacheEntry.meta.hashOfConfig, hashOfConfig); + }); + + it("stores results (except source) in file entry", () => { + const expectedCachedResults = Object.assign( + {}, + fakeErrorResults, + { + source: null, + }, + ); + + assert.deepStrictEqual( + cacheEntry.meta.results, + expectedCachedResults, + ); + }); + }); + }); + + describe("reconcile", () => { + let reconcileStub, lintResultsCache; + + before(() => { + reconcileStub = sandbox.stub(); + + fileEntryCacheStubs.create = () => ({ + reconcile: reconcileStub, + }); + }); + + after(() => { + delete fileEntryCacheStubs.create; + }); + + beforeEach(() => { + lintResultsCache = new LintResultCache( + cacheFileLocation, + "metadata", + ); + }); + + it("calls reconcile on the underlying cache", () => { + lintResultsCache.reconcile(); + + assert.isTrue(reconcileStub.calledOnce); + }); + }); }); diff --git a/tests/lib/cli-engine/load-rules.js b/tests/lib/cli-engine/load-rules.js index beebecfb3863..d4a047f85364 100644 --- a/tests/lib/cli-engine/load-rules.js +++ b/tests/lib/cli-engine/load-rules.js @@ -17,17 +17,20 @@ const loadRules = require("../../../lib/cli-engine/load-rules"); //----------------------------------------------------------------------------- describe("when given an invalid rules directory", () => { - it("should throw an error", () => { - assert.throws(() => { - loadRules("invalidDir"); - }); - }); + it("should throw an error", () => { + assert.throws(() => { + loadRules("invalidDir"); + }); + }); }); describe("when given a valid rules directory", () => { - it("should load rules and not throw an error", () => { - const rules = loadRules("tests/fixtures/rules", process.cwd()); + it("should load rules and not throw an error", () => { + const rules = loadRules("tests/fixtures/rules", process.cwd()); - assert.strictEqual(rules["fixture-rule"], require(require.resolve("../../fixtures/rules/fixture-rule"))); - }); + assert.strictEqual( + rules["fixture-rule"], + require(require.resolve("../../fixtures/rules/fixture-rule")), + ); + }); }); diff --git a/tests/lib/cli.js b/tests/lib/cli.js index c69498231d0b..55007eb44d32 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -15,14 +15,15 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - stdAssert = require("assert"), - { ESLint } = require("../../lib/eslint"), - BuiltinRules = require("../../lib/rules"), - path = require("path"), - sinon = require("sinon"), - fs = require("fs"), - os = require("os"), - sh = require("shelljs"); + stdAssert = require("node:assert"), + { ESLint, LegacyESLint } = require("../../lib/eslint"), + BuiltinRules = require("../../lib/rules"), + path = require("node:path"), + sinon = require("sinon"), + fs = require("node:fs"), + os = require("node:os"), + sh = require("shelljs"), + { WarningService } = require("../../lib/services/warning-service"); const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); @@ -31,1623 +32,3201 @@ const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); //------------------------------------------------------------------------------ describe("cli", () => { - let fixtureDir; - const log = { - info: sinon.spy(), - error: sinon.spy() - }; - const RuntimeInfo = { - environment: sinon.stub(), - version: sinon.stub() - }; - const cli = proxyquire("../../lib/cli", { - "./shared/logging": log, - "./shared/runtime-info": RuntimeInfo - }); - - /** - * Verify that ESLint class receives correct opts via await cli.execute(). - * @param {string} cmd CLI command. - * @param {Object} opts Options hash that should match that received by ESLint class. - * @param {string} configType The config type to work with. - * @returns {void} - */ - async function verifyESLintOpts(cmd, opts, configType) { - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match(opts)); - - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: sinon.spy() }); - - const localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(configType === "flat") }, - "./shared/logging": log - }); - - await localCLI.execute(cmd, null, configType === "flat"); - sinon.verifyAndRestore(); - } - - // verifyESLintOpts - - /** - * Returns the path inside of the fixture directory. - * @param {...string} args file path segments. - * @returns {string} The path inside the fixture directory. - * @private - */ - function getFixturePath(...args) { - return path.join(fixtureDir, ...args); - } - - // copy into clean area so as not to get "infected" by this project's .eslintrc files - before(function() { - - /* - * GitHub Actions Windows and macOS runners occasionally exhibit - * extremely slow filesystem operations, during which copying fixtures - * exceeds the default test timeout, so raise it just for this hook. - * Mocha uses `this` to set timeouts on an individual hook level. - */ - this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API - fixtureDir = `${os.tmpdir()}/eslint/fixtures`; - sh.mkdir("-p", fixtureDir); - sh.cp("-r", "./tests/fixtures/.", fixtureDir); - }); - - afterEach(() => { - log.info.resetHistory(); - log.error.resetHistory(); - }); - - after(() => { - sh.rm("-r", fixtureDir); - }); - - ["eslintrc", "flat"].forEach(configType => { - - const useFlatConfig = configType === "flat"; - - describe("execute()", () => { - - it(`should return error when text with incorrect quotes is passed as argument with configType:${configType}`, async () => { - const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; - const configFile = getFixturePath("configurations", "quotes-error.js"); - const result = await cli.execute(`${flag} -c ${configFile} --stdin --stdin-filename foo.js`, "var foo = 'bar';", useFlatConfig); - - assert.strictEqual(result, 1); - }); - - it(`should not print debug info when passed the empty string as text with configType:${configType}`, async () => { - const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; - const result = await cli.execute(["argv0", "argv1", "--stdin", flag, "--stdin-filename", "foo.js"], "", useFlatConfig); - - assert.strictEqual(result, 0); - assert.isTrue(log.info.notCalled); - }); - - it(`should exit with console error when passed unsupported arguments with configType:${configType}`, async () => { - const filePath = getFixturePath("files"); - const result = await cli.execute(`--blah --another ${filePath}`, null, useFlatConfig); - - assert.strictEqual(result, 2); - }); - - }); - - describe("flat config", () => { - const originalEnv = process.env; - const originalCwd = process.cwd; - - beforeEach(() => { - process.env = { ...originalEnv }; - }); - - afterEach(() => { - process.env = originalEnv; - process.cwd = originalCwd; - }); - - it(`should use it when an eslint.config.js is present and useFlatConfig is true:${configType}`, async () => { - process.cwd = getFixturePath; - - const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); - - // When flat config is used, we get an exit code of 2 because the --ext option is unrecognized. - assert.strictEqual(exitCode, useFlatConfig ? 2 : 0); - }); - - it(`should not use it when ESLINT_USE_FLAT_CONFIG=false even if an eslint.config.js is present:${configType}`, async () => { - process.env.ESLINT_USE_FLAT_CONFIG = "false"; - process.cwd = getFixturePath; - - const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); - - assert.strictEqual(exitCode, 0); - }); - - it(`should use it when ESLINT_USE_FLAT_CONFIG=true and useFlatConfig is true even if an eslint.config.js is not present:${configType}`, async () => { - process.env.ESLINT_USE_FLAT_CONFIG = "true"; - - // Set the CWD to outside the fixtures/ directory so that no eslint.config.js is found - process.cwd = () => getFixturePath(".."); - - const exitCode = await cli.execute(`--no-ignore --ext .js ${getFixturePath("files")}`, null, useFlatConfig); - - // When flat config is used, we get an exit code of 2 because the --ext option is unrecognized. - assert.strictEqual(exitCode, useFlatConfig ? 2 : 0); - }); - }); - - describe("when given a config with rules with options and severity level set to error", () => { - - const originalCwd = process.cwd; - - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); - - afterEach(() => { - process.cwd = originalCwd; - }); - - it(`should exit with an error status (1) with configType:${configType}`, async () => { - const configPath = getFixturePath("configurations", "quotes-error.js"); - const filePath = getFixturePath("single-quoted.js"); - const code = `--no-ignore --config ${configPath} ${filePath}`; - - const exitStatus = await cli.execute(code, null, useFlatConfig); - - assert.strictEqual(exitStatus, 1); - }); - }); - - describe("when there is a local config file", () => { - - const originalCwd = process.cwd; - - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); - - afterEach(() => { - process.cwd = originalCwd; - }); - - it(`should load the local config file with configType:${configType}`, async () => { - await cli.execute("cli/passing.js --no-ignore", null, useFlatConfig); - }); - - if (useFlatConfig) { - it(`should load the local config file with glob pattern and configType:${configType}`, async () => { - await cli.execute("cli/pass*.js --no-ignore", null, useFlatConfig); - }); - } - - // only works on Windows - if (os.platform() === "win32") { - it(`should load the local config file with Windows slashes glob pattern and configType:${configType}`, async () => { - await cli.execute("cli\\pass*.js --no-ignore", null, useFlatConfig); - }); - } - }); - - describe("Formatters", () => { - - describe("when given a valid built-in formatter name", () => { - it(`should execute without any errors with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); - const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; - const exit = await cli.execute(`${flag} -f checkstyle ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exit, 0); - }); - }); - - describe("when given a valid built-in formatter name that uses rules meta.", () => { - - const originalCwd = process.cwd; - - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); - - afterEach(() => { - process.cwd = originalCwd; - }); - - it(`should execute without any errors with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); - const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; - const exit = await cli.execute(`--no-ignore -f json-with-metadata ${filePath} ${flag}`, null, useFlatConfig); - - assert.strictEqual(exit, 0); - - /* - * Note: There is a behavior difference between eslintrc and flat config - * when using formatters. For eslintrc, rulesMeta always contains every - * rule that was loaded during the last run; for flat config, rulesMeta - * only contains meta data for the rules that triggered messages in the - * results. (Flat config uses ESLint#getRulesMetaForResults().) - */ - - // Check metadata. - const { metadata } = JSON.parse(log.info.args[0][0]); - const expectedMetadata = { - cwd: process.cwd(), - rulesMeta: useFlatConfig ? {} : Array.from(BuiltinRules).reduce((obj, [ruleId, rule]) => { - obj[ruleId] = rule.meta; - return obj; - }, {}) - }; - - assert.deepStrictEqual(metadata, expectedMetadata); - }); - }); - - describe("when the --max-warnings option is passed", () => { - const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; - - describe("and there are too many warnings", () => { - it(`should provide \`maxWarningsExceeded\` metadata to the formatter with configType:${configType}`, async () => { - const exit = await cli.execute( - `--no-ignore -f json-with-metadata --max-warnings 1 --rule 'quotes: warn' ${flag}`, - "'hello' + 'world';", - useFlatConfig - ); - - assert.strictEqual(exit, 1); - - const { metadata } = JSON.parse(log.info.args[0][0]); - - assert.deepStrictEqual( - metadata.maxWarningsExceeded, - { maxWarnings: 1, foundWarnings: 2 } - ); - }); - }); - - describe("and warnings do not exceed the limit", () => { - it(`should omit \`maxWarningsExceeded\` metadata from the formatter with configType:${configType}`, async () => { - const exit = await cli.execute( - `--no-ignore -f json-with-metadata --max-warnings 1 --rule 'quotes: warn' ${flag}`, - "'hello world';", - useFlatConfig - ); - - assert.strictEqual(exit, 0); - - const { metadata } = JSON.parse(log.info.args[0][0]); - - assert.notProperty(metadata, "maxWarningsExceeded"); - }); - }); - }); - - describe("when given an invalid built-in formatter name", () => { - it(`should execute with error: with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`-f fakeformatter ${filePath}`); - - assert.strictEqual(exit, 2); - }); - }); - - describe("when given a valid formatter path", () => { - it(`should execute without any errors with configType:${configType}`, async () => { - const formatterPath = getFixturePath("formatters", "simple.js"); - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`-f ${formatterPath} ${filePath}`); - - assert.strictEqual(exit, 0); - }); - }); - - describe("when given an invalid formatter path", () => { - - const originalCwd = process.cwd; - - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); - - afterEach(() => { - process.cwd = originalCwd; - }); - - it(`should execute with error with configType:${configType}`, async () => { - const formatterPath = getFixturePath("formatters", "file-does-not-exist.js"); - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`--no-ignore -f ${formatterPath} ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exit, 2); - }); - }); - - describe("when given an async formatter path", () => { - it(`should execute without any errors with configType:${configType}`, async () => { - const formatterPath = getFixturePath("formatters", "async.js"); - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`-f ${formatterPath} ${filePath}`); - - assert.strictEqual(log.info.getCall(0).args[0], "from async formatter"); - assert.strictEqual(exit, 0); - }); - }); - }); - - describe("Exit Codes", () => { - - const originalCwd = process.cwd; - - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); - - afterEach(() => { - process.cwd = originalCwd; - }); - - describe("when executing a file with a lint error", () => { - - it(`should exit with error with configType:${configType}`, async () => { - const filePath = getFixturePath("undef.js"); - const code = `--no-ignore --rule no-undef:2 ${filePath}`; - - const exit = await cli.execute(code, null, useFlatConfig); - - assert.strictEqual(exit, 1); - }); - }); - - describe("when using --fix-type without --fix or --fix-dry-run", () => { - it(`should exit with error with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); - const code = `--fix-type suggestion ${filePath}`; - - const exit = await cli.execute(code, null, useFlatConfig); - - assert.strictEqual(exit, 2); - }); - }); - - describe("when executing a file with a syntax error", () => { - it(`should exit with error with configType:${configType}`, async () => { - const filePath = getFixturePath("syntax-error.js"); - const exit = await cli.execute(`--no-ignore ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exit, 1); - }); - }); - - }); - - describe("when calling execute more than once", () => { - - const originalCwd = process.cwd; - - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); - - afterEach(() => { - process.cwd = originalCwd; - }); - - it(`should not print the results from previous execution with configType:${configType}`, async () => { - const filePath = getFixturePath("missing-semicolon.js"); - const passingPath = getFixturePath("passing.js"); - - await cli.execute(`--no-ignore --rule semi:2 ${filePath}`, null, useFlatConfig); - - assert.isTrue(log.info.called, "Log should have been called."); - - log.info.resetHistory(); - - await cli.execute(`--no-ignore --rule semi:2 ${passingPath}`, null, useFlatConfig); - assert.isTrue(log.info.notCalled); - - }); - }); - - describe("when executing with version flag", () => { - it(`should print out current version with configType:${configType}`, async () => { - assert.strictEqual(await cli.execute("-v", null, useFlatConfig), 0); - assert.strictEqual(log.info.callCount, 1); - }); - }); - - describe("when executing with env-info flag", () => { - - it(`should print out environment information with configType:${configType}`, async () => { - assert.strictEqual(await cli.execute("--env-info", null, useFlatConfig), 0); - assert.strictEqual(log.info.callCount, 1); - }); - - describe("With error condition", () => { - - beforeEach(() => { - RuntimeInfo.environment = sinon.stub().throws("There was an error!"); - }); - - afterEach(() => { - RuntimeInfo.environment = sinon.stub(); - }); - - it(`should print error message and return error code with configType:${configType}`, async () => { - - assert.strictEqual(await cli.execute("--env-info", null, useFlatConfig), 2); - assert.strictEqual(log.error.callCount, 1); - }); - }); - - }); - - describe("when executing with help flag", () => { - it(`should print out help with configType:${configType}`, async () => { - assert.strictEqual(await cli.execute("-h", null, useFlatConfig), 0); - assert.strictEqual(log.info.callCount, 1); - }); - }); - - describe("when executing a file with a shebang", () => { - it(`should execute without error with configType:${configType}`, async () => { - const filePath = getFixturePath("shebang.js"); - const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; - const exit = await cli.execute(`${flag} --no-ignore ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exit, 0); - }); - }); - - describe("FixtureDir Dependent Tests", () => { - - const originalCwd = process.cwd; - - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); - - afterEach(() => { - process.cwd = originalCwd; - }); - - describe("when given a config file and a directory of files", () => { - it(`should load and execute without error with configType:${configType}`, async () => { - const configPath = getFixturePath("configurations", "semi-error.js"); - const filePath = getFixturePath("formatters"); - const code = `--no-ignore --config ${configPath} ${filePath}`; - const exitStatus = await cli.execute(code, null, useFlatConfig); - - assert.strictEqual(exitStatus, 0); - }); - }); - - describe("when executing with global flag", () => { - - it(`should default defined variables to read-only with configType:${configType}`, async () => { - const filePath = getFixturePath("undef.js"); - const exit = await cli.execute(`--global baz,bat --no-ignore --rule no-global-assign:2 ${filePath}`, null, useFlatConfig); - - assert.isTrue(log.info.calledOnce); - assert.strictEqual(exit, 1); - }); - - it(`should allow defining writable global variables with configType:${configType}`, async () => { - const filePath = getFixturePath("undef.js"); - const exit = await cli.execute(`--global baz:false,bat:true --no-ignore ${filePath}`, null, useFlatConfig); - - assert.isTrue(log.info.notCalled); - assert.strictEqual(exit, 0); - }); - - it(`should allow defining variables with multiple flags with configType:${configType}`, async () => { - const filePath = getFixturePath("undef.js"); - const exit = await cli.execute(`--global baz --global bat:true --no-ignore ${filePath}`); - - assert.isTrue(log.info.notCalled); - assert.strictEqual(exit, 0); - }); - }); - - - describe("when supplied with rule flag and severity level set to error", () => { - - - it(`should exit with an error status (2) with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const code = `--no-ignore --rule 'quotes: [2, double]' ${filePath}`; - const exitStatus = await cli.execute(code, null, useFlatConfig); - - assert.strictEqual(exitStatus, 1); - }); - }); - - describe("when the quiet option is enabled", () => { - - it(`should only print error with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const cliArgs = `--no-ignore --quiet -f compact --rule 'quotes: [2, double]' --rule 'no-unused-vars: 1' ${filePath}`; - - await cli.execute(cliArgs, null, useFlatConfig); - - sinon.assert.calledOnce(log.info); - - const formattedOutput = log.info.firstCall.args[0]; - - assert.include(formattedOutput, "Error"); - assert.notInclude(formattedOutput, "Warning"); - }); - - it(`should print nothing if there are no errors with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const cliArgs = `--quiet -f compact --rule 'quotes: [1, double]' --rule 'no-unused-vars: 1' ${filePath}`; - - await cli.execute(cliArgs, null, useFlatConfig); - - sinon.assert.notCalled(log.info); - }); - }); - - - describe("no-error-on-unmatched-pattern flag", () => { - - describe("when executing without no-error-on-unmatched-pattern flag", () => { - it(`should throw an error on unmatched glob pattern with configType:${configType}`, async () => { - let filePath = getFixturePath("unmatched-patterns"); - const globPattern = "unmatched*.js"; - - if (useFlatConfig) { - filePath = filePath.replace(/\\/gu, "/"); - } - - await stdAssert.rejects(async () => { - await cli.execute(`"${filePath}/${globPattern}"`, null, useFlatConfig); - }, new Error(`No files matching '${filePath}/${globPattern}' were found.`)); - }); - - }); - - describe("when executing with no-error-on-unmatched-pattern flag", () => { - it(`should not throw an error on unmatched node glob syntax patterns with configType:${configType}`, async () => { - const filePath = getFixturePath("unmatched-patterns"); - const exit = await cli.execute(`--no-error-on-unmatched-pattern "${filePath}/unmatched*.js"`, null, useFlatConfig); - - assert.strictEqual(exit, 0); - }); - }); - - describe("when executing with no-error-on-unmatched-pattern flag and multiple patterns", () => { - it(`should not throw an error on multiple unmatched node glob syntax patterns with configType:${configType}`, async () => { - const filePath = getFixturePath("unmatched-patterns/js3"); - const exit = await cli.execute(`--no-error-on-unmatched-pattern ${filePath}/unmatched1*.js ${filePath}/unmatched2*.js`, null, useFlatConfig); - - assert.strictEqual(exit, 0); - }); - - it(`should still throw an error on when a matched pattern has lint errors with configType:${configType}`, async () => { - const filePath = getFixturePath("unmatched-patterns"); - const exit = await cli.execute(`--no-ignore --no-error-on-unmatched-pattern ${filePath}/unmatched1*.js ${filePath}/failing.js`, null, useFlatConfig); - - assert.strictEqual(exit, 1); - }); - }); - - }); - - describe("Parser Options", () => { - - describe("when given parser options", () => { - it(`should exit with error if parser options are invalid with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`--no-ignore --parser-options test111 ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exit, 2); - }); - - it(`should exit with no error if parser is valid with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:6 ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exit, 0); - }); - - it(`should exit with an error on ecmaVersion 7 feature in ecmaVersion 6 with configType:${configType}`, async () => { - const filePath = getFixturePath("passing-es7.js"); - const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:6 ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exit, 1); - }); - - it(`should exit with no error on ecmaVersion 7 feature in ecmaVersion 7 with configType:${configType}`, async () => { - const filePath = getFixturePath("passing-es7.js"); - const exit = await cli.execute(`--no-ignore --parser-options=ecmaVersion:7 ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exit, 0); - }); - - it(`should exit with no error on ecmaVersion 7 feature with config ecmaVersion 6 and command line ecmaVersion 7 with configType:${configType}`, async () => { - const configPath = useFlatConfig - ? getFixturePath("configurations", "es6.js") - : getFixturePath("configurations", "es6.json"); - const filePath = getFixturePath("passing-es7.js"); - const exit = await cli.execute(`--no-ignore --config ${configPath} --parser-options=ecmaVersion:7 ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exit, 0); - }); - }); - }); - - describe("when given the max-warnings flag", () => { - - let filePath, configFilePath; - - before(() => { - filePath = getFixturePath("max-warnings/six-warnings.js"); - configFilePath = getFixturePath(useFlatConfig ? "max-warnings/eslint.config.js" : "max-warnings/.eslintrc"); - }); - - it(`should not change exit code if warning count under threshold with configType:${configType}`, async () => { - const exitCode = await cli.execute(`--no-ignore --max-warnings 10 ${filePath} -c ${configFilePath}`, null, useFlatConfig); - - assert.strictEqual(exitCode, 0); - }); - - it(`should exit with exit code 1 if warning count exceeds threshold with configType:${configType}`, async () => { - const exitCode = await cli.execute(`--no-ignore --max-warnings 5 ${filePath} -c ${configFilePath}`, null, useFlatConfig); - - assert.strictEqual(exitCode, 1); - assert.ok(log.error.calledOnce); - assert.include(log.error.getCall(0).args[0], "ESLint found too many warnings"); - }); - - it(`should exit with exit code 1 without printing warnings if the quiet option is enabled and warning count exceeds threshold with configType:${configType}`, async () => { - const exitCode = await cli.execute(`--no-ignore --quiet --max-warnings 5 ${filePath} -c ${configFilePath}`, null, useFlatConfig); - - assert.strictEqual(exitCode, 1); - assert.ok(log.error.calledOnce); - assert.include(log.error.getCall(0).args[0], "ESLint found too many warnings"); - assert.ok(log.info.notCalled); // didn't print warnings - }); - - it(`should not change exit code if warning count equals threshold with configType:${configType}`, async () => { - const exitCode = await cli.execute(`--no-ignore --max-warnings 6 ${filePath} -c ${configFilePath}`, null, useFlatConfig); - - assert.strictEqual(exitCode, 0); - }); - - it(`should not change exit code if flag is not specified and there are warnings with configType:${configType}`, async () => { - const exitCode = await cli.execute(`-c ${configFilePath} ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exitCode, 0); - }); - }); - - describe("when given the exit-on-fatal-error flag", () => { - it(`should not change exit code if no fatal errors are reported with configType:${configType}`, async () => { - const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error.js"); - const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exitCode, 0); - }); - - it(`should exit with exit code 1 if no fatal errors are found, but rule violations are found with configType:${configType}`, async () => { - const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error-rule-violation.js"); - const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exitCode, 1); - }); - - it(`should exit with exit code 2 if fatal error is found with configType:${configType}`, async () => { - const filePath = getFixturePath("exit-on-fatal-error", "fatal-error.js"); - const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exitCode, 2); - }); - - it(`should exit with exit code 2 if fatal error is found in any file with configType:${configType}`, async () => { - const filePath = getFixturePath("exit-on-fatal-error"); - const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exitCode, 2); - }); - - - }); - - - describe("Ignores", () => { - - describe("when given a directory with eslint excluded files in the directory", () => { - it(`should throw an error and not process any files with configType:${configType}`, async () => { - const options = useFlatConfig - ? `--config ${getFixturePath("eslint.config_with_ignores.js")}` - : `--ignore-path ${getFixturePath(".eslintignore")}`; - const filePath = getFixturePath("cli"); - const expectedMessage = useFlatConfig - ? `All files matched by '${filePath.replace(/\\/gu, "/")}' are ignored.` - : `All files matched by '${filePath}' are ignored.`; - - await stdAssert.rejects(async () => { - await cli.execute(`${options} ${filePath}`, null, useFlatConfig); - }, new Error(expectedMessage)); - }); - }); - - describe("when given a file in excluded files list", () => { - it(`should not process the file with configType:${configType}`, async () => { - const options = useFlatConfig - ? `--config ${getFixturePath("eslint.config_with_ignores.js")}` - : `--ignore-path ${getFixturePath(".eslintignore")}`; - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`${options} ${filePath}`, null, useFlatConfig); - - // a warning about the ignored file - assert.isTrue(log.info.called); - assert.strictEqual(exit, 0); - }); - - it(`should process the file when forced with configType:${configType}`, async () => { - const options = useFlatConfig - ? `--config ${getFixturePath("eslint.config_with_ignores.js")}` - : `--ignore-path ${getFixturePath(".eslintignore")}`; - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`${options} --no-ignore ${filePath}`, null, useFlatConfig); - - // no warnings - assert.isFalse(log.info.called); - assert.strictEqual(exit, 0); - }); - - it(`should suppress the warning if --no-warn-ignored is passed with configType:${configType}`, async () => { - const options = useFlatConfig - ? `--config ${getFixturePath("eslint.config_with_ignores.js")}` - : `--ignore-path ${getFixturePath(".eslintignore")}`; - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`${options} --no-warn-ignored ${filePath}`, null, useFlatConfig); - - assert.isFalse(log.info.called); - - // When eslintrc is used, we get an exit code of 2 because the --no-warn-ignored option is unrecognized. - assert.strictEqual(exit, useFlatConfig ? 0 : 2); - }); - - it(`should suppress the warning if --no-warn-ignored is passed and an ignored file is passed via stdin with configType:${configType}`, async () => { - const options = useFlatConfig - ? `--config ${getFixturePath("eslint.config_with_ignores.js")}` - : `--ignore-path ${getFixturePath(".eslintignore")}`; - const filePath = getFixturePath("passing.js"); - const exit = await cli.execute(`${options} --no-warn-ignored --stdin --stdin-filename ${filePath}`, "foo", useFlatConfig); - - assert.isFalse(log.info.called); - - // When eslintrc is used, we get an exit code of 2 because the --no-warn-ignored option is unrecognized. - assert.strictEqual(exit, useFlatConfig ? 0 : 2); - }); - }); - - describe("when given a pattern to ignore", () => { - it(`should not process any files with configType:${configType}`, async () => { - const ignoredFile = getFixturePath("cli/syntax-error.js"); - const ignorePathOption = useFlatConfig - ? "" - : "--ignore-path .eslintignore_empty"; - const filePath = getFixturePath("cli/passing.js"); - const ignorePattern = useFlatConfig ? "cli/**" : "cli/"; - const exit = await cli.execute( - `--ignore-pattern ${ignorePattern} ${ignorePathOption} ${ignoredFile} ${filePath}`, null, useFlatConfig - ); - - // warnings about the ignored files - assert.isTrue(log.info.called); - assert.strictEqual(exit, 0); - }); - - it(`should interpret pattern that contains a slash as relative to cwd with configType:${configType}`, async () => { - process.cwd = () => getFixturePath("cli/ignore-pattern-relative/subdir"); - - /* - * The config file is in `cli/ignore-pattern-relative`, so this would fail - * if `subdir/**` ignore pattern is interpreted as relative to the config base path. - */ - const exit = await cli.execute("**/*.js --ignore-pattern subdir/**", null, useFlatConfig); - - assert.strictEqual(exit, 0); - - await stdAssert.rejects( - async () => await cli.execute("**/*.js --ignore-pattern subsubdir/*.js", null, useFlatConfig), - /All files matched by '\*\*\/\*\.js' are ignored/u - ); - }); - - it(`should interpret pattern that doesn't contain a slash as relative to cwd with configType:${configType}`, async () => { - process.cwd = () => getFixturePath("cli/ignore-pattern-relative/subdir/subsubdir"); - - await stdAssert.rejects( - async () => await cli.execute("**/*.js --ignore-pattern *.js", null, useFlatConfig), - /All files matched by '\*\*\/\*\.js' are ignored/u - ); - }); - - if (useFlatConfig) { - it("should ignore files if the pattern is a path to a directory (with trailing slash)", async () => { - const filePath = getFixturePath("cli/syntax-error.js"); - const exit = await cli.execute(`--ignore-pattern cli/ ${filePath}`, null, true); - - // parsing error causes exit code 1 - assert.isTrue(log.info.called); - assert.strictEqual(exit, 0); - }); - - it("should ignore files if the pattern is a path to a directory (without trailing slash)", async () => { - const filePath = getFixturePath("cli/syntax-error.js"); - const exit = await cli.execute(`--ignore-pattern cli ${filePath}`, null, true); - - // parsing error causes exit code 1 - assert.isTrue(log.info.called); - assert.strictEqual(exit, 0); - }); - } - }); - - }); - - }); - - - describe("when given a parser name", () => { - - it(`should exit with a fatal error if parser is invalid with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); - - await stdAssert.rejects(async () => await cli.execute(`--no-ignore --parser test111 ${filePath}`, null, useFlatConfig), "Cannot find module 'test111'"); - }); - - it(`should exit with no error if parser is valid with configType:${configType}`, async () => { - const filePath = getFixturePath("passing.js"); - const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; - const exit = await cli.execute(`${flag} --no-ignore --parser espree ${filePath}`, null, useFlatConfig); - - assert.strictEqual(exit, 0); - }); - - }); - - describe("when supplied with report output file path", () => { - const flag = useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"; - - afterEach(() => { - sh.rm("-rf", "tests/output"); - }); - - it(`should write the file and create dirs if they don't exist with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const code = `${flag} --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; - - await cli.execute(code, null, useFlatConfig); - - assert.include(fs.readFileSync("tests/output/eslint-output.txt", "utf8"), filePath); - assert.isTrue(log.info.notCalled); - }); - - it(`should return an error if the path is a directory with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const code = `${flag} --rule 'quotes: [1, double]' --o tests/output ${filePath}`; - - fs.mkdirSync("tests/output"); - - const exit = await cli.execute(code, null, useFlatConfig); - - assert.strictEqual(exit, 2); - assert.isTrue(log.info.notCalled); - assert.isTrue(log.error.calledOnce); - }); - - it(`should return an error if the path could not be written to with configType:${configType}`, async () => { - const filePath = getFixturePath("single-quoted.js"); - const code = `${flag} --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; - - fs.writeFileSync("tests/output", "foo"); - - const exit = await cli.execute(code, null, useFlatConfig); - - assert.strictEqual(exit, 2); - assert.isTrue(log.info.notCalled); - assert.isTrue(log.error.calledOnce); - }); - }); - - describe("when passed --no-inline-config", () => { - let localCLI; - - afterEach(() => { - sinon.verifyAndRestore(); - }); - - it(`should pass allowInlineConfig:false to ESLint when --no-inline-config is used with configType:${configType}`, async () => { - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ allowInlineConfig: false })); - - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 2, - message: "Fake message" - } - ], - errorCount: 1, - warningCount: 0 - }]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.stub(); - - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - "./shared/logging": log - }); - - await localCLI.execute("--no-inline-config .", null, useFlatConfig); - }); - - it(`should not error and allowInlineConfig should be true by default with configType:${configType}`, async () => { - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ allowInlineConfig: true })); - - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.stub(); - - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - "./shared/logging": log - }); - - const exitCode = await localCLI.execute(".", null, useFlatConfig); - - assert.strictEqual(exitCode, 0); - - }); - - }); - - describe("when passed --fix", () => { - let localCLI; - - afterEach(() => { - sinon.verifyAndRestore(); - }); - - it(`should pass fix:true to ESLint when executing on files with configType:${configType}`, async () => { - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().once(); - - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - "./shared/logging": log - }); - - const exitCode = await localCLI.execute("--fix .", null, useFlatConfig); - - assert.strictEqual(exitCode, 0); - - }); - - - it(`should rewrite files when in fix mode with configType:${configType}`, async () => { - - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 2, - message: "Fake message" - } - ], - errorCount: 1, - warningCount: 0 - }]; - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().withExactArgs(report); - - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - - "./shared/logging": log - }); - - const exitCode = await localCLI.execute("--fix .", null, useFlatConfig); - - assert.strictEqual(exitCode, 1); - - }); - - it(`should provide fix predicate and rewrite files when in fix mode and quiet mode with configType:${configType}`, async () => { - - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 1, - message: "Fake message" - } - ], - errorCount: 0, - warningCount: 1 - }]; - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: sinon.match.func })); - - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.getErrorResults = sinon.stub().returns([]); - fakeESLint.outputFixes = sinon.mock().withExactArgs(report); - - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - - "./shared/logging": log - }); - - const exitCode = await localCLI.execute("--fix --quiet .", null, useFlatConfig); - - assert.strictEqual(exitCode, 0); - - }); - - it(`should not call ESLint and return 2 when executing on text with configType:${configType}`, async () => { - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().never(); - - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - - "./shared/logging": log - }); - - const exitCode = await localCLI.execute("--fix .", "foo = bar;", useFlatConfig); - - assert.strictEqual(exitCode, 2); - }); - - }); - - describe("when passed --fix-dry-run", () => { - let localCLI; - - afterEach(() => { - sinon.verifyAndRestore(); - }); - - it(`should pass fix:true to ESLint when executing on files with configType:${configType}`, async () => { - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().never(); - - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - - "./shared/logging": log - }); - - const exitCode = await localCLI.execute("--fix-dry-run .", null, useFlatConfig); - - assert.strictEqual(exitCode, 0); - - }); - - it(`should pass fixTypes to ESLint when --fix-type is passed with configType:${configType}`, async () => { - - const expectedESLintOptions = { - fix: true, - fixTypes: ["suggestion"] - }; - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match(expectedESLintOptions)); - - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.stub(); - - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - - "./shared/logging": log - }); - - const exitCode = await localCLI.execute("--fix-dry-run --fix-type suggestion .", null, useFlatConfig); - - assert.strictEqual(exitCode, 0); - }); - - it(`should not rewrite files when in fix-dry-run mode with configType:${configType}`, async () => { - - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 2, - message: "Fake message" - } - ], - errorCount: 1, - warningCount: 0 - }]; - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().never(); - - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - - "./shared/logging": log - }); - - const exitCode = await localCLI.execute("--fix-dry-run .", null, useFlatConfig); - - assert.strictEqual(exitCode, 1); - - }); - - it(`should provide fix predicate when in fix-dry-run mode and quiet mode with configType:${configType}`, async () => { - - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 1, - message: "Fake message" - } - ], - errorCount: 0, - warningCount: 1 - }]; - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: sinon.match.func })); - - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintFiles").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.getErrorResults = sinon.stub().returns([]); - fakeESLint.outputFixes = sinon.mock().never(); - - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - - "./shared/logging": log - }); - - const exitCode = await localCLI.execute("--fix-dry-run --quiet .", null, useFlatConfig); - - assert.strictEqual(exitCode, 0); - - }); - - it(`should allow executing on text with configType:${configType}`, async () => { - - const report = [{ - filePath: "./foo.js", - output: "bar", - messages: [ - { - severity: 2, - message: "Fake message" - } - ], - errorCount: 1, - warningCount: 0 - }]; - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().withExactArgs(sinon.match({ fix: true })); - - Object.defineProperties(fakeESLint.prototype, Object.getOwnPropertyDescriptors(ESLint.prototype)); - sinon.stub(fakeESLint.prototype, "lintText").returns(report); - sinon.stub(fakeESLint.prototype, "loadFormatter").returns({ format: () => "done" }); - fakeESLint.outputFixes = sinon.mock().never(); - - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - - "./shared/logging": log - }); - - const exitCode = await localCLI.execute("--fix-dry-run .", "foo = bar;", useFlatConfig); - - assert.strictEqual(exitCode, 1); - }); - - it(`should not call ESLint and return 2 when used with --fix with configType:${configType}`, async () => { - - // create a fake ESLint class to test with - const fakeESLint = sinon.mock().never(); - - localCLI = proxyquire("../../lib/cli", { - "./eslint": { ESLint: fakeESLint }, - "./eslint/flat-eslint": { FlatESLint: fakeESLint, shouldUseFlatConfig: () => Promise.resolve(useFlatConfig) }, - - "./shared/logging": log - }); - - const exitCode = await localCLI.execute("--fix --fix-dry-run .", "foo = bar;", useFlatConfig); - - assert.strictEqual(exitCode, 2); - }); - }); - - describe("when passing --print-config", () => { - - const originalCwd = process.cwd; - - beforeEach(() => { - process.cwd = () => getFixturePath(); - }); - - afterEach(() => { - process.cwd = originalCwd; - }); - - it(`should print out the configuration with configType:${configType}`, async () => { - const filePath = getFixturePath("xxx.js"); - - const exitCode = await cli.execute(`--print-config ${filePath}`, null, useFlatConfig); - - assert.isTrue(log.info.calledOnce); - assert.strictEqual(exitCode, 0); - }); - - it(`should error if any positional file arguments are passed with configType:${configType}`, async () => { - const filePath1 = getFixturePath("files", "bar.js"); - const filePath2 = getFixturePath("files", "foo.js"); - - const exitCode = await cli.execute(`--print-config ${filePath1} ${filePath2}`, null, useFlatConfig); - - assert.isTrue(log.info.notCalled); - assert.isTrue(log.error.calledOnce); - assert.strictEqual(exitCode, 2); - }); - - it(`should error out when executing on text with configType:${configType}`, async () => { - const exitCode = await cli.execute("--print-config=myFile.js", "foo = bar;", useFlatConfig); - - assert.isTrue(log.info.notCalled); - assert.isTrue(log.error.calledOnce); - assert.strictEqual(exitCode, 2); - }); - }); - - describe("when passing --report-unused-disable-directives", () => { - describe(`config type: ${configType}`, () => { - it("errors when --report-unused-disable-directives", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives --rule "'no-console': 'error'"`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); - - assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); - assert.strictEqual(log.info.callCount, 1, "log.info is called once"); - assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); - assert.ok(log.info.firstCall.args[0].includes("1 error and 0 warning"), "has correct error and warning count"); - assert.strictEqual(exitCode, 1, "exit code should be 1"); - }); - - it("errors when --report-unused-disable-directives-severity error", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity error --rule "'no-console': 'error'"`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); - - assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); - assert.strictEqual(log.info.callCount, 1, "log.info is called once"); - assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); - assert.ok(log.info.firstCall.args[0].includes("1 error and 0 warning"), "has correct error and warning count"); - assert.strictEqual(exitCode, 1, "exit code should be 1"); - }); - - it("errors when --report-unused-disable-directives-severity 2", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity 2 --rule "'no-console': 'error'"`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); - - assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); - assert.strictEqual(log.info.callCount, 1, "log.info is called once"); - assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); - assert.ok(log.info.firstCall.args[0].includes("1 error and 0 warning"), "has correct error and warning count"); - assert.strictEqual(exitCode, 1, "exit code should be 1"); - }); - - it("warns when --report-unused-disable-directives-severity warn", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity warn --rule "'no-console': 'error'""`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); - - assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); - assert.strictEqual(log.info.callCount, 1, "log.info is called once"); - assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); - assert.ok(log.info.firstCall.args[0].includes("0 errors and 1 warning"), "has correct error and warning count"); - assert.strictEqual(exitCode, 0, "exit code should be 0"); - }); - - it("warns when --report-unused-disable-directives-severity 1", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity 1 --rule "'no-console': 'error'"`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); - - assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); - assert.strictEqual(log.info.callCount, 1, "log.info is called once"); - assert.ok(log.info.firstCall.args[0].includes("Unused eslint-disable directive (no problems were reported from 'no-console')"), "has correct message about unused directives"); - assert.ok(log.info.firstCall.args[0].includes("0 errors and 1 warning"), "has correct error and warning count"); - assert.strictEqual(exitCode, 0, "exit code should be 0"); - }); - - it("does not report when --report-unused-disable-directives-severity off", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity off --rule "'no-console': 'error'"`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); - - assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); - assert.strictEqual(log.info.callCount, 0, "log.info should not be called"); - assert.strictEqual(exitCode, 0, "exit code should be 0"); - }); - - it("does not report when --report-unused-disable-directives-severity 0", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity 0 --rule "'no-console': 'error'"`, - "foo(); // eslint-disable-line no-console", - useFlatConfig); - - assert.strictEqual(log.error.callCount, 0, "log.error should not be called"); - assert.strictEqual(log.info.callCount, 0, "log.info should not be called"); - assert.strictEqual(exitCode, 0, "exit code should be 0"); - }); - - it("fails when passing invalid string for --report-unused-disable-directives-severity", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity foo`, null, useFlatConfig); - - assert.strictEqual(log.info.callCount, 0, "log.info should not be called"); - assert.strictEqual(log.error.callCount, 1, "log.error should be called once"); - - const lines = ["Option report-unused-disable-directives-severity: 'foo' not one of off, warn, error, 0, 1, or 2."]; - - if (useFlatConfig) { - lines.push("You're using eslint.config.js, some command line flags are no longer available. Please see https://eslint.org/docs/latest/use/command-line-interface for details."); - } - assert.deepStrictEqual(log.error.firstCall.args, [lines.join("\n")], "has the right text to log.error"); - assert.strictEqual(exitCode, 2, "exit code should be 2"); - }); - - it("fails when passing both --report-unused-disable-directives and --report-unused-disable-directives-severity", async () => { - const exitCode = await cli.execute(`${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives --report-unused-disable-directives-severity warn`, null, useFlatConfig); - - assert.strictEqual(log.info.callCount, 0, "log.info should not be called"); - assert.strictEqual(log.error.callCount, 1, "log.error should be called once"); - assert.deepStrictEqual(log.error.firstCall.args, ["The --report-unused-disable-directives option and the --report-unused-disable-directives-severity option cannot be used together."], "has the right text to log.error"); - assert.strictEqual(exitCode, 2, "exit code should be 2"); - }); - }); - }); - - // --------- - }); - - - describe("when given a config file", () => { - it("should load the specified config file", async () => { - const configPath = getFixturePath(".eslintrc"); - const filePath = getFixturePath("passing.js"); - - await cli.execute(`--config ${configPath} ${filePath}`); - }); - }); - - - describe("eslintrc Only", () => { - - describe("Environments", () => { - - describe("when given a config with environment set to browser", () => { - it("should execute without any errors", async () => { - const configPath = getFixturePath("configurations", "env-browser.json"); - const filePath = getFixturePath("globals-browser.js"); - const code = `--config ${configPath} ${filePath}`; - - const exit = await cli.execute(code); - - assert.strictEqual(exit, 0); - }); - }); - - describe("when given a config with environment set to Node.js", () => { - it("should execute without any errors", async () => { - const configPath = getFixturePath("configurations", "env-node.json"); - const filePath = getFixturePath("globals-node.js"); - const code = `--config ${configPath} ${filePath}`; - - const exit = await cli.execute(code); - - assert.strictEqual(exit, 0); - }); - }); - - describe("when given a config with environment set to Nashorn", () => { - it("should execute without any errors", async () => { - const configPath = getFixturePath("configurations", "env-nashorn.json"); - const filePath = getFixturePath("globals-nashorn.js"); - const code = `--config ${configPath} ${filePath}`; - - const exit = await cli.execute(code); - - assert.strictEqual(exit, 0); - }); - }); - - describe("when given a config with environment set to WebExtensions", () => { - it("should execute without any errors", async () => { - const configPath = getFixturePath("configurations", "env-webextensions.json"); - const filePath = getFixturePath("globals-webextensions.js"); - const code = `--config ${configPath} ${filePath}`; - - const exit = await cli.execute(code); - - assert.strictEqual(exit, 0); - }); - }); - }); - - describe("when loading a custom rule", () => { - it("should return an error when rule isn't found", async () => { - const rulesPath = getFixturePath("rules", "wrong"); - const configPath = getFixturePath("rules", "eslint.json"); - const filePath = getFixturePath("rules", "test", "test-custom-rule.js"); - const code = `--rulesdir ${rulesPath} --config ${configPath} --no-ignore ${filePath}`; - - await stdAssert.rejects(async () => { - const exit = await cli.execute(code); - - assert.strictEqual(exit, 2); - }, /Error while loading rule 'custom-rule': Boom!/u); - }); - - it("should return a warning when rule is matched", async () => { - const rulesPath = getFixturePath("rules"); - const configPath = getFixturePath("rules", "eslint.json"); - const filePath = getFixturePath("rules", "test", "test-custom-rule.js"); - const code = `--rulesdir ${rulesPath} --config ${configPath} --no-ignore ${filePath}`; - - await cli.execute(code); - - assert.isTrue(log.info.calledOnce); - assert.isTrue(log.info.neverCalledWith("")); - }); - - it("should return warnings from multiple rules in different directories", async () => { - const rulesPath = getFixturePath("rules", "dir1"); - const rulesPath2 = getFixturePath("rules", "dir2"); - const configPath = getFixturePath("rules", "multi-rulesdirs.json"); - const filePath = getFixturePath("rules", "test-multi-rulesdirs.js"); - const code = `--rulesdir ${rulesPath} --rulesdir ${rulesPath2} --config ${configPath} --no-ignore ${filePath}`; - const exit = await cli.execute(code); - - const call = log.info.getCall(0); - - assert.isTrue(log.info.calledOnce); - assert.isTrue(call.args[0].includes("String!")); - assert.isTrue(call.args[0].includes("Literal!")); - assert.isTrue(call.args[0].includes("2 problems")); - assert.isTrue(log.info.neverCalledWith("")); - assert.strictEqual(exit, 1); - }); - - - }); - - describe("when executing with no-eslintrc flag", () => { - it("should ignore a local config file", async () => { - const filePath = getFixturePath("eslintrc", "quotes.js"); - const exit = await cli.execute(`--no-eslintrc --no-ignore ${filePath}`); - - assert.isTrue(log.info.notCalled); - assert.strictEqual(exit, 0); - }); - }); - - describe("when executing without no-eslintrc flag", () => { - it("should load a local config file", async () => { - const filePath = getFixturePath("eslintrc", "quotes.js"); - const exit = await cli.execute(`--no-ignore ${filePath}`); - - assert.isTrue(log.info.calledOnce); - assert.strictEqual(exit, 1); - }); - }); - - describe("when executing without env flag", () => { - it("should not define environment-specific globals", async () => { - const files = [ - getFixturePath("globals-browser.js"), - getFixturePath("globals-node.js") - ]; - - await cli.execute(`--no-eslintrc --config ./packages/js/src/configs/eslint-recommended.js --no-ignore ${files.join(" ")}`); - - assert.strictEqual(log.info.args[0][0].split("\n").length, 10); - }); - }); - - - describe("when supplied with a plugin", () => { - it("should pass plugins to ESLint", async () => { - const examplePluginName = "eslint-plugin-example"; - - await verifyESLintOpts(`--no-ignore --plugin ${examplePluginName} foo.js`, { - overrideConfig: { - plugins: [examplePluginName] - } - }); - }); - - }); - - describe("when supplied with a plugin-loading path", () => { - it("should pass the option to ESLint", async () => { - const examplePluginDirPath = "foo/bar"; - - await verifyESLintOpts(`--resolve-plugins-relative-to ${examplePluginDirPath} foo.js`, { - resolvePluginsRelativeTo: examplePluginDirPath - }); - }); - }); - - - }); - - + describe("calculateInspectConfigFlags()", () => { + const cli = require("../../lib/cli"); + + it("should return the config file in the project root when no argument is passed", async () => { + const flags = await cli.calculateInspectConfigFlags(); + + assert.deepStrictEqual(flags, [ + "--config", + path.resolve(process.cwd(), "eslint.config.js"), + "--basePath", + process.cwd(), + ]); + }); + + it("should return the override config file when an argument is passed", async () => { + const flags = await cli.calculateInspectConfigFlags("foo.js"); + + assert.deepStrictEqual(flags, [ + "--config", + path.resolve(process.cwd(), "foo.js"), + "--basePath", + process.cwd(), + ]); + }); + + it("should return the override config file when an argument is passed with a path", async () => { + const flags = await cli.calculateInspectConfigFlags("bar/foo.js"); + + assert.deepStrictEqual(flags, [ + "--config", + path.resolve(process.cwd(), "bar/foo.js"), + "--basePath", + process.cwd(), + ]); + }); + }); + + describe("execute()", () => { + let fixtureDir; + const log = { + info: sinon.spy(), + warn: sinon.spy(), + error: sinon.spy(), + }; + const RuntimeInfo = { + environment: sinon.stub(), + version: sinon.stub(), + }; + const cli = proxyquire("../../lib/cli", { + "./shared/logging": log, + "./shared/runtime-info": RuntimeInfo, + }); + + /** + * Verify that ESLint class receives correct opts via await cli.execute(). + * @param {string} cmd CLI command. + * @param {Object} opts Options hash that should match that received by ESLint class. + * @param {string} configType The config type to work with. + * @returns {void} + */ + async function verifyESLintOpts(cmd, opts, configType) { + const ActiveESLint = configType === "flat" ? ESLint : LegacyESLint; + + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().withExactArgs(sinon.match(opts)); + + Object.defineProperties( + fakeESLint.prototype, + Object.getOwnPropertyDescriptors(ActiveESLint.prototype), + ); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); + sinon + .stub(fakeESLint.prototype, "loadFormatter") + .returns({ format: sinon.spy() }); + + const localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { + ESLint: fakeESLint, + shouldUseFlatConfig: () => + Promise.resolve(configType === "flat"), + }, + "./shared/logging": log, + }); + + await localCLI.execute(cmd, null, configType === "flat"); + sinon.verifyAndRestore(); + } + + // verifyESLintOpts + + /** + * Returns the path inside of the fixture directory. + * @param {...string} args file path segments. + * @returns {string} The path inside the fixture directory. + * @private + */ + function getFixturePath(...args) { + return path.join(fixtureDir, ...args); + } + + // copy into clean area so as not to get "infected" by this project's .eslintrc files + before(function () { + /* + * GitHub Actions Windows and macOS runners occasionally exhibit + * extremely slow filesystem operations, during which copying fixtures + * exceeds the default test timeout, so raise it just for this hook. + * Mocha uses `this` to set timeouts on an individual hook level. + */ + this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API + fixtureDir = `${os.tmpdir()}/eslint/fixtures`; + sh.mkdir("-p", fixtureDir); + sh.cp("-r", "./tests/fixtures/.", fixtureDir); + }); + + beforeEach(() => { + // Silence ".eslintignore" warnings for tests + sinon.stub(WarningService.prototype, "emitESLintIgnoreWarning"); + }); + + afterEach(() => { + sinon.restore(); + log.info.resetHistory(); + log.error.resetHistory(); + log.warn.resetHistory(); + }); + + after(() => { + sh.rm("-r", fixtureDir); + }); + + ["eslintrc", "flat"].forEach(configType => { + const useFlatConfig = configType === "flat"; + const ActiveESLint = configType === "flat" ? ESLint : LegacyESLint; + + describe("execute()", () => { + it(`should return error when text with incorrect quotes is passed as argument with configType:${configType}`, async () => { + const flag = useFlatConfig + ? "--no-config-lookup" + : "--no-eslintrc"; + const configFile = getFixturePath( + "configurations", + "quotes-error.js", + ); + const result = await cli.execute( + `${flag} -c ${configFile} --stdin --stdin-filename foo.js`, + "var foo = 'bar';", + useFlatConfig, + ); + + assert.strictEqual(result, 1); + }); + + it(`should not print debug info when passed the empty string as text with configType:${configType}`, async () => { + const flag = useFlatConfig + ? "--no-config-lookup" + : "--no-eslintrc"; + const result = await cli.execute( + [ + "argv0", + "argv1", + "--stdin", + flag, + "--stdin-filename", + "foo.js", + ], + "", + useFlatConfig, + ); + + assert.strictEqual(result, 0); + assert.isTrue(log.info.notCalled); + }); + + it(`should exit with console error when passed unsupported arguments with configType:${configType}`, async () => { + const filePath = getFixturePath("files"); + const result = await cli.execute( + `--blah --another ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(result, 2); + }); + }); + + describe("flat config", () => { + const originalEnv = process.env; + const originalCwd = process.cwd; + + let emitESLintRCWarningStub; + + beforeEach(() => { + sinon.restore(); + emitESLintRCWarningStub = sinon.stub( + WarningService.prototype, + "emitESLintRCWarning", + ); + process.env = { ...originalEnv }; + }); + + afterEach(() => { + emitESLintRCWarningStub.restore(); + process.env = originalEnv; + process.cwd = originalCwd; + }); + + it(`should use it when an eslint.config.js is present and useFlatConfig is true:${configType}`, async () => { + process.cwd = getFixturePath; + + const exitCode = await cli.execute( + `--no-ignore --env es2024 ${getFixturePath("files")}`, + null, + useFlatConfig, + ); + + // When flat config is used, we get an exit code of 2 because the --env option is unrecognized. + assert.strictEqual(exitCode, useFlatConfig ? 2 : 0); + }); + + it(`should not use it when ESLINT_USE_FLAT_CONFIG=false even if an eslint.config.js is present:${configType}`, async () => { + process.env.ESLINT_USE_FLAT_CONFIG = "false"; + process.cwd = getFixturePath; + + const exitCode = await cli.execute( + `--no-ignore --env es2024 ${getFixturePath("files")}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 0); + + if (useFlatConfig) { + assert( + emitESLintRCWarningStub.calledOnce, + "calls `warningService.emitESLintRCWarning()` once", + ); + } + }); + + it(`should use it when ESLINT_USE_FLAT_CONFIG=true and useFlatConfig is true even if an eslint.config.js is not present:${configType}`, async () => { + process.env.ESLINT_USE_FLAT_CONFIG = "true"; + + // Set the CWD to outside the fixtures/ directory so that no eslint.config.js is found + process.cwd = () => getFixturePath(".."); + + const exitCode = await cli.execute( + `--no-ignore --env es2024 ${getFixturePath("files")}`, + null, + useFlatConfig, + ); + + // When flat config is used, we get an exit code of 2 because the --env option is unrecognized. + assert.strictEqual(exitCode, useFlatConfig ? 2 : 0); + }); + }); + + describe("when given a config with rules with options and severity level set to error", () => { + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + + it(`should exit with an error status (1) with configType:${configType}`, async () => { + const configPath = getFixturePath( + "configurations", + "quotes-error.js", + ); + const filePath = getFixturePath("single-quoted.js"); + const code = `--no-ignore --config ${configPath} ${filePath}`; + + const exitStatus = await cli.execute( + code, + null, + useFlatConfig, + ); + + assert.strictEqual(exitStatus, 1); + }); + }); + + describe("when there is a local config file", () => { + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + + it(`should load the local config file with configType:${configType}`, async () => { + await cli.execute( + "cli/passing.js --no-ignore", + null, + useFlatConfig, + ); + }); + + if (useFlatConfig) { + it(`should load the local config file with glob pattern and configType:${configType}`, async () => { + await cli.execute( + "cli/pass*.js --no-ignore", + null, + useFlatConfig, + ); + }); + } + + // only works on Windows + if (os.platform() === "win32") { + it(`should load the local config file with Windows slashes glob pattern and configType:${configType}`, async () => { + await cli.execute( + "cli\\pass*.js --no-ignore", + null, + useFlatConfig, + ); + }); + } + }); + + describe("Formatters", () => { + const flag = useFlatConfig + ? "--no-config-lookup" + : "--no-eslintrc"; + + describe("when given a valid built-in formatter name", () => { + it(`should execute without any errors with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute( + `${flag} -f json ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 0); + }); + }); + + describe("when given a valid built-in formatter name that uses rules meta.", () => { + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + + it(`should execute without any errors with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute( + `--no-ignore -f json-with-metadata ${filePath} ${flag}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 0); + + /* + * Note: There is a behavior difference between eslintrc and flat config + * when using formatters. For eslintrc, rulesMeta always contains every + * rule that was loaded during the last run; for flat config, rulesMeta + * only contains meta data for the rules that triggered messages in the + * results. (Flat config uses ESLint#getRulesMetaForResults().) + */ + + // Check metadata. + const { metadata } = JSON.parse(log.info.args[0][0]); + const expectedMetadata = { + cwd: process.cwd(), + rulesMeta: useFlatConfig + ? {} + : Array.from(BuiltinRules).reduce( + (obj, [ruleId, rule]) => { + obj[ruleId] = rule.meta; + return obj; + }, + {}, + ), + }; + + assert.deepStrictEqual(metadata, expectedMetadata); + }); + }); + + describe("when the --max-warnings option is passed", () => { + describe("and there are too many warnings", () => { + it(`should provide \`maxWarningsExceeded\` metadata to the formatter with configType:${configType}`, async () => { + const exit = await cli.execute( + `--no-ignore -f json-with-metadata --max-warnings 1 --rule 'quotes: warn' ${flag}`, + "'hello' + 'world';", + useFlatConfig, + ); + + assert.strictEqual(exit, 1); + + const { metadata } = JSON.parse( + log.info.args[0][0], + ); + + assert.deepStrictEqual( + metadata.maxWarningsExceeded, + { maxWarnings: 1, foundWarnings: 2 }, + ); + }); + }); + + describe("and warnings do not exceed the limit", () => { + it(`should omit \`maxWarningsExceeded\` metadata from the formatter with configType:${configType}`, async () => { + const exit = await cli.execute( + `--no-ignore -f json-with-metadata --max-warnings 1 --rule 'quotes: warn' ${flag}`, + "'hello world';", + useFlatConfig, + ); + + assert.strictEqual(exit, 0); + + const { metadata } = JSON.parse( + log.info.args[0][0], + ); + + assert.notProperty(metadata, "maxWarningsExceeded"); + }); + }); + }); + + describe("when given an invalid built-in formatter name", () => { + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + + it(`should execute with error: with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute( + `-f fakeformatter ${filePath} ${flag}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 2); + }); + }); + + describe("when given a valid formatter path", () => { + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + + it(`should execute without any errors with configType:${configType}`, async () => { + const formatterPath = getFixturePath( + "formatters", + "simple.js", + ); + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute( + `-f ${formatterPath} ${filePath} ${flag}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 0); + }); + }); + + describe("when given an invalid formatter path", () => { + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + + it(`should execute with error with configType:${configType}`, async () => { + const formatterPath = getFixturePath( + "formatters", + "file-does-not-exist.js", + ); + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute( + `--no-ignore -f ${formatterPath} ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 2); + }); + }); + + describe("when given an async formatter path", () => { + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + + it(`should execute without any errors with configType:${configType}`, async () => { + const formatterPath = getFixturePath( + "formatters", + "async.js", + ); + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute( + `-f ${formatterPath} ${filePath} ${flag}`, + null, + useFlatConfig, + ); + + assert.strictEqual( + log.info.getCall(0).args[0], + "from async formatter", + ); + assert.strictEqual(exit, 0); + }); + }); + }); + + describe("Exit Codes", () => { + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + + describe("when executing a file with a lint error", () => { + it(`should exit with error with configType:${configType}`, async () => { + const filePath = getFixturePath("undef.js"); + const code = `--no-ignore --rule no-undef:2 ${filePath}`; + + const exit = await cli.execute( + code, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 1); + }); + }); + + describe("when using --fix-type without --fix or --fix-dry-run", () => { + it(`should exit with error with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const code = `--fix-type suggestion ${filePath}`; + + const exit = await cli.execute( + code, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 2); + }); + }); + + describe("when executing a file with a syntax error", () => { + it(`should exit with error with configType:${configType}`, async () => { + const filePath = getFixturePath("syntax-error.js"); + const exit = await cli.execute( + `--no-ignore ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 1); + }); + }); + }); + + describe("when calling execute more than once", () => { + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + + it(`should not print the results from previous execution with configType:${configType}`, async () => { + const filePath = getFixturePath("missing-semicolon.js"); + const passingPath = getFixturePath("passing.js"); + + await cli.execute( + `--no-ignore --rule semi:2 ${filePath}`, + null, + useFlatConfig, + ); + + assert.isTrue( + log.info.called, + "Log should have been called.", + ); + + log.info.resetHistory(); + + await cli.execute( + `--no-ignore --rule semi:2 ${passingPath}`, + null, + useFlatConfig, + ); + assert.isTrue(log.info.notCalled); + }); + }); + + describe("when executing with version flag", () => { + it(`should print out current version with configType:${configType}`, async () => { + assert.strictEqual( + await cli.execute("-v", null, useFlatConfig), + 0, + ); + assert.strictEqual(log.info.callCount, 1); + }); + }); + + describe("when executing with env-info flag", () => { + it(`should print out environment information with configType:${configType}`, async () => { + assert.strictEqual( + await cli.execute("--env-info", null, useFlatConfig), + 0, + ); + assert.strictEqual(log.info.callCount, 1); + }); + + describe("With error condition", () => { + beforeEach(() => { + RuntimeInfo.environment = sinon + .stub() + .throws("There was an error!"); + }); + + afterEach(() => { + RuntimeInfo.environment = sinon.stub(); + }); + + it(`should print error message and return error code with configType:${configType}`, async () => { + assert.strictEqual( + await cli.execute( + "--env-info", + null, + useFlatConfig, + ), + 2, + ); + assert.strictEqual(log.error.callCount, 1); + }); + }); + }); + + describe("when executing with help flag", () => { + it(`should print out help with configType:${configType}`, async () => { + assert.strictEqual( + await cli.execute("-h", null, useFlatConfig), + 0, + ); + assert.strictEqual(log.info.callCount, 1); + }); + }); + + describe("when executing a file with a shebang", () => { + it(`should execute without error with configType:${configType}`, async () => { + const filePath = getFixturePath("shebang.js"); + const flag = useFlatConfig + ? "--no-config-lookup" + : "--no-eslintrc"; + const exit = await cli.execute( + `${flag} --no-ignore ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 0); + }); + }); + + describe("FixtureDir Dependent Tests", () => { + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + + describe("when given a config file and a directory of files", () => { + it(`should load and execute without error with configType:${configType}`, async () => { + const configPath = getFixturePath( + "configurations", + "semi-error.js", + ); + const filePath = getFixturePath("formatters"); + const code = `--no-ignore --config ${configPath} ${filePath}`; + const exitStatus = await cli.execute( + code, + null, + useFlatConfig, + ); + + assert.strictEqual(exitStatus, 0); + }); + }); + + describe("when executing with global flag", () => { + it(`should default defined variables to read-only with configType:${configType}`, async () => { + const filePath = getFixturePath("undef.js"); + const exit = await cli.execute( + `--global baz,bat --no-ignore --rule no-global-assign:2 ${filePath}`, + null, + useFlatConfig, + ); + + assert.isTrue(log.info.calledOnce); + assert.strictEqual(exit, 1); + }); + + it(`should allow defining writable global variables with configType:${configType}`, async () => { + const filePath = getFixturePath("undef.js"); + const exit = await cli.execute( + `--global baz:false,bat:true --no-ignore ${filePath}`, + null, + useFlatConfig, + ); + + assert.isTrue(log.info.notCalled); + assert.strictEqual(exit, 0); + }); + + it(`should allow defining variables with multiple flags with configType:${configType}`, async () => { + const filePath = getFixturePath("undef.js"); + const exit = await cli.execute( + `--global baz --global bat:true --no-ignore ${filePath}`, + null, + useFlatConfig, + ); + + assert.isTrue(log.info.notCalled); + assert.strictEqual(exit, 0); + }); + }); + + describe("when supplied with rule flag and severity level set to error", () => { + it(`should exit with an error status (2) with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const code = `--no-ignore --rule 'quotes: [2, double]' ${filePath}`; + const exitStatus = await cli.execute( + code, + null, + useFlatConfig, + ); + + assert.strictEqual(exitStatus, 1); + }); + }); + + describe("when the quiet option is enabled", () => { + it(`should only print error with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const cliArgs = `--no-ignore --quiet -f stylish --rule 'quotes: [2, double]' --rule 'no-undef: 1' ${filePath}`; + + await cli.execute(cliArgs, null, useFlatConfig); + + sinon.assert.calledOnce(log.info); + + const formattedOutput = log.info.firstCall.args[0]; + + assert.include( + formattedOutput, + "(1 error, 0 warnings)", + ); + }); + + it(`should print nothing if there are no errors with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const cliArgs = `--no-ignore --quiet -f stylish --rule 'quotes: [1, double]' --rule 'no-undef: 1' ${filePath}`; + + await cli.execute(cliArgs, null, useFlatConfig); + + sinon.assert.notCalled(log.info); + }); + + if (useFlatConfig) { + it(`should not run rules set to 'warn' with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const configPath = getFixturePath( + "eslint.config-rule-throws.js", + ); + const cliArgs = `--quiet --config ${configPath}' ${filePath}`; + + const exit = await cli.execute( + cliArgs, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 0); + }); + + it(`should run rules set to 'warn' while maxWarnings is set with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const configPath = getFixturePath( + "eslint.config-rule-throws.js", + ); + const cliArgs = `--quiet --max-warnings=1 --config ${configPath}' ${filePath}`; + + await stdAssert.rejects(async () => { + await cli.execute(cliArgs, null, useFlatConfig); + }); + }); + } + }); + + describe("no-error-on-unmatched-pattern flag", () => { + describe("when executing without no-error-on-unmatched-pattern flag", () => { + it(`should throw an error on unmatched glob pattern with configType:${configType}`, async () => { + let filePath = getFixturePath("unmatched-patterns"); + const globPattern = "unmatched*.js"; + + if (useFlatConfig) { + filePath = filePath.replace(/\\/gu, "/"); + } + + await stdAssert.rejects( + async () => { + await cli.execute( + `"${filePath}/${globPattern}"`, + null, + useFlatConfig, + ); + }, + new Error( + `No files matching '${filePath}/${globPattern}' were found.`, + ), + ); + }); + }); + + describe("when executing with no-error-on-unmatched-pattern flag", () => { + it(`should not throw an error on unmatched node glob syntax patterns with configType:${configType}`, async () => { + const filePath = + getFixturePath("unmatched-patterns"); + const exit = await cli.execute( + `--no-error-on-unmatched-pattern "${filePath}/unmatched*.js"`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 0); + }); + }); + + describe("when executing with no-error-on-unmatched-pattern flag and multiple patterns", () => { + it(`should not throw an error on multiple unmatched node glob syntax patterns with configType:${configType}`, async () => { + const filePath = getFixturePath( + "unmatched-patterns/js3", + ); + const exit = await cli.execute( + `--no-error-on-unmatched-pattern ${filePath}/unmatched1*.js ${filePath}/unmatched2*.js`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 0); + }); + + it(`should still throw an error on when a matched pattern has lint errors with configType:${configType}`, async () => { + const filePath = + getFixturePath("unmatched-patterns"); + const exit = await cli.execute( + `--no-ignore --no-error-on-unmatched-pattern ${filePath}/unmatched1*.js ${filePath}/failing.js`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 1); + }); + }); + }); + + describe("Parser Options", () => { + describe("when given parser options", () => { + it(`should exit with error if parser options are invalid with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute( + `--no-ignore --parser-options test111 ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 2); + }); + + it(`should exit with no error if parser is valid with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute( + `--no-ignore --parser-options=ecmaVersion:6 ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 0); + }); + + it(`should exit with an error on ecmaVersion 7 feature in ecmaVersion 6 with configType:${configType}`, async () => { + const filePath = getFixturePath("passing-es7.js"); + const exit = await cli.execute( + `--no-ignore --parser-options=ecmaVersion:6 ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 1); + }); + + it(`should exit with no error on ecmaVersion 7 feature in ecmaVersion 7 with configType:${configType}`, async () => { + const filePath = getFixturePath("passing-es7.js"); + const exit = await cli.execute( + `--no-ignore --parser-options=ecmaVersion:7 ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 0); + }); + + it(`should exit with no error on ecmaVersion 7 feature with config ecmaVersion 6 and command line ecmaVersion 7 with configType:${configType}`, async () => { + const configPath = useFlatConfig + ? getFixturePath("configurations", "es6.js") + : getFixturePath("configurations", "es6.json"); + const filePath = getFixturePath("passing-es7.js"); + const exit = await cli.execute( + `--no-ignore --config ${configPath} --parser-options=ecmaVersion:7 ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 0); + }); + }); + }); + + describe("when given the max-warnings flag", () => { + let filePath, configFilePath; + + before(() => { + filePath = getFixturePath( + "max-warnings/six-warnings.js", + ); + configFilePath = getFixturePath( + useFlatConfig + ? "max-warnings/eslint.config.js" + : "max-warnings/.eslintrc", + ); + }); + + it(`should not change exit code if warning count under threshold with configType:${configType}`, async () => { + const exitCode = await cli.execute( + `--no-ignore --max-warnings 10 ${filePath} -c ${configFilePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 0); + }); + + it(`should exit with exit code 1 if warning count exceeds threshold with configType:${configType}`, async () => { + const exitCode = await cli.execute( + `--no-ignore --max-warnings 5 ${filePath} -c ${configFilePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 1); + assert.ok(log.error.calledOnce); + assert.include( + log.error.getCall(0).args[0], + "ESLint found too many warnings", + ); + }); + + it(`should exit with exit code 1 without printing warnings if the quiet option is enabled and warning count exceeds threshold with configType:${configType}`, async () => { + const exitCode = await cli.execute( + `--no-ignore --quiet --max-warnings 5 ${filePath} -c ${configFilePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 1); + assert.ok(log.error.calledOnce); + assert.include( + log.error.getCall(0).args[0], + "ESLint found too many warnings", + ); + assert.ok(log.info.notCalled); // didn't print warnings + }); + + it(`should not change exit code if warning count equals threshold with configType:${configType}`, async () => { + const exitCode = await cli.execute( + `--no-ignore --max-warnings 6 ${filePath} -c ${configFilePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 0); + }); + + it(`should not change exit code if flag is not specified and there are warnings with configType:${configType}`, async () => { + const exitCode = await cli.execute( + `-c ${configFilePath} ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 0); + }); + }); + + describe("when given the exit-on-fatal-error flag", () => { + it(`should not change exit code if no fatal errors are reported with configType:${configType}`, async () => { + const filePath = getFixturePath( + "exit-on-fatal-error", + "no-fatal-error.js", + ); + const exitCode = await cli.execute( + `--no-ignore --exit-on-fatal-error ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 0); + }); + + it(`should exit with exit code 1 if no fatal errors are found, but rule violations are found with configType:${configType}`, async () => { + const filePath = getFixturePath( + "exit-on-fatal-error", + "no-fatal-error-rule-violation.js", + ); + const exitCode = await cli.execute( + `--no-ignore --exit-on-fatal-error ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 1); + }); + + it(`should exit with exit code 2 if fatal error is found with configType:${configType}`, async () => { + const filePath = getFixturePath( + "exit-on-fatal-error", + "fatal-error.js", + ); + const exitCode = await cli.execute( + `--no-ignore --exit-on-fatal-error ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 2); + }); + + it(`should exit with exit code 2 if fatal error is found in any file with configType:${configType}`, async () => { + const filePath = getFixturePath("exit-on-fatal-error"); + const exitCode = await cli.execute( + `--no-ignore --exit-on-fatal-error ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 2); + }); + }); + + describe("Ignores", () => { + describe("when given a directory with eslint excluded files in the directory", () => { + it(`should throw an error and not process any files with configType:${configType}`, async () => { + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; + const filePath = getFixturePath("cli"); + const expectedMessage = useFlatConfig + ? `All files matched by '${filePath.replace(/\\/gu, "/")}' are ignored.` + : `All files matched by '${filePath}' are ignored.`; + + await stdAssert.rejects(async () => { + await cli.execute( + `${options} ${filePath}`, + null, + useFlatConfig, + ); + }, new Error(expectedMessage)); + }); + }); + + describe("when given a file in excluded files list", () => { + it(`should not process the file with configType:${configType}`, async () => { + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute( + `${options} ${filePath}`, + null, + useFlatConfig, + ); + + // a warning about the ignored file + assert.isTrue(log.info.called); + assert.strictEqual(exit, 0); + }); + + it(`should process the file when forced with configType:${configType}`, async () => { + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute( + `${options} --no-ignore ${filePath}`, + null, + useFlatConfig, + ); + + // no warnings + assert.isFalse(log.info.called); + assert.strictEqual(exit, 0); + }); + + it(`should suppress the warning if --no-warn-ignored is passed with configType:${configType}`, async () => { + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute( + `${options} --no-warn-ignored ${filePath}`, + null, + useFlatConfig, + ); + + assert.isFalse(log.info.called); + + // When eslintrc is used, we get an exit code of 2 because the --no-warn-ignored option is unrecognized. + assert.strictEqual(exit, useFlatConfig ? 0 : 2); + }); + + it(`should not lint anything when no files are passed if --pass-on-no-patterns is passed with configType:${configType}`, async () => { + const exit = await cli.execute( + "--pass-on-no-patterns", + null, + useFlatConfig, + ); + + assert.isFalse(log.info.called); + assert.strictEqual(exit, 0); + }); + + it(`should suppress the warning if --no-warn-ignored is passed and an ignored file is passed via stdin with configType:${configType}`, async () => { + const options = useFlatConfig + ? `--config ${getFixturePath("eslint.config-with-ignores.js")}` + : `--ignore-path ${getFixturePath(".eslintignore")}`; + const filePath = getFixturePath("passing.js"); + const exit = await cli.execute( + `${options} --no-warn-ignored --stdin --stdin-filename ${filePath}`, + "foo", + useFlatConfig, + ); + + assert.isFalse(log.info.called); + + // When eslintrc is used, we get an exit code of 2 because the --no-warn-ignored option is unrecognized. + assert.strictEqual(exit, useFlatConfig ? 0 : 2); + }); + }); + + describe("when given a pattern to ignore", () => { + it(`should not process any files with configType:${configType}`, async () => { + const ignoredFile = getFixturePath( + "cli/syntax-error.js", + ); + const ignorePathOption = useFlatConfig + ? "" + : "--ignore-path .eslintignore_empty"; + const filePath = getFixturePath("cli/passing.js"); + const ignorePattern = useFlatConfig + ? "cli/**" + : "cli/"; + const exit = await cli.execute( + `--ignore-pattern ${ignorePattern} ${ignorePathOption} ${ignoredFile} ${filePath}`, + null, + useFlatConfig, + ); + + // warnings about the ignored files + assert.isTrue(log.info.called); + assert.strictEqual(exit, 0); + }); + + it(`should interpret pattern that contains a slash as relative to cwd with configType:${configType}`, async () => { + process.cwd = () => + getFixturePath( + "cli/ignore-pattern-relative/subdir", + ); + + /* + * The config file is in `cli/ignore-pattern-relative`, so this would fail + * if `subdir/**` ignore pattern is interpreted as relative to the config base path. + */ + const exit = await cli.execute( + "**/*.js --ignore-pattern subdir/**", + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 0); + + await stdAssert.rejects( + async () => + await cli.execute( + "**/*.js --ignore-pattern subsubdir/*.js", + null, + useFlatConfig, + ), + /All files matched by '\*\*\/\*\.js' are ignored/u, + ); + }); + + it(`should interpret pattern that doesn't contain a slash as relative to cwd with configType:${configType}`, async () => { + process.cwd = () => + getFixturePath( + "cli/ignore-pattern-relative/subdir/subsubdir", + ); + + await stdAssert.rejects( + async () => + await cli.execute( + "**/*.js --ignore-pattern *.js", + null, + useFlatConfig, + ), + /All files matched by '\*\*\/\*\.js' are ignored/u, + ); + }); + + if (useFlatConfig) { + it("should ignore files if the pattern is a path to a directory (with trailing slash)", async () => { + const filePath = getFixturePath( + "cli/syntax-error.js", + ); + const exit = await cli.execute( + `--ignore-pattern cli/ ${filePath}`, + null, + true, + ); + + // parsing error causes exit code 1 + assert.isTrue(log.info.called); + assert.strictEqual(exit, 0); + }); + + it("should ignore files if the pattern is a path to a directory (without trailing slash)", async () => { + const filePath = getFixturePath( + "cli/syntax-error.js", + ); + const exit = await cli.execute( + `--ignore-pattern cli ${filePath}`, + null, + true, + ); + + // parsing error causes exit code 1 + assert.isTrue(log.info.called); + assert.strictEqual(exit, 0); + }); + } + }); + }); + }); + + describe("when given a parser name", () => { + it(`should exit with a fatal error if parser is invalid with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + + await stdAssert.rejects( + async () => + await cli.execute( + `--no-ignore --parser test111 ${filePath}`, + null, + useFlatConfig, + ), + "Cannot find module 'test111'", + ); + }); + + it(`should exit with no error if parser is valid with configType:${configType}`, async () => { + const filePath = getFixturePath("passing.js"); + const flag = useFlatConfig + ? "--no-config-lookup" + : "--no-eslintrc"; + const exit = await cli.execute( + `${flag} --no-ignore --parser espree ${filePath}`, + null, + useFlatConfig, + ); + + assert.strictEqual(exit, 0); + }); + }); + + describe("when supplied with report output file path", () => { + const flag = useFlatConfig + ? "--no-config-lookup" + : "--no-eslintrc"; + + afterEach(() => { + sh.rm("-rf", "tests/output"); + }); + + it(`should write the file and create dirs if they don't exist with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const code = `${flag} --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; + + await cli.execute(code, null, useFlatConfig); + + assert.include( + fs.readFileSync( + "tests/output/eslint-output.txt", + "utf8", + ), + filePath, + ); + assert.isTrue(log.info.notCalled); + }); + + // https://github.com/eslint/eslint/issues/17660 + it(`should write the file and create dirs if they don't exist even when output is empty with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const code = `${flag} --rule 'quotes: [1, single]' --o tests/output/eslint-output.txt ${filePath}`; + + // TODO: fix this test to: await cli.execute(code, null, useFlatConfig); + await cli.execute(code, "var a = 'b'", useFlatConfig); + + assert.isTrue( + fs.existsSync("tests/output/eslint-output.txt"), + ); + assert.strictEqual( + fs.readFileSync( + "tests/output/eslint-output.txt", + "utf8", + ), + "", + ); + assert.isTrue(log.info.notCalled); + }); + + it(`should return an error if the path is a directory with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const code = `${flag} --rule 'quotes: [1, double]' --o tests/output ${filePath}`; + + fs.mkdirSync("tests/output"); + + const exit = await cli.execute(code, null, useFlatConfig); + + assert.strictEqual(exit, 2); + assert.isTrue(log.info.notCalled); + assert.isTrue(log.error.calledOnce); + }); + + it(`should return an error if the path could not be written to with configType:${configType}`, async () => { + const filePath = getFixturePath("single-quoted.js"); + const code = `${flag} --rule 'quotes: [1, double]' --o tests/output/eslint-output.txt ${filePath}`; + + fs.writeFileSync("tests/output", "foo"); + + const exit = await cli.execute(code, null, useFlatConfig); + + assert.strictEqual(exit, 2); + assert.isTrue(log.info.notCalled); + assert.isTrue(log.error.calledOnce); + }); + }); + + describe("when passed --no-inline-config", () => { + let localCLI; + + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it(`should pass allowInlineConfig:false to ESLint when --no-inline-config is used with configType:${configType}`, async () => { + // create a fake ESLint class to test with + const fakeESLint = sinon + .mock() + .withExactArgs( + sinon.match({ allowInlineConfig: false }), + ); + + Object.defineProperties( + fakeESLint.prototype, + Object.getOwnPropertyDescriptors( + ActiveESLint.prototype, + ), + ); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([ + { + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 2, + message: "Fake message", + }, + ], + errorCount: 1, + warningCount: 0, + }, + ]); + sinon + .stub(fakeESLint.prototype, "loadFormatter") + .returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.stub(); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { + ESLint: fakeESLint, + shouldUseFlatConfig: () => + Promise.resolve(useFlatConfig), + }, + "./shared/logging": log, + }); + + await localCLI.execute( + "--no-inline-config .", + null, + useFlatConfig, + ); + }); + + it(`should not error and allowInlineConfig should be true by default with configType:${configType}`, async () => { + // create a fake ESLint class to test with + const fakeESLint = sinon + .mock() + .withExactArgs( + sinon.match({ allowInlineConfig: true }), + ); + + Object.defineProperties( + fakeESLint.prototype, + Object.getOwnPropertyDescriptors( + ActiveESLint.prototype, + ), + ); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); + sinon + .stub(fakeESLint.prototype, "loadFormatter") + .returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.stub(); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { + ESLint: fakeESLint, + shouldUseFlatConfig: () => + Promise.resolve(useFlatConfig), + }, + "./shared/logging": log, + }); + + const exitCode = await localCLI.execute( + ".", + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 0); + }); + }); + + describe("when passed --fix", () => { + let localCLI; + + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it(`should pass fix:true to ESLint when executing on files with configType:${configType}`, async () => { + // create a fake ESLint class to test with + const fakeESLint = sinon + .mock() + .withExactArgs(sinon.match({ fix: true })); + + Object.defineProperties( + fakeESLint.prototype, + Object.getOwnPropertyDescriptors( + ActiveESLint.prototype, + ), + ); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); + sinon + .stub(fakeESLint.prototype, "loadFormatter") + .returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().once(); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { + ESLint: fakeESLint, + shouldUseFlatConfig: () => + Promise.resolve(useFlatConfig), + }, + "./shared/logging": log, + }); + + const exitCode = await localCLI.execute( + "--fix .", + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 0); + }); + + it(`should rewrite files when in fix mode with configType:${configType}`, async () => { + const report = [ + { + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 2, + message: "Fake message", + }, + ], + errorCount: 1, + warningCount: 0, + }, + ]; + + // create a fake ESLint class to test with + const fakeESLint = sinon + .mock() + .withExactArgs(sinon.match({ fix: true })); + + Object.defineProperties( + fakeESLint.prototype, + Object.getOwnPropertyDescriptors( + ActiveESLint.prototype, + ), + ); + sinon + .stub(fakeESLint.prototype, "lintFiles") + .returns(report); + sinon + .stub(fakeESLint.prototype, "loadFormatter") + .returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().withExactArgs(report); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { + ESLint: fakeESLint, + shouldUseFlatConfig: () => + Promise.resolve(useFlatConfig), + }, + "./shared/logging": log, + }); + + const exitCode = await localCLI.execute( + "--fix .", + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 1); + }); + + it(`should provide fix predicate and rewrite files when in fix mode and quiet mode with configType:${configType}`, async () => { + const report = [ + { + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 1, + message: "Fake message", + }, + ], + errorCount: 0, + warningCount: 1, + }, + ]; + + // create a fake ESLint class to test with + const fakeESLint = sinon + .mock() + .withExactArgs(sinon.match({ fix: sinon.match.func })); + + Object.defineProperties( + fakeESLint.prototype, + Object.getOwnPropertyDescriptors( + ActiveESLint.prototype, + ), + ); + sinon + .stub(fakeESLint.prototype, "lintFiles") + .returns(report); + sinon + .stub(fakeESLint.prototype, "loadFormatter") + .returns({ format: () => "done" }); + fakeESLint.getErrorResults = sinon.stub().returns([]); + fakeESLint.outputFixes = sinon.mock().withExactArgs(report); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { + ESLint: fakeESLint, + shouldUseFlatConfig: () => + Promise.resolve(useFlatConfig), + }, + "./shared/logging": log, + }); + + const exitCode = await localCLI.execute( + "--fix --quiet .", + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 0); + }); + + it(`should not call ESLint and return 2 when executing on text with configType:${configType}`, async () => { + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().never(); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { + ESLint: fakeESLint, + shouldUseFlatConfig: () => + Promise.resolve(useFlatConfig), + }, + "./shared/logging": log, + }); + + const exitCode = await localCLI.execute( + "--fix .", + "foo = bar;", + useFlatConfig, + ); + + assert.strictEqual(exitCode, 2); + }); + }); + + describe("when passed --fix-dry-run", () => { + let localCLI; + + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it(`should pass fix:true to ESLint when executing on files with configType:${configType}`, async () => { + // create a fake ESLint class to test with + const fakeESLint = sinon + .mock() + .withExactArgs(sinon.match({ fix: true })); + + Object.defineProperties( + fakeESLint.prototype, + Object.getOwnPropertyDescriptors( + ActiveESLint.prototype, + ), + ); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); + sinon + .stub(fakeESLint.prototype, "loadFormatter") + .returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().never(); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { + ESLint: fakeESLint, + shouldUseFlatConfig: () => + Promise.resolve(useFlatConfig), + }, + + "./shared/logging": log, + }); + + const exitCode = await localCLI.execute( + "--fix-dry-run .", + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 0); + }); + + it(`should pass fixTypes to ESLint when --fix-type is passed with configType:${configType}`, async () => { + const expectedESLintOptions = { + fix: true, + fixTypes: ["suggestion"], + }; + + // create a fake ESLint class to test with + const fakeESLint = sinon + .mock() + .withExactArgs(sinon.match(expectedESLintOptions)); + + Object.defineProperties( + fakeESLint.prototype, + Object.getOwnPropertyDescriptors( + ActiveESLint.prototype, + ), + ); + sinon.stub(fakeESLint.prototype, "lintFiles").returns([]); + sinon + .stub(fakeESLint.prototype, "loadFormatter") + .returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.stub(); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { + ESLint: fakeESLint, + shouldUseFlatConfig: () => + Promise.resolve(useFlatConfig), + }, + + "./shared/logging": log, + }); + + const exitCode = await localCLI.execute( + "--fix-dry-run --fix-type suggestion .", + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 0); + }); + + it(`should not rewrite files when in fix-dry-run mode with configType:${configType}`, async () => { + const report = [ + { + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 2, + message: "Fake message", + }, + ], + errorCount: 1, + warningCount: 0, + }, + ]; + + // create a fake ESLint class to test with + const fakeESLint = sinon + .mock() + .withExactArgs(sinon.match({ fix: true })); + + Object.defineProperties( + fakeESLint.prototype, + Object.getOwnPropertyDescriptors( + ActiveESLint.prototype, + ), + ); + sinon + .stub(fakeESLint.prototype, "lintFiles") + .returns(report); + sinon + .stub(fakeESLint.prototype, "loadFormatter") + .returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().never(); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { + ESLint: fakeESLint, + shouldUseFlatConfig: () => + Promise.resolve(useFlatConfig), + }, + + "./shared/logging": log, + }); + + const exitCode = await localCLI.execute( + "--fix-dry-run .", + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 1); + }); + + it(`should provide fix predicate when in fix-dry-run mode and quiet mode with configType:${configType}`, async () => { + const report = [ + { + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 1, + message: "Fake message", + }, + ], + errorCount: 0, + warningCount: 1, + }, + ]; + + // create a fake ESLint class to test with + const fakeESLint = sinon + .mock() + .withExactArgs(sinon.match({ fix: sinon.match.func })); + + Object.defineProperties( + fakeESLint.prototype, + Object.getOwnPropertyDescriptors( + ActiveESLint.prototype, + ), + ); + sinon + .stub(fakeESLint.prototype, "lintFiles") + .returns(report); + sinon + .stub(fakeESLint.prototype, "loadFormatter") + .returns({ format: () => "done" }); + fakeESLint.getErrorResults = sinon.stub().returns([]); + fakeESLint.outputFixes = sinon.mock().never(); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { + ESLint: fakeESLint, + shouldUseFlatConfig: () => + Promise.resolve(useFlatConfig), + }, + + "./shared/logging": log, + }); + + const exitCode = await localCLI.execute( + "--fix-dry-run --quiet .", + null, + useFlatConfig, + ); + + assert.strictEqual(exitCode, 0); + }); + + it(`should allow executing on text with configType:${configType}`, async () => { + const report = [ + { + filePath: "./foo.js", + output: "bar", + messages: [ + { + severity: 2, + message: "Fake message", + }, + ], + errorCount: 1, + warningCount: 0, + }, + ]; + + // create a fake ESLint class to test with + const fakeESLint = sinon + .mock() + .withExactArgs(sinon.match({ fix: true })); + + Object.defineProperties( + fakeESLint.prototype, + Object.getOwnPropertyDescriptors( + ActiveESLint.prototype, + ), + ); + sinon + .stub(fakeESLint.prototype, "lintText") + .returns(report); + sinon + .stub(fakeESLint.prototype, "loadFormatter") + .returns({ format: () => "done" }); + fakeESLint.outputFixes = sinon.mock().never(); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { + ESLint: fakeESLint, + shouldUseFlatConfig: () => + Promise.resolve(useFlatConfig), + }, + + "./shared/logging": log, + }); + + const exitCode = await localCLI.execute( + "--fix-dry-run .", + "foo = bar;", + useFlatConfig, + ); + + assert.strictEqual(exitCode, 1); + }); + + it(`should not call ESLint and return 2 when used with --fix with configType:${configType}`, async () => { + // create a fake ESLint class to test with + const fakeESLint = sinon.mock().never(); + + localCLI = proxyquire("../../lib/cli", { + "./eslint": { LegacyESLint: fakeESLint }, + "./eslint/eslint": { + ESLint: fakeESLint, + shouldUseFlatConfig: () => + Promise.resolve(useFlatConfig), + }, + "./shared/logging": log, + }); + + const exitCode = await localCLI.execute( + "--fix --fix-dry-run .", + "foo = bar;", + useFlatConfig, + ); + + assert.strictEqual(exitCode, 2); + }); + }); + + describe("when passing --print-config", () => { + const originalCwd = process.cwd; + + beforeEach(() => { + process.cwd = () => getFixturePath(); + }); + + afterEach(() => { + process.cwd = originalCwd; + }); + + it(`should print out the configuration with configType:${configType}`, async () => { + const filePath = getFixturePath("xxx.js"); + + const exitCode = await cli.execute( + `--print-config ${filePath}`, + null, + useFlatConfig, + ); + + assert.isTrue(log.info.calledOnce); + assert.strictEqual(exitCode, 0); + }); + + it(`should error if any positional file arguments are passed with configType:${configType}`, async () => { + const filePath1 = getFixturePath("files", "bar.js"); + const filePath2 = getFixturePath("files", "foo.js"); + + const exitCode = await cli.execute( + `--print-config ${filePath1} ${filePath2}`, + null, + useFlatConfig, + ); + + assert.isTrue(log.info.notCalled); + assert.isTrue(log.error.calledOnce); + assert.strictEqual(exitCode, 2); + }); + + it(`should error out when executing on text with configType:${configType}`, async () => { + const exitCode = await cli.execute( + "--print-config=myFile.js", + "foo = bar;", + useFlatConfig, + ); + + assert.isTrue(log.info.notCalled); + assert.isTrue(log.error.calledOnce); + assert.strictEqual(exitCode, 2); + }); + }); + + describe("when passing --report-unused-disable-directives", () => { + describe(`config type: ${configType}`, () => { + it("errors when --report-unused-disable-directives", async () => { + const exitCode = await cli.execute( + `${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives --rule "'no-console': 'error'"`, + "foo(); // eslint-disable-line no-console", + useFlatConfig, + ); + + assert.strictEqual( + log.error.callCount, + 0, + "log.error should not be called", + ); + assert.strictEqual( + log.info.callCount, + 1, + "log.info is called once", + ); + assert.ok( + log.info.firstCall.args[0].includes( + "Unused eslint-disable directive (no problems were reported from 'no-console')", + ), + "has correct message about unused directives", + ); + assert.ok( + log.info.firstCall.args[0].includes( + "1 error and 0 warning", + ), + "has correct error and warning count", + ); + assert.strictEqual( + exitCode, + 1, + "exit code should be 1", + ); + }); + + it("errors when --report-unused-disable-directives-severity error", async () => { + const exitCode = await cli.execute( + `${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity error --rule "'no-console': 'error'"`, + "foo(); // eslint-disable-line no-console", + useFlatConfig, + ); + + assert.strictEqual( + log.error.callCount, + 0, + "log.error should not be called", + ); + assert.strictEqual( + log.info.callCount, + 1, + "log.info is called once", + ); + assert.ok( + log.info.firstCall.args[0].includes( + "Unused eslint-disable directive (no problems were reported from 'no-console')", + ), + "has correct message about unused directives", + ); + assert.ok( + log.info.firstCall.args[0].includes( + "1 error and 0 warning", + ), + "has correct error and warning count", + ); + assert.strictEqual( + exitCode, + 1, + "exit code should be 1", + ); + }); + + it("errors when --report-unused-disable-directives-severity 2", async () => { + const exitCode = await cli.execute( + `${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity 2 --rule "'no-console': 'error'"`, + "foo(); // eslint-disable-line no-console", + useFlatConfig, + ); + + assert.strictEqual( + log.error.callCount, + 0, + "log.error should not be called", + ); + assert.strictEqual( + log.info.callCount, + 1, + "log.info is called once", + ); + assert.ok( + log.info.firstCall.args[0].includes( + "Unused eslint-disable directive (no problems were reported from 'no-console')", + ), + "has correct message about unused directives", + ); + assert.ok( + log.info.firstCall.args[0].includes( + "1 error and 0 warning", + ), + "has correct error and warning count", + ); + assert.strictEqual( + exitCode, + 1, + "exit code should be 1", + ); + }); + + it("warns when --report-unused-disable-directives-severity warn", async () => { + const exitCode = await cli.execute( + `${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity warn --rule "'no-console': 'error'""`, + "foo(); // eslint-disable-line no-console", + useFlatConfig, + ); + + assert.strictEqual( + log.error.callCount, + 0, + "log.error should not be called", + ); + assert.strictEqual( + log.info.callCount, + 1, + "log.info is called once", + ); + assert.ok( + log.info.firstCall.args[0].includes( + "Unused eslint-disable directive (no problems were reported from 'no-console')", + ), + "has correct message about unused directives", + ); + assert.ok( + log.info.firstCall.args[0].includes( + "0 errors and 1 warning", + ), + "has correct error and warning count", + ); + assert.strictEqual( + exitCode, + 0, + "exit code should be 0", + ); + }); + + it("warns when --report-unused-disable-directives-severity 1", async () => { + const exitCode = await cli.execute( + `${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity 1 --rule "'no-console': 'error'"`, + "foo(); // eslint-disable-line no-console", + useFlatConfig, + ); + + assert.strictEqual( + log.error.callCount, + 0, + "log.error should not be called", + ); + assert.strictEqual( + log.info.callCount, + 1, + "log.info is called once", + ); + assert.ok( + log.info.firstCall.args[0].includes( + "Unused eslint-disable directive (no problems were reported from 'no-console')", + ), + "has correct message about unused directives", + ); + assert.ok( + log.info.firstCall.args[0].includes( + "0 errors and 1 warning", + ), + "has correct error and warning count", + ); + assert.strictEqual( + exitCode, + 0, + "exit code should be 0", + ); + }); + + it("does not report when --report-unused-disable-directives-severity off", async () => { + const exitCode = await cli.execute( + `${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity off --rule "'no-console': 'error'"`, + "foo(); // eslint-disable-line no-console", + useFlatConfig, + ); + + assert.strictEqual( + log.error.callCount, + 0, + "log.error should not be called", + ); + assert.strictEqual( + log.info.callCount, + 0, + "log.info should not be called", + ); + assert.strictEqual( + exitCode, + 0, + "exit code should be 0", + ); + }); + + it("does not report when --report-unused-disable-directives-severity 0", async () => { + const exitCode = await cli.execute( + `${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity 0 --rule "'no-console': 'error'"`, + "foo(); // eslint-disable-line no-console", + useFlatConfig, + ); + + assert.strictEqual( + log.error.callCount, + 0, + "log.error should not be called", + ); + assert.strictEqual( + log.info.callCount, + 0, + "log.info should not be called", + ); + assert.strictEqual( + exitCode, + 0, + "exit code should be 0", + ); + }); + + it("fails when passing invalid string for --report-unused-disable-directives-severity", async () => { + const exitCode = await cli.execute( + `${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives-severity foo`, + null, + useFlatConfig, + ); + + assert.strictEqual( + log.info.callCount, + 0, + "log.info should not be called", + ); + assert.strictEqual( + log.error.callCount, + 1, + "log.error should be called once", + ); + + const lines = [ + "Option report-unused-disable-directives-severity: 'foo' not one of off, warn, error, 0, 1, or 2.", + ]; + + if (useFlatConfig) { + lines.push( + "You're using eslint.config.js, some command line flags are no longer available. Please see https://eslint.org/docs/latest/use/command-line-interface for details.", + ); + } + assert.deepStrictEqual( + log.error.firstCall.args, + [lines.join("\n")], + "has the right text to log.error", + ); + assert.strictEqual( + exitCode, + 2, + "exit code should be 2", + ); + }); + + it("fails when passing both --report-unused-disable-directives and --report-unused-disable-directives-severity", async () => { + const exitCode = await cli.execute( + `${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --report-unused-disable-directives --report-unused-disable-directives-severity warn`, + null, + useFlatConfig, + ); + + assert.strictEqual( + log.info.callCount, + 0, + "log.info should not be called", + ); + assert.strictEqual( + log.error.callCount, + 1, + "log.error should be called once", + ); + assert.deepStrictEqual( + log.error.firstCall.args, + [ + "The --report-unused-disable-directives option and the --report-unused-disable-directives-severity option cannot be used together.", + ], + "has the right text to log.error", + ); + assert.strictEqual( + exitCode, + 2, + "exit code should be 2", + ); + }); + + it("warns by default in flat config only", async () => { + const exitCode = await cli.execute( + `${useFlatConfig ? "--no-config-lookup" : "--no-eslintrc"} --rule "'no-console': 'error'"`, + "foo(); // eslint-disable-line no-console", + useFlatConfig, + ); + + if (useFlatConfig) { + assert.strictEqual( + log.error.callCount, + 0, + "log.error should not be called", + ); + assert.strictEqual( + log.info.callCount, + 1, + "log.info is called once", + ); + assert.ok( + log.info.firstCall.args[0].includes( + "Unused eslint-disable directive (no problems were reported from 'no-console')", + ), + "has correct message about unused directives", + ); + assert.ok( + log.info.firstCall.args[0].includes( + "0 errors and 1 warning", + ), + "has correct error and warning count", + ); + assert.strictEqual( + exitCode, + 0, + "exit code should be 0", + ); + } else { + assert.strictEqual( + log.error.callCount, + 0, + "log.error should not be called", + ); + assert.strictEqual( + log.info.callCount, + 0, + "log.info should not be called", + ); + assert.strictEqual( + exitCode, + 0, + "exit code should be 0", + ); + } + }); + }); + }); + }); + + describe("when given a config file", () => { + it("should load the specified config file", async () => { + const configPath = getFixturePath("eslint.config.js"); + const filePath = getFixturePath("passing.js"); + + await cli.execute(`--config ${configPath} ${filePath}`); + }); + }); + + describe("eslintrc Only", () => { + describe("Environments", () => { + describe("when given a config with environment set to browser", () => { + it("should execute without any errors", async () => { + const configPath = getFixturePath( + "configurations", + "env-browser.json", + ); + const filePath = getFixturePath("globals-browser.js"); + const code = `--config ${configPath} ${filePath}`; + + const exit = await cli.execute(code, null, false); + + assert.strictEqual(exit, 0); + }); + }); + + describe("when given a config with environment set to Node.js", () => { + it("should execute without any errors", async () => { + const configPath = getFixturePath( + "configurations", + "env-node.json", + ); + const filePath = getFixturePath("globals-node.js"); + const code = `--config ${configPath} ${filePath}`; + + const exit = await cli.execute(code, null, false); + + assert.strictEqual(exit, 0); + }); + }); + + describe("when given a config with environment set to Nashorn", () => { + it("should execute without any errors", async () => { + const configPath = getFixturePath( + "configurations", + "env-nashorn.json", + ); + const filePath = getFixturePath("globals-nashorn.js"); + const code = `--config ${configPath} ${filePath}`; + + const exit = await cli.execute(code, null, false); + + assert.strictEqual(exit, 0); + }); + }); + + describe("when given a config with environment set to WebExtensions", () => { + it("should execute without any errors", async () => { + const configPath = getFixturePath( + "configurations", + "env-webextensions.json", + ); + const filePath = getFixturePath( + "globals-webextensions.js", + ); + const code = `--config ${configPath} ${filePath}`; + + const exit = await cli.execute(code, null, false); + + assert.strictEqual(exit, 0); + }); + }); + }); + + describe("when loading a custom rule", () => { + it("should return an error when rule isn't found", async () => { + const rulesPath = getFixturePath("rules", "wrong"); + const configPath = getFixturePath("rules", "eslint.json"); + const filePath = getFixturePath( + "rules", + "test", + "test-custom-rule.js", + ); + const code = `--rulesdir ${rulesPath} --config ${configPath} --no-ignore ${filePath}`; + + await stdAssert.rejects(async () => { + const exit = await cli.execute(code, null, false); + + assert.strictEqual(exit, 2); + }, /Error while loading rule 'custom-rule': Boom!/u); + }); + + it("should return a warning when rule is matched", async () => { + const rulesPath = getFixturePath("rules"); + const configPath = getFixturePath("rules", "eslint.json"); + const filePath = getFixturePath( + "rules", + "test", + "test-custom-rule.js", + ); + const code = `--rulesdir ${rulesPath} --config ${configPath} --no-ignore ${filePath}`; + + await cli.execute(code, null, false); + + assert.isTrue(log.info.calledOnce); + assert.isTrue(log.info.neverCalledWith("")); + }); + + it("should return warnings from multiple rules in different directories", async () => { + const rulesPath = getFixturePath("rules", "dir1"); + const rulesPath2 = getFixturePath("rules", "dir2"); + const configPath = getFixturePath( + "rules", + "multi-rulesdirs.json", + ); + const filePath = getFixturePath( + "rules", + "test-multi-rulesdirs.js", + ); + const code = `--rulesdir ${rulesPath} --rulesdir ${rulesPath2} --config ${configPath} --no-ignore ${filePath}`; + const exit = await cli.execute(code, null, false); + + const call = log.info.getCall(0); + + assert.isTrue(log.info.calledOnce); + assert.isTrue(call.args[0].includes("String!")); + assert.isTrue(call.args[0].includes("Literal!")); + assert.isTrue(call.args[0].includes("2 problems")); + assert.isTrue(log.info.neverCalledWith("")); + assert.strictEqual(exit, 1); + }); + }); + + describe("when executing with no-eslintrc flag", () => { + it("should ignore a local config file", async () => { + const filePath = getFixturePath("eslintrc", "quotes.js"); + const exit = await cli.execute( + `--no-eslintrc --no-ignore ${filePath}`, + null, + false, + ); + + assert.isTrue(log.info.notCalled); + assert.strictEqual(exit, 0); + }); + }); + + describe("when executing without no-eslintrc flag", () => { + it("should load a local config file", async () => { + const filePath = getFixturePath("eslintrc", "quotes.js"); + const exit = await cli.execute( + `--no-ignore ${filePath}`, + null, + false, + ); + + assert.isTrue(log.info.calledOnce); + assert.strictEqual(exit, 1); + }); + }); + + describe("when executing without env flag", () => { + it("should not define environment-specific globals", async () => { + const files = [ + getFixturePath("globals-browser.js"), + getFixturePath("globals-node.js"), + ]; + + await cli.execute( + `--no-eslintrc --config ./tests/fixtures/config-file/js/.eslintrc.js --no-ignore ${files.join(" ")}`, + null, + false, + ); + + assert.strictEqual( + log.info.args[0][0].split("\n").length, + 10, + ); + }); + }); + + describe("when supplied with a plugin", () => { + it("should pass plugins to ESLint", async () => { + const examplePluginName = "eslint-plugin-example"; + + await verifyESLintOpts( + `--no-ignore --plugin ${examplePluginName} foo.js`, + { + overrideConfig: { + plugins: [examplePluginName], + }, + }, + ); + }); + }); + + describe("when supplied with a plugin-loading path", () => { + it("should pass the option to ESLint", async () => { + const examplePluginDirPath = "foo/bar"; + + await verifyESLintOpts( + `--resolve-plugins-relative-to ${examplePluginDirPath} foo.js`, + { + resolvePluginsRelativeTo: examplePluginDirPath, + }, + ); + }); + }); + }); + + describe("flat Only", () => { + describe("`--plugin` option", () => { + let originalCwd; + + beforeEach(() => { + originalCwd = process.cwd(); + process.chdir(getFixturePath("plugins")); + }); + + afterEach(() => { + process.chdir(originalCwd); + originalCwd = void 0; + }); + + it("should load a plugin from a CommonJS package", async () => { + const code = + "--plugin hello-cjs --rule 'hello-cjs/hello: error' ../files/*.js"; + + const exitCode = await cli.execute(code, null, true); + + assert.strictEqual(exitCode, 1); + assert.ok(log.info.calledOnce); + assert.include( + log.info.firstCall.firstArg, + "Hello CommonJS!", + ); + }); + + it("should load a plugin from an ESM package", async () => { + const code = + "--plugin hello-esm --rule 'hello-esm/hello: error' ../files/*.js"; + + const exitCode = await cli.execute(code, null, true); + + assert.strictEqual(exitCode, 1); + assert.ok(log.info.calledOnce); + assert.include(log.info.firstCall.firstArg, "Hello ESM!"); + }); + + it("should load multiple plugins", async () => { + const code = + "--plugin 'hello-cjs, hello-esm' --rule 'hello-cjs/hello: warn, hello-esm/hello: error' ../files/*.js"; + + const exitCode = await cli.execute(code, null, true); + + assert.strictEqual(exitCode, 1); + assert.ok(log.info.calledOnce); + assert.include( + log.info.firstCall.firstArg, + "Hello CommonJS!", + ); + assert.include(log.info.firstCall.firstArg, "Hello ESM!"); + }); + + it("should resolve plugins specified with 'eslint-plugin-'", async () => { + const code = + "--plugin 'eslint-plugin-schema-array, @scope/eslint-plugin-example' --rule 'schema-array/rule1: warn, @scope/example/test: warn' ../passing.js"; + + const exitCode = await cli.execute(code, null, true); + + assert.strictEqual(exitCode, 0); + }); + + it("should resolve plugins in the parent directory's node_module subdirectory", async () => { + process.chdir("subdir"); + const code = "--plugin 'example, @scope/example' file.js"; + + const exitCode = await cli.execute(code, null, true); + + assert.strictEqual(exitCode, 0); + }); + + it("should fail if a plugin is not found", async () => { + const code = + "--plugin 'example, no-such-plugin' ../passing.js"; + + await stdAssert.rejects( + cli.execute(code, null, true), + ({ message }) => { + assert( + message.startsWith( + "Cannot find module 'eslint-plugin-no-such-plugin'\n", + ), + `Unexpected error message:\n${message}`, + ); + return true; + }, + ); + }); + + it("should fail if a plugin throws an error while loading", async () => { + const code = + "--plugin 'example, throws-on-load' ../passing.js"; + + await stdAssert.rejects(cli.execute(code, null, true), { + message: "error thrown while loading this module", + }); + }); + + it("should fail to load a plugin from a package without a default export", async () => { + const code = + "--plugin 'example, no-default-export' ../passing.js"; + + await stdAssert.rejects(cli.execute(code, null, true), { + message: + '"eslint-plugin-no-default-export" cannot be used with the `--plugin` option because its default module does not provide a `default` export', + }); + }); + }); + + describe("--flag option", () => { + let processStub; + + beforeEach(() => { + sinon.restore(); + processStub = sinon + .stub(process, "emitWarning") + .withArgs( + sinon.match.any, + sinon.match(/^ESLintInactiveFlag_/u), + ) + .returns(); + }); + + afterEach(() => { + sinon.restore(); + delete process.env.ESLINT_FLAGS; + }); + + it("should throw an error when an inactive flag whose feature has been abandoned is used", async () => { + const configPath = getFixturePath("eslint.config.js"); + const filePath = getFixturePath("passing.js"); + const input = `--flag test_only_abandoned --config ${configPath} ${filePath}`; + + await stdAssert.rejects(async () => { + await cli.execute(input, null, true); + }, /The flag 'test_only_abandoned' is inactive: This feature has been abandoned\./u); + }); + + it("should throw an error when an inactive flag whose feature has been abandoned is used in an environment variable", async () => { + const configPath = getFixturePath("eslint.config.js"); + const filePath = getFixturePath("passing.js"); + + process.env.ESLINT_FLAGS = "test_only_abandoned"; + const input = `--config ${configPath} ${filePath}`; + + await stdAssert.rejects(async () => { + await cli.execute(input, null, true); + }, /The flag 'test_only_abandoned' is inactive: This feature has been abandoned\./u); + }); + + it("should error out when an unknown flag is used", async () => { + const configPath = getFixturePath("eslint.config.js"); + const filePath = getFixturePath("passing.js"); + const input = `--flag test_only_oldx --config ${configPath} ${filePath}`; + + await stdAssert.rejects(async () => { + await cli.execute(input, null, true); + }, /Unknown flag 'test_only_oldx'\./u); + }); + + it("should error out when an unknown flag is used in an environment variable", async () => { + const configPath = getFixturePath("eslint.config.js"); + const filePath = getFixturePath("passing.js"); + const input = `--config ${configPath} ${filePath}`; + + process.env.ESLINT_FLAGS = "test_only_oldx"; + + await stdAssert.rejects(async () => { + await cli.execute(input, null, true); + }, /Unknown flag 'test_only_oldx'\./u); + }); + + it("should emit a warning and not error out when an inactive flag that has been replaced by another flag is used", async () => { + const configPath = getFixturePath("eslint.config.js"); + const filePath = getFixturePath("passing.js"); + const input = `--flag test_only_replaced --config ${configPath} ${filePath}`; + const exitCode = await cli.execute(input, null, true); + + assert.strictEqual( + processStub.callCount, + 1, + "calls `process.emitWarning()` for flags once", + ); + assert.deepStrictEqual(processStub.getCall(0).args, [ + "The flag 'test_only_replaced' is inactive: This flag has been renamed 'test_only' to reflect its stabilization. Please use 'test_only' instead.", + "ESLintInactiveFlag_test_only_replaced", + ]); + sinon.assert.notCalled(log.error); + assert.strictEqual(exitCode, 0); + }); + + it("should emit a warning and not error out when an inactive flag that has been replaced by another flag is used in an environment variable", async () => { + const configPath = getFixturePath("eslint.config.js"); + const filePath = getFixturePath("passing.js"); + const input = `--config ${configPath} ${filePath}`; + + process.env.ESLINT_FLAGS = "test_only_replaced"; + + const exitCode = await cli.execute(input, null, true); + + assert.strictEqual( + processStub.callCount, + 1, + "calls `process.emitWarning()` for flags once", + ); + assert.deepStrictEqual(processStub.getCall(0).args, [ + "The flag 'test_only_replaced' is inactive: This flag has been renamed 'test_only' to reflect its stabilization. Please use 'test_only' instead.", + "ESLintInactiveFlag_test_only_replaced", + ]); + sinon.assert.notCalled(log.error); + assert.strictEqual(exitCode, 0); + }); + + it("should emit a warning and not error out when an inactive flag whose feature is enabled by default is used", async () => { + const configPath = getFixturePath("eslint.config.js"); + const filePath = getFixturePath("passing.js"); + const input = `--flag test_only_enabled_by_default --config ${configPath} ${filePath}`; + const exitCode = await cli.execute(input, null, true); + + assert.strictEqual( + processStub.callCount, + 1, + "calls `process.emitWarning()` for flags once", + ); + assert.deepStrictEqual(processStub.getCall(0).args, [ + "The flag 'test_only_enabled_by_default' is inactive: This feature is now enabled by default.", + "ESLintInactiveFlag_test_only_enabled_by_default", + ]); + sinon.assert.notCalled(log.error); + assert.strictEqual(exitCode, 0); + }); + + it("should emit a warning and not error out when an inactive flag whose feature is enabled by default is used in an environment variable", async () => { + const configPath = getFixturePath("eslint.config.js"); + const filePath = getFixturePath("passing.js"); + const input = `--config ${configPath} ${filePath}`; + + process.env.ESLINT_FLAGS = "test_only_enabled_by_default"; + + const exitCode = await cli.execute(input, null, true); + assert.strictEqual( + processStub.callCount, + 1, + "calls `process.emitWarning()` for flags once", + ); + assert.deepStrictEqual(processStub.getCall(0).args, [ + "The flag 'test_only_enabled_by_default' is inactive: This feature is now enabled by default.", + "ESLintInactiveFlag_test_only_enabled_by_default", + ]); + sinon.assert.notCalled(log.error); + assert.strictEqual(exitCode, 0); + }); + + it("should not error when a valid flag is used", async () => { + const configPath = getFixturePath("eslint.config.js"); + const filePath = getFixturePath("passing.js"); + const input = `--flag test_only --config ${configPath} ${filePath}`; + const exitCode = await cli.execute(input, null, true); + + sinon.assert.notCalled(log.error); + assert.strictEqual(exitCode, 0); + }); + + it("should not error when a valid flag is used in an environment variable", async () => { + const configPath = getFixturePath("eslint.config.js"); + const filePath = getFixturePath("passing.js"); + const input = `--config ${configPath} ${filePath}`; + + process.env.ESLINT_FLAGS = "test_only"; + + const exitCode = await cli.execute(input, null, true); + + sinon.assert.notCalled(log.error); + assert.strictEqual(exitCode, 0); + }); + + it("should error when a valid flag is used in an environment variable with an abandoned flag", async () => { + const configPath = getFixturePath("eslint.config.js"); + const filePath = getFixturePath("passing.js"); + const input = `--config ${configPath} ${filePath}`; + + process.env.ESLINT_FLAGS = "test_only,test_only_abandoned"; + + await stdAssert.rejects(async () => { + await cli.execute(input, null, true); + }, /The flag 'test_only_abandoned' is inactive: This feature has been abandoned\./u); + }); + }); + + describe("--report-unused-inline-configs option", () => { + it("does not report when --report-unused-inline-configs 0", async () => { + const exitCode = await cli.execute( + "--no-config-lookup --report-unused-inline-configs 0 --rule \"'no-console': 'error'\"", + "/* eslint no-console: 'error' */", + true, + ); + + assert.strictEqual( + log.error.callCount, + 0, + "log.error should not be called", + ); + assert.strictEqual( + log.info.callCount, + 0, + "log.info should not be called", + ); + assert.strictEqual(exitCode, 0, "exit code should be 0"); + }); + + [ + [1, 0, "0 errors, 1 warning"], + ["warn", 0, "0 errors, 1 warning"], + [2, 1, "1 error, 0 warnings"], + ["error", 1, "1 error, 0 warnings"], + ].forEach(([setting, status, descriptor]) => { + it(`reports when --report-unused-inline-configs ${setting}`, async () => { + const exitCode = await cli.execute( + `--no-config-lookup --report-unused-inline-configs ${setting} --rule "'no-console': 'error'"`, + "/* eslint no-console: 'error' */", + true, + ); + + assert.strictEqual( + log.info.callCount, + 1, + "log.info is called once", + ); + assert.ok( + log.info.firstCall.args[0].includes( + "Unused inline config ('no-console' is already configured to 'error')", + ), + "has correct message about unused inline config", + ); + assert.ok( + log.info.firstCall.args[0].includes(descriptor), + "has correct error and warning count", + ); + assert.strictEqual( + exitCode, + status, + `exit code should be ${exitCode}`, + ); + }); + }); + + it("fails when passing invalid string for --report-unused-inline-configs", async () => { + const exitCode = await cli.execute( + "--no-config-lookup --report-unused-inline-configs foo", + null, + true, + ); + + assert.strictEqual( + log.info.callCount, + 0, + "log.info should not be called", + ); + assert.strictEqual( + log.error.callCount, + 1, + "log.error should be called once", + ); + + const lines = [ + "Option report-unused-inline-configs: 'foo' not one of off, warn, error, 0, 1, or 2.", + "You're using eslint.config.js, some command line flags are no longer available. Please see https://eslint.org/docs/latest/use/command-line-interface for details.", + ]; + + assert.deepStrictEqual( + log.error.firstCall.args, + [lines.join("\n")], + "has the right text to log.error", + ); + assert.strictEqual(exitCode, 2, "exit code should be 2"); + }); + }); + + describe("--ext option", () => { + let originalCwd; + + beforeEach(() => { + originalCwd = process.cwd(); + process.chdir(getFixturePath("file-extensions")); + }); + + afterEach(() => { + process.chdir(originalCwd); + originalCwd = void 0; + }); + + it("when not provided, without config file only default extensions should be linted", async () => { + const exitCode = await cli.execute( + "--no-config-lookup -f json .", + null, + true, + ); + + assert.strictEqual(exitCode, 0, "exit code should be 0"); + + const results = JSON.parse(log.info.args[0][0]); + + assert.deepStrictEqual( + results.map(({ filePath }) => filePath).sort(), + ["a.js", "b.mjs", "c.cjs", "eslint.config.js"].map( + filename => path.resolve(filename), + ), + ); + }); + + it("when not provided, only default extensions and extensions from the config file should be linted", async () => { + const exitCode = await cli.execute("-f json .", null, true); + + assert.strictEqual(exitCode, 0, "exit code should be 0"); + + const results = JSON.parse(log.info.args[0][0]); + + assert.deepStrictEqual( + results.map(({ filePath }) => filePath).sort(), + [ + "a.js", + "b.mjs", + "c.cjs", + "d.jsx", + "eslint.config.js", + ].map(filename => path.resolve(filename)), + ); + }); + + it("should include an additional extension when specified with dot", async () => { + const exitCode = await cli.execute( + "-f json --ext .ts .", + null, + true, + ); + + assert.strictEqual(exitCode, 0, "exit code should be 0"); + + const results = JSON.parse(log.info.args[0][0]); + + assert.deepStrictEqual( + results.map(({ filePath }) => filePath).sort(), + [ + "a.js", + "b.mjs", + "c.cjs", + "d.jsx", + "eslint.config.js", + "f.ts", + ].map(filename => path.resolve(filename)), + ); + }); + + it("should include an additional extension when specified without dot", async () => { + const exitCode = await cli.execute( + "-f json --ext ts .", + null, + true, + ); + + assert.strictEqual(exitCode, 0, "exit code should be 0"); + + const results = JSON.parse(log.info.args[0][0]); + + // should not include "foots" + assert.deepStrictEqual( + results.map(({ filePath }) => filePath).sort(), + [ + "a.js", + "b.mjs", + "c.cjs", + "d.jsx", + "eslint.config.js", + "f.ts", + ].map(filename => path.resolve(filename)), + ); + }); + + it("should include multiple additional extensions when specified by repeating the option", async () => { + const exitCode = await cli.execute( + "-f json --ext .ts --ext tsx .", + null, + true, + ); + + assert.strictEqual(exitCode, 0, "exit code should be 0"); + + const results = JSON.parse(log.info.args[0][0]); + + assert.deepStrictEqual( + results.map(({ filePath }) => filePath).sort(), + [ + "a.js", + "b.mjs", + "c.cjs", + "d.jsx", + "eslint.config.js", + "f.ts", + "g.tsx", + ].map(filename => path.resolve(filename)), + ); + }); + + it("should include multiple additional extensions when specified with comma-delimited list", async () => { + const exitCode = await cli.execute( + "-f json --ext .ts,.tsx .", + null, + true, + ); + + assert.strictEqual(exitCode, 0, "exit code should be 0"); + + const results = JSON.parse(log.info.args[0][0]); + + assert.deepStrictEqual( + results.map(({ filePath }) => filePath).sort(), + [ + "a.js", + "b.mjs", + "c.cjs", + "d.jsx", + "eslint.config.js", + "f.ts", + "g.tsx", + ].map(filename => path.resolve(filename)), + ); + }); + + it('should fail when passing --ext ""', async () => { + // When passing "" on command line, its corresponding item in process.argv[] is an empty string + const exitCode = await cli.execute( + ["argv0", "argv1", "--ext", ""], + null, + true, + ); + + assert.strictEqual(exitCode, 2, "exit code should be 2"); + assert.strictEqual( + log.info.callCount, + 0, + "log.info should not be called", + ); + assert.strictEqual( + log.error.callCount, + 1, + "log.error should be called once", + ); + assert.deepStrictEqual( + log.error.firstCall.args[0], + "The --ext option value cannot be empty.", + ); + }); + + it("should fail when passing --ext ,ts", async () => { + const exitCode = await cli.execute("--ext ,ts", null, true); + + assert.strictEqual(exitCode, 2, "exit code should be 2"); + assert.strictEqual( + log.info.callCount, + 0, + "log.info should not be called", + ); + assert.strictEqual( + log.error.callCount, + 1, + "log.error should be called once", + ); + assert.deepStrictEqual( + log.error.firstCall.args[0], + "The --ext option arguments cannot be empty strings. Found an empty string at index 0.", + ); + }); + + it("should fail when passing --ext ts,,tsx", async () => { + const exitCode = await cli.execute( + "--ext ts,,tsx", + null, + true, + ); + + assert.strictEqual(exitCode, 2, "exit code should be 2"); + assert.strictEqual( + log.info.callCount, + 0, + "log.info should not be called", + ); + assert.strictEqual( + log.error.callCount, + 1, + "log.error should be called once", + ); + assert.deepStrictEqual( + log.error.firstCall.args[0], + "The --ext option arguments cannot be empty strings. Found an empty string at index 1.", + ); + }); + }); + + describe("v10_config_lookup_from_file", () => { + const flag = "v10_config_lookup_from_file"; + + it("should throw an error when text is passed and no config file is found", async () => { + await stdAssert.rejects( + () => + cli.execute( + `--flag ${flag} --stdin --stdin-filename /foo.js"`, + "var foo = 'bar';", + true, + ), + /Could not find config file/u, + ); + }); + }); + }); + }); }); diff --git a/tests/lib/config/config-loader.js b/tests/lib/config/config-loader.js new file mode 100644 index 000000000000..3035a4358f54 --- /dev/null +++ b/tests/lib/config/config-loader.js @@ -0,0 +1,305 @@ +/** + * @fileoverview Tests for config loader classes. + * @author Milos Djermanovic + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("node:assert"); +const path = require("node:path"); +const sinon = require("sinon"); +const { + ConfigLoader, + LegacyConfigLoader, +} = require("../../../lib/config/config-loader"); +const { WarningService } = require("../../../lib/services/warning-service"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const fixtureDir = path.resolve(__dirname, "../../fixtures"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("Config loaders", () => { + afterEach(() => { + sinon.restore(); + }); + + [ConfigLoader, LegacyConfigLoader].forEach(ConfigLoaderClass => { + describe(`${ConfigLoaderClass.name} class`, () => { + describe("findConfigFileForPath()", () => { + it("should lookup config file only once for multiple files in same directory", async () => { + const cwd = path.resolve( + fixtureDir, + "simple-valid-project-2", + ); + + const locateConfigFileToUse = sinon.spy( + ConfigLoader, + "locateConfigFileToUse", + ); + + const configLoader = new ConfigLoaderClass({ + cwd, + ignoreEnabled: true, + }); + + const [path1, path2] = await Promise.all([ + configLoader.findConfigFileForPath( + path.resolve(cwd, "foo.js"), + ), + configLoader.findConfigFileForPath( + path.resolve(cwd, "bar.js"), + ), + ]); + + const configFile = path.resolve(cwd, "eslint.config.js"); + + assert.strictEqual(path1, configFile); + assert.strictEqual(path2, configFile); + + assert.strictEqual( + locateConfigFileToUse.callCount, + 1, + "Expected `ConfigLoader.locateConfigFileToUse` to be called exactly once", + ); + }); + }); + + describe("loadConfigArrayForFile()", () => { + // https://github.com/eslint/eslint/issues/19025 + it("should lookup config file only once and create config array only once for multiple files in same directory", async () => { + const cwd = path.resolve( + fixtureDir, + "simple-valid-project-2", + ); + + const locateConfigFileToUse = sinon.spy( + ConfigLoader, + "locateConfigFileToUse", + ); + const calculateConfigArray = sinon.spy( + ConfigLoader, + "calculateConfigArray", + ); + + const configLoader = new ConfigLoaderClass({ + cwd, + ignoreEnabled: true, + }); + + const [configArray1, configArray2] = await Promise.all([ + configLoader.loadConfigArrayForFile( + path.resolve(cwd, "foo.js"), + ), + configLoader.loadConfigArrayForFile( + path.resolve(cwd, "bar.js"), + ), + ]); + + assert( + Array.isArray(configArray1), + "Expected `loadConfigArrayForFile()` to return a config array", + ); + assert( + configArray1 === configArray2, + "Expected config array instances for `foo.js` and `bar.js` to be the same", + ); + + assert.strictEqual( + locateConfigFileToUse.callCount, + 1, + "Expected `ConfigLoader.locateConfigFileToUse` to be called exactly once", + ); + assert.strictEqual( + calculateConfigArray.callCount, + 1, + "Expected `ConfigLoader.calculateConfigArray` to be called exactly once", + ); + }); + + it("should not error when loading an empty CommonJS config file", async () => { + const cwd = path.resolve(fixtureDir, "empty-config-file"); + + const warningService = new WarningService(); + warningService.emitEmptyConfigWarning = sinon.spy(); + const configLoader = new ConfigLoaderClass({ + cwd, + ignoreEnabled: true, + configFile: "cjs/eslint.config.cjs", + warningService, + }); + + const configArray = + await configLoader.loadConfigArrayForFile( + path.resolve(cwd, "cjs/foo.js"), + ); + + assert( + Array.isArray(configArray), + "Expected `loadConfigArrayForFile()` to return a config array", + ); + assert( + warningService.emitEmptyConfigWarning.calledOnce, + "Expected `warningService.emitEmptyConfigWarning` to be called once", + ); + }); + + it("should not error when loading an empty ESM config file", async () => { + const cwd = path.resolve(fixtureDir, "empty-config-file"); + + const warningService = new WarningService(); + warningService.emitEmptyConfigWarning = sinon.spy(); + const configLoader = new ConfigLoaderClass({ + cwd, + ignoreEnabled: true, + configFile: "esm/eslint.config.mjs", + warningService, + }); + + const configArray = + await configLoader.loadConfigArrayForFile( + path.resolve(cwd, "esm/foo.js"), + ); + + assert( + Array.isArray(configArray), + "Expected `loadConfigArrayForFile()` to return a config array", + ); + assert( + warningService.emitEmptyConfigWarning.calledOnce, + "Expected `warningService.emitEmptyConfigWarning` to be called once", + ); + }); + + it("should not error when loading an ESM config file with an empty array", async () => { + const cwd = path.resolve(fixtureDir, "empty-config-file"); + + const warningService = new WarningService(); + warningService.emitEmptyConfigWarning = sinon.spy(); + const configLoader = new ConfigLoaderClass({ + cwd, + ignoreEnabled: true, + configFile: "esm/eslint.config.empty-array.mjs", + warningService, + }); + + const configArray = + await configLoader.loadConfigArrayForFile( + path.resolve(cwd, "mjs/foo.js"), + ); + + assert( + Array.isArray(configArray), + "Expected `loadConfigArrayForFile()` to return a config array", + ); + assert( + warningService.emitEmptyConfigWarning.calledOnce, + "Expected `warningService.emitEmptyConfigWarning` to be called once", + ); + }); + + it("should throw an error when loading an ESM config file with null", async () => { + const cwd = path.resolve(fixtureDir, "empty-config-file"); + + const configLoader = new ConfigLoaderClass({ + cwd, + ignoreEnabled: true, + configFile: "esm/eslint.config.null.mjs", + }); + + let error; + + try { + await configLoader.loadConfigArrayForFile( + path.resolve(cwd, "mjs/foo.js"), + ); + } catch (err) { + error = err; + } + + assert(error); + assert.strictEqual( + error.message, + "Config (unnamed): Unexpected null config at user-defined index 0.", + ); + }); + + it("should throw an error when loading an ESM config with 0", async () => { + const cwd = path.resolve(fixtureDir, "empty-config-file"); + + const configLoader = new ConfigLoaderClass({ + cwd, + ignoreEnabled: true, + configFile: "esm/eslint.config.zero.mjs", + }); + + let error; + + try { + await configLoader.loadConfigArrayForFile( + path.resolve(cwd, "mjs/foo.js"), + ); + } catch (err) { + error = err; + } + + assert(error); + assert.strictEqual( + error.message, + "Config (unnamed): Unexpected non-object config at user-defined index 0.", + ); + }); + }); + + describe("getCachedConfigArrayForFile()", () => { + it("should throw an error if calculating the config array is not yet complete", async () => { + let error; + + const cwd = path.resolve( + fixtureDir, + "simple-valid-project-2", + ); + const filePath = path.resolve(cwd, "foo.js"); + + const configLoader = new ConfigLoaderClass({ + cwd, + ignoreEnabled: true, + }); + + const originalCalculateConfigArray = + ConfigLoader.calculateConfigArray; + + sinon + .stub(ConfigLoader, "calculateConfigArray") + .callsFake((...args) => { + process.nextTick(() => { + try { + configLoader.getCachedConfigArrayForFile( + filePath, + ); + } catch (e) { + error = e; + } + }); + + return originalCalculateConfigArray(...args); + }); + + await configLoader.loadConfigArrayForFile(filePath); + + assert(error, "An error was expected"); + assert.match(error.message, /has not yet been calculated/u); + }); + }); + }); + }); +}); diff --git a/tests/lib/config/config.js b/tests/lib/config/config.js new file mode 100644 index 000000000000..a028b3049bc7 --- /dev/null +++ b/tests/lib/config/config.js @@ -0,0 +1,969 @@ +/** + * @fileoverview Tests for Config + * @author Nicholas C. Zakas + */ + +/* eslint no-new: "off" -- new is needed to test constructor */ + +"use strict"; + +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + +const { Config } = require("../../../lib/config/config"); +const assert = require("chai").assert; +const sinon = require("sinon"); + +//----------------------------------------------------------------------------- +// Helper Functions +//----------------------------------------------------------------------------- + +/** + * Creates a mock language object with default properties + * @param {Object} overrides Properties to override + * @returns {Object} Mock language object + */ +function createMockLanguage(overrides = {}) { + return { + validateLanguageOptions() {}, + normalizeLanguageOptions: options => options, + meta: { + name: "testLang", + version: "1.0.0", + }, + ...overrides, + }; +} + +/** + * Creates a mock plugin object with default properties + * @param {Object} overrides Properties to override + * @returns {Object} Mock plugin object + */ +function createMockPlugin(overrides = {}) { + return { + meta: { + name: "testPlugin", + version: "1.0.0", + }, + ...overrides, + }; +} + +/** + * Creates a mock processor object with default properties + * @param {Object} overrides Properties to override + * @returns {Object} Mock processor object + */ +function createMockProcessor(overrides = {}) { + return { + meta: { + name: "testProcessor", + version: "1.0.0", + }, + preprocess() {}, + postprocess() {}, + ...overrides, + }; +} + +/** + * Creates a basic config structure for testing + * @param {Object} configOptions Config options to merge + * @param {Object} mockLanguage Mock language to use + * @returns {Object} Config options object + */ +function createBasicConfigOptions(configOptions = {}, mockLanguage = null) { + const language = mockLanguage || createMockLanguage(); + + return { + language: "test/lang", + plugins: { + test: { + languages: { + lang: language, + }, + }, + }, + ...configOptions, + }; +} + +/** + * Creates a mock rule object with default schema + * @param {Object} overrides Properties to override + * @returns {Object} Mock rule object + */ +function createMockRule(overrides = {}) { + return { + meta: { + schema: [ + { + type: "object", + properties: { + option1: { type: "boolean" }, + }, + }, + ], + ...overrides.meta, + }, + ...overrides, + }; +} + +//----------------------------------------------------------------------------- +// Tests +//----------------------------------------------------------------------------- + +describe("Config", () => { + describe("static getRuleOptionsSchema", () => { + const noOptionsSchema = { + type: "array", + minItems: 0, + maxItems: 0, + }; + + it("should return schema that doesn't accept options if rule doesn't have `meta`", () => { + const rule = {}; + const result = Config.getRuleOptionsSchema(rule); + + assert.deepStrictEqual(result, noOptionsSchema); + }); + + it("should return schema that doesn't accept options if rule doesn't have `meta.schema`", () => { + const rule = { meta: {} }; + const result = Config.getRuleOptionsSchema(rule); + + assert.deepStrictEqual(result, noOptionsSchema); + }); + + it("should return schema that doesn't accept options if `meta.schema` is `undefined`", () => { + const rule = { meta: { schema: void 0 } }; + const result = Config.getRuleOptionsSchema(rule); + + assert.deepStrictEqual(result, noOptionsSchema); + }); + + it("should return schema that doesn't accept options if `meta.schema` is `[]`", () => { + const rule = { meta: { schema: [] } }; + const result = Config.getRuleOptionsSchema(rule); + + assert.deepStrictEqual(result, noOptionsSchema); + }); + + it("should return JSON Schema definition object if `meta.schema` is in the array form", () => { + const firstOption = { enum: ["always", "never"] }; + const rule = { meta: { schema: [firstOption] } }; + const result = Config.getRuleOptionsSchema(rule); + + assert.deepStrictEqual(result, { + type: "array", + items: [firstOption], + minItems: 0, + maxItems: 1, + }); + }); + + it("should return `meta.schema` as is if `meta.schema` is an object", () => { + const schema = { + type: "array", + items: [ + { + enum: ["always", "never"], + }, + ], + }; + const rule = { meta: { schema } }; + const result = Config.getRuleOptionsSchema(rule); + + assert.deepStrictEqual(result, schema); + }); + + it("should return `null` if `meta.schema` is `false`", () => { + const rule = { meta: { schema: false } }; + const result = Config.getRuleOptionsSchema(rule); + + assert.strictEqual(result, null); + }); + + [null, true, 0, 1, "", "always", () => {}].forEach(schema => { + it(`should throw an error if \`meta.schema\` is ${typeof schema} ${schema}`, () => { + const rule = { meta: { schema } }; + + assert.throws(() => { + Config.getRuleOptionsSchema(rule); + }, "Rule's `meta.schema` must be an array or object"); + }); + }); + + it("should ignore top-level `schema` property", () => { + const rule = { schema: { enum: ["always", "never"] } }; + const result = Config.getRuleOptionsSchema(rule); + + assert.deepStrictEqual(result, noOptionsSchema); + }); + }); + + describe("static getRuleNumericSeverity", () => { + it("should return 0 for 'off'", () => { + const result = Config.getRuleNumericSeverity("off"); + assert.strictEqual(result, 0); + }); + + it("should return 1 for 'warn'", () => { + const result = Config.getRuleNumericSeverity("warn"); + assert.strictEqual(result, 1); + }); + + it("should return 2 for 'error'", () => { + const result = Config.getRuleNumericSeverity("error"); + assert.strictEqual(result, 2); + }); + + it("should return 0 for 0", () => { + const result = Config.getRuleNumericSeverity(0); + assert.strictEqual(result, 0); + }); + + it("should return 1 for 1", () => { + const result = Config.getRuleNumericSeverity(1); + assert.strictEqual(result, 1); + }); + + it("should return 2 for 2", () => { + const result = Config.getRuleNumericSeverity(2); + assert.strictEqual(result, 2); + }); + + it("should handle rule config arrays", () => { + const result = Config.getRuleNumericSeverity([ + "error", + { option: true }, + ]); + assert.strictEqual(result, 2); + }); + + it("should be case-insensitive for string values", () => { + const result = Config.getRuleNumericSeverity("ERROR"); + assert.strictEqual(result, 2); + }); + + it("should return 0 for invalid severity strings", () => { + const result = Config.getRuleNumericSeverity("invalid"); + assert.strictEqual(result, 0); + }); + + it("should return 0 for non-severity values", () => { + const result = Config.getRuleNumericSeverity(null); + assert.strictEqual(result, 0); + }); + }); + + describe("constructor", () => { + let mockLanguage; + + beforeEach(() => { + mockLanguage = createMockLanguage({ + validateLanguageOptions: sinon.stub(), + normalizeLanguageOptions: sinon.spy(options => options), + }); + }); + + it("should throw error when language is not provided", () => { + assert.throws(() => { + new Config({}); + }, "Key 'language' is required."); + }); + + it("should throw error when language is not found in plugins", () => { + assert.throws(() => { + new Config({ + language: "test/lang", + plugins: { + test: { + // No languages + }, + }, + }); + }, /Could not find "lang" in plugin "test"/u); + }); + + it("should correctly set up language from plugins", () => { + const config = new Config( + createBasicConfigOptions({}, mockLanguage), + ); + + assert.strictEqual(config.language, mockLanguage); + assert.isTrue(mockLanguage.validateLanguageOptions.called); + }); + + it("should correctly merge language options with default language options", () => { + const languageWithDefaults = createMockLanguage({ + defaultLanguageOptions: { parser: "default" }, + }); + + const config = new Config( + createBasicConfigOptions( + { + languageOptions: { ecmaVersion: 2022 }, + }, + languageWithDefaults, + ), + ); + + assert.deepStrictEqual(config.languageOptions, { + parser: "default", + ecmaVersion: 2022, + }); + }); + + it("should throw error when processor is not found in plugins", () => { + assert.throws(() => { + new Config( + createBasicConfigOptions( + { + processor: "test/proc", + }, + mockLanguage, + ), + ); + }, /Could not find "proc" in plugin "test"/u); + }); + + it("should correctly set up processor from plugins", () => { + const mockProcessor = createMockProcessor(); + const config = new Config( + createBasicConfigOptions({ + plugins: { + test: { + languages: { + lang: mockLanguage, + }, + processors: { + proc: mockProcessor, + }, + }, + }, + processor: "test/proc", + }), + ); + + assert.strictEqual(config.processor, mockProcessor); + }); + + it("should accept processor object directly", () => { + const mockProcessor = createMockProcessor({ + meta: { name: "test-processor" }, + }); + + const config = new Config( + createBasicConfigOptions( + { + processor: mockProcessor, + }, + mockLanguage, + ), + ); + + assert.strictEqual(config.processor, mockProcessor); + }); + + it("should throw error when processor is not string or object", () => { + assert.throws(() => { + new Config( + createBasicConfigOptions( + { + processor: 123, + }, + mockLanguage, + ), + ); + }, "Expected an object or a string"); + }); + + it("should normalize rules configuration", () => { + const mockRule = { meta: {} }; + const config = new Config( + createBasicConfigOptions({ + plugins: { + test: { + languages: { + lang: mockLanguage, + }, + rules: {}, + }, + "@": { + rules: { + "test-rule": mockRule, + }, + }, + }, + rules: { + "test-rule": "error", + }, + }), + ); + + assert.deepStrictEqual(config.rules["test-rule"], [2]); + }); + + it("should normalize rules with options", () => { + const mockRule = createMockRule(); + const config = new Config( + createBasicConfigOptions({ + plugins: { + test: { + languages: { + lang: mockLanguage, + }, + rules: {}, + }, + "@": { + rules: { + "test-rule": mockRule, + }, + }, + }, + rules: { + "test-rule": ["warn", { option1: true }], + }, + }), + ); + + assert.deepStrictEqual(config.rules["test-rule"], [ + 1, + { option1: true }, + ]); + }); + + it("should apply rule's defaultOptions when present", () => { + const mockRule = createMockRule({ + meta: { + schema: [ + { + type: "object", + properties: { + option1: { type: "boolean" }, + defaultOption: { type: "boolean" }, + }, + }, + ], + defaultOptions: [{ defaultOption: true }], + }, + }); + + const config = new Config( + createBasicConfigOptions({ + plugins: { + test: { + languages: { + lang: mockLanguage, + }, + rules: {}, + }, + "@": { + rules: { + "test-rule": mockRule, + }, + }, + }, + rules: { + "test-rule": ["error", { option1: true }], + }, + }), + ); + + assert.deepStrictEqual(config.rules["test-rule"], [ + 2, + { defaultOption: true, option1: true }, + ]); + }); + }); + + describe("getRuleDefinition", () => { + it("should retrieve rule definition from plugins", () => { + const mockRule = { meta: {} }; + const config = new Config( + createBasicConfigOptions({ + plugins: { + test: { + languages: { + lang: createMockLanguage(), + }, + rules: { + "test-rule": mockRule, + }, + }, + }, + }), + ); + + const rule = config.getRuleDefinition("test/test-rule"); + assert.strictEqual(rule, mockRule); + }); + + it("should retrieve core rule definition", () => { + const mockRule = { meta: {} }; + const config = new Config( + createBasicConfigOptions({ + plugins: { + test: { + languages: { + lang: createMockLanguage(), + }, + }, + "@": { + rules: { + "core-rule": mockRule, + }, + }, + }, + }), + ); + + const rule = config.getRuleDefinition("core-rule"); + assert.strictEqual(rule, mockRule); + }); + }); + + describe("toJSON", () => { + it("should convert config to JSON representation", () => { + const mockLanguage = createMockLanguage(); + const mockProcessor = createMockProcessor(); + const mockPlugin = createMockPlugin(); + + const config = new Config({ + language: "test/lang", + plugins: { + test: { + ...mockPlugin, + languages: { + lang: mockLanguage, + }, + }, + }, + processor: mockProcessor, + languageOptions: { + ecmaVersion: 2022, + sourceType: "module", + parser: { + meta: { + name: "testParser", + }, + parse() {}, + }, + }, + }); + + const json = config.toJSON(); + assert.deepStrictEqual(json, { + plugins: ["test:testPlugin@1.0.0"], + language: "test/lang", + processor: "testProcessor@1.0.0", + languageOptions: { + ecmaVersion: 2022, + sourceType: "module", + parser: "testParser", + }, + }); + }); + + it("should serialize when a language option has a toJSON() method and a function", () => { + const mockLanguage = createMockLanguage({ + normalizeLanguageOptions(options) { + options.syntax.toJSON = () => "syntax"; + return options; + }, + }); + + const mockPlugin = createMockPlugin({ + languages: { + lang: mockLanguage, + }, + }); + + const config = new Config({ + language: "test/lang", + plugins: { + test: mockPlugin, + }, + languageOptions: { + syntax: { + atrule: { + name() {}, + }, + }, + }, + }); + const json = config.toJSON(); + assert.deepStrictEqual(json, { + plugins: ["test:testPlugin@1.0.0"], + processor: void 0, + language: "test/lang", + languageOptions: { + syntax: "syntax", + }, + }); + }); + + it("should pass through the value when toJSON is not a function", () => { + const mockLanguage = createMockLanguage({ + normalizeLanguageOptions(options) { + options.syntax.toJSON = "not a function"; + return options; + }, + }); + + const mockPlugin = createMockPlugin({ + languages: { + lang: mockLanguage, + }, + }); + + const config = new Config({ + language: "test/lang", + plugins: { + test: mockPlugin, + }, + languageOptions: { + syntax: { + toJSON: "not a function", + }, + }, + }); + const json = config.toJSON(); + assert.deepStrictEqual(json, { + plugins: ["test:testPlugin@1.0.0"], + processor: void 0, + language: "test/lang", + languageOptions: { + syntax: { + toJSON: "not a function", + }, + }, + }); + }); + + it("should only call toJSON on a parent and not on a child object", () => { + const mockLanguage = createMockLanguage({ + normalizeLanguageOptions(options) { + options.syntax.block.toJSON = () => "block"; + options.syntax.block.selector.toJSON = () => "selector"; + return options; + }, + }); + + const mockPlugin = createMockPlugin({ + languages: { + lang: mockLanguage, + }, + }); + + const config = new Config({ + language: "test/lang", + plugins: { + test: mockPlugin, + }, + languageOptions: { + syntax: { + block: { + selector: { + value: "test", + }, + }, + }, + }, + }); + const json = config.toJSON(); + assert.deepStrictEqual(json, { + plugins: ["test:testPlugin@1.0.0"], + processor: void 0, + language: "test/lang", + languageOptions: { + syntax: { + block: "block", + }, + }, + }); + }); + + it("should call languageOptions.toJSON() when present instead of serializing the object", () => { + const mockLanguage = createMockLanguage({ + normalizeLanguageOptions(options) { + options.toJSON = () => "languageOptions"; + return options; + }, + }); + + const mockPlugin = createMockPlugin({ + languages: { + lang: mockLanguage, + }, + }); + + const config = new Config({ + language: "test/lang", + plugins: { + test: mockPlugin, + }, + languageOptions: { + syntax: { + someValue: "string", + }, + }, + }); + + const json = config.toJSON(); + assert.deepStrictEqual(json, { + plugins: ["test:testPlugin@1.0.0"], + processor: void 0, + language: "test/lang", + languageOptions: "languageOptions", + }); + }); + + it("should call toJSON() on language option even when object has meta information", () => { + const mockLanguage = createMockLanguage({ + normalizeLanguageOptions(options) { + options.someObject.toJSON = () => "someObject"; + return options; + }, + }); + const mockPlugin = createMockPlugin({ + languages: { + lang: mockLanguage, + }, + }); + const config = new Config({ + language: "test/lang", + plugins: { + test: mockPlugin, + }, + languageOptions: { + someObject: { + meta: { + name: "testMeta", + }, + }, + }, + }); + const json = config.toJSON(); + assert.deepStrictEqual(json, { + plugins: ["test:testPlugin@1.0.0"], + processor: void 0, + language: "test/lang", + languageOptions: { + someObject: "someObject", + }, + }); + }); + + it("should throw an error when toJSON() returns a function", () => { + const mockLanguage = createMockLanguage({ + normalizeLanguageOptions(options) { + options.syntax.toJSON = () => () => "function"; + return options; + }, + }); + + const mockPlugin = createMockPlugin({ + languages: { + lang: mockLanguage, + }, + }); + + const config = new Config({ + language: "test/lang", + plugins: { + test: mockPlugin, + }, + languageOptions: { + syntax: { + someValue: "string", + }, + }, + }); + + assert.throws(() => { + config.toJSON(); + }, 'Cannot serialize key "syntax" in "languageOptions": Function values are not supported.'); + }); + + it("should throw an error when languageOptions.toJSON() returns a function", () => { + const mockLanguage = createMockLanguage({ + normalizeLanguageOptions(options) { + options.toJSON = () => () => "function"; + return options; + }, + }); + const mockPlugin = createMockPlugin({ + languages: { + lang: mockLanguage, + }, + }); + const config = new Config({ + language: "test/lang", + plugins: { + test: mockPlugin, + }, + languageOptions: { + syntax: { + someValue: "string", + }, + }, + }); + assert.throws(() => { + config.toJSON(); + }, 'Cannot serialize key "toJSON" in "languageOptions": Function values are not supported.'); + }); + + it("should throw when processor doesn't have meta information", () => { + const mockLanguage = createMockLanguage({ + meta: { + name: "testLang", + }, + }); + + const mockProcessor = createMockProcessor({ + meta: void 0, // Missing meta property + }); + delete mockProcessor.meta; // Completely remove meta + + const config = new Config({ + language: "test/lang", + plugins: { + test: { + languages: { + lang: mockLanguage, + }, + }, + }, + processor: mockProcessor, + }); + + assert.throws(() => { + config.toJSON(); + }, "Could not serialize processor object (missing 'meta' object)."); + }); + }); + + describe("validateRulesConfig", () => { + let config; + + const mockRule = createMockRule({ + meta: { + schema: { + type: "array", + items: [ + { + type: "object", + properties: { + valid: { type: "boolean" }, + }, + additionalProperties: false, + }, + ], + }, + }, + }); + + beforeEach(() => { + config = new Config( + createBasicConfigOptions({ + plugins: { + test: { + languages: { + lang: createMockLanguage(), + }, + }, + "@": { + rules: { + "error-rule": {}, + "warn-rule": {}, + "off-rule": {}, + "test-rule": mockRule, + "test-broken-rule": { + meta: { schema: 123 }, // Invalid schema + }, + "test-no-schema": { + meta: { schema: false }, // No schema + }, + }, + }, + }, + rules: { + "error-rule": "error", + "warn-rule": "warn", + "off-rule": "off", + }, + }), + ); + }); + + it("should throw when config is not provided", () => { + assert.throws(() => { + config.validateRulesConfig(); + }, "Config is required for validation."); + }); + + it("should not validate disabled rules", () => { + // This should not throw + config.validateRulesConfig({ + "error-rule": ["off"], + }); + }); + + it("should throw when rule is not found", () => { + assert.throws(() => { + config.validateRulesConfig({ + "test/missing-rule": ["error"], + }); + }, /Could not find "missing-rule" in plugin "test"/u); + }); + + it("should throw when rule options don't match schema", () => { + assert.throws(() => { + config.validateRulesConfig({ + "test-rule": ["error", { invalid: true }], + }); + }, /Unexpected property "invalid"/u); + }); + + it("should throw when rule schema is invalid", () => { + assert.throws(() => { + config.validateRulesConfig({ + "test-broken-rule": ["error"], + }); + }, /Rule's `meta.schema` must be an array or object/u); + }); + + it("should validate rule options successfully", () => { + config.validateRulesConfig({ + "test-rule": ["error", { valid: true }], + }); + }); + + it("should skip validation when `meta.schema` is false", () => { + // This should not throw, even with invalid options + config.validateRulesConfig({ + "test-no-schema": [ + "error", + "this", + "would", + "normally", + "fail", + ], + }); + }); + + it("should skip __proto__ in rules", () => { + const rules = { "test-rule": ["error"] }; + + /* eslint-disable-next-line no-proto -- Testing __proto__ behavior */ + rules.__proto__ = ["error"]; + + config.validateRulesConfig(rules); + }); + }); +}); diff --git a/tests/lib/config/flat-config-array.js b/tests/lib/config/flat-config-array.js index 79f80bce4255..e2e01cdd7c62 100644 --- a/tests/lib/config/flat-config-array.js +++ b/tests/lib/config/flat-config-array.js @@ -11,72 +11,150 @@ const { FlatConfigArray } = require("../../../lib/config/flat-config-array"); const assert = require("chai").assert; -const { - all: allConfig, - recommended: recommendedConfig -} = require("@eslint/js").configs; const stringify = require("json-stable-stringify-without-jsonify"); const espree = require("espree"); +const jslang = require("../../../lib/languages/js"); +const { LATEST_ECMA_VERSION } = require("../../../conf/ecma-version"); //----------------------------------------------------------------------------- // Helpers //----------------------------------------------------------------------------- const baseConfig = { - files: ["**/*.js"], - plugins: { - "@": { - rules: { - foo: { - meta: { - schema: { - type: "array", - items: [ - { - enum: ["always", "never"] - } - ], - minItems: 0, - maxItems: 1 - } - } - - }, - bar: { - - }, - baz: { - - }, - - // old-style - boom() {}, - - foo2: { - meta: { - schema: { - type: "array", - items: { - type: "string" - }, - uniqueItems: true, - minItems: 1 - } - } - } - } - }, - test1: { - rules: { - match: {} - } - }, - test2: { - rules: { - nomatch: {} - } - } - } + files: ["**/*.js"], + language: "@/js", + plugins: { + "@": { + languages: { + js: jslang, + }, + rules: { + foo: { + meta: { + schema: { + type: "array", + items: [ + { + enum: ["always", "never"], + }, + ], + minItems: 0, + maxItems: 1, + }, + }, + }, + bar: {}, + baz: {}, + "prefer-const": { + meta: { + schema: [ + { + type: "object", + properties: { + destructuring: { + enum: ["any", "all"], + default: "any", + }, + ignoreReadBeforeAssign: { + type: "boolean", + default: false, + }, + }, + additionalProperties: false, + }, + ], + }, + }, + "prefer-destructuring": { + meta: { + schema: [ + { + oneOf: [ + { + type: "object", + properties: { + VariableDeclarator: { + type: "object", + properties: { + array: { + type: "boolean", + }, + object: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + AssignmentExpression: { + type: "object", + properties: { + array: { + type: "boolean", + }, + object: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + { + type: "object", + properties: { + array: { + type: "boolean", + }, + object: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + }, + { + type: "object", + properties: { + enforceForRenamedProperties: { + type: "boolean", + }, + }, + additionalProperties: false, + }, + ], + }, + }, + + // old-style + boom() {}, + + foo2: { + meta: { + schema: { + type: "array", + items: { + type: "string", + }, + uniqueItems: true, + minItems: 1, + }, + }, + }, + }, + }, + test1: { + rules: { + match: {}, + }, + }, + test2: { + rules: { + nomatch: {}, + }, + }, + }, }; /** @@ -85,9 +163,9 @@ const baseConfig = { * @returns {FlatConfigArray} The config array; */ function createFlatConfigArray(configs) { - return new FlatConfigArray(configs, { - baseConfig: [baseConfig] - }); + return new FlatConfigArray(configs, { + baseConfig: [baseConfig], + }); } /** @@ -100,13 +178,23 @@ function createFlatConfigArray(configs) { * expected result. */ async function assertMergedResult(values, result) { - const configs = createFlatConfigArray(values); + const configs = createFlatConfigArray(values); - await configs.normalize(); + await configs.normalize(); - const config = configs.getConfig("foo.js"); + const config = configs.getConfig("foo.js"); - assert.deepStrictEqual(config, result); + if (!result.language) { + result.language = jslang; + } + + if (!result.languageOptions) { + result.languageOptions = jslang.normalizeLanguageOptions( + jslang.defaultLanguageOptions, + ); + } + + assert.deepStrictEqual(config, result); } /** @@ -118,31 +206,12 @@ async function assertMergedResult(values, result) { * has an unexpected message. */ async function assertInvalidConfig(values, message) { - const configs = createFlatConfigArray(values); - - await configs.normalize(); - - assert.throws(() => { - configs.getConfig("foo.js"); - }, message); -} - -/** - * Normalizes the rule configs to an array with severity to match - * how Flat Config merges rule options. - * @param {Object} rulesConfig The rules config portion of a config. - * @returns {Array} The rules config object. - */ -function normalizeRuleConfig(rulesConfig) { - const rulesConfigCopy = { - ...rulesConfig - }; - - for (const ruleId of Object.keys(rulesConfigCopy)) { - rulesConfigCopy[ruleId] = [2]; - } + const configs = createFlatConfigArray(values); - return rulesConfigCopy; + assert.throws(() => { + configs.normalizeSync(); + configs.getConfig("foo.js"); + }, message); } //----------------------------------------------------------------------------- @@ -150,1908 +219,2915 @@ function normalizeRuleConfig(rulesConfig) { //----------------------------------------------------------------------------- describe("FlatConfigArray", () => { - - it("should allow noniterable baseConfig objects", () => { - const base = { - languageOptions: { - parserOptions: { - foo: true - } - } - }; - - const configs = new FlatConfigArray([], { - baseConfig: base - }); - - // should not throw error - configs.normalizeSync(); - }); - - it("should not reuse languageOptions.parserOptions across configs", () => { - const base = [{ - files: ["**/*.js"], - languageOptions: { - parserOptions: { - foo: true - } - } - }]; - - const configs = new FlatConfigArray([], { - baseConfig: base - }); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.notStrictEqual(base[0].languageOptions, config.languageOptions); - assert.notStrictEqual(base[0].languageOptions.parserOptions, config.languageOptions.parserOptions, "parserOptions should be new object"); - }); - - describe("Serialization of configs", () => { - - it("should convert config into normalized JSON object", () => { - - const configs = new FlatConfigArray([{ - plugins: { - a: {}, - b: {} - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - const expected = { - plugins: ["@", "a", "b"], - languageOptions: { - ecmaVersion: "latest", - sourceType: "module", - parser: `espree@${espree.version}`, - parserOptions: {} - }, - processor: void 0 - }; - const actual = config.toJSON(); - - assert.deepStrictEqual(actual, expected); - - assert.strictEqual(stringify(actual), stringify(expected)); - }); - - it("should convert config with plugin name/version into normalized JSON object", () => { - - const configs = new FlatConfigArray([{ - plugins: { - a: {}, - b: { - name: "b-plugin", - version: "2.3.1" - } - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - const expected = { - plugins: ["@", "a", "b:b-plugin@2.3.1"], - languageOptions: { - ecmaVersion: "latest", - sourceType: "module", - parser: `espree@${espree.version}`, - parserOptions: {} - }, - processor: void 0 - }; - const actual = config.toJSON(); - - assert.deepStrictEqual(actual, expected); - - assert.strictEqual(stringify(actual), stringify(expected)); - }); - - it("should convert config with plugin meta into normalized JSON object", () => { - - const configs = new FlatConfigArray([{ - plugins: { - a: {}, - b: { - meta: { - name: "b-plugin", - version: "2.3.1" - } - } - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - const expected = { - plugins: ["@", "a", "b:b-plugin@2.3.1"], - languageOptions: { - ecmaVersion: "latest", - sourceType: "module", - parser: `espree@${espree.version}`, - parserOptions: {} - }, - processor: void 0 - }; - const actual = config.toJSON(); - - assert.deepStrictEqual(actual, expected); - - assert.strictEqual(stringify(actual), stringify(expected)); - }); - - it("should throw an error when config with unnamed parser object is normalized", () => { - - const configs = new FlatConfigArray([{ - languageOptions: { - parser: { - parse() { /* empty */ } - } - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.throws(() => { - config.toJSON(); - }, /Could not serialize parser/u); - - }); - - it("should throw an error when config with unnamed parser object with empty meta object is normalized", () => { - - const configs = new FlatConfigArray([{ - languageOptions: { - parser: { - meta: {}, - parse() { /* empty */ } - } - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.throws(() => { - config.toJSON(); - }, /Could not serialize parser/u); - - }); - - it("should throw an error when config with unnamed parser object with only meta version is normalized", () => { - - const configs = new FlatConfigArray([{ - languageOptions: { - parser: { - meta: { - version: "0.1.1" - }, - parse() { /* empty */ } - } - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.throws(() => { - config.toJSON(); - }, /Could not serialize parser/u); - - }); - - it("should not throw an error when config with named parser object is normalized", () => { - - const configs = new FlatConfigArray([{ - languageOptions: { - parser: { - meta: { - name: "custom-parser" - }, - parse() { /* empty */ } - } - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.deepStrictEqual(config.toJSON(), { - languageOptions: { - ecmaVersion: "latest", - parser: "custom-parser", - parserOptions: {}, - sourceType: "module" - }, - plugins: ["@"], - processor: void 0 - }); - - }); - - it("should not throw an error when config with named and versioned parser object is normalized", () => { - - const configs = new FlatConfigArray([{ - languageOptions: { - parser: { - meta: { - name: "custom-parser", - version: "0.1.0" - }, - parse() { /* empty */ } - } - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.deepStrictEqual(config.toJSON(), { - languageOptions: { - ecmaVersion: "latest", - parser: "custom-parser@0.1.0", - parserOptions: {}, - sourceType: "module" - }, - plugins: ["@"], - processor: void 0 - }); - - }); - - it("should not throw an error when config with meta-named and versioned parser object is normalized", () => { - - const configs = new FlatConfigArray([{ - languageOptions: { - parser: { - meta: { - name: "custom-parser" - }, - version: "0.1.0", - parse() { /* empty */ } - } - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.deepStrictEqual(config.toJSON(), { - languageOptions: { - ecmaVersion: "latest", - parser: "custom-parser@0.1.0", - parserOptions: {}, - sourceType: "module" - }, - plugins: ["@"], - processor: void 0 - }); - - }); - - it("should not throw an error when config with named and versioned parser object outside of meta object is normalized", () => { - - const configs = new FlatConfigArray([{ - languageOptions: { - parser: { - name: "custom-parser", - version: "0.1.0", - parse() { /* empty */ } - } - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.deepStrictEqual(config.toJSON(), { - languageOptions: { - ecmaVersion: "latest", - parser: "custom-parser@0.1.0", - parserOptions: {}, - sourceType: "module" - }, - plugins: ["@"], - processor: void 0 - }); - - }); - - it("should throw an error when config with unnamed processor object is normalized", () => { - - const configs = new FlatConfigArray([{ - processor: { - preprocess() { /* empty */ }, - postprocess() { /* empty */ } - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.throws(() => { - config.toJSON(); - }, /Could not serialize processor/u); - - }); - - it("should throw an error when config with processor object with empty meta object is normalized", () => { - - const configs = new FlatConfigArray([{ - processor: { - meta: {}, - preprocess() { /* empty */ }, - postprocess() { /* empty */ } - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.throws(() => { - config.toJSON(); - }, /Could not serialize processor/u); - - }); - - - it("should not throw an error when config with named processor object is normalized", () => { - - const configs = new FlatConfigArray([{ - processor: { - meta: { - name: "custom-processor" - }, - preprocess() { /* empty */ }, - postprocess() { /* empty */ } - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.deepStrictEqual(config.toJSON(), { - languageOptions: { - ecmaVersion: "latest", - parser: `espree@${espree.version}`, - parserOptions: {}, - sourceType: "module" - }, - plugins: ["@"], - processor: "custom-processor" - }); - - }); - - it("should not throw an error when config with named processor object without meta is normalized", () => { - - const configs = new FlatConfigArray([{ - processor: { - name: "custom-processor", - preprocess() { /* empty */ }, - postprocess() { /* empty */ } - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.deepStrictEqual(config.toJSON(), { - languageOptions: { - ecmaVersion: "latest", - parser: `espree@${espree.version}`, - parserOptions: {}, - sourceType: "module" - }, - plugins: ["@"], - processor: "custom-processor" - }); - - }); - - it("should not throw an error when config with named and versioned processor object is normalized", () => { - - const configs = new FlatConfigArray([{ - processor: { - meta: { - name: "custom-processor", - version: "1.2.3" - }, - preprocess() { /* empty */ }, - postprocess() { /* empty */ } - } - }]); - - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.deepStrictEqual(config.toJSON(), { - languageOptions: { - ecmaVersion: "latest", - parser: `espree@${espree.version}`, - parserOptions: {}, - sourceType: "module" - }, - plugins: ["@"], - processor: "custom-processor@1.2.3" - }); - - }); - - it("should not throw an error when config with named and versioned processor object without meta is normalized", () => { - - const configs = new FlatConfigArray([{ - processor: { - name: "custom-processor", - version: "1.2.3", - preprocess() { /* empty */ }, - postprocess() { /* empty */ } - } - }]); - - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.deepStrictEqual(config.toJSON(), { - languageOptions: { - ecmaVersion: "latest", - parser: `espree@${espree.version}`, - parserOptions: {}, - sourceType: "module" - }, - plugins: ["@"], - processor: "custom-processor@1.2.3" - }); - - }); - - }); - - describe("Special configs", () => { - it("eslint:recommended is replaced with an actual config", async () => { - const configs = new FlatConfigArray(["eslint:recommended"]); - - await configs.normalize(); - const config = configs.getConfig("foo.js"); - - assert.deepStrictEqual(config.rules, normalizeRuleConfig(recommendedConfig.rules)); - }); - - it("eslint:all is replaced with an actual config", async () => { - const configs = new FlatConfigArray(["eslint:all"]); - - await configs.normalize(); - const config = configs.getConfig("foo.js"); - - assert.deepStrictEqual(config.rules, normalizeRuleConfig(allConfig.rules)); - }); - }); - - describe("Config Properties", () => { - - describe("settings", () => { - - it("should merge two objects", () => assertMergedResult([ - { - settings: { - a: true, - b: false - } - }, - { - settings: { - c: true, - d: false - } - } - ], { - plugins: baseConfig.plugins, - - settings: { - a: true, - b: false, - c: true, - d: false - } - })); - - it("should merge two objects when second object has overrides", () => assertMergedResult([ - { - settings: { - a: true, - b: false, - d: [1, 2], - e: [5, 6] - } - }, - { - settings: { - c: true, - a: false, - d: [3, 4] - } - } - ], { - plugins: baseConfig.plugins, - - settings: { - a: false, - b: false, - c: true, - d: [3, 4], - e: [5, 6] - } - })); - - it("should deeply merge two objects when second object has overrides", () => assertMergedResult([ - { - settings: { - object: { - a: true, - b: false - } - } - }, - { - settings: { - object: { - c: true, - a: false - } - } - } - ], { - plugins: baseConfig.plugins, - - settings: { - object: { - a: false, - b: false, - c: true - } - } - })); - - it("should merge an object and undefined into one object", () => assertMergedResult([ - { - settings: { - a: true, - b: false - } - }, - { - } - ], { - plugins: baseConfig.plugins, - - settings: { - a: true, - b: false - } - })); - - it("should merge undefined and an object into one object", () => assertMergedResult([ - { - }, - { - settings: { - a: true, - b: false - } - } - ], { - plugins: baseConfig.plugins, - - settings: { - a: true, - b: false - } - })); - - }); - - describe("plugins", () => { - - const pluginA = {}; - const pluginB = {}; - const pluginC = {}; - - it("should merge two objects", () => assertMergedResult([ - { - plugins: { - a: pluginA, - b: pluginB - } - }, - { - plugins: { - c: pluginC - } - } - ], { - plugins: { - a: pluginA, - b: pluginB, - c: pluginC, - ...baseConfig.plugins - } - })); - - it("should merge an object and undefined into one object", () => assertMergedResult([ - { - plugins: { - a: pluginA, - b: pluginB - } - }, - { - } - ], { - plugins: { - a: pluginA, - b: pluginB, - ...baseConfig.plugins - } - })); - - it("should error when attempting to redefine a plugin", async () => { - - await assertInvalidConfig([ - { - plugins: { - a: pluginA, - b: pluginB - } - }, - { - plugins: { - a: pluginC - } - } - ], "Cannot redefine plugin \"a\"."); - }); - - it("should error when plugin is not an object", async () => { - - await assertInvalidConfig([ - { - plugins: { - a: true - } - } - ], "Key \"a\": Expected an object."); - }); - - - }); - - describe("processor", () => { - - it("should merge two values when second is a string", () => { - - const stubProcessor = { - preprocess() {}, - postprocess() {} - }; - - return assertMergedResult([ - { - processor: { - preprocess() {}, - postprocess() {} - } - }, - { - plugins: { - markdown: { - processors: { - markdown: stubProcessor - } - } - }, - processor: "markdown/markdown" - } - ], { - plugins: { - markdown: { - processors: { - markdown: stubProcessor - } - }, - ...baseConfig.plugins - }, - processor: stubProcessor - }); - }); - - it("should merge two values when second is an object", () => { - - const processor = { - preprocess() { }, - postprocess() { } - }; - - return assertMergedResult([ - { - processor: "markdown/markdown" - }, - { - processor - } - ], { - plugins: baseConfig.plugins, - - processor - }); - }); - - it("should error when an invalid string is used", async () => { - - await assertInvalidConfig([ - { - processor: "foo" - } - ], "pluginName/objectName"); - }); - - it("should error when an empty string is used", async () => { - - await assertInvalidConfig([ - { - processor: "" - } - ], "pluginName/objectName"); - }); - - it("should error when an invalid processor is used", async () => { - await assertInvalidConfig([ - { - processor: {} - } - ], "Object must have a preprocess() and a postprocess() method."); - - }); - - it("should error when a processor cannot be found in a plugin", async () => { - await assertInvalidConfig([ - { - plugins: { - foo: {} - }, - processor: "foo/bar" - } - ], /Could not find "bar" in plugin "foo"/u); - - }); - - }); - - describe("linterOptions", () => { - - it("should error when an unexpected key is found", async () => { - - await assertInvalidConfig([ - { - linterOptions: { - foo: true - } - } - ], "Unexpected key \"foo\" found."); - - }); - - describe("noInlineConfig", () => { - - it("should error when an unexpected value is found", async () => { - - await assertInvalidConfig([ - { - linterOptions: { - noInlineConfig: "true" - } - } - ], "Expected a Boolean."); - }); - - it("should merge two objects when second object has overrides", () => assertMergedResult([ - { - linterOptions: { - noInlineConfig: true - } - }, - { - linterOptions: { - noInlineConfig: false - } - } - ], { - plugins: baseConfig.plugins, - - linterOptions: { - noInlineConfig: false - } - })); - - it("should merge an object and undefined into one object", () => assertMergedResult([ - { - linterOptions: { - noInlineConfig: false - } - }, - { - } - ], { - plugins: baseConfig.plugins, - - linterOptions: { - noInlineConfig: false - } - })); - - it("should merge undefined and an object into one object", () => assertMergedResult([ - { - }, - { - linterOptions: { - noInlineConfig: false - } - } - ], { - plugins: baseConfig.plugins, - - linterOptions: { - noInlineConfig: false - } - })); - - - }); - describe("reportUnusedDisableDirectives", () => { - - it("should error when an unexpected value is found", async () => { - - await assertInvalidConfig([ - { - linterOptions: { - reportUnusedDisableDirectives: {} - } - } - ], /Expected one of: "error", "warn", "off", 0, 1, 2, or a boolean./u); - }); - - it("should merge two objects when second object has overrides", () => assertMergedResult([ - { - linterOptions: { - reportUnusedDisableDirectives: "off" - } - }, - { - linterOptions: { - reportUnusedDisableDirectives: "warn" - } - } - ], { - plugins: baseConfig.plugins, - - linterOptions: { - reportUnusedDisableDirectives: 1 - } - })); - - it("should merge an object and undefined into one object", () => assertMergedResult([ - {}, - { - linterOptions: { - reportUnusedDisableDirectives: "warn" - } - } - ], { - plugins: baseConfig.plugins, - - linterOptions: { - reportUnusedDisableDirectives: 1 - } - })); - - - }); - - }); - - describe("languageOptions", () => { - - it("should error when an unexpected key is found", async () => { - - await assertInvalidConfig([ - { - languageOptions: { - foo: true - } - } - ], "Unexpected key \"foo\" found."); - - }); - - it("should merge two languageOptions objects with different properties", () => assertMergedResult([ - { - languageOptions: { - ecmaVersion: 2019 - } - }, - { - languageOptions: { - sourceType: "commonjs" - } - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - ecmaVersion: 2019, - sourceType: "commonjs" - } - })); - - describe("ecmaVersion", () => { - - it("should error when an unexpected value is found", async () => { - - await assertInvalidConfig([ - { - languageOptions: { - ecmaVersion: "true" - } - } - ], /Key "languageOptions": Key "ecmaVersion": Expected a number or "latest"\./u); - }); - - it("should merge two objects when second object has overrides", () => assertMergedResult([ - { - languageOptions: { - ecmaVersion: 2019 - } - }, - { - languageOptions: { - ecmaVersion: 2021 - } - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - ecmaVersion: 2021 - } - })); - - it("should merge an object and undefined into one object", () => assertMergedResult([ - { - languageOptions: { - ecmaVersion: 2021 - } - }, - { - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - ecmaVersion: 2021 - } - })); - - - it("should merge undefined and an object into one object", () => assertMergedResult([ - { - }, - { - languageOptions: { - ecmaVersion: 2021 - } - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - ecmaVersion: 2021 - } - })); - - - }); - - describe("sourceType", () => { - - it("should error when an unexpected value is found", async () => { - - await assertInvalidConfig([ - { - languageOptions: { - sourceType: "true" - } - } - ], "Expected \"script\", \"module\", or \"commonjs\"."); - }); - - it("should merge two objects when second object has overrides", () => assertMergedResult([ - { - languageOptions: { - sourceType: "module" - } - }, - { - languageOptions: { - sourceType: "script" - } - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - sourceType: "script" - } - })); - - it("should merge an object and undefined into one object", () => assertMergedResult([ - { - languageOptions: { - sourceType: "script" - } - }, - { - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - sourceType: "script" - } - })); - - - it("should merge undefined and an object into one object", () => assertMergedResult([ - { - }, - { - languageOptions: { - sourceType: "module" - } - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - sourceType: "module" - } - })); - - - }); - - describe("globals", () => { - - it("should error when an unexpected value is found", async () => { - - await assertInvalidConfig([ - { - languageOptions: { - globals: "true" - } - } - ], "Expected an object."); - }); - - it("should error when an unexpected key value is found", async () => { - - await assertInvalidConfig([ - { - languageOptions: { - globals: { - foo: "truex" - } - } - } - ], "Key \"foo\": Expected \"readonly\", \"writable\", or \"off\"."); - }); - - it("should error when a global has leading whitespace", async () => { - - await assertInvalidConfig([ - { - languageOptions: { - globals: { - " foo": "readonly" - } - } - } - ], /Global " foo" has leading or trailing whitespace/u); - }); - - it("should error when a global has trailing whitespace", async () => { - - await assertInvalidConfig([ - { - languageOptions: { - globals: { - "foo ": "readonly" - } - } - } - ], /Global "foo " has leading or trailing whitespace/u); - }); - - it("should merge two objects when second object has different keys", () => assertMergedResult([ - { - languageOptions: { - globals: { - foo: "readonly" - } - } - }, - { - languageOptions: { - globals: { - bar: "writable" - } - } - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - globals: { - foo: "readonly", - bar: "writable" - } - } - })); - - it("should merge two objects when second object has overrides", () => assertMergedResult([ - { - languageOptions: { - globals: { - foo: null - } - } - }, - { - languageOptions: { - globals: { - foo: "writeable" - } - } - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - globals: { - foo: "writeable" - } - } - })); - - it("should merge an object and undefined into one object", () => assertMergedResult([ - { - languageOptions: { - globals: { - foo: "readable" - } - } - }, - { - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - globals: { - foo: "readable" - } - } - })); - - - it("should merge undefined and an object into one object", () => assertMergedResult([ - { - }, - { - languageOptions: { - globals: { - foo: "false" - } - } - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - globals: { - foo: "false" - } - } - })); - - - }); - - describe("parser", () => { - - it("should error when an unexpected value is found", async () => { - - await assertInvalidConfig([ - { - languageOptions: { - parser: true - } - } - ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); - }); - - it("should error when a null is found", async () => { - - await assertInvalidConfig([ - { - languageOptions: { - parser: null - } - } - ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); - }); - - it("should error when a parser is a string", async () => { - - await assertInvalidConfig([ - { - languageOptions: { - parser: "foo/bar" - } - } - ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); - }); - - it("should error when a value doesn't have a parse() method", async () => { - - await assertInvalidConfig([ - { - languageOptions: { - parser: {} - } - } - ], "Key \"languageOptions\": Key \"parser\": Expected object with parse() or parseForESLint() method."); - }); - - it("should merge two objects when second object has overrides", () => { - - const parser = { parse() {} }; - const stubParser = { parse() { } }; - - return assertMergedResult([ - { - languageOptions: { - parser - } - }, - { - languageOptions: { - parser: stubParser - } - } - ], { - plugins: { - ...baseConfig.plugins - }, - languageOptions: { - parser: stubParser - } - }); - }); - - it("should merge an object and undefined into one object", () => { - - const stubParser = { parse() { } }; - - return assertMergedResult([ - { - languageOptions: { - parser: stubParser - } - }, - { - } - ], { - plugins: { - ...baseConfig.plugins - }, - - languageOptions: { - parser: stubParser - } - }); - - }); - - - it("should merge undefined and an object into one object", () => { - - const stubParser = { parse() {} }; - - return assertMergedResult([ - { - }, - { - languageOptions: { - parser: stubParser - } - } - ], { - plugins: { - ...baseConfig.plugins - }, - - languageOptions: { - parser: stubParser - } - }); - - }); - - }); - - - describe("parserOptions", () => { - - it("should error when an unexpected value is found", async () => { - - await assertInvalidConfig([ - { - languageOptions: { - parserOptions: "true" - } - } - ], "Expected an object."); - }); - - it("should merge two objects when second object has different keys", () => assertMergedResult([ - { - languageOptions: { - parserOptions: { - foo: "whatever" - } - } - }, - { - languageOptions: { - parserOptions: { - bar: "baz" - } - } - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - parserOptions: { - foo: "whatever", - bar: "baz" - } - } - })); - - it("should deeply merge two objects when second object has different keys", () => assertMergedResult([ - { - languageOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true - } - } - } - }, - { - languageOptions: { - parserOptions: { - ecmaFeatures: { - globalReturn: true - } - } - } - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true, - globalReturn: true - } - } - } - })); - - it("should deeply merge two objects when second object has missing key", () => assertMergedResult([ - { - languageOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true - } - } - } - }, - { - languageOptions: { - ecmaVersion: 2021 - } - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - ecmaVersion: 2021, - parserOptions: { - ecmaFeatures: { - jsx: true - } - } - } - })); - - it("should merge two objects when second object has overrides", () => assertMergedResult([ - { - languageOptions: { - parserOptions: { - foo: "whatever" - } - } - }, - { - languageOptions: { - parserOptions: { - foo: "bar" - } - } - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - parserOptions: { - foo: "bar" - } - } - })); - - it("should merge an object and undefined into one object", () => assertMergedResult([ - { - languageOptions: { - parserOptions: { - foo: "whatever" - } - } - }, - { - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - parserOptions: { - foo: "whatever" - } - } - })); - - - it("should merge undefined and an object into one object", () => assertMergedResult([ - { - }, - { - languageOptions: { - parserOptions: { - foo: "bar" - } - } - } - ], { - plugins: baseConfig.plugins, - - languageOptions: { - parserOptions: { - foo: "bar" - } - } - })); - - - }); - - - }); - - describe("rules", () => { - - it("should error when an unexpected value is found", async () => { - - await assertInvalidConfig([ - { - rules: true - } - ], "Expected an object."); - }); - - it("should error when an invalid rule severity is set", async () => { - - await assertInvalidConfig([ - { - rules: { - foo: true - } - } - ], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2."); - }); - - it("should error when an invalid rule severity of the right type is set", async () => { - - await assertInvalidConfig([ - { - rules: { - foo: 3 - } - } - ], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2."); - }); - - it("should error when a string rule severity is not in lowercase", async () => { - - await assertInvalidConfig([ - { - rules: { - foo: "Error" - } - } - ], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2."); - }); - - it("should error when an invalid rule severity is set in an array", async () => { - - await assertInvalidConfig([ - { - rules: { - foo: [true] - } - } - ], "Key \"rules\": Key \"foo\": Expected severity of \"off\", 0, \"warn\", 1, \"error\", or 2."); - }); - - it("should error when rule doesn't exist", async () => { - - await assertInvalidConfig([ - { - rules: { - foox: [1, "bar"] - } - } - ], /Key "rules": Key "foox": Could not find "foox" in plugin "@"./u); - }); - - it("should error and suggest alternative when rule doesn't exist", async () => { - - await assertInvalidConfig([ - { - rules: { - "test2/match": "error" - } - } - ], /Key "rules": Key "test2\/match": Could not find "match" in plugin "test2"\. Did you mean "test1\/match"\?/u); - }); - - it("should error when plugin for rule doesn't exist", async () => { - - await assertInvalidConfig([ - { - rules: { - "doesnt-exist/match": "error" - } - } - ], /Key "rules": Key "doesnt-exist\/match": Could not find plugin "doesnt-exist"\./u); - }); - - it("should error when rule options don't match schema", async () => { - - await assertInvalidConfig([ - { - rules: { - foo: [1, "bar"] - } - } - ], /Value "bar" should be equal to one of the allowed values/u); - }); - - it("should error when rule options don't match schema requiring at least one item", async () => { - - await assertInvalidConfig([ - { - rules: { - foo2: 1 - } - } - ], /Value \[\] should NOT have fewer than 1 items/u); - }); - - it("should merge two objects", () => assertMergedResult([ - { - rules: { - foo: 1, - bar: "error" - } - }, - { - rules: { - baz: "warn", - boom: 0 - } - } - ], { - plugins: baseConfig.plugins, - - rules: { - foo: [1], - bar: [2], - baz: [1], - boom: [0] - } - })); - - it("should merge two objects when second object has simple overrides", () => assertMergedResult([ - { - rules: { - foo: [1, "always"], - bar: "error" - } - }, - { - rules: { - foo: "error", - bar: 0 - } - } - ], { - plugins: baseConfig.plugins, - - rules: { - foo: [2, "always"], - bar: [0] - } - })); - - it("should merge two objects when second object has array overrides", () => assertMergedResult([ - { - rules: { - foo: 1, - foo2: "error" - } - }, - { - rules: { - foo: ["error", "never"], - foo2: ["warn", "foo"] - } - } - ], { - plugins: baseConfig.plugins, - rules: { - foo: [2, "never"], - foo2: [1, "foo"] - } - })); - - it("should merge two objects and options when second object overrides without options", () => assertMergedResult([ - { - rules: { - foo: [1, "always"], - bar: "error" - } - }, - { - plugins: { - "@foo/baz/boom": { - rules: { - bang: {} - } - } - }, - rules: { - foo: ["error"], - bar: 0, - "@foo/baz/boom/bang": "error" - } - } - ], { - plugins: { - ...baseConfig.plugins, - "@foo/baz/boom": { - rules: { - bang: {} - } - } - }, - rules: { - foo: [2, "always"], - bar: [0], - "@foo/baz/boom/bang": [2] - } - })); - - it("should merge an object and undefined into one object", () => assertMergedResult([ - { - rules: { - foo: 0, - bar: 1 - } - }, - { - } - ], { - plugins: baseConfig.plugins, - rules: { - foo: [0], - bar: [1] - } - })); - - it("should merge a rule that doesn't exist without error when the rule is off", () => assertMergedResult([ - { - rules: { - foo: 0, - bar: 1 - } - }, - { - rules: { - nonExistentRule: 0, - nonExistentRule2: ["off", "bar"] - } - } - ], { - plugins: baseConfig.plugins, - rules: { - foo: [0], - bar: [1], - nonExistentRule: [0], - nonExistentRule2: [0, "bar"] - } - })); - - }); - - describe("Invalid Keys", () => { - - [ - "env", - "extends", - "globals", - "ignorePatterns", - "noInlineConfig", - "overrides", - "parser", - "parserOptions", - "reportUnusedDisableDirectives", - "root" - ].forEach(key => { - - it(`should error when a ${key} key is found`, async () => { - await assertInvalidConfig([ - { - [key]: "foo" - } - ], `Key "${key}": This appears to be in eslintrc format rather than flat config format.`); - - }); - }); - - it("should error when plugins is an array", async () => { - await assertInvalidConfig([ - { - plugins: ["foo"] - } - ], "Key \"plugins\": This appears to be in eslintrc format (array of strings) rather than flat config format (object)."); - - }); - - - }); - - }); - - // https://github.com/eslint/eslint/issues/12592 - describe("Shared references between rule configs", () => { - - it("shared rule config should not cause a rule validation error", () => { - - const ruleConfig = ["error", {}]; - - const configs = new FlatConfigArray([{ - rules: { - camelcase: ruleConfig, - "default-case": ruleConfig - } - }]); - - configs.normalizeSync(); - - const config = configs.getConfig("foo.js"); - - assert.deepStrictEqual(config.rules, { - camelcase: [2, { - ignoreDestructuring: false, - ignoreGlobals: false, - ignoreImports: false - }], - "default-case": [2, {}] - }); - - }); - - - it("should throw rule validation error for camelcase", async () => { - - const ruleConfig = ["error", {}]; - - const configs = new FlatConfigArray([ - { - rules: { - camelcase: ruleConfig - } - }, - { - rules: { - "default-case": ruleConfig, - - - camelcase: [ - "error", - { - ignoreDestructuring: Date - } - - ] - } - } - ]); - - configs.normalizeSync(); - - // exact error may differ based on structuredClone implementation so just test prefix - assert.throws(() => { - configs.getConfig("foo.js"); - }, /Key "rules": Key "camelcase":/u); - - }); - - }); + it("should allow noniterable baseConfig objects", () => { + const base = { + languageOptions: { + parserOptions: { + foo: true, + }, + }, + }; + + const configs = new FlatConfigArray([], { + baseConfig: base, + }); + + // should not throw error + configs.normalizeSync(); + }); + + it("should not reuse languageOptions.parserOptions across configs", () => { + const base = [ + { + files: ["**/*.js"], + plugins: { + "@": { + languages: { + js: jslang, + }, + }, + }, + language: "@/js", + languageOptions: { + parserOptions: { + foo: true, + }, + }, + }, + ]; + + const configs = new FlatConfigArray([], { + baseConfig: base, + }); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.notStrictEqual(base[0].languageOptions, config.languageOptions); + assert.notStrictEqual( + base[0].languageOptions.parserOptions, + config.languageOptions.parserOptions, + "parserOptions should be new object", + ); + }); + + describe("Serialization of configs", () => { + it("should convert config into normalized JSON object", () => { + const configs = new FlatConfigArray([ + { + plugins: { + a: {}, + b: {}, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + const expected = { + plugins: ["@", "a", "b"], + language: "@/js", + languageOptions: { + ecmaVersion: LATEST_ECMA_VERSION, + sourceType: "module", + parser: `espree@${espree.version}`, + parserOptions: { + sourceType: "module", + }, + }, + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + processor: void 0, + }; + const actual = config.toJSON(); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(stringify(actual), stringify(expected)); + }); + + it("should convert config with plugin name/version into normalized JSON object", () => { + const configs = new FlatConfigArray([ + { + plugins: { + a: {}, + b: { + name: "b-plugin", + version: "2.3.1", + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + const expected = { + plugins: ["@", "a", "b:b-plugin@2.3.1"], + language: "@/js", + languageOptions: { + ecmaVersion: LATEST_ECMA_VERSION, + sourceType: "module", + parser: `espree@${espree.version}`, + parserOptions: { + sourceType: "module", + }, + }, + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + processor: void 0, + }; + const actual = config.toJSON(); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(stringify(actual), stringify(expected)); + }); + + it("should convert config with plugin meta into normalized JSON object", () => { + const configs = new FlatConfigArray([ + { + plugins: { + a: {}, + b: { + meta: { + name: "b-plugin", + version: "2.3.1", + }, + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + const expected = { + plugins: ["@", "a", "b:b-plugin@2.3.1"], + language: "@/js", + languageOptions: { + ecmaVersion: LATEST_ECMA_VERSION, + sourceType: "module", + parser: `espree@${espree.version}`, + parserOptions: { + sourceType: "module", + }, + }, + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + processor: void 0, + }; + const actual = config.toJSON(); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(stringify(actual), stringify(expected)); + }); + + it("should convert config with languageOptions.globals.name into normalized JSON object", () => { + const configs = new FlatConfigArray([ + { + languageOptions: { + globals: { + name: "off", + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + const expected = { + plugins: ["@"], + language: "@/js", + languageOptions: { + ecmaVersion: LATEST_ECMA_VERSION, + sourceType: "module", + parser: `espree@${espree.version}`, + parserOptions: { + sourceType: "module", + }, + globals: { + name: "off", + }, + }, + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + processor: void 0, + }; + const actual = config.toJSON(); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(stringify(actual), stringify(expected)); + }); + + it("should serialize languageOptions as an empty object if neither configured nor default languageOptions are specified", () => { + const configs = new FlatConfigArray([ + { + files: ["**/*.my"], + plugins: { + test: { + languages: { + my: { + validateLanguageOptions() {}, + }, + }, + }, + }, + language: "test/my", + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("file.my"); + + const expected = { + plugins: ["@", "test"], + language: "test/my", + languageOptions: {}, + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + processor: void 0, + }; + const actual = config.toJSON(); + + assert.deepStrictEqual(actual, expected); + + assert.strictEqual(stringify(actual), stringify(expected)); + }); + + it("should throw an error when config with unnamed parser object is normalized", () => { + const configs = new FlatConfigArray([ + { + languageOptions: { + parser: { + parse() { + /* empty */ + }, + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Cannot serialize key "parse"/u); + }); + + it("should throw an error when config with unnamed parser object with empty meta object is normalized", () => { + const configs = new FlatConfigArray([ + { + languageOptions: { + parser: { + meta: {}, + parse() { + /* empty */ + }, + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Cannot serialize key "parse"/u); + }); + + it("should throw an error when config with unnamed parser object with only meta version is normalized", () => { + const configs = new FlatConfigArray([ + { + languageOptions: { + parser: { + meta: { + version: "0.1.1", + }, + parse() { + /* empty */ + }, + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Cannot serialize key "parse"/u); + }); + + it("should not throw an error when config with named parser object is normalized", () => { + const configs = new FlatConfigArray([ + { + languageOptions: { + parser: { + meta: { + name: "custom-parser", + }, + parse() { + /* empty */ + }, + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + language: "@/js", + languageOptions: { + ecmaVersion: LATEST_ECMA_VERSION, + parser: "custom-parser", + parserOptions: {}, + sourceType: "module", + }, + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + plugins: ["@"], + processor: void 0, + }); + }); + + it("should not throw an error when config with named and versioned parser object is normalized", () => { + const configs = new FlatConfigArray([ + { + languageOptions: { + parser: { + meta: { + name: "custom-parser", + version: "0.1.0", + }, + parse() { + /* empty */ + }, + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + language: "@/js", + languageOptions: { + ecmaVersion: LATEST_ECMA_VERSION, + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module", + }, + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + plugins: ["@"], + processor: void 0, + }); + }); + + it("should not throw an error when config with meta-named and versioned parser object is normalized", () => { + const configs = new FlatConfigArray([ + { + languageOptions: { + parser: { + meta: { + name: "custom-parser", + }, + version: "0.1.0", + parse() { + /* empty */ + }, + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + language: "@/js", + languageOptions: { + ecmaVersion: LATEST_ECMA_VERSION, + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module", + }, + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + plugins: ["@"], + processor: void 0, + }); + }); + + it("should not throw an error when config with named and versioned parser object outside of meta object is normalized", () => { + const configs = new FlatConfigArray([ + { + languageOptions: { + parser: { + name: "custom-parser", + version: "0.1.0", + parse() { + /* empty */ + }, + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + language: "@/js", + languageOptions: { + ecmaVersion: LATEST_ECMA_VERSION, + parser: "custom-parser@0.1.0", + parserOptions: {}, + sourceType: "module", + }, + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + plugins: ["@"], + processor: void 0, + }); + }); + + it("should throw an error when config with unnamed processor object is normalized", () => { + const configs = new FlatConfigArray([ + { + processor: { + preprocess() { + /* empty */ + }, + postprocess() { + /* empty */ + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Could not serialize processor/u); + }); + + it("should throw an error when config with processor object with empty meta object is normalized", () => { + const configs = new FlatConfigArray([ + { + processor: { + meta: {}, + preprocess() { + /* empty */ + }, + postprocess() { + /* empty */ + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.throws(() => { + config.toJSON(); + }, /Could not serialize processor/u); + }); + + it("should not throw an error when config with named processor object is normalized", () => { + const configs = new FlatConfigArray([ + { + processor: { + meta: { + name: "custom-processor", + }, + preprocess() { + /* empty */ + }, + postprocess() { + /* empty */ + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + language: "@/js", + languageOptions: { + ecmaVersion: LATEST_ECMA_VERSION, + parser: `espree@${espree.version}`, + parserOptions: { + sourceType: "module", + }, + sourceType: "module", + }, + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + plugins: ["@"], + processor: "custom-processor", + }); + }); + + it("should not throw an error when config with named processor object without meta is normalized", () => { + const configs = new FlatConfigArray([ + { + processor: { + name: "custom-processor", + preprocess() { + /* empty */ + }, + postprocess() { + /* empty */ + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + language: "@/js", + languageOptions: { + ecmaVersion: LATEST_ECMA_VERSION, + parser: `espree@${espree.version}`, + parserOptions: { + sourceType: "module", + }, + sourceType: "module", + }, + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + plugins: ["@"], + processor: "custom-processor", + }); + }); + + it("should not throw an error when config with named and versioned processor object is normalized", () => { + const configs = new FlatConfigArray([ + { + processor: { + meta: { + name: "custom-processor", + version: "1.2.3", + }, + preprocess() { + /* empty */ + }, + postprocess() { + /* empty */ + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + language: "@/js", + languageOptions: { + ecmaVersion: LATEST_ECMA_VERSION, + parser: `espree@${espree.version}`, + parserOptions: { + sourceType: "module", + }, + sourceType: "module", + }, + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + plugins: ["@"], + processor: "custom-processor@1.2.3", + }); + }); + + it("should not throw an error when config with named and versioned processor object without meta is normalized", () => { + const configs = new FlatConfigArray([ + { + processor: { + name: "custom-processor", + version: "1.2.3", + preprocess() { + /* empty */ + }, + postprocess() { + /* empty */ + }, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.toJSON(), { + language: "@/js", + languageOptions: { + ecmaVersion: LATEST_ECMA_VERSION, + parser: `espree@${espree.version}`, + parserOptions: { + sourceType: "module", + }, + sourceType: "module", + }, + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + plugins: ["@"], + processor: "custom-processor@1.2.3", + }); + }); + }); + + describe("Config array elements", () => { + it("should error on 'eslint:recommended' string config", async () => { + await assertInvalidConfig( + ["eslint:recommended"], + "Config (unnamed): Unexpected non-object config at original index 0.", + ); + }); + + it("should error on 'eslint:all' string config", async () => { + await assertInvalidConfig( + ["eslint:all"], + "Config (unnamed): Unexpected non-object config at original index 0.", + ); + }); + + it("should throw an error when undefined original config is normalized", () => { + const configs = new FlatConfigArray([void 0]); + + assert.throws(() => { + configs.normalizeSync(); + }, "Config (unnamed): Unexpected undefined config at original index 0."); + }); + + it("should throw an error when undefined original config is normalized asynchronously", async () => { + const configs = new FlatConfigArray([void 0]); + + try { + await configs.normalize(); + assert.fail("Error not thrown"); + } catch (error) { + assert.strictEqual( + error.message, + "Config (unnamed): Unexpected undefined config at original index 0.", + ); + } + }); + + it("should throw an error when null original config is normalized", () => { + const configs = new FlatConfigArray([null]); + + assert.throws(() => { + configs.normalizeSync(); + }, "Config (unnamed): Unexpected null config at original index 0."); + }); + + it("should throw an error when null original config is normalized asynchronously", async () => { + const configs = new FlatConfigArray([null]); + + try { + await configs.normalize(); + assert.fail("Error not thrown"); + } catch (error) { + assert.strictEqual( + error.message, + "Config (unnamed): Unexpected null config at original index 0.", + ); + } + }); + + it("should throw an error when undefined base config is normalized", () => { + const configs = new FlatConfigArray([], { baseConfig: [void 0] }); + + assert.throws(() => { + configs.normalizeSync(); + }, "Config (unnamed): Unexpected undefined config at base index 0."); + }); + + it("should throw an error when undefined base config is normalized asynchronously", async () => { + const configs = new FlatConfigArray([], { baseConfig: [void 0] }); + + try { + await configs.normalize(); + assert.fail("Error not thrown"); + } catch (error) { + assert.strictEqual( + error.message, + "Config (unnamed): Unexpected undefined config at base index 0.", + ); + } + }); + + it("should throw an error when null base config is normalized", () => { + const configs = new FlatConfigArray([], { baseConfig: [null] }); + + assert.throws(() => { + configs.normalizeSync(); + }, "Config (unnamed): Unexpected null config at base index 0."); + }); + + it("should throw an error when null base config is normalized asynchronously", async () => { + const configs = new FlatConfigArray([], { baseConfig: [null] }); + + try { + await configs.normalize(); + assert.fail("Error not thrown"); + } catch (error) { + assert.strictEqual( + error.message, + "Config (unnamed): Unexpected null config at base index 0.", + ); + } + }); + + it("should throw an error when undefined user-defined config is normalized", () => { + const configs = new FlatConfigArray([]); + + configs.push(void 0); + + assert.throws(() => { + configs.normalizeSync(); + }, "Config (unnamed): Unexpected undefined config at user-defined index 0."); + }); + + it("should throw an error when undefined user-defined config is normalized asynchronously", async () => { + const configs = new FlatConfigArray([]); + + configs.push(void 0); + + try { + await configs.normalize(); + assert.fail("Error not thrown"); + } catch (error) { + assert.strictEqual( + error.message, + "Config (unnamed): Unexpected undefined config at user-defined index 0.", + ); + } + }); + + it("should throw an error when null user-defined config is normalized", () => { + const configs = new FlatConfigArray([]); + + configs.push(null); + + assert.throws(() => { + configs.normalizeSync(); + }, "Config (unnamed): Unexpected null config at user-defined index 0."); + }); + + it("should throw an error when null user-defined config is normalized asynchronously", async () => { + const configs = new FlatConfigArray([]); + + configs.push(null); + + try { + await configs.normalize(); + assert.fail("Error not thrown"); + } catch (error) { + assert.strictEqual( + error.message, + "Config (unnamed): Unexpected null config at user-defined index 0.", + ); + } + }); + }); + + describe("Config Properties", () => { + describe("settings", () => { + it("should merge two objects", () => + assertMergedResult( + [ + { + settings: { + a: true, + b: false, + }, + }, + { + settings: { + c: true, + d: false, + }, + }, + ], + { + plugins: baseConfig.plugins, + + settings: { + a: true, + b: false, + c: true, + d: false, + }, + }, + )); + + it("should merge two objects when second object has overrides", () => + assertMergedResult( + [ + { + settings: { + a: true, + b: false, + d: [1, 2], + e: [5, 6], + }, + }, + { + settings: { + c: true, + a: false, + d: [3, 4], + }, + }, + ], + { + plugins: baseConfig.plugins, + + settings: { + a: false, + b: false, + c: true, + d: [3, 4], + e: [5, 6], + }, + }, + )); + + it("should deeply merge two objects when second object has overrides", () => + assertMergedResult( + [ + { + settings: { + object: { + a: true, + b: false, + }, + }, + }, + { + settings: { + object: { + c: true, + a: false, + }, + }, + }, + ], + { + plugins: baseConfig.plugins, + + settings: { + object: { + a: false, + b: false, + c: true, + }, + }, + }, + )); + + it("should merge an object and undefined into one object", () => + assertMergedResult( + [ + { + settings: { + a: true, + b: false, + }, + }, + {}, + ], + { + plugins: baseConfig.plugins, + + settings: { + a: true, + b: false, + }, + }, + )); + + it("should merge undefined and an object into one object", () => + assertMergedResult( + [ + {}, + { + settings: { + a: true, + b: false, + }, + }, + ], + { + plugins: baseConfig.plugins, + + settings: { + a: true, + b: false, + }, + }, + )); + }); + + describe("plugins", () => { + const pluginA = {}; + const pluginB = {}; + const pluginC = {}; + + it("should merge two objects", () => + assertMergedResult( + [ + { + plugins: { + a: pluginA, + b: pluginB, + }, + }, + { + plugins: { + c: pluginC, + }, + }, + ], + { + plugins: { + a: pluginA, + b: pluginB, + c: pluginC, + ...baseConfig.plugins, + }, + }, + )); + + it("should merge an object and undefined into one object", () => + assertMergedResult( + [ + { + plugins: { + a: pluginA, + b: pluginB, + }, + }, + {}, + ], + { + plugins: { + a: pluginA, + b: pluginB, + ...baseConfig.plugins, + }, + }, + )); + + it("should error when attempting to redefine a plugin", async () => { + await assertInvalidConfig( + [ + { + plugins: { + a: pluginA, + b: pluginB, + }, + }, + { + plugins: { + a: pluginC, + }, + }, + ], + 'Cannot redefine plugin "a".', + ); + }); + + it("should error when plugin is not an object", async () => { + await assertInvalidConfig( + [ + { + plugins: { + a: true, + }, + }, + ], + 'Key "a": Expected an object.', + ); + }); + }); + + describe("processor", () => { + it("should merge two values when second is a string", () => { + const stubProcessor = { + preprocess() {}, + postprocess() {}, + }; + + return assertMergedResult( + [ + { + processor: { + preprocess() {}, + postprocess() {}, + }, + }, + { + plugins: { + markdown: { + processors: { + markdown: stubProcessor, + }, + }, + }, + processor: "markdown/markdown", + }, + ], + { + plugins: { + markdown: { + processors: { + markdown: stubProcessor, + }, + }, + ...baseConfig.plugins, + }, + processor: stubProcessor, + }, + ); + }); + + it("should merge two values when second is an object", () => { + const processor = { + preprocess() {}, + postprocess() {}, + }; + + return assertMergedResult( + [ + { + processor: "markdown/markdown", + }, + { + processor, + }, + ], + { + plugins: baseConfig.plugins, + + processor, + }, + ); + }); + + it("should error when an invalid string is used", async () => { + await assertInvalidConfig( + [ + { + processor: "foo", + }, + ], + "pluginName/objectName", + ); + }); + + it("should error when an empty string is used", async () => { + await assertInvalidConfig( + [ + { + processor: "", + }, + ], + "pluginName/objectName", + ); + }); + + it("should error when an invalid processor is used", async () => { + await assertInvalidConfig( + [ + { + processor: {}, + }, + ], + "Object must have a preprocess() and a postprocess() method.", + ); + }); + + it("should error when a processor cannot be found in a plugin", async () => { + await assertInvalidConfig( + [ + { + plugins: { + foo: {}, + }, + processor: "foo/bar", + }, + ], + /Could not find "bar" in plugin "foo"/u, + ); + }); + }); + + describe("linterOptions", () => { + it("should error when an unexpected key is found", async () => { + await assertInvalidConfig( + [ + { + linterOptions: { + foo: true, + }, + }, + ], + 'Unexpected key "foo" found.', + ); + }); + + describe("noInlineConfig", () => { + it("should error when an unexpected value is found", async () => { + await assertInvalidConfig( + [ + { + linterOptions: { + noInlineConfig: "true", + }, + }, + ], + "Expected a Boolean.", + ); + }); + + it("should merge two objects when second object has overrides", () => + assertMergedResult( + [ + { + linterOptions: { + noInlineConfig: true, + }, + }, + { + linterOptions: { + noInlineConfig: false, + }, + }, + ], + { + plugins: baseConfig.plugins, + + linterOptions: { + noInlineConfig: false, + }, + }, + )); + + it("should merge an object and undefined into one object", () => + assertMergedResult( + [ + { + linterOptions: { + noInlineConfig: false, + }, + }, + {}, + ], + { + plugins: baseConfig.plugins, + + linterOptions: { + noInlineConfig: false, + }, + }, + )); + + it("should merge undefined and an object into one object", () => + assertMergedResult( + [ + {}, + { + linterOptions: { + noInlineConfig: false, + }, + }, + ], + { + plugins: baseConfig.plugins, + + linterOptions: { + noInlineConfig: false, + }, + }, + )); + }); + describe("reportUnusedDisableDirectives", () => { + it("should error when an unexpected value is found", async () => { + await assertInvalidConfig( + [ + { + linterOptions: { + reportUnusedDisableDirectives: {}, + }, + }, + ], + /Key "linterOptions": Key "reportUnusedDisableDirectives": Expected one of: "error", "warn", "off", 0, 1, 2, or a boolean./u, + ); + }); + + it("should merge two objects when second object has overrides", () => + assertMergedResult( + [ + { + linterOptions: { + reportUnusedDisableDirectives: "off", + }, + }, + { + linterOptions: { + reportUnusedDisableDirectives: "warn", + }, + }, + ], + { + plugins: baseConfig.plugins, + + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + }, + )); + + it("should merge an object and undefined into one object", () => + assertMergedResult( + [ + {}, + { + linterOptions: { + reportUnusedDisableDirectives: "warn", + }, + }, + ], + { + plugins: baseConfig.plugins, + + linterOptions: { + reportUnusedDisableDirectives: 1, + }, + }, + )); + }); + + describe("reportUnusedInlineConfigs", () => { + it("should error when an unexpected value is found", async () => { + await assertInvalidConfig( + [ + { + linterOptions: { + reportUnusedInlineConfigs: {}, + }, + }, + ], + /Key "linterOptions": Key "reportUnusedInlineConfigs": Expected one of: "error", "warn", "off", 0, 1, or 2./u, + ); + }); + + it("should merge two objects when second object has overrides", () => + assertMergedResult( + [ + { + linterOptions: { + reportUnusedInlineConfigs: "off", + }, + }, + { + linterOptions: { + reportUnusedInlineConfigs: "warn", + }, + }, + ], + { + plugins: baseConfig.plugins, + + linterOptions: { + reportUnusedInlineConfigs: 1, + }, + }, + )); + + it("should merge an object and undefined into one object", () => + assertMergedResult( + [ + {}, + { + linterOptions: { + reportUnusedInlineConfigs: "warn", + }, + }, + ], + { + plugins: baseConfig.plugins, + + linterOptions: { + reportUnusedInlineConfigs: 1, + }, + }, + )); + }); + }); + + describe("languageOptions", () => { + it("should error when an unexpected key is found", async () => { + await assertInvalidConfig( + [ + { + language: "@/js", + languageOptions: { + foo: true, + }, + }, + ], + 'Unexpected key "foo" found.', + ); + }); + + it("should merge two languageOptions objects with different properties", () => + assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + ecmaVersion: 2019, + }, + }, + { + languageOptions: { + sourceType: "commonjs", + }, + }, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + ecmaVersion: 2019, + sourceType: "commonjs", + parserOptions: { + sourceType: "commonjs", + }, + }, + }, + )); + + it("should get default languageOptions from the language", async () => { + const configs = new FlatConfigArray([ + { + files: ["**/*.my"], + plugins: { + test: { + languages: { + my: { + defaultLanguageOptions: { + foo: 42, + }, + validateLanguageOptions() {}, + }, + }, + }, + }, + language: "test/my", + }, + ]); + + await configs.normalize(); + + const config = configs.getConfig("file.my"); + + assert.deepStrictEqual(config.languageOptions, { foo: 42 }); + }); + + it("should merge configured languageOptions over default languageOptions from the language", async () => { + const configs = new FlatConfigArray([ + { + files: ["**/*.my"], + plugins: { + test: { + languages: { + my: { + defaultLanguageOptions: { + foo: 42, + bar: 42, + }, + validateLanguageOptions() {}, + }, + }, + }, + }, + language: "test/my", + languageOptions: { + bar: 43, + }, + }, + ]); + + await configs.normalize(); + + const config = configs.getConfig("file.my"); + + assert.deepStrictEqual(config.languageOptions, { + foo: 42, + bar: 43, + }); + }); + + it("should use configured languageOptions when default languageOptions are not specified", async () => { + const configs = new FlatConfigArray([ + { + files: ["**/*.my"], + plugins: { + test: { + languages: { + my: { + validateLanguageOptions() {}, + }, + }, + }, + }, + language: "test/my", + languageOptions: { + bar: 43, + }, + }, + ]); + + await configs.normalize(); + + const config = configs.getConfig("file.my"); + + assert.deepStrictEqual(config.languageOptions, { bar: 43 }); + }); + + it("should default to an empty object if neither configured nor default languageOptions are specified", async () => { + const configs = new FlatConfigArray([ + { + files: ["**/*.my"], + plugins: { + test: { + languages: { + my: { + validateLanguageOptions() {}, + }, + }, + }, + }, + language: "test/my", + }, + ]); + + await configs.normalize(); + + const config = configs.getConfig("file.my"); + + assert.isObject(config.languageOptions); + assert.strictEqual( + Object.keys(config.languageOptions).length, + 0, + ); + }); + + describe("ecmaVersion", () => { + it("should error when an unexpected value is found", async () => { + await assertInvalidConfig( + [ + { + language: "@/js", + languageOptions: { + ecmaVersion: "true", + }, + }, + ], + /Key "languageOptions": Key "ecmaVersion": Expected a number or "latest"\./u, + ); + }); + + it("should merge two objects when second object has overrides", () => + assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + ecmaVersion: 2019, + }, + }, + { + languageOptions: { + ecmaVersion: 2021, + }, + }, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + ecmaVersion: 2021, + }, + }, + )); + + it("should merge an object and undefined into one object", () => + assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + ecmaVersion: 2021, + }, + }, + {}, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + ecmaVersion: 2021, + }, + }, + )); + + it("should merge undefined and an object into one object", () => + assertMergedResult( + [ + {}, + { + language: "@/js", + languageOptions: { + ecmaVersion: 2021, + }, + }, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + ecmaVersion: 2021, + }, + }, + )); + }); + + describe("sourceType", () => { + it("should error when an unexpected value is found", async () => { + await assertInvalidConfig( + [ + { + language: "@/js", + languageOptions: { + sourceType: "true", + }, + }, + ], + 'Expected "script", "module", or "commonjs".', + ); + }); + + it("should merge two objects when second object has overrides", () => + assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + sourceType: "module", + }, + }, + { + languageOptions: { + sourceType: "script", + }, + }, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + sourceType: "script", + parserOptions: { + sourceType: "script", + }, + }, + }, + )); + + it("should merge an object and undefined into one object", () => + assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + sourceType: "script", + }, + }, + {}, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + sourceType: "script", + parserOptions: { + sourceType: "script", + }, + }, + }, + )); + + it("should merge undefined and an object into one object", () => + assertMergedResult( + [ + {}, + { + language: "@/js", + languageOptions: { + sourceType: "module", + }, + }, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + sourceType: "module", + }, + }, + )); + }); + + describe("globals", () => { + it("should error when an unexpected value is found", async () => { + await assertInvalidConfig( + [ + { + language: "@/js", + languageOptions: { + globals: "true", + }, + }, + ], + "Expected an object.", + ); + }); + + it("should error when an unexpected key value is found", async () => { + await assertInvalidConfig( + [ + { + language: "@/js", + languageOptions: { + globals: { + foo: "truex", + }, + }, + }, + ], + 'Key "foo": Expected "readonly", "writable", or "off".', + ); + }); + + it("should error when a global has leading whitespace", async () => { + await assertInvalidConfig( + [ + { + language: "@/js", + languageOptions: { + globals: { + " foo": "readonly", + }, + }, + }, + ], + /Global " foo" has leading or trailing whitespace/u, + ); + }); + + it("should error when a global has trailing whitespace", async () => { + await assertInvalidConfig( + [ + { + language: "@/js", + languageOptions: { + globals: { + "foo ": "readonly", + }, + }, + }, + ], + /Global "foo " has leading or trailing whitespace/u, + ); + }); + + it("should merge two objects when second object has different keys", () => + assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + globals: { + foo: "readonly", + }, + }, + }, + { + languageOptions: { + globals: { + bar: "writable", + }, + }, + }, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + globals: { + foo: "readonly", + bar: "writable", + }, + }, + }, + )); + + it("should merge two objects when second object has overrides", () => + assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + globals: { + foo: null, + }, + }, + }, + { + languageOptions: { + globals: { + foo: "writeable", + }, + }, + }, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + globals: { + foo: "writeable", + }, + }, + }, + )); + + it("should merge an object and undefined into one object", () => + assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + globals: { + foo: "readable", + }, + }, + }, + {}, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + globals: { + foo: "readable", + }, + }, + }, + )); + + it("should merge undefined and an object into one object", () => + assertMergedResult( + [ + {}, + { + language: "@/js", + languageOptions: { + globals: { + foo: "false", + }, + }, + }, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + globals: { + foo: "false", + }, + }, + }, + )); + + it("should merge string and an object into one object", () => + assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + globals: "foo", + }, + }, + { + languageOptions: { + globals: { + foo: "false", + }, + }, + }, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + globals: { + foo: "false", + }, + }, + }, + )); + }); + + describe("parser", () => { + it("should error when an unexpected value is found", async () => { + await assertInvalidConfig( + [ + { + language: "@/js", + languageOptions: { + parser: true, + }, + }, + ], + 'Key "languageOptions": Key "parser": Expected object with parse() or parseForESLint() method.', + ); + }); + + it("should error when a null is found", async () => { + await assertInvalidConfig( + [ + { + language: "@/js", + languageOptions: { + parser: null, + }, + }, + ], + 'Key "languageOptions": Key "parser": Expected object with parse() or parseForESLint() method.', + ); + }); + + it("should error when a parser is a string", async () => { + await assertInvalidConfig( + [ + { + language: "@/js", + languageOptions: { + parser: "foo/bar", + }, + }, + ], + 'Key "languageOptions": Key "parser": Expected object with parse() or parseForESLint() method.', + ); + }); + + it("should error when a value doesn't have a parse() method", async () => { + await assertInvalidConfig( + [ + { + language: "@/js", + languageOptions: { + parser: {}, + }, + }, + ], + 'Key "languageOptions": Key "parser": Expected object with parse() or parseForESLint() method.', + ); + }); + + it("should merge two objects when second object has overrides", () => { + const parser = { parse() {} }; + const stubParser = { parse() {} }; + + return assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + parser, + }, + }, + { + languageOptions: { + parser: stubParser, + }, + }, + ], + { + plugins: { + ...baseConfig.plugins, + }, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + parser: stubParser, + }, + }, + ); + }); + + it("should merge an object and undefined into one object", () => { + const stubParser = { parse() {} }; + + return assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + parser: stubParser, + }, + }, + {}, + ], + { + plugins: { + ...baseConfig.plugins, + }, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + parser: stubParser, + }, + }, + ); + }); + + it("should merge undefined and an object into one object", () => { + const stubParser = { parse() {} }; + + return assertMergedResult( + [ + {}, + { + language: "@/js", + languageOptions: { + parser: stubParser, + }, + }, + ], + { + plugins: { + ...baseConfig.plugins, + }, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + parser: stubParser, + }, + }, + ); + }); + }); + + describe("parserOptions", () => { + it("should error when an unexpected value is found", async () => { + await assertInvalidConfig( + [ + { + language: "@/js", + languageOptions: { + parserOptions: "true", + }, + }, + ], + "Expected an object.", + ); + }); + + it("should merge two objects when second object has different keys", () => + assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + parserOptions: { + foo: "whatever", + }, + }, + }, + { + languageOptions: { + parserOptions: { + bar: "baz", + }, + }, + }, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + parserOptions: { + foo: "whatever", + bar: "baz", + sourceType: "module", + }, + }, + }, + )); + + it("should deeply merge two objects when second object has different keys", () => + assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, + { + languageOptions: { + parserOptions: { + ecmaFeatures: { + globalReturn: true, + }, + }, + }, + }, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + parserOptions: { + ecmaFeatures: { + jsx: true, + globalReturn: false, + }, + sourceType: "module", + }, + }, + }, + )); + + it("should deeply merge two objects when second object has missing key", () => + assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, + { + languageOptions: { + ecmaVersion: 2021, + }, + }, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + ecmaVersion: 2021, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + sourceType: "module", + }, + }, + }, + )); + + it("should merge two objects when second object has overrides", () => + assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + parserOptions: { + foo: "whatever", + }, + }, + }, + { + languageOptions: { + parserOptions: { + foo: "bar", + }, + }, + }, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + parserOptions: { + foo: "bar", + sourceType: "module", + }, + }, + }, + )); + + it("should merge an object and undefined into one object", () => + assertMergedResult( + [ + { + language: "@/js", + languageOptions: { + parserOptions: { + foo: "whatever", + }, + }, + }, + {}, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + parserOptions: { + foo: "whatever", + sourceType: "module", + }, + }, + }, + )); + + it("should merge undefined and an object into one object", () => + assertMergedResult( + [ + {}, + { + language: "@/js", + languageOptions: { + parserOptions: { + foo: "bar", + }, + }, + }, + ], + { + plugins: baseConfig.plugins, + language: jslang, + languageOptions: { + ...jslang.defaultLanguageOptions, + parserOptions: { + foo: "bar", + sourceType: "module", + }, + }, + }, + )); + }); + }); + + describe("rules", () => { + it("should error when an unexpected value is found", async () => { + await assertInvalidConfig( + [ + { + rules: true, + }, + ], + "Expected an object.", + ); + }); + + it("should error when an invalid rule severity is set", async () => { + await assertInvalidConfig( + [ + { + rules: { + foo: true, + }, + }, + ], + 'Key "rules": Key "foo": Expected severity of "off", 0, "warn", 1, "error", or 2.', + ); + }); + + it("should error when an invalid rule severity of the right type is set", async () => { + await assertInvalidConfig( + [ + { + rules: { + foo: 3, + }, + }, + ], + 'Key "rules": Key "foo": Expected severity of "off", 0, "warn", 1, "error", or 2.', + ); + }); + + it("should error when a string rule severity is not in lowercase", async () => { + await assertInvalidConfig( + [ + { + rules: { + foo: "Error", + }, + }, + ], + 'Key "rules": Key "foo": Expected severity of "off", 0, "warn", 1, "error", or 2.', + ); + }); + + it("should error when an invalid rule severity is set in an array", async () => { + await assertInvalidConfig( + [ + { + rules: { + foo: [true], + }, + }, + ], + 'Key "rules": Key "foo": Expected severity of "off", 0, "warn", 1, "error", or 2.', + ); + }); + + it("should error when rule doesn't exist", async () => { + await assertInvalidConfig( + [ + { + rules: { + foox: [1, "bar"], + }, + }, + ], + /Key "rules": Key "foox": Could not find "foox" in plugin "@"./u, + ); + }); + + it("should error and suggest alternative when rule doesn't exist", async () => { + await assertInvalidConfig( + [ + { + rules: { + "test2/match": "error", + }, + }, + ], + /Key "rules": Key "test2\/match": Could not find "match" in plugin "test2"\. Did you mean "test1\/match"\?/u, + ); + }); + + it("should error when plugin for rule doesn't exist", async () => { + await assertInvalidConfig( + [ + { + rules: { + "doesnt-exist/match": "error", + }, + }, + ], + /Key "rules": Key "doesnt-exist\/match": Could not find plugin "doesnt-exist" in configuration\./u, + ); + }); + + it("should error when rule options don't match schema", async () => { + await assertInvalidConfig( + [ + { + rules: { + foo: [1, "bar"], + }, + }, + ], + /Value "bar" should be equal to one of the allowed values/u, + ); + }); + + it("should error when rule options don't match schema requiring at least one item", async () => { + await assertInvalidConfig( + [ + { + rules: { + foo2: 1, + }, + }, + ], + /Value \[\] should NOT have fewer than 1 items/u, + ); + }); + + [null, true, 0, 1, "", "always", () => {}].forEach(schema => { + it(`should error with a message that contains the rule name when a configured rule has invalid \`meta.schema\` (${schema})`, async () => { + await assertInvalidConfig( + [ + { + plugins: { + foo: { + rules: { + bar: { + meta: { + schema, + }, + }, + }, + }, + }, + rules: { + "foo/bar": "error", + }, + }, + ], + "Error while processing options validation schema of rule 'foo/bar': Rule's `meta.schema` must be an array or object", + ); + }); + }); + + it("should error with a message that contains the rule name when a configured rule has invalid `meta.schema` (invalid JSON Schema definition)", async () => { + await assertInvalidConfig( + [ + { + plugins: { + foo: { + rules: { + bar: { + meta: { + schema: { minItems: [] }, + }, + }, + }, + }, + }, + rules: { + "foo/bar": "error", + }, + }, + ], + "Error while processing options validation schema of rule 'foo/bar': minItems must be number", + ); + }); + + it("should allow rules with `schema:false` to have any configurations", async () => { + const configs = new FlatConfigArray([ + { + plugins: { + foo: { + rules: { + bar: { + meta: { + schema: false, + }, + create() { + return {}; + }, + }, + baz: { + meta: { + schema: false, + }, + create() { + return {}; + }, + }, + }, + }, + }, + }, + { + rules: { + "foo/bar": "error", + "foo/baz": ["error", "always"], + }, + }, + ]); + + await configs.normalize(); + + // does not throw + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.rules, { + "foo/bar": [2], + "foo/baz": [2, "always"], + }); + }); + + it("should allow rules without `meta` to be configured without options", async () => { + const configs = new FlatConfigArray([ + { + plugins: { + foo: { + rules: { + bar: { + create() { + return {}; + }, + }, + }, + }, + }, + }, + { + rules: { + "foo/bar": "error", + }, + }, + ]); + + await configs.normalize(); + + // does not throw + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.rules, { + "foo/bar": [2], + }); + }); + + it("should allow rules without `meta.schema` to be configured without options", async () => { + const configs = new FlatConfigArray([ + { + plugins: { + foo: { + rules: { + meta: {}, + bar: { + create() { + return {}; + }, + }, + }, + }, + }, + }, + { + rules: { + "foo/bar": "error", + }, + }, + ]); + + await configs.normalize(); + + // does not throw + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.rules, { + "foo/bar": [2], + }); + }); + + it("should throw if a rule without `meta` is configured with an option", async () => { + await assertInvalidConfig( + [ + { + plugins: { + foo: { + rules: { + bar: { + create() { + return {}; + }, + }, + }, + }, + }, + }, + { + rules: { + "foo/bar": ["error", "always"], + }, + }, + ], + /should NOT have more than 0 items/u, + ); + }); + + it("should throw if a rule without `meta.schema` is configured with an option", async () => { + await assertInvalidConfig( + [ + { + plugins: { + foo: { + rules: { + bar: { + meta: {}, + create() { + return {}; + }, + }, + }, + }, + }, + }, + { + rules: { + "foo/bar": ["error", "always"], + }, + }, + ], + /should NOT have more than 0 items/u, + ); + }); + + it("should merge two objects", () => + assertMergedResult( + [ + { + rules: { + foo: 1, + bar: "error", + }, + }, + { + rules: { + baz: "warn", + boom: 0, + }, + }, + ], + { + plugins: baseConfig.plugins, + + rules: { + foo: [1], + bar: [2], + baz: [1], + boom: [0], + }, + }, + )); + + it("should merge two objects when second object has simple overrides", () => + assertMergedResult( + [ + { + rules: { + foo: [1, "always"], + bar: "error", + }, + }, + { + rules: { + foo: "error", + bar: 0, + }, + }, + ], + { + plugins: baseConfig.plugins, + + rules: { + foo: [2, "always"], + bar: [0], + }, + }, + )); + + it("should merge two objects when second object has array overrides", () => + assertMergedResult( + [ + { + rules: { + foo: 1, + foo2: "error", + }, + }, + { + rules: { + foo: ["error", "never"], + foo2: ["warn", "foo"], + }, + }, + ], + { + plugins: baseConfig.plugins, + rules: { + foo: [2, "never"], + foo2: [1, "foo"], + }, + }, + )); + + it("should merge two objects and options when second object overrides without options", () => + assertMergedResult( + [ + { + rules: { + foo: [1, "always"], + bar: "error", + }, + }, + { + plugins: { + "@foo/baz/boom": { + rules: { + bang: {}, + }, + }, + }, + rules: { + foo: ["error"], + bar: 0, + "@foo/baz/boom/bang": "error", + }, + }, + ], + { + plugins: { + ...baseConfig.plugins, + "@foo/baz/boom": { + rules: { + bang: {}, + }, + }, + }, + rules: { + foo: [2, "always"], + bar: [0], + "@foo/baz/boom/bang": [2], + }, + }, + )); + + it("should merge an object and undefined into one object", () => + assertMergedResult( + [ + { + rules: { + foo: 0, + bar: 1, + }, + }, + {}, + ], + { + plugins: baseConfig.plugins, + rules: { + foo: [0], + bar: [1], + }, + }, + )); + + it("should merge a rule that doesn't exist without error when the rule is off", () => + assertMergedResult( + [ + { + rules: { + foo: 0, + bar: 1, + }, + }, + { + rules: { + nonExistentRule: 0, + nonExistentRule2: ["off", "bar"], + }, + }, + ], + { + plugins: baseConfig.plugins, + rules: { + foo: [0], + bar: [1], + nonExistentRule: [0], + nonExistentRule2: [0, "bar"], + }, + }, + )); + + it("should error show expected properties", async () => { + await assertInvalidConfig( + [ + { + rules: { + "prefer-const": ["error", { destruct: true }], + }, + }, + ], + 'Unexpected property "destruct". Expected properties: "destructuring", "ignoreReadBeforeAssign"', + ); + + await assertInvalidConfig( + [ + { + rules: { + "prefer-destructuring": [ + "error", + { obj: true }, + ], + }, + }, + ], + 'Unexpected property "obj". Expected properties: "VariableDeclarator", "AssignmentExpression"', + ); + + await assertInvalidConfig( + [ + { + rules: { + "prefer-destructuring": [ + "error", + { obj: true }, + ], + }, + }, + ], + 'Unexpected property "obj". Expected properties: "array", "object"', + ); + + await assertInvalidConfig( + [ + { + rules: { + "prefer-destructuring": [ + "error", + { object: true }, + { enforceRenamedProperties: true }, + ], + }, + }, + ], + 'Unexpected property "enforceRenamedProperties". Expected properties: "enforceForRenamedProperties"', + ); + }); + }); + + describe("Invalid Keys", () => { + [ + "env", + "extends", + "globals", + "ignorePatterns", + "noInlineConfig", + "overrides", + "parser", + "parserOptions", + "reportUnusedDisableDirectives", + "root", + ].forEach(key => { + it(`should error when a ${key} key is found`, async () => { + await assertInvalidConfig( + [ + { + [key]: "foo", + }, + ], + `Key "${key}": This appears to be in eslintrc format rather than flat config format.`, + ); + }); + }); + + it("should error when plugins is an array", async () => { + await assertInvalidConfig( + [ + { + plugins: ["foo"], + }, + ], + 'Key "plugins": This appears to be in eslintrc format (array of strings) rather than flat config format (object).', + ); + }); + }); + }); + + // https://github.com/eslint/eslint/issues/12592 + describe("Shared references between rule configs", () => { + it("shared rule config should not cause a rule validation error", () => { + const ruleConfig = ["error", {}]; + + const configs = new FlatConfigArray([ + { + rules: { + camelcase: ruleConfig, + "default-case": ruleConfig, + }, + }, + ]); + + configs.normalizeSync(); + + const config = configs.getConfig("foo.js"); + + assert.deepStrictEqual(config.rules, { + camelcase: [ + 2, + { + allow: [], + ignoreDestructuring: false, + ignoreGlobals: false, + ignoreImports: false, + properties: "always", + }, + ], + "default-case": [2, {}], + }); + }); + + it("should throw rule validation error for camelcase", async () => { + const ruleConfig = ["error", {}]; + + const configs = new FlatConfigArray([ + { + rules: { + camelcase: ruleConfig, + }, + }, + { + rules: { + "default-case": ruleConfig, + + camelcase: [ + "error", + { + ignoreDestructuring: Date, + }, + ], + }, + }, + ]); + + configs.normalizeSync(); + + // exact error may differ based on structuredClone implementation so just test prefix + assert.throws(() => { + configs.getConfig("foo.js"); + }, /Key "rules": Key "camelcase":/u); + }); + }); }); diff --git a/tests/lib/config/flat-config-helpers.js b/tests/lib/config/flat-config-helpers.js deleted file mode 100644 index 004fb82b13c3..000000000000 --- a/tests/lib/config/flat-config-helpers.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @fileoverview Tests for FlatConfigArray - * @author Nicholas C. Zakas - */ - -"use strict"; - -//----------------------------------------------------------------------------- -// Requirements -//----------------------------------------------------------------------------- - -const { - parseRuleId, - getRuleFromConfig -} = require("../../../lib/config/flat-config-helpers"); -const assert = require("chai").assert; - -//----------------------------------------------------------------------------- -// Tests -//----------------------------------------------------------------------------- - -describe("Config Helpers", () => { - - - describe("parseRuleId()", () => { - - it("should return plugin name and rule name for core rule", () => { - const result = parseRuleId("foo"); - - assert.deepStrictEqual(result, { - pluginName: "@", - ruleName: "foo" - }); - }); - - it("should return plugin name and rule name with a/b format", () => { - const result = parseRuleId("test/foo"); - - assert.deepStrictEqual(result, { - pluginName: "test", - ruleName: "foo" - }); - }); - - it("should return plugin name and rule name with a/b/c format", () => { - const result = parseRuleId("node/no-unsupported-features/es-builtins"); - - assert.deepStrictEqual(result, { - pluginName: "node", - ruleName: "no-unsupported-features/es-builtins" - }); - }); - - it("should return plugin name and rule name with @a/b/c format", () => { - const result = parseRuleId("@test/foo/bar"); - - assert.deepStrictEqual(result, { - pluginName: "@test/foo", - ruleName: "bar" - }); - }); - }); - - describe("getRuleFromConfig", () => { - it("should retrieve rule from plugin in config", () => { - const rule = {}; - const config = { - plugins: { - test: { - rules: { - one: rule - } - } - } - }; - - const result = getRuleFromConfig("test/one", config); - - assert.strictEqual(result, rule); - - }); - - it("should retrieve rule from core in config", () => { - const rule = {}; - const config = { - plugins: { - "@": { - rules: { - semi: rule - } - } - } - }; - - const result = getRuleFromConfig("semi", config); - - assert.strictEqual(result, rule); - - }); - }); - -}); diff --git a/tests/lib/config/flat-config-schema.js b/tests/lib/config/flat-config-schema.js new file mode 100644 index 000000000000..f351667736b1 --- /dev/null +++ b/tests/lib/config/flat-config-schema.js @@ -0,0 +1,345 @@ +/** + * @fileoverview Tests for flatConfigSchema + * @author Francesco Trotta + */ + +"use strict"; + +const { flatConfigSchema } = require("../../../lib/config/flat-config-schema"); +const { assert } = require("chai"); +const { + Legacy: { ConfigArray }, +} = require("@eslint/eslintrc"); + +/** + * This function checks the result of merging two values in eslintrc config. + * It uses deep strict equality to compare the actual and the expected results. + * This is useful to ensure that the flat config merge logic behaves similarly to the old logic. + * When eslintrc is removed, this function and its invocations can be also removed. + * @param {Object} [first] The base object. + * @param {Object} [second] The overrides object. + * @param {Object} [expectedResult] The expected reults of merging first and second values. + * @returns {void} + */ +function confirmLegacyMergeResult(first, second, expectedResult) { + const configArray = new ConfigArray( + { settings: first }, + { settings: second }, + ); + const config = configArray.extractConfig("/file"); + const actualResult = config.settings; + + assert.deepStrictEqual(actualResult, expectedResult); +} + +describe("merge", () => { + const { merge } = flatConfigSchema.settings; + + it("merges two objects", () => { + const first = { foo: 42 }; + const second = { bar: "baz" }; + const result = merge(first, second); + + assert.deepStrictEqual(result, { ...first, ...second }); + confirmLegacyMergeResult(first, second, result); + }); + + it("returns an emtpy object if both values are undefined", () => { + const result = merge(void 0, void 0); + + assert.deepStrictEqual(result, {}); + confirmLegacyMergeResult(void 0, void 0, result); + }); + + it("returns an object equal to the first one if the second one is undefined", () => { + const first = { foo: 42, bar: "baz" }; + const result = merge(first, void 0); + + assert.deepStrictEqual(result, first); + assert.notStrictEqual(result, first); + confirmLegacyMergeResult(first, void 0, result); + }); + + it("returns an object equal to the second one if the first one is undefined", () => { + const second = { foo: 42, bar: "baz" }; + const result = merge(void 0, second); + + assert.deepStrictEqual(result, second); + assert.notStrictEqual(result, second); + confirmLegacyMergeResult(void 0, second, result); + }); + + it("does not preserve the type of merged objects", () => { + const first = new Set(["foo", "bar"]); + const second = new Set(["baz"]); + const result = merge(first, second); + + assert.deepStrictEqual(result, {}); + confirmLegacyMergeResult(first, second, result); + }); + + it("merges two objects in a property", () => { + const first = { foo: { bar: "baz" } }; + const second = { foo: { qux: 42 } }; + const result = merge(first, second); + + assert.deepStrictEqual(result, { foo: { bar: "baz", qux: 42 } }); + confirmLegacyMergeResult(first, second, result); + }); + + it("overwrites an object in a property with an array", () => { + const first = { someProperty: { 1: "foo", bar: "baz" } }; + const second = { someProperty: ["qux"] }; + const result = merge(first, second); + + assert.deepStrictEqual(result, second); + assert.strictEqual(result.someProperty, second.someProperty); + }); + + it("overwrites an array in a property with another array", () => { + const first = { someProperty: ["foo", "bar", void 0, "baz"] }; + const second = { someProperty: ["qux", void 0, 42] }; + const result = merge(first, second); + + assert.deepStrictEqual(result, second); + assert.strictEqual(result.someProperty, second.someProperty); + }); + + it("overwrites an array in a property with an object", () => { + const first = { foo: ["foobar"] }; + const second = { foo: { 1: "qux", bar: "baz" } }; + const result = merge(first, second); + + assert.deepStrictEqual(result, second); + assert.strictEqual(result.foo, second.foo); + }); + + it("does not override a value in a property with undefined", () => { + const first = { foo: { bar: "baz" } }; + const second = { foo: void 0 }; + const result = merge(first, second); + + assert.deepStrictEqual(result, first); + assert.notStrictEqual(result, first); + confirmLegacyMergeResult(first, second, result); + }); + + it("does not change the prototype of a merged object", () => { + const first = { foo: 42 }; + const second = { bar: "baz", ["__proto__"]: { qux: true } }; + const result = merge(first, second); + + assert.strictEqual(Object.getPrototypeOf(result), Object.prototype); + confirmLegacyMergeResult(first, second, result); + }); + + it("does not merge the '__proto__' property", () => { + const first = { ["__proto__"]: { foo: 42 } }; + const second = { ["__proto__"]: { bar: "baz" } }; + const result = merge(first, second); + + assert.deepStrictEqual(result, {}); + confirmLegacyMergeResult(first, second, result); + }); + + it("overrides a value in a property with null", () => { + const first = { foo: { bar: "baz" } }; + const second = { foo: null }; + const result = merge(first, second); + + assert.deepStrictEqual(result, second); + assert.notStrictEqual(result, second); + confirmLegacyMergeResult(first, second, result); + }); + + it("overrides a value in a property with a non-nullish primitive", () => { + const first = { foo: { bar: "baz" } }; + const second = { foo: 42 }; + const result = merge(first, second); + + assert.deepStrictEqual(result, second); + assert.notStrictEqual(result, second); + confirmLegacyMergeResult(first, second, result); + }); + + it("overrides an object in a property with a string", () => { + const first = { foo: { bar: "baz" } }; + const second = { foo: "qux" }; + const result = merge(first, second); + + assert.deepStrictEqual(result, second); + assert.notStrictEqual(result, first); + confirmLegacyMergeResult(first, second, result); + }); + + it("overrides a value in a property with a function", () => { + const first = { someProperty: { foo: 42 } }; + const second = { someProperty() {} }; + const result = merge(first, second); + + assert.deepStrictEqual(result, second); + assert.notProperty(result.someProperty, "foo"); + confirmLegacyMergeResult(first, second, result); + }); + + it("overrides a function in a property with an object", () => { + const first = { someProperty: Object.assign(() => {}, { foo: "bar" }) }; + const second = { someProperty: { baz: "qux" } }; + const result = merge(first, second); + + assert.deepStrictEqual(result, second); + assert.notProperty(result.someProperty, "foo"); + confirmLegacyMergeResult(first, second, result); + }); + + it("sets properties to undefined", () => { + const first = { foo: void 0, bar: void 0 }; + const second = { foo: void 0, baz: void 0 }; + const result = merge(first, second); + + assert.deepStrictEqual(result, { + foo: void 0, + bar: void 0, + baz: void 0, + }); + }); + + it("considers only own enumerable properties", () => { + const first = { + __proto__: { inherited1: "A" }, // non-own properties are not considered + included1: "B", + notMerged1: { first: true }, + }; + const second = { + __proto__: { inherited2: "C" }, // non-own properties are not considered + included2: "D", + notMerged2: { second: true }, + }; + + // non-enumerable properties are not considered + Object.defineProperty(first, "notMerged2", { + enumerable: false, + value: { first: true }, + }); + Object.defineProperty(second, "notMerged1", { + enumerable: false, + value: { second: true }, + }); + + const result = merge(first, second); + + assert.deepStrictEqual(result, { + included1: "B", + included2: "D", + notMerged1: { first: true }, + notMerged2: { second: true }, + }); + confirmLegacyMergeResult(first, second, result); + }); + + it("merges objects with self-references", () => { + const first = { foo: 42 }; + + first.first = first; + const second = { bar: "baz" }; + + second.second = second; + const result = merge(first, second); + + assert.strictEqual(result.first, first); + assert.deepStrictEqual(result.second, second); + + const expected = { foo: 42, bar: "baz" }; + + expected.first = first; + expected.second = second; + assert.deepStrictEqual(result, expected); + }); + + it("merges objects with overlapping self-references", () => { + const first = { foo: 42 }; + + first.reference = first; + const second = { bar: "baz" }; + + second.reference = second; + + const result = merge(first, second); + + assert.strictEqual(result.reference, result); + + const expected = { foo: 42, bar: "baz" }; + + expected.reference = expected; + assert.deepStrictEqual(result, expected); + }); + + it("merges objects with cross-references", () => { + const first = { foo: 42 }; + const second = { bar: "baz" }; + + first.second = second; + second.first = first; + + const result = merge(first, second); + + assert.deepStrictEqual(result.first, first); + assert.strictEqual(result.second, second); + + const expected = { foo: 42, bar: "baz" }; + + expected.first = first; + expected.second = second; + assert.deepStrictEqual(result, expected); + }); + + it("merges objects with overlapping cross-references", () => { + const first = { foo: 42 }; + const second = { bar: "baz" }; + + first.reference = second; + second.reference = first; + + const result = merge(first, second); + + assert.strictEqual(result, result.reference.reference); + + const expected = { + foo: 42, + bar: "baz", + reference: { foo: 42, bar: "baz" }, + }; + + expected.reference.reference = expected; + assert.deepStrictEqual(result, expected); + }); + + it("produces the same results for the same combinations of property values", () => { + const firstCommon = { foo: 42 }; + const secondCommon = { bar: "baz" }; + const first = { + a: firstCommon, + b: firstCommon, + c: { foo: "different" }, + d: firstCommon, + }; + const second = { + a: secondCommon, + b: { bar: "something else" }, + c: secondCommon, + d: secondCommon, + }; + const result = merge(first, second); + + assert.deepStrictEqual(result.a, result.d); + + const expected = { + a: { foo: 42, bar: "baz" }, + b: { foo: 42, bar: "something else" }, + c: { foo: "different", bar: "baz" }, + d: { foo: 42, bar: "baz" }, + }; + + assert.deepStrictEqual(result, expected); + }); +}); diff --git a/tests/lib/eslint/eslint.config.js b/tests/lib/eslint/eslint.config.js index 6b389dc670ec..f8bdd00b5f8e 100644 --- a/tests/lib/eslint/eslint.config.js +++ b/tests/lib/eslint/eslint.config.js @@ -1,11 +1,11 @@ -"use strict"; +/* eslint strict: off -- config used for testing only */ module.exports = { - rules: { - quotes: 2, - "no-var": 2, - "eol-last": 2, - strict: 2, - "no-unused-vars": 2 - } + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: 2, + "no-unused-vars": 2, + }, }; diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js index 040722fcf64e..6598d2199c64 100644 --- a/tests/lib/eslint/eslint.js +++ b/tests/lib/eslint/eslint.js @@ -6,7289 +6,15003 @@ "use strict"; +/** + * @import { ESLintOptions } from '../../../lib/eslint/eslint.js'; + */ + //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"); -const fs = require("fs"); -const os = require("os"); -const path = require("path"); +const assert = require("node:assert"); +const util = require("node:util"); +const fs = require("node:fs"); +const fsp = require("node:fs/promises"); +const os = require("node:os"); +const path = require("node:path"); +const timers = require("node:timers/promises"); const escapeStringRegExp = require("escape-string-regexp"); const fCache = require("file-entry-cache"); const sinon = require("sinon"); const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); const shell = require("shelljs"); -const { - Legacy: { - CascadingConfigArrayFactory - } -} = require("@eslint/eslintrc"); const hash = require("../../../lib/cli-engine/hash"); const { unIndent, createCustomTeardown } = require("../../_utils"); +const { shouldUseFlatConfig } = require("../../../lib/eslint/eslint"); +const { defaultConfig } = require("../../../lib/config/default-config"); const coreRules = require("../../../lib/rules"); -const childProcess = require("child_process"); +const espree = require("espree"); +const { WarningService } = require("../../../lib/services/warning-service"); //------------------------------------------------------------------------------ -// Tests +// Constants //------------------------------------------------------------------------------ -describe("ESLint", () => { - const examplePluginName = "eslint-plugin-example"; - const examplePluginNameWithNamespace = "@eslint/eslint-plugin-example"; - const examplePlugin = { - rules: { - "example-rule": require("../../fixtures/rules/custom-rule"), - "make-syntax-error": require("../../fixtures/rules/make-syntax-error-rule") - } - }; - const examplePreprocessorName = "eslint-plugin-processor"; - const originalDir = process.cwd(); - const fixtureDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint/fixtures"); - - /** @type {import("../../../lib/eslint/eslint").ESLint} */ - let ESLint; - - /** - * Returns the path inside of the fixture directory. - * @param {...string} args file path segments. - * @returns {string} The path inside the fixture directory. - * @private - */ - function getFixturePath(...args) { - const filepath = path.join(fixtureDir, ...args); - - try { - return fs.realpathSync(filepath); - } catch { - return filepath; - } - } - - /** - * Create the ESLint object by mocking some of the plugins - * @param {Object} options options for ESLint - * @returns {ESLint} engine object - * @private - */ - function eslintWithPlugins(options) { - return new ESLint({ - ...options, - plugins: { - [examplePluginName]: examplePlugin, - [examplePluginNameWithNamespace]: examplePlugin, - [examplePreprocessorName]: require("../../fixtures/processors/custom-processor") - } - }); - } - - /** - * Call the last argument. - * @param {any[]} args Arguments - * @returns {void} - */ - function callLastArgument(...args) { - process.nextTick(args[args.length - 1], null); - } - - // copy into clean area so as not to get "infected" by this project's .eslintrc files - before(function() { - - /* - * GitHub Actions Windows and macOS runners occasionally exhibit - * extremely slow filesystem operations, during which copying fixtures - * exceeds the default test timeout, so raise it just for this hook. - * Mocha uses `this` to set timeouts on an individual hook level. - */ - this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API - shell.mkdir("-p", fixtureDir); - shell.cp("-r", "./tests/fixtures/.", fixtureDir); - }); - - beforeEach(() => { - ({ ESLint } = require("../../../lib/eslint/eslint")); - }); - - after(() => { - shell.rm("-r", fixtureDir); - }); - - describe("ESLint constructor function", () => { - it("the default value of 'options.cwd' should be the current working directory.", async () => { - process.chdir(__dirname); - try { - const engine = new ESLint({ useEslintrc: false }); - const results = await engine.lintFiles("eslint.js"); - - assert.strictEqual(path.dirname(results[0].filePath), __dirname); - } finally { - process.chdir(originalDir); - } - }); - - it("should normalize 'options.cwd'.", async () => { - const cwd = getFixturePath("example-app3"); - const engine = new ESLint({ - cwd: `${cwd}${path.sep}foo${path.sep}..`, // `/foo/..` should be normalized to `` - useEslintrc: false, - overrideConfig: { - plugins: ["test"], - rules: { - "test/report-cwd": "error" - } - } - }); - const results = await engine.lintText(""); - - assert.strictEqual(results[0].messages[0].ruleId, "test/report-cwd"); - assert.strictEqual(results[0].messages[0].message, cwd); - - const formatter = await engine.loadFormatter("cwd"); - - assert.strictEqual(formatter.format(results), cwd); - }); - - it("should report one fatal message when given a path by --ignore-path that is not a file when ignore is true.", () => { - assert.throws(() => { - // eslint-disable-next-line no-new -- Check for throwing - new ESLint({ ignorePath: fixtureDir }); - }, new RegExp(escapeStringRegExp(`Cannot read .eslintignore file: ${fixtureDir}\nError: EISDIR: illegal operation on a directory, read`), "u")); - }); - - // https://github.com/eslint/eslint/issues/2380 - it("should not modify baseConfig when format is specified", () => { - const customBaseConfig = { root: true }; - - new ESLint({ baseConfig: customBaseConfig }); // eslint-disable-line no-new -- Check for argument side effects - - assert.deepStrictEqual(customBaseConfig, { root: true }); - }); - - it("should throw readable messages if removed options are present", () => { - assert.throws( - () => new ESLint({ - cacheFile: "", - configFile: "", - envs: [], - globals: [], - ignorePattern: [], - parser: "", - parserOptions: {}, - rules: {}, - plugins: [] - }), - new RegExp(escapeStringRegExp([ - "Invalid Options:", - "- Unknown options: cacheFile, configFile, envs, globals, ignorePattern, parser, parserOptions, rules", - "- 'cacheFile' has been removed. Please use the 'cacheLocation' option instead.", - "- 'configFile' has been removed. Please use the 'overrideConfigFile' option instead.", - "- 'envs' has been removed. Please use the 'overrideConfig.env' option instead.", - "- 'globals' has been removed. Please use the 'overrideConfig.globals' option instead.", - "- 'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.", - "- 'parser' has been removed. Please use the 'overrideConfig.parser' option instead.", - "- 'parserOptions' has been removed. Please use the 'overrideConfig.parserOptions' option instead.", - "- 'rules' has been removed. Please use the 'overrideConfig.rules' option instead.", - "- 'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead." - ].join("\n")), "u") - ); - }); - - it("should throw readable messages if wrong type values are given to options", () => { - assert.throws( - () => new ESLint({ - allowInlineConfig: "", - baseConfig: "", - cache: "", - cacheLocation: "", - cwd: "foo", - errorOnUnmatchedPattern: "", - extensions: "", - fix: "", - fixTypes: ["xyz"], - globInputPaths: "", - ignore: "", - ignorePath: "", - overrideConfig: "", - overrideConfigFile: "", - plugins: "", - reportUnusedDisableDirectives: "", - resolvePluginsRelativeTo: "", - rulePaths: "", - useEslintrc: "" - }), - new RegExp(escapeStringRegExp([ - "Invalid Options:", - "- 'allowInlineConfig' must be a boolean.", - "- 'baseConfig' must be an object or null.", - "- 'cache' must be a boolean.", - "- 'cacheLocation' must be a non-empty string.", - "- 'cwd' must be an absolute path.", - "- 'errorOnUnmatchedPattern' must be a boolean.", - "- 'extensions' must be an array of non-empty strings or null.", - "- 'fix' must be a boolean or a function.", - "- 'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".", - "- 'globInputPaths' must be a boolean.", - "- 'ignore' must be a boolean.", - "- 'ignorePath' must be a non-empty string or null.", - "- 'overrideConfig' must be an object or null.", - "- 'overrideConfigFile' must be a non-empty string or null.", - "- 'plugins' must be an object or null.", - "- 'reportUnusedDisableDirectives' must be any of \"error\", \"warn\", \"off\", and null.", - "- 'resolvePluginsRelativeTo' must be a non-empty string or null.", - "- 'rulePaths' must be an array of non-empty strings.", - "- 'useEslintrc' must be a boolean." - ].join("\n")), "u") - ); - }); - - it("should throw readable messages if 'plugins' option contains empty key", () => { - assert.throws( - () => new ESLint({ - plugins: { - "eslint-plugin-foo": {}, - "eslint-plugin-bar": {}, - "": {} - } - }), - new RegExp(escapeStringRegExp([ - "Invalid Options:", - "- 'plugins' must not include an empty string." - ].join("\n")), "u") - ); - }); - }); - - describe("lintText()", () => { - let eslint; - - describe("when using local cwd .eslintrc", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: path.join(os.tmpdir(), "eslint/multiple-rules-config"), - files: { - ".eslintrc.json": { - root: true, - rules: { - quotes: 2, - "no-var": 2, - "eol-last": 2, - strict: [2, "global"], - "no-unused-vars": 2 - }, - env: { - node: true - } - } - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("should report the total and per file errors", async () => { - eslint = new ESLint({ cwd: getPath() }); - const results = await eslint.lintText("var foo = 'bar';"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 5); - assert.strictEqual(results[0].messages[0].ruleId, "strict"); - assert.strictEqual(results[0].messages[1].ruleId, "no-var"); - assert.strictEqual(results[0].messages[2].ruleId, "no-unused-vars"); - assert.strictEqual(results[0].messages[3].ruleId, "quotes"); - assert.strictEqual(results[0].messages[4].ruleId, "eol-last"); - assert.strictEqual(results[0].fixableErrorCount, 3); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 2); - assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); - assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); - }); - - it("should report the total and per file warnings", async () => { - eslint = new ESLint({ - cwd: getPath(), - overrideConfig: { - rules: { - quotes: 1, - "no-var": 1, - "eol-last": 1, - strict: 1, - "no-unused-vars": 1 - } - } - }); - const results = await eslint.lintText("var foo = 'bar';"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 5); - assert.strictEqual(results[0].messages[0].ruleId, "strict"); - assert.strictEqual(results[0].messages[1].ruleId, "no-var"); - assert.strictEqual(results[0].messages[2].ruleId, "no-unused-vars"); - assert.strictEqual(results[0].messages[3].ruleId, "quotes"); - assert.strictEqual(results[0].messages[4].ruleId, "eol-last"); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 3); - assert.strictEqual(results[0].usedDeprecatedRules.length, 2); - assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); - assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); - }); - }); - - it("should report one message when using specific config file", async () => { - eslint = new ESLint({ - overrideConfigFile: "fixtures/configurations/quotes-error.json", - useEslintrc: false, - cwd: getFixturePath("..") - }); - const results = await eslint.lintText("var foo = 'bar';"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].output, void 0); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 1); - assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); - }); - - it("should report the filename when passed in", async () => { - eslint = new ESLint({ - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "test.js" }; - const results = await eslint.lintText("var foo = 'bar';", options); - - assert.strictEqual(results[0].filePath, getFixturePath("test.js")); - }); - - it("should return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is true", async () => { - eslint = new ESLint({ - ignorePath: getFixturePath(".eslintignore"), - cwd: getFixturePath("..") - }); - const options = { filePath: "fixtures/passing.js", warnIgnored: true }; - const results = await eslint.lintText("var bar = foo;", options); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("passing.js")); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."); - assert.strictEqual(results[0].messages[0].output, void 0); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 0); - }); - - it("should not return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is false", async () => { - eslint = new ESLint({ - ignorePath: getFixturePath(".eslintignore"), - cwd: getFixturePath("..") - }); - const options = { - filePath: "fixtures/passing.js", - warnIgnored: false - }; - - // intentional parsing error - const results = await eslint.lintText("va r bar = foo;", options); - - // should not report anything because the file is ignored - assert.strictEqual(results.length, 0); - }); - - it("should suppress excluded file warnings by default", async () => { - eslint = new ESLint({ - ignorePath: getFixturePath(".eslintignore"), - cwd: getFixturePath("..") - }); - const options = { filePath: "fixtures/passing.js" }; - const results = await eslint.lintText("var bar = foo;", options); - - // should not report anything because there are no errors - assert.strictEqual(results.length, 0); - }); - - it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", async () => { - eslint = new ESLint({ - ignorePath: "fixtures/.eslintignore", - cwd: getFixturePath(".."), - ignore: false, - useEslintrc: false, - overrideConfig: { - rules: { - "no-undef": 2 - } - } - }); - const options = { filePath: "fixtures/passing.js" }; - const results = await eslint.lintText("var bar = foo;", options); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("passing.js")); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[0].output, void 0); - }); - - it("should return a message and fixed text when in fix mode", async () => { - eslint = new ESLint({ - useEslintrc: false, - fix: true, - overrideConfig: { - rules: { - semi: 2 - } - }, - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "passing.js" }; - const results = await eslint.lintText("var bar = foo", options); - - assert.deepStrictEqual(results, [ - { - filePath: getFixturePath("passing.js"), - messages: [], - suppressedMessages: [], - errorCount: 0, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var bar = foo;", - usedDeprecatedRules: [{ - ruleId: "semi", - replacedBy: [] - }] - } - ]); - }); - - it("should use eslint:recommended rules when eslint:recommended configuration is specified", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - extends: ["eslint:recommended"] - }, - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "file.js" }; - const results = await eslint.lintText("foo ()", options); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - assert.strictEqual(results[0].messages[0].severity, 2); - }); - - it("should use eslint:all rules when eslint:all configuration is specified", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - extends: ["eslint:all"] - }, - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "file.js" }; - const results = await eslint.lintText("if (true) { foo() }", options); - - assert.strictEqual(results.length, 1); - - const { messages } = results[0]; - - // Some rules that should report errors in the given code. Not all, as we don't want to update this test when we add new rules. - const expectedRules = ["no-undef", "no-constant-condition"]; - - expectedRules.forEach(ruleId => { - const messageFromRule = messages.find(message => message.ruleId === ruleId); - - assert.ok( - typeof messageFromRule === "object" && messageFromRule !== null, // LintMessage object - `Expected a message from rule '${ruleId}'` - ); - assert.strictEqual(messageFromRule.severity, 2); - }); - - }); - - it("correctly autofixes semicolon-conflicting-fixes", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true - }); - const inputPath = getFixturePath("autofix/semicolon-conflicting-fixes.js"); - const outputPath = getFixturePath("autofix/semicolon-conflicting-fixes.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("correctly autofixes return-conflicting-fixes", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true - }); - const inputPath = getFixturePath("autofix/return-conflicting-fixes.js"); - const outputPath = getFixturePath("autofix/return-conflicting-fixes.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - describe("Fix Types", () => { - it("should throw an error when an invalid fix type is specified", () => { - assert.throws(() => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - fixTypes: ["layou"] - }); - }, /'fixTypes' must be an array of any of "directive", "problem", "suggestion", and "layout"\./iu); - }); - - it("should not fix any rules when fixTypes is used without fix", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: false, - fixTypes: ["layout"] - }); - const inputPath = getFixturePath("fix-types/fix-only-semi.js"); - const results = await eslint.lintFiles([inputPath]); - - assert.strictEqual(results[0].output, void 0); - }); - - it("should not fix non-style rules when fixTypes has only 'layout'", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - fixTypes: ["layout"] - }); - const inputPath = getFixturePath("fix-types/fix-only-semi.js"); - const outputPath = getFixturePath("fix-types/fix-only-semi.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should not fix style or problem rules when fixTypes has only 'suggestion'", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - fixTypes: ["suggestion"] - }); - const inputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.js"); - const outputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should fix both style and problem rules when fixTypes has 'suggestion' and 'layout'", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - fixTypes: ["suggestion", "layout"] - }); - const inputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.js"); - const outputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should not throw an error when a rule doesn't have a 'meta' property", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - fixTypes: ["layout"], - rulePaths: [getFixturePath("rules", "fix-types-test")] - }); - const inputPath = getFixturePath("fix-types/ignore-missing-meta.js"); - const outputPath = getFixturePath("fix-types/ignore-missing-meta.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should not throw an error when a rule is loaded after initialization with lintFiles()", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - fixTypes: ["layout"], - plugins: { - test: { - rules: { - "no-program": require(getFixturePath("rules", "fix-types-test", "no-program.js")) - } - } - } - }); - const inputPath = getFixturePath("fix-types/ignore-missing-meta.js"); - const outputPath = getFixturePath("fix-types/ignore-missing-meta.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should not throw an error when a rule is loaded after initialization with lintText()", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - fixTypes: ["layout"], - plugins: { - test: { - rules: { - "no-program": require(getFixturePath("rules", "fix-types-test", "no-program.js")) - } - } - } - }); - const inputPath = getFixturePath("fix-types/ignore-missing-meta.js"); - const outputPath = getFixturePath("fix-types/ignore-missing-meta.expected.js"); - const results = await eslint.lintText(fs.readFileSync(inputPath, { encoding: "utf8" }), { filePath: inputPath }); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - }); - - it("should return a message and omit fixed text when in fix mode and fixes aren't done", async () => { - eslint = new ESLint({ - useEslintrc: false, - fix: true, - overrideConfig: { - rules: { - "no-undef": 2 - } - }, - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "passing.js" }; - const results = await eslint.lintText("var bar = foo", options); - - assert.deepStrictEqual(results, [ - { - filePath: getFixturePath("passing.js"), - messages: [ - { - ruleId: "no-undef", - severity: 2, - messageId: "undef", - message: "'foo' is not defined.", - line: 1, - column: 11, - endLine: 1, - endColumn: 14, - nodeType: "Identifier" - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - source: "var bar = foo", - usedDeprecatedRules: [] - } - ]); - }); - - it("should not delete code if there is a syntax error after trying to autofix.", async () => { - eslint = eslintWithPlugins({ - useEslintrc: false, - fix: true, - overrideConfig: { - plugins: ["example"], - rules: { - "example/make-syntax-error": "error" - } - }, - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "test.js" }; - const results = await eslint.lintText("var bar = foo", options); - - assert.deepStrictEqual(results, [ - { - filePath: getFixturePath("test.js"), - messages: [ - { - ruleId: null, - fatal: true, - severity: 2, - message: "Parsing error: Unexpected token is", - line: 1, - column: 19, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var bar = foothis is a syntax error.", - usedDeprecatedRules: [] - } - ]); - }); - - it("should not crash even if there are any syntax error since the first time.", async () => { - eslint = new ESLint({ - useEslintrc: false, - fix: true, - overrideConfig: { - rules: { - "example/make-syntax-error": "error" - } - }, - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "test.js" }; - const results = await eslint.lintText("var bar =", options); - - assert.deepStrictEqual(results, [ - { - filePath: getFixturePath("test.js"), - messages: [ - { - ruleId: null, - fatal: true, - severity: 2, - message: "Parsing error: Unexpected token", - line: 1, - column: 10, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 0, - source: "var bar =", - usedDeprecatedRules: [] - } - ]); - }); - - it("should return source code of file in `source` property when errors are present", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - rules: { semi: 2 } - } - }); - const results = await eslint.lintText("var foo = 'bar'"); - - assert.strictEqual(results[0].source, "var foo = 'bar'"); - }); - - it("should return source code of file in `source` property when warnings are present", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - rules: { semi: 1 } - } - }); - const results = await eslint.lintText("var foo = 'bar'"); - - assert.strictEqual(results[0].source, "var foo = 'bar'"); - }); - - - it("should not return a `source` property when no errors or warnings are present", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - rules: { semi: 2 } - } - }); - const results = await eslint.lintText("var foo = 'bar';"); - - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].source, void 0); - }); - - it("should not return a `source` property when fixes are applied", async () => { - eslint = new ESLint({ - useEslintrc: false, - fix: true, - overrideConfig: { - rules: { - semi: 2, - "no-unused-vars": 2 - } - } - }); - const results = await eslint.lintText("var msg = 'hi' + foo\n"); - - assert.strictEqual(results[0].source, void 0); - assert.strictEqual(results[0].output, "var msg = 'hi' + foo;\n"); - }); - - it("should return a `source` property when a parsing error has occurred", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - rules: { eqeqeq: 2 } - } - }); - const results = await eslint.lintText("var bar = foothis is a syntax error.\n return bar;"); - - assert.deepStrictEqual(results, [ - { - filePath: "", - messages: [ - { - ruleId: null, - fatal: true, - severity: 2, - message: "Parsing error: Unexpected token is", - line: 1, - column: 19, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 0, - source: "var bar = foothis is a syntax error.\n return bar;", - usedDeprecatedRules: [] - } - ]); - }); - - // https://github.com/eslint/eslint/issues/5547 - it("should respect default ignore rules, even with --no-ignore", async () => { - eslint = new ESLint({ - cwd: getFixturePath(), - ignore: false - }); - const results = await eslint.lintText("var bar = foo;", { filePath: "node_modules/passing.js", warnIgnored: true }); - const expectedMsg = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("node_modules/passing.js")); - assert.strictEqual(results[0].messages[0].message, expectedMsg); - }); - - describe('plugin shorthand notation ("@scope" for "@scope/eslint-plugin")', () => { - const Module = require("module"); - let originalFindPath = null; - - /* eslint-disable no-underscore-dangle -- Override Node API */ - before(() => { - originalFindPath = Module._findPath; - Module._findPath = function(id, ...otherArgs) { - if (id === "@scope/eslint-plugin") { - return path.resolve(__dirname, "../../fixtures/plugin-shorthand/basic/node_modules/@scope/eslint-plugin/index.js"); - } - return originalFindPath.call(this, id, ...otherArgs); - }; - }); - after(() => { - Module._findPath = originalFindPath; - }); - /* eslint-enable no-underscore-dangle -- Override Node API */ - - it("should resolve 'plugins:[\"@scope\"]' to 'node_modules/@scope/eslint-plugin'.", async () => { - eslint = new ESLint({ cwd: getFixturePath("plugin-shorthand/basic") }); - const [result] = await eslint.lintText("var x = 0", { filePath: "index.js" }); - - assert.strictEqual(result.filePath, getFixturePath("plugin-shorthand/basic/index.js")); - assert.strictEqual(result.messages[0].ruleId, "@scope/rule"); - assert.strictEqual(result.messages[0].message, "OK"); - }); - - it("should resolve 'extends:[\"plugin:@scope/recommended\"]' to 'node_modules/@scope/eslint-plugin'.", async () => { - eslint = new ESLint({ cwd: getFixturePath("plugin-shorthand/extends") }); - const [result] = await eslint.lintText("var x = 0", { filePath: "index.js" }); - - assert.strictEqual(result.filePath, getFixturePath("plugin-shorthand/extends/index.js")); - assert.strictEqual(result.messages[0].ruleId, "@scope/rule"); - assert.strictEqual(result.messages[0].message, "OK"); - }); - }); - - it("should warn when deprecated rules are found in a config", async () => { - eslint = new ESLint({ - cwd: originalDir, - useEslintrc: false, - overrideConfigFile: "tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml" - }); - const [result] = await eslint.lintText("foo"); - - assert.deepStrictEqual( - result.usedDeprecatedRules, - [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] - ); - }); - - it("should throw if non-string value is given to 'code' parameter", async () => { - eslint = new ESLint(); - await assert.rejects(() => eslint.lintText(100), /'code' must be a string/u); - }); - - it("should throw if non-object value is given to 'options' parameter", async () => { - eslint = new ESLint(); - await assert.rejects(() => eslint.lintText("var a = 0", "foo.js"), /'options' must be an object, null, or undefined/u); - }); - - it("should throw if 'options' argument contains unknown key", async () => { - eslint = new ESLint(); - await assert.rejects(() => eslint.lintText("var a = 0", { filename: "foo.js" }), /'options' must not include the unknown option\(s\): filename/u); - }); - - it("should throw if non-string value is given to 'options.filePath' option", async () => { - eslint = new ESLint(); - await assert.rejects(() => eslint.lintText("var a = 0", { filePath: "" }), /'options.filePath' must be a non-empty string or undefined/u); - }); - - it("should throw if non-boolean value is given to 'options.warnIgnored' option", async () => { - eslint = new ESLint(); - await assert.rejects(() => eslint.lintText("var a = 0", { warnIgnored: "" }), /'options.warnIgnored' must be a boolean or undefined/u); - }); - }); - - describe("lintFiles()", () => { - - /** @type {InstanceType} */ - let eslint; - - it("should use correct parser when custom parser is specified", async () => { - eslint = new ESLint({ - cwd: originalDir, - ignore: false - }); - const filePath = path.resolve(__dirname, "../../fixtures/configurations/parser/custom.js"); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].message, "Parsing error: Boom!"); - }); - - it("should report zero messages when given a config file and a valid file", async () => { - eslint = new ESLint({ - cwd: originalDir, - useEslintrc: false, - ignore: false, - overrideConfigFile: "tests/fixtures/simple-valid-project/.eslintrc.js" - }); - const results = await eslint.lintFiles(["tests/fixtures/simple-valid-project/**/foo*.js"]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - }); - - it("should handle multiple patterns with overlapping files", async () => { - eslint = new ESLint({ - cwd: originalDir, - useEslintrc: false, - ignore: false, - overrideConfigFile: "tests/fixtures/simple-valid-project/.eslintrc.js" - }); - const results = await eslint.lintFiles([ - "tests/fixtures/simple-valid-project/**/foo*.js", - "tests/fixtures/simple-valid-project/foo.?s", - "tests/fixtures/simple-valid-project/{foo,src/foobar}.js" - ]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - }); - - it("should report zero messages when given a config file and a valid file and espree as parser", async () => { - eslint = new ESLint({ - overrideConfig: { - parser: "espree", - parserOptions: { - ecmaVersion: 2021 - } - }, - useEslintrc: false - }); - const results = await eslint.lintFiles(["lib/cli.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should report zero messages when given a config file and a valid file and esprima as parser", async () => { - eslint = new ESLint({ - overrideConfig: { - parser: "esprima" - }, - useEslintrc: false, - ignore: false - }); - const results = await eslint.lintFiles(["tests/fixtures/passing.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should throw an error when given a config file and a valid file and invalid parser", async () => { - eslint = new ESLint({ - overrideConfig: { - parser: "test11" - }, - useEslintrc: false - }); - - await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Cannot find module 'test11'/u); - }); - - it("should report zero messages when given a directory with a .js2 file", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - extensions: [".js2"] - }); - const results = await eslint.lintFiles([getFixturePath("files/foo.js2")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should fall back to defaults when extensions is set to an empty array", async () => { - eslint = new ESLint({ - cwd: getFixturePath("configurations"), - overrideConfigFile: getFixturePath("configurations", "quotes-error.json"), - extensions: [] - }); - const results = await eslint.lintFiles([getFixturePath("single-quoted.js")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); - }); - - it("should report zero messages when given a directory with a .js and a .js2 file", async () => { - eslint = new ESLint({ - extensions: [".js", ".js2"], - ignore: false, - cwd: getFixturePath("..") - }); - const results = await eslint.lintFiles(["fixtures/files/"]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - }); - - it("should report zero messages when given a '**' pattern with a .js and a .js2 file", async () => { - eslint = new ESLint({ - extensions: [".js", ".js2"], - ignore: false, - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles(["fixtures/files/*"]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - }); - - it("should resolve globs when 'globInputPaths' option is true", async () => { - eslint = new ESLint({ - extensions: [".js", ".js2"], - ignore: false, - cwd: getFixturePath("..") - }); - const results = await eslint.lintFiles(["fixtures/files/*"]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - }); - - it("should not resolve globs when 'globInputPaths' option is false", async () => { - eslint = new ESLint({ - extensions: [".js", ".js2"], - ignore: false, - cwd: getFixturePath(".."), - globInputPaths: false - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["fixtures/files/*"]); - }, /No files matching 'fixtures\/files\/\*' were found \(glob was disabled\)\./u); - }); - - it("should report on all files passed explicitly, even if ignored by default", async () => { - eslint = new ESLint({ - cwd: getFixturePath("cli-engine") - }); - const results = await eslint.lintFiles(["node_modules/foo.js"]); - const expectedMsg = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].message, expectedMsg); - }); - - it("should report on globs with explicit inclusion of dotfiles, even though ignored by default", async () => { - eslint = new ESLint({ - cwd: getFixturePath("cli-engine"), - overrideConfig: { - rules: { - quotes: [2, "single"] - } - } - }); - const results = await eslint.lintFiles(["hidden/.hiddenfolder/*.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); - }); - - it("should not check default ignored files without --no-ignore flag", async () => { - eslint = new ESLint({ - cwd: getFixturePath("cli-engine") - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["node_modules"]); - }, /All files matched by 'node_modules' are ignored\./u); - }); - - // https://github.com/eslint/eslint/issues/5547 - it("should not check node_modules files even with --no-ignore flag", async () => { - eslint = new ESLint({ - cwd: getFixturePath("cli-engine"), - ignore: false - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["node_modules"]); - }, /All files matched by 'node_modules' are ignored\./u); - }); - - it("should not check .hidden files if they are passed explicitly without --no-ignore flag", async () => { - eslint = new ESLint({ - cwd: getFixturePath(".."), - useEslintrc: false, - overrideConfig: { - rules: { - quotes: [2, "single"] - } - } - }); - const results = await eslint.lintFiles(["fixtures/files/.bar.js"]); - const expectedMsg = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].message, expectedMsg); - }); - - // https://github.com/eslint/eslint/issues/12873 - it("should not check files within a .hidden folder if they are passed explicitly without the --no-ignore flag", async () => { - eslint = new ESLint({ - cwd: getFixturePath("cli-engine"), - useEslintrc: false, - overrideConfig: { - rules: { - quotes: [2, "single"] - } - } - }); - const results = await eslint.lintFiles(["hidden/.hiddenfolder/double-quotes.js"]); - const expectedMsg = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].message, expectedMsg); - }); - - it("should check .hidden files if they are passed explicitly with --no-ignore flag", async () => { - eslint = new ESLint({ - cwd: getFixturePath(".."), - ignore: false, - useEslintrc: false, - overrideConfig: { - rules: { - quotes: [2, "single"] - } - } - }); - const results = await eslint.lintFiles(["fixtures/files/.bar.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - }); - - it("should check .hidden files if they are unignored with an --ignore-pattern", async () => { - eslint = new ESLint({ - cwd: getFixturePath("cli-engine"), - ignore: true, - useEslintrc: false, - overrideConfig: { - ignorePatterns: "!.hidden*", - rules: { - quotes: [2, "single"] - } - } - }); - const results = await eslint.lintFiles(["hidden/"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - }); - - it("should report zero messages when given a pattern with a .js and a .js2 file", async () => { - eslint = new ESLint({ - extensions: [".js", ".js2"], - ignore: false, - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles(["fixtures/files/*.?s*"]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - }); - - it("should return one error message when given a config with rules with options and severity level set to error", async () => { - eslint = new ESLint({ - cwd: getFixturePath("configurations"), - overrideConfigFile: getFixturePath("configurations", "quotes-error.json") - }); - const results = await eslint.lintFiles([getFixturePath("single-quoted.js")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); - }); - - it("should return 3 messages when given a config file and a directory of 3 valid files", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "semi-error.json") - }); - const fixturePath = getFixturePath("formatters"); - const results = await eslint.lintFiles([fixturePath]); - - assert.strictEqual(results.length, 5); - assert.strictEqual(path.relative(fixturePath, results[0].filePath), "async.js"); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(path.relative(fixturePath, results[1].filePath), "broken.js"); - assert.strictEqual(results[1].errorCount, 0); - assert.strictEqual(results[1].warningCount, 0); - assert.strictEqual(results[1].fixableErrorCount, 0); - assert.strictEqual(results[1].fixableWarningCount, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(path.relative(fixturePath, results[2].filePath), "cwd.js"); - assert.strictEqual(results[2].errorCount, 0); - assert.strictEqual(results[2].warningCount, 0); - assert.strictEqual(results[2].fixableErrorCount, 0); - assert.strictEqual(results[2].fixableWarningCount, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(path.relative(fixturePath, results[3].filePath), "simple.js"); - assert.strictEqual(results[3].errorCount, 0); - assert.strictEqual(results[3].warningCount, 0); - assert.strictEqual(results[3].fixableErrorCount, 0); - assert.strictEqual(results[3].fixableWarningCount, 0); - assert.strictEqual(results[3].messages.length, 0); - assert.strictEqual(path.relative(fixturePath, results[4].filePath), path.join("test", "simple.js")); - assert.strictEqual(results[4].errorCount, 0); - assert.strictEqual(results[4].warningCount, 0); - assert.strictEqual(results[4].fixableErrorCount, 0); - assert.strictEqual(results[4].fixableWarningCount, 0); - assert.strictEqual(results[4].messages.length, 0); - }); - - it("should process when file is given by not specifying extensions", async () => { - eslint = new ESLint({ - ignore: false, - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles(["fixtures/files/foo.js2"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should return zero messages when given a config with environment set to browser", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "env-browser.json") - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should return zero messages when given an option to set environment to browser", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfig: { - env: { browser: true }, - rules: { - "no-alert": 0, - "no-undef": 2 - } - } - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should return zero messages when given a config with environment set to Node.js", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "env-node.json") - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-node.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should not return results from previous call when calling more than once", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - ignore: false, - overrideConfig: { - rules: { - semi: 2 - } - } - }); - const failFilePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); - const passFilePath = fs.realpathSync(getFixturePath("passing.js")); - - let results = await eslint.lintFiles([failFilePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, failFilePath); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].messages[0].severity, 2); - - results = await eslint.lintFiles([passFilePath]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, passFilePath); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should throw an error when given a directory with all eslint excluded files in the directory", async () => { - eslint = new ESLint({ - ignorePath: getFixturePath(".eslintignore") - }); - - await assert.rejects(async () => { - await eslint.lintFiles([getFixturePath("./cli-engine/")]); - }, new RegExp(escapeStringRegExp(`All files matched by '${getFixturePath("./cli-engine/")}' are ignored.`), "u")); - }); - - it("should throw an error when all given files are ignored", async () => { - eslint = new ESLint({ - useEslintrc: false, - ignorePath: getFixturePath(".eslintignore") - }); - await assert.rejects(async () => { - await eslint.lintFiles(["tests/fixtures/cli-engine/"]); - }, /All files matched by 'tests\/fixtures\/cli-engine\/' are ignored\./u); - }); - - it("should throw an error when all given files are ignored even with a `./` prefix", async () => { - eslint = new ESLint({ - useEslintrc: false, - ignorePath: getFixturePath(".eslintignore") - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); - }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); - }); - - // https://github.com/eslint/eslint/issues/3788 - it("should ignore one-level down node_modules when ignore file has 'node_modules/' in it", async () => { - eslint = new ESLint({ - ignorePath: getFixturePath("cli-engine", "nested_node_modules", ".eslintignore"), - useEslintrc: false, - overrideConfig: { - rules: { - quotes: [2, "double"] - } - }, - cwd: getFixturePath("cli-engine", "nested_node_modules") - }); - const results = await eslint.lintFiles(["."]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - }); - - // https://github.com/eslint/eslint/issues/3812 - it("should ignore all files and throw an error when tests/fixtures/ is in ignore file", async () => { - eslint = new ESLint({ - ignorePath: getFixturePath("cli-engine/.eslintignore2"), - useEslintrc: false, - overrideConfig: { - rules: { - quotes: [2, "double"] - } - } - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); - }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); - }); - - // https://github.com/eslint/eslint/issues/15642 - it("should ignore files that are ignored by patterns with escaped brackets", async () => { - eslint = new ESLint({ - ignorePath: getFixturePath("ignored-paths", ".eslintignoreWithEscapedBrackets"), - useEslintrc: false, - cwd: getFixturePath("ignored-paths") - }); - - // Only `brackets/index.js` should be linted. Other files in `brackets/` should be ignored. - const results = await eslint.lintFiles(["brackets/*.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("ignored-paths", "brackets", "index.js")); - }); - - it("should throw an error when all given files are ignored via ignore-pattern", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - ignorePatterns: "tests/fixtures/single-quoted.js" - } - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["tests/fixtures/*-quoted.js"]); - }, /All files matched by 'tests\/fixtures\/\*-quoted\.js' are ignored\./u); - }); - - it("should return a warning when an explicitly given file is ignored", async () => { - eslint = new ESLint({ - ignorePath: getFixturePath(".eslintignore"), - cwd: getFixturePath() - }); - const filePath = getFixturePath("passing.js"); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override."); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - }); - - it("should return two messages when given a file in excluded files list while ignore is off", async () => { - eslint = new ESLint({ - ignorePath: getFixturePath(".eslintignore"), - ignore: false, - overrideConfig: { - rules: { - "no-undef": 2 - } - } - }); - const filePath = fs.realpathSync(getFixturePath("undef.js")); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[1].ruleId, "no-undef"); - assert.strictEqual(results[0].messages[1].severity, 2); - }); - - it("should return zero messages when executing a file with a shebang", async () => { - eslint = new ESLint({ - ignore: false - }); - const results = await eslint.lintFiles([getFixturePath("shebang.js")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should give a warning when loading a custom rule that doesn't exist", async () => { - eslint = new ESLint({ - ignore: false, - rulePaths: [getFixturePath("rules", "dir1")], - overrideConfigFile: getFixturePath("rules", "missing-rule.json") - }); - const results = await eslint.lintFiles([getFixturePath("rules", "test", "test-custom-rule.js")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "missing-rule"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[0].message, "Definition for rule 'missing-rule' was not found."); - }); - - it("should throw an error when loading a bad custom rule", async () => { - eslint = new ESLint({ - ignore: false, - rulePaths: [getFixturePath("rules", "wrong")], - overrideConfigFile: getFixturePath("rules", "eslint.json") - }); - - - await assert.rejects(async () => { - await eslint.lintFiles([getFixturePath("rules", "test", "test-custom-rule.js")]); - }, /Error while loading rule 'custom-rule'/u); - }); - - it("should return one message when a custom rule matches a file", async () => { - eslint = new ESLint({ - ignore: false, - useEslintrc: false, - rulePaths: [getFixturePath("rules/")], - overrideConfigFile: getFixturePath("rules", "eslint.json") - }); - const filePath = fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js")); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "custom-rule"); - assert.strictEqual(results[0].messages[0].severity, 1); - }); - - it("should load custom rule from the provided cwd", async () => { - const cwd = path.resolve(getFixturePath("rules")); - - eslint = new ESLint({ - ignore: false, - cwd, - rulePaths: ["./"], - overrideConfigFile: "eslint.json" - }); - const filePath = fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js")); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "custom-rule"); - assert.strictEqual(results[0].messages[0].severity, 1); - }); - - it("should return messages when multiple custom rules match a file", async () => { - eslint = new ESLint({ - ignore: false, - rulePaths: [ - getFixturePath("rules", "dir1"), - getFixturePath("rules", "dir2") - ], - overrideConfigFile: getFixturePath("rules", "multi-rulesdirs.json") - }); - const filePath = fs.realpathSync(getFixturePath("rules", "test-multi-rulesdirs.js")); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "no-literals"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[1].ruleId, "no-strings"); - assert.strictEqual(results[0].messages[1].severity, 2); - }); - - it("should return zero messages when executing without useEslintrc flag", async () => { - eslint = new ESLint({ - ignore: false, - useEslintrc: false - }); - const filePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should return zero messages when executing without useEslintrc flag in Node.js environment", async () => { - eslint = new ESLint({ - ignore: false, - useEslintrc: false, - overrideConfig: { - env: { node: true } - } - }); - const filePath = fs.realpathSync(getFixturePath("process-exit.js")); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should return zero messages and ignore .eslintrc files when executing with no-eslintrc flag", async () => { - eslint = new ESLint({ - ignore: false, - useEslintrc: false, - overrideConfig: { - env: { node: true } - } - }); - const filePath = fs.realpathSync(getFixturePath("eslintrc", "quotes.js")); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should return zero messages and ignore package.json files when executing with no-eslintrc flag", async () => { - eslint = new ESLint({ - ignore: false, - useEslintrc: false, - overrideConfig: { - env: { node: true } - } - }); - const filePath = fs.realpathSync(getFixturePath("packagejson", "quotes.js")); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 0); - }); - - it("should warn when deprecated rules are configured", async () => { - eslint = new ESLint({ - cwd: originalDir, - useEslintrc: false, - overrideConfig: { - rules: { - "indent-legacy": 1, - "require-jsdoc": 1, - "valid-jsdoc": 1 - } - } - }); - const results = await eslint.lintFiles(["lib/cli*.js"]); - - assert.deepStrictEqual( - results[0].usedDeprecatedRules, - [ - { ruleId: "indent-legacy", replacedBy: ["indent"] }, - { ruleId: "require-jsdoc", replacedBy: [] }, - { ruleId: "valid-jsdoc", replacedBy: [] } - ] - ); - }); - - it("should not warn when deprecated rules are not configured", async () => { - eslint = new ESLint({ - cwd: originalDir, - useEslintrc: false, - overrideConfig: { - rules: { eqeqeq: 1, "valid-jsdoc": 0, "require-jsdoc": 0 } - } - }); - const results = await eslint.lintFiles(["lib/cli*.js"]); - - assert.deepStrictEqual(results[0].usedDeprecatedRules, []); - }); - - it("should warn when deprecated rules are found in a config", async () => { - eslint = new ESLint({ - cwd: originalDir, - overrideConfigFile: "tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml", - useEslintrc: false - }); - const results = await eslint.lintFiles(["lib/cli*.js"]); - - assert.deepStrictEqual( - results[0].usedDeprecatedRules, - [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] - ); - }); - - describe("Fix Mode", () => { - it("should return fixed text on multiple files when in fix mode", async () => { - - /** - * Converts CRLF to LF in output. - * This is a workaround for git's autocrlf option on Windows. - * @param {Object} result A result object to convert. - * @returns {void} - */ - function convertCRLF(result) { - if (result && result.output) { - result.output = result.output.replace(/\r\n/gu, "\n"); - } - } - - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - fix: true, - overrideConfig: { - rules: { - semi: 2, - quotes: [2, "double"], - eqeqeq: 2, - "no-undef": 2, - "space-infix-ops": 2 - } - } - }); - const results = await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - - results.forEach(convertCRLF); - assert.deepStrictEqual(results, [ - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/multipass.js")), - messages: [], - suppressedMessages: [], - errorCount: 0, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "true ? \"yes\" : \"no\";\n", - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/ok.js")), - messages: [], - suppressedMessages: [], - errorCount: 0, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes-semi-eqeqeq.js")), - messages: [ - { - column: 9, - line: 2, - endColumn: 11, - endLine: 2, - message: "Expected '===' and instead saw '=='.", - messageId: "unexpected", - nodeType: "BinaryExpression", - ruleId: "eqeqeq", - severity: 2 - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n", - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes.js")), - messages: [ - { - column: 18, - line: 1, - endColumn: 21, - endLine: 1, - messageId: "undef", - message: "'foo' is not defined.", - nodeType: "Identifier", - ruleId: "no-undef", - severity: 2 - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var msg = \"hi\" + foo;\n", - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - } - ]); - }); - - it("should run autofix even if files are cached without autofix results", async () => { - const baseOptions = { - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - overrideConfig: { - rules: { - semi: 2, - quotes: [2, "double"], - eqeqeq: 2, - "no-undef": 2, - "space-infix-ops": 2 - } - } - }; - - eslint = new ESLint(Object.assign({}, baseOptions, { cache: true, fix: false })); - - // Do initial lint run and populate the cache file - await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - - eslint = new ESLint(Object.assign({}, baseOptions, { cache: true, fix: true })); - const results = await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - - assert(results.some(result => result.output)); - }); - }); - - // These tests have to do with https://github.com/eslint/eslint/issues/963 - - describe("configuration hierarchy", () => { - - // Default configuration - blank - it("should return zero messages when executing with no .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - // No default configuration rules - conf/environments.js (/*eslint-env node*/) - it("should return zero messages when executing with no .eslintrc in the Node.js environment", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes-node.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - // Project configuration - first level .eslintrc - it("should return zero messages when executing with .eslintrc in the Node.js environment", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/process-exit.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - // Project configuration - first level .eslintrc - it("should return zero messages when executing with .eslintrc in the Node.js environment", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/process-exit.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - // Project configuration - first level .eslintrc - it("should return one message when executing with .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - }); - - // Project configuration - second level .eslintrc - it("should return one message when executing with local .eslintrc that overrides parent .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "no-console"); - assert.strictEqual(results[0].messages[0].severity, 1); - }); - - // Project configuration - third level .eslintrc - it("should return one message when executing with local .eslintrc that overrides parent and grandparent .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/subsubbroken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 1); - }); - - // Project configuration - first level package.json - it("should return one message when executing with package.json", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 1); - }); - - // Project configuration - second level package.json - it("should return zero messages when executing with local package.json that overrides parent package.json", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/subsubdir/wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - // Project configuration - third level package.json - it("should return one message when executing with local package.json that overrides parent and grandparent package.json", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/subdir/subsubdir/subsubsubdir/wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - }); - - // Project configuration - .eslintrc overrides package.json in same directory - it("should return one message when executing with .eslintrc that overrides a package.json in the same directory", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/packagejson/wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - }); - - // Command line configuration - --config with first level .eslintrc - it("should return two messages when executing with config file that adds to local .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/add-conf.yaml` - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[1].ruleId, "semi"); - assert.strictEqual(results[0].messages[1].severity, 1); - }); - - // Command line configuration - --config with first level .eslintrc - it("should return no messages when executing with config file that overrides local .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/override-conf.yaml` - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - // Command line configuration - --config with second level .eslintrc - it("should return two messages when executing with config file that adds to local and parent .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/add-conf.yaml` - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "no-console"); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[1].ruleId, "semi"); - assert.strictEqual(results[0].messages[1].severity, 1); - }); - - // Command line configuration - --config with second level .eslintrc - it("should return one message when executing with config file that overrides local and parent .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("config-hierarchy/broken/override-conf.yaml") - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "no-console"); - assert.strictEqual(results[0].messages[0].severity, 1); - }); - - // Command line configuration - --config with first level .eslintrc - it("should return no messages when executing with config file that overrides local .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/override-conf.yaml` - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - }); - - // Command line configuration - --rule with --config and first level .eslintrc - it("should return one message when executing with command line rule and config file that overrides local .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("config-hierarchy/broken/override-conf.yaml"), - overrideConfig: { - rules: { - quotes: [1, "double"] - } - } - }); - const results = await eslint.lintFiles([fs.realpathSync(`${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`)]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 1); - }); - - // Command line configuration - --rule with --config and first level .eslintrc - it("should return one message when executing with command line rule and config file that overrides local .eslintrc", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("/config-hierarchy/broken/override-conf.yaml"), - overrideConfig: { - rules: { - quotes: [1, "double"] - } - } - }); - const results = await eslint.lintFiles([getFixturePath("config-hierarchy/broken/console-wrong-quotes.js")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 1); - }); - }); - - describe("plugins", () => { - it("should return two messages when executing with config file that specifies a plugin", async () => { - eslint = eslintWithPlugins({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "plugins-with-prefix.json"), - useEslintrc: false - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test/test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); - }); - - it("should return two messages when executing with config file that specifies a plugin with namespace", async () => { - eslint = eslintWithPlugins({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "plugins-with-prefix-and-namespace.json"), - useEslintrc: false - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "@eslint/example/example-rule"); - }); - - it("should return two messages when executing with config file that specifies a plugin without prefix", async () => { - eslint = eslintWithPlugins({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "plugins-without-prefix.json"), - useEslintrc: false - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); - }); - - it("should return two messages when executing with config file that specifies a plugin without prefix and with namespace", async () => { - eslint = eslintWithPlugins({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "plugins-without-prefix-with-namespace.json"), - useEslintrc: false - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "@eslint/example/example-rule"); - }); - - it("should return two messages when executing with cli option that specifies a plugin", async () => { - eslint = eslintWithPlugins({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - overrideConfig: { - plugins: ["example"], - rules: { "example/example-rule": 1 } - } - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); - }); - - it("should return two messages when executing with cli option that specifies preloaded plugin", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - overrideConfig: { - plugins: ["test"], - rules: { "test/example-rule": 1 } - }, - plugins: { - "eslint-plugin-test": { rules: { "example-rule": require("../../fixtures/rules/custom-rule") } } - } - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "test/example-rule"); - }); - - it("should return two messages when executing with `baseConfig` that extends preloaded plugin config", async () => { - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - baseConfig: { - extends: ["plugin:test/preset"] - }, - plugins: { - test: { - rules: { - "example-rule": require("../../fixtures/rules/custom-rule") - }, - configs: { - preset: { - rules: { - "test/example-rule": 1 - }, - plugins: ["test"] - } - } - } - } - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "test/example-rule"); - }); - - it("should load plugins from the `loadPluginsRelativeTo` directory, if specified", async () => { - eslint = new ESLint({ - resolvePluginsRelativeTo: getFixturePath("plugins"), - baseConfig: { - plugins: ["with-rules"], - rules: { "with-rules/rule1": "error" } - }, - useEslintrc: false - }); - const results = await eslint.lintText("foo"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "with-rules/rule1"); - assert.strictEqual(results[0].messages[0].message, "Rule report from plugin"); - }); - }); - - describe("cache", () => { - - /** - * helper method to delete a file without caring about exceptions - * @param {string} filePath The file path - * @returns {void} - */ - function doDelete(filePath) { - try { - fs.unlinkSync(filePath); - } catch { - - /* - * we don't care if the file didn't exist, since our - * intention was to remove the file - */ - } - } - - let cacheFilePath; - - beforeEach(() => { - cacheFilePath = null; - }); - - afterEach(() => { - sinon.restore(); - if (cacheFilePath) { - doDelete(cacheFilePath); - } - }); - - describe("when cacheLocation is a directory or looks like a directory", () => { - - const cwd = getFixturePath(); - - /** - * helper method to delete the cache files created during testing - * @returns {void} - */ - function deleteCacheDir() { - try { - - /* - * `fs.rmdir(path, { recursive: true })` is deprecated and will be removed. - * Use `fs.rm(path, { recursive: true })` instead. - * When supporting Node.js 14.14.0+, the compatibility condition can be removed for `fs.rmdir`. - */ - // eslint-disable-next-line n/no-unsupported-features/node-builtins -- just checking if it exists - if (typeof fs.rm === "function") { - - // eslint-disable-next-line n/no-unsupported-features/node-builtins -- conditionally used - fs.rmSync(path.resolve(cwd, "tmp/.cacheFileDir/"), { recursive: true, force: true }); - } else { - fs.rmdirSync(path.resolve(cwd, "tmp/.cacheFileDir/"), { recursive: true, force: true }); - } - - } catch { - - /* - * we don't care if the file didn't exist, since our - * intention was to remove the file - */ - } - } - beforeEach(() => { - deleteCacheDir(); - }); - - afterEach(() => { - deleteCacheDir(); - }); - - it("should create the directory and the cache file inside it when cacheLocation ends with a slash", async () => { - assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); - - eslint = new ESLint({ - useEslintrc: false, - cwd, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: "./tmp/.cacheFileDir/", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"], - ignore: false - }); - const file = getFixturePath("cache/src", "test-file.js"); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); - }); +const JITI_VERSIONS = ["jiti", "jiti-v2.0", "jiti-v2.1"]; - it("should create the cache file inside existing cacheLocation directory when cacheLocation ends with a slash", async () => { - assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); - - fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { recursive: true }); - - eslint = new ESLint({ - useEslintrc: false, - cwd, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: "./tmp/.cacheFileDir/", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false - }); - const file = getFixturePath("cache/src", "test-file.js"); - - await eslint.lintFiles([file]); +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ - assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); - }); +/** + * Creates a directory if it doesn't already exist. + * @param {string} dirPath The path to the directory that should exist. + * @returns {void} + */ +function ensureDirectoryExists(dirPath) { + try { + fs.statSync(dirPath); + } catch { + fs.mkdirSync(dirPath); + } +} - it("should create the cache file inside existing cacheLocation directory when cacheLocation doesn't end with a path separator", async () => { - assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); +/** + * Does nothing for a given time. + * @param {number} time Time in ms. + * @returns {Promise} + */ +async function sleep(time) { + await util.promisify(setTimeout)(time); +} - fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { recursive: true }); +/** + * An object mapping file extensions to their corresponding + * ESLint configuration file names. + * @satisfies {Record} + */ +const eslintConfigFiles = { + ts: "eslint.config.ts", + mts: "eslint.config.mts", + cts: "eslint.config.cts", + js: "eslint.config.js", + mjs: "eslint.config.mjs", + cjs: "eslint.config.cjs", +}; - eslint = new ESLint({ - useEslintrc: false, - cwd, +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ - // specifying cache true the cache will be created - cache: true, - cacheLocation: "./tmp/.cacheFileDir", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false - }); - const file = getFixturePath("cache/src", "test-file.js"); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); - }); - }); - - it("should create the cache file inside cwd when no cacheLocation provided", async () => { - const cwd = path.resolve(getFixturePath("cli-engine")); - - cacheFilePath = path.resolve(cwd, ".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new ESLint({ - useEslintrc: false, - cache: true, - cwd, - overrideConfig: { - rules: { - "no-console": 0 - } - }, - extensions: ["js"], - ignore: false - }); - const file = getFixturePath("cli-engine", "console.js"); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created at provided cwd"); - }); - - it("should invalidate the cache if the configuration changed between executions", async () => { - const cwd = getFixturePath("cache/src"); - - cacheFilePath = path.resolve(cwd, ".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new ESLint({ - useEslintrc: false, - cwd, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"], - ignore: false - }); - - let spy = sinon.spy(fs, "readFileSync"); - - let file = getFixturePath("cache/src", "test-file.js"); - - file = fs.realpathSync(file); - const results = await eslint.lintFiles([file]); - - for (const { errorCount, warningCount } of results) { - assert.strictEqual(errorCount + warningCount, 0, "the file should have passed linting without errors or warnings"); - } - assert(spy.calledWith(file), "ESLint should have read the file because there was no cache file"); - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - // destroy the spy - sinon.restore(); - - eslint = new ESLint({ - useEslintrc: false, - cwd, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - extensions: ["js"], - ignore: false - }); - - // create a new spy - spy = sinon.spy(fs, "readFileSync"); - - const [newResult] = await eslint.lintFiles([file]); - - assert(spy.calledWith(file), "ESLint should have read the file again because it's considered changed because the config changed"); - assert.strictEqual(newResult.errorCount, 1, "since configuration changed the cache should have not been used and one error should have been reported"); - assert.strictEqual(newResult.messages[0].ruleId, "no-console"); - assert(shell.test("-f", cacheFilePath), "The cache for ESLint should still exist"); - }); - - it("should remember the files from a previous run and do not operate on them if not changed", async () => { - const cwd = getFixturePath("cache/src"); - - cacheFilePath = path.resolve(cwd, ".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new ESLint({ - useEslintrc: false, - cwd, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"], - ignore: false - }); - - let spy = sinon.spy(fs, "readFileSync"); - - let file = getFixturePath("cache/src", "test-file.js"); - - file = fs.realpathSync(file); - - const result = await eslint.lintFiles([file]); - - assert(spy.calledWith(file), "ESLint should have read the file because there was no cache file"); - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - // destroy the spy - sinon.restore(); - - eslint = new ESLint({ - useEslintrc: false, - cwd, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"], - ignore: false - }); - - // create a new spy - spy = sinon.spy(fs, "readFileSync"); - - const cachedResult = await eslint.lintFiles([file]); - - assert.deepStrictEqual(result, cachedResult, "the result should have been the same"); - - // assert the file was not processed because the cache was used - assert(!spy.calledWith(file), "the file should not have been reloaded"); - }); - - it("when `cacheLocation` is specified, should create the cache file with `cache:true` and then delete it with `cache:false`", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - const eslintOptions = { - useEslintrc: false, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"], - cwd: path.join(fixtureDir, "..") - }; - - eslint = new ESLint(eslintOptions); - - let file = getFixturePath("cache/src", "test-file.js"); - - file = fs.realpathSync(file); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - eslintOptions.cache = false; - eslint = new ESLint(eslintOptions); - - await eslint.lintFiles([file]); - - assert(!shell.test("-f", cacheFilePath), "the cache for eslint should have been deleted since last run did not use the cache"); - }); - - it("should not throw an error if the cache file to be deleted does not exist on a read-only file system", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - // Simulate a read-only file system. - sinon.stub(fs, "unlinkSync").throws( - Object.assign(new Error("read-only file system"), { code: "EROFS" }) - ); - - const eslintOptions = { - useEslintrc: false, - - // specifying cache true the cache will be created - cache: false, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"], - cwd: path.join(fixtureDir, "..") - }; - - eslint = new ESLint(eslintOptions); - - const file = getFixturePath("cache/src", "test-file.js"); - - await eslint.lintFiles([file]); - - assert(fs.unlinkSync.calledWithExactly(cacheFilePath), "Expected attempt to delete the cache was not made."); - }); - - it("should store in the cache a file that has lint messages and a file that doesn't have lint messages", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"] - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const result = await eslint.lintFiles([badFile, goodFile]); - const [badFileResult, goodFileResult] = result; - - assert.notStrictEqual(badFileResult.errorCount + badFileResult.warningCount, 0, "the bad file should have some lint errors or warnings"); - assert.strictEqual(goodFileResult.errorCount + badFileResult.warningCount, 0, "the good file should have passed linting without errors or warnings"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - const fileCache = fCache.createFromFile(cacheFilePath); - const { cache } = fileCache; - - assert.strictEqual(typeof cache.getKey(goodFile), "object", "the entry for the good file should have been in the cache"); - assert.strictEqual(typeof cache.getKey(badFile), "object", "the entry for the bad file should have been in the cache"); - const cachedResult = await eslint.lintFiles([badFile, goodFile]); - - assert.deepStrictEqual(result, cachedResult, "result should be the same with or without cache"); - }); - - it("should not contain in the cache a file that was deleted", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"] - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const toBeDeletedFile = fs.realpathSync(getFixturePath("cache/src", "file-to-delete.js")); - - await eslint.lintFiles([badFile, goodFile, toBeDeletedFile]); - const fileCache = fCache.createFromFile(cacheFilePath); - let { cache } = fileCache; - - assert.strictEqual(typeof cache.getKey(toBeDeletedFile), "object", "the entry for the file to be deleted should have been in the cache"); - - // delete the file from the file system - fs.unlinkSync(toBeDeletedFile); - - /* - * file-entry-cache@2.0.0 will remove from the cache deleted files - * even when they were not part of the array of files to be analyzed - */ - await eslint.lintFiles([badFile, goodFile]); - - cache = JSON.parse(fs.readFileSync(cacheFilePath)); - - assert.strictEqual(typeof cache[0][toBeDeletedFile], "undefined", "the entry for the file to be deleted should not have been in the cache"); - - // make sure that the previos assertion checks the right place - assert.notStrictEqual(typeof cache[0][badFile], "undefined", "the entry for the bad file should have been in the cache"); - assert.notStrictEqual(typeof cache[0][goodFile], "undefined", "the entry for the good file should have been in the cache"); - }); - - it("should contain files that were not visited in the cache provided they still exist", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"] - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const testFile2 = fs.realpathSync(getFixturePath("cache/src", "test-file2.js")); - - await eslint.lintFiles([badFile, goodFile, testFile2]); - - let fileCache = fCache.createFromFile(cacheFilePath); - let { cache } = fileCache; - - assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 should have been in the cache"); - - /* - * we pass a different set of files minus test-file2 - * previous version of file-entry-cache would remove the non visited - * entries. 2.0.0 version will keep them unless they don't exist - */ - await eslint.lintFiles([badFile, goodFile]); - - fileCache = fCache.createFromFile(cacheFilePath); - cache = fileCache.cache; - - assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 should have been in the cache"); - }); - - it("should not delete cache when executing on text", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used - - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"] - }); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); - - await eslint.lintText("var foo = 'bar';"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); - }); - - it("should not delete cache when executing on text with a provided filename", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used - - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"] - }); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); - - await eslint.lintText("var bar = foo;", { filePath: "fixtures/passing.js" }); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); - }); - - it("should not delete cache when executing on files with --cache flag", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - fs.writeFileSync(cacheFilePath, ""); - - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"] - }); - const file = getFixturePath("cli-engine", "console.js"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); - }); - - it("should delete cache when executing on files without --cache flag", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used - - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"] - }); - const file = getFixturePath("cli-engine", "console.js"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); - - await eslint.lintFiles([file]); - - assert(!shell.test("-f", cacheFilePath), "the cache for eslint should have been deleted"); - }); - - it("should use the specified cache file", async () => { - cacheFilePath = path.resolve(".cache/custom-cache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new ESLint({ - useEslintrc: false, - - // specify a custom cache file - cacheLocation: cacheFilePath, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"], - cwd: path.join(fixtureDir, "..") - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const result = await eslint.lintFiles([badFile, goodFile]); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - const fileCache = fCache.createFromFile(cacheFilePath); - const { cache } = fileCache; - - assert(typeof cache.getKey(goodFile) === "object", "the entry for the good file should have been in the cache"); - assert(typeof cache.getKey(badFile) === "object", "the entry for the bad file should have been in the cache"); - - const cachedResult = await eslint.lintFiles([badFile, goodFile]); - - assert.deepStrictEqual(result, cachedResult, "result should be the same with or without cache"); - }); - - // https://github.com/eslint/eslint/issues/13507 - it("should not store `usedDeprecatedRules` in the cache file", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - const deprecatedRuleId = "space-in-parens"; - - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - [deprecatedRuleId]: 2 - } - } - }); - - const filePath = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - - /* - * Run linting on the same file 3 times to cover multiple cases: - * Run 1: Lint result wasn't already cached. - * Run 2: Lint result was already cached. The cached lint result is used but the cache is reconciled before the run ends. - * Run 3: Lint result was already cached. The cached lint result was being used throughout the previous run, so possible - * mutations in the previous run that occured after the cache was reconciled may have side effects for this run. - */ - for (let i = 0; i < 3; i++) { - const [result] = await eslint.lintFiles([filePath]); - - assert( - result.usedDeprecatedRules && result.usedDeprecatedRules.some(rule => rule.ruleId === deprecatedRuleId), - "the deprecated rule should have been in result.usedDeprecatedRules" - ); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - const fileCache = fCache.create(cacheFilePath); - const descriptor = fileCache.getFileDescriptor(filePath); - - assert(typeof descriptor === "object", "an entry for the file should have been in the cache file"); - assert(typeof descriptor.meta.results === "object", "lint result for the file should have been in its cache entry in the cache file"); - assert(typeof descriptor.meta.results.usedDeprecatedRules === "undefined", "lint result in the cache file contains `usedDeprecatedRules`"); - } - - }); - - // https://github.com/eslint/eslint/issues/13507 - it("should store `source` as `null` in the cache file if the lint result has `source` property", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-unused-vars": 2 - } - } - }); - - const filePath = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - - /* - * Run linting on the same file 3 times to cover multiple cases: - * Run 1: Lint result wasn't already cached. - * Run 2: Lint result was already cached. The cached lint result is used but the cache is reconciled before the run ends. - * Run 3: Lint result was already cached. The cached lint result was being used throughout the previous run, so possible - * mutations in the previous run that occured after the cache was reconciled may have side effects for this run. - */ - for (let i = 0; i < 3; i++) { - const [result] = await eslint.lintFiles([filePath]); - - assert(typeof result.source === "string", "the result should have contained the `source` property"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - const fileCache = fCache.create(cacheFilePath); - const descriptor = fileCache.getFileDescriptor(filePath); - - assert(typeof descriptor === "object", "an entry for the file should have been in the cache file"); - assert(typeof descriptor.meta.results === "object", "lint result for the file should have been in its cache entry in the cache file"); - - // if the lint result contains `source`, it should be stored as `null` in the cache file - assert.strictEqual(descriptor.meta.results.source, null, "lint result in the cache file contains non-null `source`"); - } - - }); - - describe("cacheStrategy", () => { - it("should detect changes using a file's modification time when set to 'metadata'", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - cacheStrategy: "metadata", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"] - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - - await eslint.lintFiles([badFile, goodFile]); - let fileCache = fCache.createFromFile(cacheFilePath); - const entries = fileCache.normalizeEntries([badFile, goodFile]); - - entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); - }); - - // this should result in a changed entry - shell.touch(goodFile); - fileCache = fCache.createFromFile(cacheFilePath); - assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} should have been unchanged`); - assert(fileCache.getFileDescriptor(goodFile).changed === true, `the entry for ${goodFile} should have been changed`); - }); - - it("should not detect changes using a file's modification time when set to 'content'", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - cacheStrategy: "content", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"] - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - - await eslint.lintFiles([badFile, goodFile]); - let fileCache = fCache.createFromFile(cacheFilePath, true); - let entries = fileCache.normalizeEntries([badFile, goodFile]); - - entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); - }); - - // this should NOT result in a changed entry - shell.touch(goodFile); - fileCache = fCache.createFromFile(cacheFilePath, true); - entries = fileCache.normalizeEntries([badFile, goodFile]); - entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} should have remained unchanged`); - }); - }); - - it("should detect changes using a file's contents when set to 'content'", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - cacheStrategy: "content", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - extensions: ["js"] - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const goodFileCopy = path.resolve(`${path.dirname(goodFile)}`, "test-file-copy.js"); - - shell.cp(goodFile, goodFileCopy); - - await eslint.lintFiles([badFile, goodFileCopy]); - let fileCache = fCache.createFromFile(cacheFilePath, true); - const entries = fileCache.normalizeEntries([badFile, goodFileCopy]); - - entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); - }); - - // this should result in a changed entry - shell.sed("-i", "abc", "xzy", goodFileCopy); - fileCache = fCache.createFromFile(cacheFilePath, true); - assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} should have been unchanged`); - assert(fileCache.getFileDescriptor(goodFileCopy).changed === true, `the entry for ${goodFileCopy} should have been changed`); - }); - }); - }); - - describe("processors", () => { - it("should return two messages when executing with config file that specifies a processor", async () => { - eslint = eslintWithPlugins({ - overrideConfigFile: getFixturePath("configurations", "processors.json"), - useEslintrc: false, - extensions: ["js", "txt"], - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("processors", "test", "test-processor.txt"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - }); - - it("should return two messages when executing with config file that specifies preloaded processor", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - plugins: ["test-processor"], - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - extensions: ["js", "txt"], - cwd: path.join(fixtureDir, ".."), - plugins: { - "test-processor": { - processors: { - ".txt": { - preprocess(text) { - return [text]; - }, - postprocess(messages) { - return messages[0]; - } - } - } - } - } - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("processors", "test", "test-processor.txt"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - }); - - it("should run processors when calling lintFiles with config file that specifies a processor", async () => { - eslint = eslintWithPlugins({ - overrideConfigFile: getFixturePath("configurations", "processors.json"), - useEslintrc: false, - extensions: ["js", "txt"], - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([getFixturePath("processors", "test", "test-processor.txt")]); - - assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); - assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); - }); - - it("should run processors when calling lintFiles with config file that specifies preloaded processor", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - plugins: ["test-processor"], - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - extensions: ["js", "txt"], - cwd: path.join(fixtureDir, ".."), - plugins: { - "test-processor": { - processors: { - ".txt": { - preprocess(text) { - return [text.replace("a()", "b()")]; - }, - postprocess(messages) { - messages[0][0].ruleId = "post-processed"; - return messages[0]; - } - } - } - } - } - }); - const results = await eslint.lintFiles([getFixturePath("processors", "test", "test-processor.txt")]); - - assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); - assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); - }); - - it("should run processors when calling lintText with config file that specifies a processor", async () => { - eslint = eslintWithPlugins({ - overrideConfigFile: getFixturePath("configurations", "processors.json"), - useEslintrc: false, - extensions: ["js", "txt"], - ignore: false - }); - const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); - - assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); - assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); - }); - - it("should run processors when calling lintText with config file that specifies preloaded processor", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - plugins: ["test-processor"], - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - extensions: ["js", "txt"], - ignore: false, - plugins: { - "test-processor": { - processors: { - ".txt": { - preprocess(text) { - return [text.replace("a()", "b()")]; - }, - postprocess(messages) { - messages[0][0].ruleId = "post-processed"; - return messages[0]; +describe("ESLint", () => { + const { ConfigLoader } = require("../../../lib/config/config-loader.js"); + + const examplePluginName = "eslint-plugin-example"; + const examplePluginNameWithNamespace = "@eslint/eslint-plugin-example"; + const examplePlugin = { + rules: { + "example-rule": require("../../fixtures/rules/custom-rule"), + "make-syntax-error": require("../../fixtures/rules/make-syntax-error-rule"), + }, + }; + const examplePreprocessorName = "eslint-plugin-processor"; + const patternProcessor = require("../../fixtures/processors/pattern-processor"); + const exampleMarkdownPlugin = { + processors: { + markdown: patternProcessor.defineProcessor( + /```(\w+)\n(.+?)\n```(?:\n|$)/gsu, + ), + }, + }; + const originalDir = process.cwd(); + const fixtureDir = path.resolve( + fs.realpathSync(os.tmpdir()), + "eslint/fixtures", + ); + + /** @typedef {typeof import("../../../lib/eslint/eslint").ESLint} ESLint */ + + /** @type {ESLint} */ + let ESLint; + + /** + * Returns the path inside of the fixture directory. + * @param {...string} args file path segments. + * @returns {string} The path inside the fixture directory. + * @private + */ + function getFixturePath(...args) { + const filepath = path.join(fixtureDir, ...args); + + try { + return fs.realpathSync(filepath); + } catch { + return filepath; + } + } + + /** + * Create the ESLint object by mocking some of the plugins + * @param {ESLintOptions} options options for ESLint + * @returns {InstanceType} engine object + * @private + */ + function eslintWithPlugins(options) { + return new ESLint({ + ...options, + plugins: { + [examplePluginName]: examplePlugin, + [examplePluginNameWithNamespace]: examplePlugin, + [examplePreprocessorName]: require("../../fixtures/processors/custom-processor"), + }, + }); + } + + // copy into clean area so as not to get "infected" by this project's .eslintrc files + before(function () { + /* + * GitHub Actions Windows and macOS runners occasionally exhibit + * extremely slow filesystem operations, during which copying fixtures + * exceeds the default test timeout, so raise it just for this hook. + * Mocha uses `this` to set timeouts on an individual hook level. + */ + this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API + shell.mkdir("-p", fixtureDir); + shell.cp("-r", "./tests/fixtures/.", fixtureDir); + }); + + after(() => { + shell.rm("-r", fixtureDir); + }); + + beforeEach(() => { + ({ ESLint } = require("../../../lib/eslint/eslint")); + + // Silence ".eslintignore" warnings for tests + sinon.stub(WarningService.prototype, "emitESLintIgnoreWarning"); + }); + + afterEach(() => { + sinon.restore(); + }); + + [[], ["v10_config_lookup_from_file"]].forEach(flags => { + /** + * Configuration flags for TypeScript integration in Node.js, + * including existing {@linkcode flags} and + * `"unstable_native_nodejs_ts_config"`. + * @satisfies {ESLintOptions['flags']} + */ + const nativeTSConfigFileFlags = [ + ...flags, + "unstable_native_nodejs_ts_config", + ]; + + describe("ESLint constructor function", () => { + it("should have a static property indicating the configType being used", () => { + assert.strictEqual(ESLint.configType, "flat"); + }); + + it("should have the defaultConfig static property", () => { + assert.deepStrictEqual(ESLint.defaultConfig, defaultConfig); + }); + + it("the default value of 'options.cwd' should be the current working directory.", async () => { + process.chdir(__dirname); + try { + const engine = new ESLint({ flags }); + const results = await engine.lintFiles("eslint.js"); + + assert.strictEqual( + path.dirname(results[0].filePath), + __dirname, + ); + } finally { + process.chdir(originalDir); + } + }); + + it("should normalize 'options.cwd'.", async () => { + const cwd = getFixturePath("example-app3"); + const engine = new ESLint({ + flags, + cwd: `${cwd}${path.sep}foo${path.sep}..`, // `/foo/..` should be normalized to `` + overrideConfigFile: true, + overrideConfig: { + plugins: { + test: require( + path.join( + cwd, + "node_modules", + "eslint-plugin-test", + ), + ), + }, + rules: { + "test/report-cwd": "error", + }, + }, + }); + const results = await engine.lintText(""); + + assert.strictEqual( + results[0].messages[0].ruleId, + "test/report-cwd", + ); + assert.strictEqual(results[0].messages[0].message, cwd); + + const formatter = await engine.loadFormatter("cwd"); + + assert.strictEqual(formatter.format(results), cwd); + }); + + // https://github.com/eslint/eslint/issues/2380 + it("should not modify baseConfig in the constructor", () => { + const customBaseConfig = { root: true }; + + new ESLint({ baseConfig: customBaseConfig, flags }); // eslint-disable-line no-new -- Check for argument side effects + + assert.deepStrictEqual(customBaseConfig, { root: true }); + }); + + it("should throw readable messages if removed options are present", () => { + assert.throws( + () => + new ESLint({ + flags, + cacheFile: "", + configFile: "", + envs: [], + globals: [], + ignorePath: ".gitignore", + ignorePattern: [], + parser: "", + parserOptions: {}, + rules: {}, + plugins: [], + reportUnusedDisableDirectives: "error", + }), + new RegExp( + escapeStringRegExp( + [ + "Invalid Options:", + "- Unknown options: cacheFile, configFile, envs, globals, ignorePath, ignorePattern, parser, parserOptions, rules, reportUnusedDisableDirectives", + ].join("\n"), + ), + "u", + ), + ); + }); + + it("should throw readable messages if wrong type values are given to options", () => { + assert.throws( + () => + new ESLint({ + flags, + allowInlineConfig: "", + baseConfig: "", + cache: "", + cacheLocation: "", + cwd: "foo", + errorOnUnmatchedPattern: "", + fix: "", + fixTypes: ["xyz"], + globInputPaths: "", + ignore: "", + ignorePatterns: "", + overrideConfig: "", + overrideConfigFile: "", + plugins: "", + warnIgnored: "", + ruleFilter: "", + }), + new RegExp( + escapeStringRegExp( + [ + "Invalid Options:", + "- 'allowInlineConfig' must be a boolean.", + "- 'baseConfig' must be an object or null.", + "- 'cache' must be a boolean.", + "- 'cacheLocation' must be a non-empty string.", + "- 'cwd' must be an absolute path.", + "- 'errorOnUnmatchedPattern' must be a boolean.", + "- 'fix' must be a boolean or a function.", + '- \'fixTypes\' must be an array of any of "directive", "problem", "suggestion", and "layout".', + "- 'globInputPaths' must be a boolean.", + "- 'ignore' must be a boolean.", + "- 'ignorePatterns' must be an array of non-empty strings or null.", + "- 'overrideConfig' must be an object or null.", + "- 'overrideConfigFile' must be a non-empty string, null, or true.", + "- 'plugins' must be an object or null.", + "- 'warnIgnored' must be a boolean.", + "- 'ruleFilter' must be a function.", + ].join("\n"), + ), + "u", + ), + ); + }); + + it("should throw readable messages if 'ignorePatterns' is not an array of non-empty strings.", () => { + const invalidIgnorePatterns = [ + () => {}, + false, + {}, + "", + "foo", + [[]], + [() => {}], + [false], + [{}], + [""], + ["foo", ""], + ["foo", "", "bar"], + ["foo", false, "bar"], + ]; + + invalidIgnorePatterns.forEach(ignorePatterns => { + assert.throws( + () => new ESLint({ ignorePatterns, flags }), + new RegExp( + escapeStringRegExp( + [ + "Invalid Options:", + "- 'ignorePatterns' must be an array of non-empty strings or null.", + ].join("\n"), + ), + "u", + ), + ); + }); + }); + + it("should throw readable messages if 'plugins' option contains empty key", () => { + assert.throws( + () => + new ESLint({ + flags, + plugins: { + "eslint-plugin-foo": {}, + "eslint-plugin-bar": {}, + "": {}, + }, + }), + new RegExp( + escapeStringRegExp( + [ + "Invalid Options:", + "- 'plugins' must not include an empty string.", + ].join("\n"), + ), + "u", + ), + ); + }); + + it("should warn if .eslintignore file is present", async () => { + const cwd = getFixturePath("ignored-paths"); + + sinon.restore(); + const emitESLintIgnoreWarningStub = sinon.stub( + WarningService.prototype, + "emitESLintIgnoreWarning", + ); + + // eslint-disable-next-line no-new -- for testing purpose only + new ESLint({ cwd, flags }); + + assert( + emitESLintIgnoreWarningStub.calledOnce, + "calls `warningService.emitESLintIgnoreWarning()` once", + ); + }); + }); + + describe("hasFlag", () => { + /** @type {InstanceType} */ + let eslint; + + let processStub; + + beforeEach(() => { + sinon.restore(); + processStub = sinon + .stub(process, "emitWarning") + .withArgs( + sinon.match.any, + sinon.match(/^ESLintInactiveFlag_/u), + ) + .returns(); + }); + + afterEach(() => { + delete process.env.ESLINT_FLAGS; + }); + + it("should return true if the flag is present and active", () => { + eslint = new ESLint({ + cwd: getFixturePath(), + flags: ["test_only"], + }); + + assert.strictEqual(eslint.hasFlag("test_only"), true); + }); + + it("should return true if the flag is present and active with ESLINT_FLAGS", () => { + process.env.ESLINT_FLAGS = "test_only"; + eslint = new ESLint({ + cwd: getFixturePath(), + }); + assert.strictEqual(eslint.hasFlag("test_only"), true); + }); + + it("should merge flags passed through API with flags passed through ESLINT_FLAGS", () => { + process.env.ESLINT_FLAGS = "test_only"; + eslint = new ESLint({ + cwd: getFixturePath(), + flags: ["test_only_2"], + }); + assert.strictEqual(eslint.hasFlag("test_only"), true); + assert.strictEqual(eslint.hasFlag("test_only_2"), true); + }); + + it("should return true for multiple flags in ESLINT_FLAGS if the flag is present and active and one is duplicated in the API", () => { + process.env.ESLINT_FLAGS = "test_only,test_only_2"; + + eslint = new ESLint({ + cwd: getFixturePath(), + flags: ["test_only"], // intentional duplication + }); + + assert.strictEqual(eslint.hasFlag("test_only"), true); + assert.strictEqual(eslint.hasFlag("test_only_2"), true); + }); + + it("should return true for multiple flags in ESLINT_FLAGS if the flag is present and active and there is leading and trailing white space", () => { + process.env.ESLINT_FLAGS = " test_only, test_only_2 "; + + eslint = new ESLint({ + cwd: getFixturePath(), + }); + + assert.strictEqual(eslint.hasFlag("test_only"), true); + assert.strictEqual(eslint.hasFlag("test_only_2"), true); + }); + + it("should return true for the replacement flag if an inactive flag that has been replaced is used", () => { + eslint = new ESLint({ + cwd: getFixturePath(), + flags: ["test_only_replaced"], + }); + + assert.strictEqual(eslint.hasFlag("test_only"), true); + assert.strictEqual( + processStub.callCount, + 1, + "calls `process.emitWarning()` for flags once", + ); + assert.deepStrictEqual(processStub.getCall(0).args, [ + "The flag 'test_only_replaced' is inactive: This flag has been renamed 'test_only' to reflect its stabilization. Please use 'test_only' instead.", + "ESLintInactiveFlag_test_only_replaced", + ]); + }); + + it("should return false if an inactive flag whose feature is enabled by default is used", () => { + eslint = new ESLint({ + cwd: getFixturePath(), + flags: ["test_only_enabled_by_default"], + }); + + assert.strictEqual( + eslint.hasFlag("test_only_enabled_by_default"), + false, + ); + assert.strictEqual( + processStub.callCount, + 1, + "calls `process.emitWarning()` for flags once", + ); + assert.deepStrictEqual(processStub.getCall(0).args, [ + "The flag 'test_only_enabled_by_default' is inactive: This feature is now enabled by default.", + "ESLintInactiveFlag_test_only_enabled_by_default", + ]); + }); + + it("should throw an error if an inactive flag whose feature has been abandoned is used", () => { + assert.throws(() => { + eslint = new ESLint({ + cwd: getFixturePath(), + flags: ["test_only_abandoned"], + }); + }, /The flag 'test_only_abandoned' is inactive: This feature has been abandoned/u); + }); + + it("should throw an error if an inactive flag whose feature has been abandoned is used in ESLINT_FLAGS", () => { + process.env.ESLINT_FLAGS = "test_only_abandoned"; + assert.throws(() => { + eslint = new ESLint({ + cwd: getFixturePath(), + }); + }, /The flag 'test_only_abandoned' is inactive: This feature has been abandoned/u); + }); + + it("should throw an error if the flag is unknown", () => { + assert.throws(() => { + eslint = new ESLint({ + cwd: getFixturePath(), + flags: ["foo_bar"], + }); + }, /Unknown flag 'foo_bar'/u); + }); + + it("should throw an error if the flag is unknown in ESLINT_FLAGS", () => { + process.env.ESLINT_FLAGS = "foo_bar"; + assert.throws(() => { + eslint = new ESLint({ + cwd: getFixturePath(), + }); + }, /Unknown flag 'foo_bar'/u); + }); + + it("should return false if the flag is not present", () => { + eslint = new ESLint({ cwd: getFixturePath() }); + + assert.strictEqual(eslint.hasFlag("x_feature"), false); + }); + + // TODO: Remove in ESLint v10 when the flag is removed + it("should not throw an error if the flag 'unstable_ts_config' is used", () => { + eslint = new ESLint({ + flags: [...flags, "unstable_ts_config"], + }); + + assert.strictEqual(eslint.hasFlag("unstable_ts_config"), false); + assert.strictEqual( + processStub.callCount, + 1, + "calls `process.emitWarning()` for flags once", + ); + assert.deepStrictEqual(processStub.getCall(0).args, [ + "The flag 'unstable_ts_config' is inactive: This feature is now enabled by default.", + "ESLintInactiveFlag_unstable_ts_config", + ]); + }); + }); + + describe("lintText()", () => { + /** @type {InstanceType} */ + let eslint; + + it("should report the total and per file errors when using local cwd eslint.config.js", async () => { + eslint = new ESLint({ + flags, + cwd: __dirname, + }); + + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 4); + assert.strictEqual(results[0].messages[0].ruleId, "no-var"); + assert.strictEqual( + results[0].messages[1].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[2].ruleId, "quotes"); + assert.strictEqual(results[0].messages[3].ruleId, "eol-last"); + assert.strictEqual(results[0].fixableErrorCount, 3); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 2); + assert.strictEqual( + results[0].usedDeprecatedRules[0].ruleId, + "quotes", + ); + assert.strictEqual( + results[0].usedDeprecatedRules[1].ruleId, + "eol-last", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should report the total and per file warnings when not using a config file", async () => { + eslint = new ESLint({ + flags, + overrideConfig: { + rules: { + quotes: 1, + "no-var": 1, + "eol-last": 1, + "no-unused-vars": 1, + }, + }, + overrideConfigFile: true, + }); + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 4); + assert.strictEqual(results[0].messages[0].ruleId, "no-var"); + assert.strictEqual( + results[0].messages[1].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[2].ruleId, "quotes"); + assert.strictEqual(results[0].messages[3].ruleId, "eol-last"); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 3); + assert.strictEqual(results[0].usedDeprecatedRules.length, 2); + assert.strictEqual( + results[0].usedDeprecatedRules[0].ruleId, + "quotes", + ); + assert.strictEqual( + results[0].usedDeprecatedRules[1].ruleId, + "eol-last", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should report one message when using specific config file", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: + "fixtures/configurations/quotes-error.js", + cwd: getFixturePath(".."), + }); + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 1); + assert.strictEqual( + results[0].usedDeprecatedRules[0].ruleId, + "quotes", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should report the filename when passed in", async () => { + eslint = new ESLint({ + flags, + ignore: false, + cwd: getFixturePath(), + }); + const options = { filePath: "test.js" }; + const results = await eslint.lintText( + "var foo = 'bar';", + options, + ); + + assert.strictEqual( + results[0].filePath, + getFixturePath("test.js"), + ); + }); + + it("should return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is true", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(".."), + overrideConfigFile: + "fixtures/eslint.config-with-ignores.js", + }); + + const options = { + filePath: "fixtures/passing.js", + warnIgnored: true, + }; + const results = await eslint.lintText( + "var bar = foo;", + options, + ); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("passing.js"), + ); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + 'File ignored because of a matching ignore pattern. Use "--no-ignore" to disable file ignore settings or use "--no-warn-ignored" to suppress this warning.', + ); + assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return a warning when given a filename without a matching config by --stdin-filename if warnIgnored is true", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(".."), + overrideConfigFile: true, + }); + + const options = { + filePath: "fixtures/file.ts", + warnIgnored: true, + }; + const results = await eslint.lintText( + "type foo = { bar: string };", + options, + ); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("file.ts"), + ); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + "File ignored because no matching configuration was supplied.", + ); + assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return a warning when given a filename outside the base path by --stdin-filename if warnIgnored is true", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(), + overrideConfigFile: true, + }); + + const options = { filePath: "../file.js", warnIgnored: true }; + const results = await eslint.lintText( + "var bar = foo;", + options, + ); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("../file.js"), + ); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + "File ignored because outside of base path.", + ); + assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + if (os.platform() === "win32") { + it("should return a warning when given a filename on a different drive by --stdin-filename if warnIgnored is true on Windows", async () => { + const currentRoot = path.resolve("\\"); + const otherRoot = currentRoot === "A:\\" ? "B:\\" : "A:\\"; + + eslint = new ESLint({ + flags, + cwd: getFixturePath(), + overrideConfigFile: true, + }); + + const filePath = `${otherRoot}file.js`; + const options = { filePath, warnIgnored: true }; + const results = await eslint.lintText( + "var bar = foo;", + options, + ); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + "File ignored because outside of base path.", + ); + assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual( + results[0].usedDeprecatedRules.length, + 0, + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + } + + it("should return a warning when given a filename by --stdin-filename in excluded files list if constructor warnIgnored is false, but lintText warnIgnored is true", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(".."), + overrideConfigFile: + "fixtures/eslint.config-with-ignores.js", + warnIgnored: false, + }); + + const options = { + filePath: "fixtures/passing.js", + warnIgnored: true, + }; + const results = await eslint.lintText( + "var bar = foo;", + options, + ); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("passing.js"), + ); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + 'File ignored because of a matching ignore pattern. Use "--no-ignore" to disable file ignore settings or use "--no-warn-ignored" to suppress this warning.', + ); + assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should not return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is false", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(".."), + overrideConfigFile: + "fixtures/eslint.config-with-ignores.js", + }); + const options = { + filePath: "fixtures/passing.js", + warnIgnored: false, + }; + + // intentional parsing error + const results = await eslint.lintText( + "va r bar = foo;", + options, + ); + + // should not report anything because the file is ignored + assert.strictEqual(results.length, 0); + }); + + it("should not return a warning when given a filename by --stdin-filename in excluded files list if constructor warnIgnored is false", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(".."), + overrideConfigFile: + "fixtures/eslint.config-with-ignores.js", + warnIgnored: false, + }); + const options = { filePath: "fixtures/passing.js" }; + const results = await eslint.lintText( + "var bar = foo;", + options, + ); + + // should not report anything because the warning is suppressed + assert.strictEqual(results.length, 0); + }); + + it("should throw an error when there's no config file for a stdin file", () => { + eslint = new ESLint({ + flags, + cwd: "/", + }); + const options = { filePath: "fixtures/passing.js" }; + + return assert.rejects( + () => eslint.lintText("var bar = foo;", options), + /Could not find config file/u, + ); + }); + + it("should show excluded file warnings by default", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(".."), + overrideConfigFile: + "fixtures/eslint.config-with-ignores.js", + }); + const options = { filePath: "fixtures/passing.js" }; + const results = await eslint.lintText( + "var bar = foo;", + options, + ); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].messages[0].message, + 'File ignored because of a matching ignore pattern. Use "--no-ignore" to disable file ignore settings or use "--no-warn-ignored" to suppress this warning.', + ); + }); + + it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(".."), + ignore: false, + overrideConfigFile: + "fixtures/eslint.config-with-ignores.js", + overrideConfig: { + rules: { + "no-undef": 2, + }, + }, + }); + const options = { filePath: "fixtures/passing.js" }; + const results = await eslint.lintText( + "var bar = foo;", + options, + ); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("passing.js"), + ); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return a message and fixed text when in fix mode", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + fix: true, + overrideConfig: { + rules: { + semi: 2, + }, + }, + ignore: false, + cwd: getFixturePath(), + }); + const options = { filePath: "passing.js" }; + const results = await eslint.lintText("var bar = foo", options); + + assert.deepStrictEqual(results, [ + { + filePath: getFixturePath("passing.js"), + messages: [], + suppressedMessages: [], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var bar = foo;", + usedDeprecatedRules: [ + { + ruleId: "semi", + replacedBy: ["@stylistic/semi"], + info: coreRules.get("semi").meta.deprecated, + }, + ], + }, + ]); + }); + + it("should return a message and omit fixed text when in fix mode and fixes aren't done", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + fix: true, + overrideConfig: { + rules: { + "no-undef": 2, + }, + }, + ignore: false, + cwd: getFixturePath(), + }); + const options = { filePath: "passing.js" }; + const results = await eslint.lintText("var bar = foo", options); + + assert.deepStrictEqual(results, [ + { + filePath: getFixturePath("passing.js"), + messages: [ + { + ruleId: "no-undef", + severity: 2, + messageId: "undef", + message: "'foo' is not defined.", + line: 1, + column: 11, + endLine: 1, + endColumn: 14, + nodeType: "Identifier", + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "var bar = foo", + usedDeprecatedRules: [], + }, + ]); + }); + + it("should not delete code if there is a syntax error after trying to autofix.", async () => { + eslint = eslintWithPlugins({ + flags, + overrideConfigFile: true, + fix: true, + overrideConfig: { + rules: { + "example/make-syntax-error": "error", + }, + }, + ignore: false, + cwd: getFixturePath("."), + }); + const options = { filePath: "test.js" }; + const results = await eslint.lintText("var bar = foo", options); + + assert.deepStrictEqual(results, [ + { + filePath: getFixturePath("test.js"), + messages: [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Parsing error: Unexpected token is", + line: 1, + column: 19, + nodeType: null, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var bar = foothis is a syntax error.", + usedDeprecatedRules: [], + }, + ]); + }); + + it("should not crash even if there are any syntax error since the first time.", async () => { + eslint = eslintWithPlugins({ + flags, + overrideConfigFile: true, + fix: true, + overrideConfig: { + rules: { + "example/make-syntax-error": "error", + }, + }, + ignore: false, + cwd: getFixturePath(), + }); + const options = { filePath: "test.js" }; + const results = await eslint.lintText("var bar =", options); + + assert.deepStrictEqual(results, [ + { + filePath: getFixturePath("test.js"), + messages: [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Parsing error: Unexpected token", + line: 1, + column: 10, + nodeType: null, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "var bar =", + usedDeprecatedRules: [], + }, + ]); + }); + + it("should return source code of file in `source` property when errors are present", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { semi: 2 }, + }, + }); + const results = await eslint.lintText("var foo = 'bar'"); + + assert.strictEqual(results[0].source, "var foo = 'bar'"); + }); + + it("should return source code of file in `source` property when warnings are present", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { semi: 1 }, + }, + }); + const results = await eslint.lintText("var foo = 'bar'"); + + assert.strictEqual(results[0].source, "var foo = 'bar'"); + }); + + it("should not return a `source` property when no errors or warnings are present", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { semi: 2 }, + }, + }); + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].source, void 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should not return a `source` property when fixes are applied", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + fix: true, + overrideConfig: { + rules: { + semi: 2, + "no-unused-vars": 2, + }, + }, + }); + const results = await eslint.lintText("var msg = 'hi' + foo\n"); + + assert.strictEqual(results[0].source, void 0); + assert.strictEqual( + results[0].output, + "var msg = 'hi' + foo;\n", + ); + }); + + it("should return a `source` property when a parsing error has occurred", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { eqeqeq: 2 }, + }, + }); + const results = await eslint.lintText( + "var bar = foothis is a syntax error.\n return bar;", + ); + + assert.deepStrictEqual(results, [ + { + filePath: "", + messages: [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Parsing error: Unexpected token is", + line: 1, + column: 19, + nodeType: null, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "var bar = foothis is a syntax error.\n return bar;", + usedDeprecatedRules: [], + }, + ]); + }); + + // https://github.com/eslint/eslint/issues/5547 + it("should respect default ignore rules (ignoring node_modules), even with --no-ignore", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(), + ignore: false, + }); + const results = await eslint.lintText("var bar = foo;", { + filePath: "node_modules/passing.js", + warnIgnored: true, + }); + const expectedMsg = + 'File ignored by default because it is located under the node_modules directory. Use ignore pattern "!**/node_modules/" to disable file ignore settings or use "--no-warn-ignored" to suppress this warning.'; + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("node_modules/passing.js"), + ); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should warn when deprecated rules are found in a config", async () => { + eslint = new ESLint({ + flags, + cwd: originalDir, + overrideConfigFile: + "tests/fixtures/cli-engine/deprecated-rule-config/eslint.config.js", + }); + const [result] = await eslint.lintText("foo"); + + assert.deepStrictEqual(result.usedDeprecatedRules, [ + { + ruleId: "indent-legacy", + replacedBy: ["@stylistic/indent"], + info: coreRules.get("indent-legacy")?.meta.deprecated, + }, + ]); + }); + + it("should throw if eslint.config.js file is not present", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(".."), + }); + await assert.rejects( + () => eslint.lintText("var foo = 'bar';"), + /Could not find config file/u, + ); + }); + + it("should throw if eslint.config.js file is not present even if overrideConfig was passed", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(".."), + overrideConfig: { + rules: { + "no-unused-vars": 2, + }, + }, + }); + await assert.rejects( + () => eslint.lintText("var foo = 'bar';"), + /Could not find config file/u, + ); + }); + + it("should not throw if eslint.config.js file is not present and overrideConfigFile is `true`", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(".."), + overrideConfigFile: true, + }); + await eslint.lintText("var foo = 'bar';"); + }); + + it("should not throw if eslint.config.js file is not present and overrideConfigFile is path to a config file", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(".."), + overrideConfigFile: + "fixtures/configurations/quotes-error.js", + }); + await eslint.lintText("var foo = 'bar';"); + }); + + it("should throw if overrideConfigFile is path to a file that doesn't exist", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(""), + overrideConfigFile: "does-not-exist.js", + }); + await assert.rejects( + () => eslint.lintText("var foo = 'bar';"), + { code: "ENOENT" }, + ); + }); + + it("should throw if non-string value is given to 'code' parameter", async () => { + eslint = new ESLint({ flags }); + await assert.rejects( + () => eslint.lintText(100), + /'code' must be a string/u, + ); + }); + + it("should throw if non-object value is given to 'options' parameter", async () => { + eslint = new ESLint({ flags }); + await assert.rejects( + () => eslint.lintText("var a = 0", "foo.js"), + /'options' must be an object, null, or undefined/u, + ); + }); + + it("should throw if 'options' argument contains unknown key", async () => { + eslint = new ESLint({ flags }); + await assert.rejects( + () => eslint.lintText("var a = 0", { filename: "foo.js" }), + /'options' must not include the unknown option\(s\): filename/u, + ); + }); + + it("should throw if non-string value is given to 'options.filePath' option", async () => { + eslint = new ESLint({ flags }); + await assert.rejects( + () => eslint.lintText("var a = 0", { filePath: "" }), + /'options.filePath' must be a non-empty string or undefined/u, + ); + }); + + it("should throw if non-boolean value is given to 'options.warnIgnored' option", async () => { + eslint = new ESLint({ flags }); + await assert.rejects( + () => eslint.lintText("var a = 0", { warnIgnored: "" }), + /'options.warnIgnored' must be a boolean or undefined/u, + ); + }); + + it("should work with config file that exports a promise", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("promise-config"), + }); + const results = await eslint.lintText('var foo = "bar";'); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + }); + + describe("Alternate config files", () => { + it("should find eslint.config.mjs when present", async () => { + const cwd = getFixturePath("mjs-config"); + + eslint = new ESLint({ + flags, + cwd, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should find eslint.config.cjs when present", async () => { + const cwd = getFixturePath("cjs-config"); + + eslint = new ESLint({ + flags, + cwd, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should favor eslint.config.js when eslint.config.mjs and eslint.config.cjs are present", async () => { + const cwd = getFixturePath("js-mjs-cjs-config"); + + eslint = new ESLint({ + flags, + cwd, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should favor eslint.config.mjs when eslint.config.cjs is present", async () => { + const cwd = getFixturePath("mjs-cjs-config"); + + eslint = new ESLint({ + flags, + cwd, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + }); + + describe("TypeScript config files", () => { + JITI_VERSIONS.forEach(jitiVersion => { + describe(`Loading TypeScript config files with ${jitiVersion}`, () => { + if (jitiVersion !== "jiti") { + beforeEach(() => { + sinon + .stub(ConfigLoader, "loadJiti") + .callsFake(() => + Promise.resolve({ + createJiti: + require(jitiVersion).createJiti, + version: require( + `${jitiVersion}/package.json`, + ).version, + }), + ); + }); + } + + it("should find and load eslint.config.ts when present", async () => { + const cwd = getFixturePath("ts-config-files", "ts"); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts when we have "type": "commonjs" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-commonjs", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts when we have "type": "module" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-module", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should load eslint.config.ts with const enums", async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "const-enums", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should load eslint.config.ts with local namespace", async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "local-namespace", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should allow passing a TS config file to `overrideConfigFile`", async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "custom-config", + ); + + eslint = new ESLint({ + cwd, + flags, + overrideConfigFile: getFixturePath( + "ts-config-files", + "ts", + "custom-config", + "eslint.custom.config.ts", + ), + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should find and load eslint.config.mts when present", async () => { + const cwd = getFixturePath( + "ts-config-files", + "mts", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.mts when we have "type": "commonjs" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "mts", + "with-type-commonjs", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.mts config file when we have "type": "module" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "mts", + "with-type-module", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should find and load eslint.config.cts when present", async () => { + const cwd = getFixturePath( + "ts-config-files", + "cts", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.cts config file when we have "type": "commonjs" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "cts", + "with-type-commonjs", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load .cts config file when we have "type": "module" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "cts", + "with-type-module", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should successfully load a TS config file that exports a promise", async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "exports-promise", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintText("foo;"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should load a CommonJS TS config file that exports undefined with a helpful warning message", async () => { + sinon.restore(); + + const cwd = getFixturePath("ts-config-files", "ts"); + const processStub = sinon.stub( + process, + "emitWarning", + ); + + eslint = new ESLint({ + cwd, + flags, + overrideConfigFile: + "eslint.undefined.config.ts", + }); + + await eslint.lintText("foo"); + + assert.strictEqual( + processStub.callCount, + 1, + "calls `process.emitWarning()` once", + ); + assert.strictEqual( + processStub.getCall(0).args[1], + "ESLintEmptyConfigWarning", + ); + }); + }); + }); + + it("should fail to load a TS config file if jiti is not installed", async () => { + sinon.stub(ConfigLoader, "loadJiti").rejects(); + + const cwd = getFixturePath("ts-config-files", "ts"); + + eslint = new ESLint({ + cwd, + flags, + }); + + await assert.rejects(eslint.lintText("foo();"), { + message: + "The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.", + }); + }); + + it("should fail to load a TS config file if an outdated version of jiti is installed", async () => { + sinon + .stub(ConfigLoader, "loadJiti") + .resolves({ createJiti: void 0, version: "1.21.7" }); + + const cwd = getFixturePath("ts-config-files", "ts"); + + eslint = new ESLint({ + cwd, + flags, + }); + + await assert.rejects(eslint.lintText("foo();"), { + message: + "You are using an outdated version of the 'jiti' library. Please update to the latest version of 'jiti' to ensure compatibility and access to the latest features.", + }); + }); + + it("should handle jiti interopDefault edge cases", async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "jiti-interopDefault", + ); + + await fsp.writeFile( + path.join(cwd, "eslint.config.ts"), + ` + import plugin from "./plugin"; + + export default plugin.configs.recommended; + + // Autogenerated on ${new Date().toISOString()}.`, + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + // eslint-disable-next-line n/no-unsupported-features/node-builtins -- it's still an experimental feature. + (typeof process.features.typescript === "string" + ? describe + : describe.skip)( + "Loading TypeScript config files natively", + () => { + beforeEach(() => { + sinon.stub(ConfigLoader, "loadJiti").rejects(); + }); + + const cwd = getFixturePath( + "ts-config-files", + "ts", + "native", + ); + + const overrideConfigFile = "eslint.config.ts"; + + it("should load a TS config file when --experimental-strip-types is enabled", async () => { + const configFileContent = `import type { FlatConfig } from "./helper.ts";\nexport default ${JSON.stringify( + [{ rules: { "no-undef": 2 } }], + null, + 2, + )} satisfies FlatConfig[];`; + + const teardown = createCustomTeardown({ + cwd, + files: { + [overrideConfigFile]: configFileContent, + "foo.js": "foo;", + "helper.ts": + 'import type { Linter } from "eslint";\nexport type FlatConfig = Linter.Config;\n', + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile, + flags: nativeTSConfigFileFlags, + }); + + const results = await eslint.lintText("foo;"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + // eslint-disable-next-line n/no-unsupported-features/node-builtins -- it's still an experimental feature. + (process.features.typescript === "transform" + ? it + : it.skip)( + "should load a TS config file when --experimental-transform-types is enabled", + async () => { + const configFileContent = + 'import { ESLintNameSpace } from "./helper.ts";\nexport default [ { rules: { "no-undef": ESLintNameSpace.StringSeverity.Error } }];\n'; + + const teardown = createCustomTeardown({ + cwd, + files: { + [overrideConfigFile]: configFileContent, + "foo.js": "foo;", + "helper.ts": + 'export namespace ESLintNameSpace {\n export const enum StringSeverity {\n "Off" = "off",\n "Warn" = "warn",\n "Error" = "error",\n }\n}\n', + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile, + flags: nativeTSConfigFileFlags, + }); + + const results = await eslint.lintText("foo;"); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].messages.length, + 1, + ); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }, + ); + }, + ); + }); + + it("should pass BOM through processors", async () => { + eslint = new ESLint({ + overrideConfigFile: true, + overrideConfig: [ + { + files: ["**/*.myjs"], + processor: { + preprocess(text, filename) { + return [{ text, filename }]; + }, + postprocess(messages) { + return messages.flat(); + }, + supportsAutofix: true, + }, + rules: { + "unicode-bom": ["error", "never"], + }, + }, + ], + cwd: path.join(fixtureDir), + }); + const results = await eslint.lintText( + "\uFEFFvar foo = 'bar';", + { filePath: "test.myjs" }, + ); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "unicode-bom", + ); + }); + }); + + describe("lintFiles()", () => { + /** @type {InstanceType} */ + let eslint; + + it("should use correct parser when custom parser is specified", async () => { + const filePath = path.resolve( + __dirname, + "../../fixtures/configurations/parser/custom.js", + ); + + eslint = new ESLint({ + flags, + cwd: originalDir, + ignore: false, + overrideConfigFile: true, + overrideConfig: { + languageOptions: { + parser: require(filePath), + }, + }, + }); + + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].message, + "Parsing error: Boom!", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should report zero messages when given a config file and a valid file", async () => { + eslint = new ESLint({ + flags, + cwd: originalDir, + overrideConfigFile: + "tests/fixtures/simple-valid-project/eslint.config.js", + }); + const results = await eslint.lintFiles([ + "tests/fixtures/simple-valid-project/**/foo*.js", + ]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should handle multiple patterns with overlapping files", async () => { + eslint = new ESLint({ + flags, + cwd: originalDir, + overrideConfigFile: + "tests/fixtures/simple-valid-project/eslint.config.js", + }); + const results = await eslint.lintFiles([ + "tests/fixtures/simple-valid-project/**/foo*.js", + "tests/fixtures/simple-valid-project/foo.?s", + "tests/fixtures/simple-valid-project/{foo,src/foobar}.js", + ]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should report zero messages when given a config file and a valid file and espree as parser", async () => { + eslint = new ESLint({ + flags, + overrideConfig: { + languageOptions: { + parser: require("espree"), + parserOptions: { + ecmaVersion: 2021, + }, + }, + }, + overrideConfigFile: true, + }); + const results = await eslint.lintFiles(["lib/cli.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should report zero messages when given a config file and a valid file and esprima as parser", async () => { + eslint = new ESLint({ + flags, + overrideConfig: { + languageOptions: { + parser: require("esprima"), + }, + }, + overrideConfigFile: true, + ignore: false, + }); + const results = await eslint.lintFiles([ + "tests/fixtures/passing.js", + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + describe("Missing Configuration File", () => { + const workDirName = "no-config-file"; + const workDir = path.resolve( + fs.realpathSync(os.tmpdir()), + "eslint/no-config", + ); + + // copy into clean area so as not to get "infected" by other config files + before(() => { + shell.mkdir("-p", workDir); + shell.cp("-r", `./tests/fixtures/${workDirName}`, workDir); + }); + + after(() => { + shell.rm("-r", workDir); + }); + + it(`${flags}:should throw if eslint.config.js file is not present`, async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + await assert.rejects( + () => eslint.lintFiles("no-config-file/*.js"), + /Could not find config file/u, + ); + }); + + it("should throw if eslint.config.js file is not present even if overrideConfig was passed", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(".."), + overrideConfig: { + rules: { + "no-unused-vars": 2, + }, + }, + }); + await assert.rejects( + () => eslint.lintFiles("no-config/no-config-file/*.js"), + /Could not find config file/u, + ); + }); + + it("should throw if eslint.config.js file is not present even if overrideConfig was passed and a file path is given", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(".."), + overrideConfig: { + rules: { + "no-unused-vars": 2, + }, + }, + }); + await assert.rejects( + () => + eslint.lintFiles("no-config/no-config-file/foo.js"), + /Could not find config file/u, + ); + }); + + it("should not throw if eslint.config.js file is not present and overrideConfigFile is `true`", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + overrideConfigFile: true, + }); + await eslint.lintFiles("no-config-file/*.js"); + }); + + it("should not throw if eslint.config.js file is not present and overrideConfigFile is path to a config file", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + overrideConfigFile: path.join( + fixtureDir, + "configurations/quotes-error.js", + ), + }); + await eslint.lintFiles("no-config-file/*.js"); + }); + }); + + it("should throw if overrideConfigFile is path to a file that doesn't exist", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(), + overrideConfigFile: "does-not-exist.js", + }); + await assert.rejects(() => eslint.lintFiles("undef*.js"), { + code: "ENOENT", + }); + }); + + it("should throw an error when given a config file and a valid file and invalid parser", async () => { + eslint = new ESLint({ + flags, + overrideConfig: { + languageOptions: { + parser: "test11", + }, + }, + overrideConfigFile: true, + }); + + await assert.rejects( + async () => await eslint.lintFiles(["lib/cli.js"]), + /Expected object with parse\(\) or parseForESLint\(\) method/u, + ); + }); + + // https://github.com/eslint/eslint/issues/18407 + it("should work in case when `fsp.readFile()` returns an object that is not an instance of Promise from this realm", async () => { + /** + * Promise wrapper + */ + class PromiseLike { + constructor(promise) { + this.promise = promise; + } + then(...args) { + return new PromiseLike(this.promise.then(...args)); + } + catch(...args) { + return new PromiseLike(this.promise.catch(...args)); + } + finally(...args) { + return new PromiseLike(this.promise.finally(...args)); + } + } + + const spy = sinon.spy( + (...args) => new PromiseLike(fsp.readFile(...args)), + ); + + const { ESLint: LocalESLint } = proxyquire( + "../../../lib/eslint/eslint", + { + "node:fs/promises": { + readFile: spy, + "@noCallThru": false, // allows calling other methods of `fs/promises` + }, + }, + ); + + const testDir = "tests/fixtures/simple-valid-project"; + const expectedLintedFiles = [ + path.resolve(testDir, "foo.js"), + path.resolve(testDir, "src", "foobar.js"), + ]; + + eslint = new LocalESLint({ + flags, + cwd: originalDir, + overrideConfigFile: path.resolve( + testDir, + "eslint.config.js", + ), + }); + + const results = await eslint.lintFiles([ + `${testDir}/**/foo*.js`, + ]); + + assert.strictEqual(results.length, expectedLintedFiles.length); + + expectedLintedFiles.forEach((file, index) => { + assert( + spy.calledWith(file), + `Spy was not called with ${file}`, + ); + assert.strictEqual(results[index].filePath, file); + assert.strictEqual(results[index].messages.length, 0); + assert.strictEqual( + results[index].suppressedMessages.length, + 0, + ); + }); + }); + + describe("Overlapping searches", () => { + it("should not lint the same file multiple times when the file path was passed multiple times", async () => { + const cwd = getFixturePath(); + + eslint = new ESLint({ + flags, + cwd, + overrideConfigFile: true, + }); + + const results = await eslint.lintFiles([ + "files/foo.js", + "files/../files/foo.js", + "files/foo.js", + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(cwd, "files/foo.js"), + ); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should not lint the same file multiple times when the file path and a pattern that matches the file were passed", async () => { + const cwd = getFixturePath(); + + eslint = new ESLint({ + flags, + cwd, + overrideConfigFile: true, + }); + + const results = await eslint.lintFiles([ + "files/foo.js", + "files/foo*", + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(cwd, "files/foo.js"), + ); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should not lint the same file multiple times when multiple patterns that match the file were passed", async () => { + const cwd = getFixturePath(); + + eslint = new ESLint({ + flags, + cwd, + overrideConfigFile: true, + }); + + const results = await eslint.lintFiles([ + "files/f*.js", + "files/foo*", + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(cwd, "files/foo.js"), + ); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + }); + + describe("Invalid inputs", () => { + [ + ["a string with a single space", " "], + ["an array with one empty string", [""]], + ["an array with two empty strings", ["", ""]], + ["undefined", void 0], + ].forEach(([name, value]) => { + it(`should throw an error when passed ${name}`, async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + }); + + await assert.rejects( + async () => await eslint.lintFiles(value), + /'patterns' must be a non-empty string or an array of non-empty strings/u, + ); + }); + }); + }); + + describe("Normalized inputs", () => { + [ + ["an empty string", ""], + ["an empty array", []], + ].forEach(([name, value]) => { + it(`should normalize to '.' when ${name} is passed`, async () => { + eslint = new ESLint({ + flags, + ignore: false, + cwd: getFixturePath("files"), + overrideConfig: { files: ["**/*.js"] }, + overrideConfigFile: + getFixturePath("eslint.config.js"), + }); + const results = await eslint.lintFiles(value); + + assert.strictEqual(results.length, 2); + assert.strictEqual( + results[0].filePath, + getFixturePath("files/.bar.js"), + ); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual( + results[1].filePath, + getFixturePath("files/foo.js"), + ); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual( + results[0].suppressedMessages.length, + 0, + ); + }); + + it(`should return an empty array when ${name} is passed with passOnNoPatterns: true`, async () => { + eslint = new ESLint({ + flags, + ignore: false, + cwd: getFixturePath("files"), + overrideConfig: { files: ["**/*.js"] }, + overrideConfigFile: + getFixturePath("eslint.config.js"), + passOnNoPatterns: true, + }); + const results = await eslint.lintFiles(value); + + assert.strictEqual(results.length, 0); + }); + }); + }); + + it("should report zero messages when given a directory with a .js2 file", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("eslint.config.js"), + overrideConfig: { + files: ["**/*.js2"], + }, + }); + const results = await eslint.lintFiles([ + getFixturePath("files/foo.js2"), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should report zero messages when given a directory with a .js and a .js2 file", async () => { + eslint = new ESLint({ + flags, + ignore: false, + cwd: getFixturePath(".."), + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + overrideConfigFile: getFixturePath("eslint.config.js"), + }); + const results = await eslint.lintFiles(["fixtures/files/"]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/18550 + it("should skip files with non-standard extensions when they're matched only by a '*' files pattern", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("files"), + overrideConfig: { files: ["*"] }, + overrideConfigFile: true, + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 2); + assert( + results.every(result => + /^\.[cm]?js$/u.test(path.extname(result.filePath)), + ), + "File with a non-standard extension was linted", + ); + }); + + // https://github.com/eslint/eslint/issues/16413 + it("should find files and report zero messages when given a parent directory with a .js", async () => { + eslint = new ESLint({ + flags, + ignore: false, + cwd: getFixturePath("example-app/subdir"), + }); + const results = await eslint.lintFiles(["../*.js"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/16038 + it("should allow files patterns with '..' inside", async () => { + eslint = new ESLint({ + flags, + ignore: false, + cwd: getFixturePath("dots-in-files"), + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual( + results[0].filePath, + getFixturePath("dots-in-files/a..b.js"), + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/16299 + it("should only find files in the subdir1 directory when given a directory name", async () => { + eslint = new ESLint({ + flags, + ignore: false, + cwd: getFixturePath("example-app2"), + }); + const results = await eslint.lintFiles(["subdir1"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual( + results[0].filePath, + getFixturePath("example-app2/subdir1/a.js"), + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/14742 + it("should run", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("{curly-path}", "server"), + }); + const results = await eslint.lintFiles(["src/**/*.{js,json}"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-console"); + assert.strictEqual( + results[0].filePath, + getFixturePath("{curly-path}/server/src/two.js"), + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should work with config file that exports a promise", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("promise-config"), + }); + const results = await eslint.lintFiles(["a*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("promise-config", "a.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + }); + + // https://github.com/eslint/eslint/issues/16265 + describe("Dot files in searches", () => { + it("should find dot files in current directory when a . pattern is used", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("dot-files"), + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual( + results[0].filePath, + getFixturePath("dot-files/.a.js"), + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual( + results[1].filePath, + getFixturePath("dot-files/.c.js"), + ); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual( + results[2].filePath, + getFixturePath("dot-files/b.js"), + ); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + + it("should find dot files in current directory when a *.js pattern is used", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("dot-files"), + }); + const results = await eslint.lintFiles(["*.js"]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual( + results[0].filePath, + getFixturePath("dot-files/.a.js"), + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual( + results[1].filePath, + getFixturePath("dot-files/.c.js"), + ); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual( + results[2].filePath, + getFixturePath("dot-files/b.js"), + ); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + + it("should find dot files in current directory when a .a.js pattern is used", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("dot-files"), + }); + const results = await eslint.lintFiles([".a.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual( + results[0].filePath, + getFixturePath("dot-files/.a.js"), + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + }); + + // https://github.com/eslint/eslint/issues/16275 + describe("Glob patterns without matches", () => { + it("should throw an error for a missing pattern when combined with a found pattern", async () => { + eslint = new ESLint({ + flags, + ignore: false, + cwd: getFixturePath("example-app2"), + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + "subdir1", + "doesnotexist/*.js", + ]); + }, /No files matching 'doesnotexist\/\*\.js' were found/u); + }); + + it("should throw an error for an ignored directory pattern when combined with a found pattern", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir2"], + }, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + "subdir1/*.js", + "subdir2/*.js", + ]); + }, /All files matched by 'subdir2\/\*\.js' are ignored/u); + }); + + it("should throw an error for an ignored file pattern when combined with a found pattern", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir2/*.js"], + }, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + "subdir1/*.js", + "subdir2/*.js", + ]); + }, /All files matched by 'subdir2\/\*\.js' are ignored/u); + }); + + it("should always throw an error for the first unmatched file pattern", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir1/*.js", "subdir2/*.js"], + }, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + "doesnotexist1/*.js", + "doesnotexist2/*.js", + ]); + }, /No files matching 'doesnotexist1\/\*\.js' were found/u); + + await assert.rejects(async () => { + await eslint.lintFiles([ + "doesnotexist1/*.js", + "subdir1/*.js", + ]); + }, /No files matching 'doesnotexist1\/\*\.js' were found/u); + + await assert.rejects(async () => { + await eslint.lintFiles([ + "subdir1/*.js", + "doesnotexist1/*.js", + ]); + }, /All files matched by 'subdir1\/\*\.js' are ignored/u); + + await assert.rejects(async () => { + await eslint.lintFiles([ + "subdir1/*.js", + "subdir2/*.js", + ]); + }, /All files matched by 'subdir1\/\*\.js' are ignored/u); + }); + + it("should not throw an error for an ignored file pattern when errorOnUnmatchedPattern is false", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("example-app2"), + overrideConfig: { + ignores: ["subdir2/*.js"], + }, + errorOnUnmatchedPattern: false, + }); + + const results = await eslint.lintFiles(["subdir2/*.js"]); + + assert.strictEqual(results.length, 0); + }); + + it("should not throw an error for a non-existing file pattern when errorOnUnmatchedPattern is false", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("example-app2"), + errorOnUnmatchedPattern: false, + }); + + const results = await eslint.lintFiles(["doesexist/*.js"]); + + assert.strictEqual(results.length, 0); + }); + }); + + // https://github.com/eslint/eslint/issues/16260 + describe("Globbing based on configs", () => { + it("should report zero messages when given a directory with a .js and config file specifying a subdirectory", async () => { + eslint = new ESLint({ + flags, + ignore: false, + cwd: getFixturePath("shallow-glob"), + }); + const results = await eslint.lintFiles(["target-dir"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should glob for .jsx file in a subdirectory of the passed-in directory and not glob for any other patterns", async () => { + eslint = new ESLint({ + flags, + ignore: false, + overrideConfigFile: true, + overrideConfig: { + files: ["subdir/**/*.jsx", "target-dir/*.js"], + languageOptions: { + parserOptions: { + jsx: true, + }, + }, + }, + cwd: getFixturePath("shallow-glob"), + }); + const results = await eslint.lintFiles([ + "subdir/subsubdir", + ]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath( + "shallow-glob/subdir/subsubdir/broken.js", + ), + ); + assert( + results[0].messages[0].fatal, + "Fatal error expected.", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + results[1].filePath, + getFixturePath( + "shallow-glob/subdir/subsubdir/plain.jsx", + ), + ); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + }); + + it("should glob for all files in subdir when passed-in on the command line with a partial matching glob", async () => { + eslint = new ESLint({ + flags, + ignore: false, + overrideConfigFile: true, + overrideConfig: { + files: ["s*/subsubdir/*.jsx", "target-dir/*.js"], + languageOptions: { + parserOptions: { + jsx: true, + }, + }, + }, + cwd: getFixturePath("shallow-glob"), + }); + const results = await eslint.lintFiles(["subdir"]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 1); + assert( + results[0].messages[0].fatal, + "Fatal error expected.", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 1); + assert( + results[0].messages[0].fatal, + "Fatal error expected.", + ); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + }); + + describe("Globbing based on configs with negated patterns and arrays in `files`", () => { + // https://github.com/eslint/eslint/issues/19813 + it("should not include custom extensions when negated pattern is specified in `files`", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("file-extensions"), + overrideConfigFile: true, + overrideConfig: [ + { + files: ["!foo.js"], + }, + { + files: ["!foo.jsx"], + }, + { + files: ["!foo.ts"], + }, + { + files: ["!g.tsx"], + }, + ], + }); + const results = await eslint.lintFiles(["."]); + + // should not include d.jsx, f.ts, and other extensions that are not linted by default + assert.strictEqual(results.length, 4); + assert.deepStrictEqual( + results.map(({ filePath }) => path.basename(filePath)), + ["a.js", "b.mjs", "c.cjs", "eslint.config.js"], + ); + }); + + it("should not include custom extensions when negated pattern is specified in an array in `files`", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("file-extensions"), + overrideConfigFile: true, + overrideConfig: [ + { + files: [["*", "!foo.js"]], + }, + { + files: [["!foo.js", "*"]], + }, + { + files: [["*", "!foo.ts"]], + }, + { + files: [["!foo.ts", "*"]], + }, + { + files: [["*", "!g.tsx"]], + }, + { + files: [["!g.tsx", "*"]], + }, + ], + }); + const results = await eslint.lintFiles(["."]); + + // should not include d.jsx, f.ts, and other extensions that are not linted by default + assert.strictEqual(results.length, 4); + assert.deepStrictEqual( + results.map(({ filePath }) => path.basename(filePath)), + ["a.js", "b.mjs", "c.cjs", "eslint.config.js"], + ); + }); + + // https://github.com/eslint/eslint/issues/19814 + it("should include custom extensions when matched by a non-universal pattern specified in an array in `files`", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("file-extensions", ".."), + overrideConfigFile: true, + overrideConfig: [ + { + files: [["**/*.jsx", "file-extensions/*"]], + }, + { + files: [["file-extensions/*", "**/*.ts"]], + }, + ], + }); + const results = await eslint.lintFiles(["file-extensions"]); + + // should include d.jsx and f.ts, but not other extensions that are not linted by default + assert.strictEqual(results.length, 6); + assert.deepStrictEqual( + results.map(({ filePath }) => path.basename(filePath)), + [ + "a.js", + "b.mjs", + "c.cjs", + "d.jsx", + "eslint.config.js", + "f.ts", + ], + ); + }); + }); + + it("should report zero messages when given a '**' pattern with a .js and a .js2 file", async () => { + eslint = new ESLint({ + flags, + ignore: false, + cwd: path.join(fixtureDir, ".."), + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + overrideConfigFile: getFixturePath("eslint.config.js"), + }); + const results = await eslint.lintFiles(["fixtures/files/*"]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + + it("should resolve globs when 'globInputPaths' option is true", async () => { + eslint = new ESLint({ + flags, + ignore: false, + cwd: getFixturePath(".."), + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + overrideConfigFile: getFixturePath("eslint.config.js"), + }); + const results = await eslint.lintFiles(["fixtures/files/*"]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + + // only works on a Windows machine + if (os.platform() === "win32") { + it("should resolve globs with Windows slashes when 'globInputPaths' option is true", async () => { + eslint = new ESLint({ + flags, + ignore: false, + cwd: getFixturePath(".."), + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + overrideConfigFile: getFixturePath("eslint.config.js"), + }); + const results = await eslint.lintFiles([ + "fixtures\\files\\*", + ]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + } + + it("should not resolve globs when 'globInputPaths' option is false", async () => { + eslint = new ESLint({ + flags, + ignore: false, + cwd: getFixturePath(".."), + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + overrideConfigFile: true, + globInputPaths: false, + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["fixtures/files/*"]); + }, /No files matching 'fixtures\/files\/\*' were found \(glob was disabled\)\./u); + }); + + describe("Ignoring Files", () => { + it("should report on a file in the node_modules folder passed explicitly, even if ignored by default", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("cli-engine"), + }); + const results = await eslint.lintFiles([ + "node_modules/foo.js", + ]); + const expectedMsg = + 'File ignored by default because it is located under the node_modules directory. Use ignore pattern "!**/node_modules/" to disable file ignore settings or use "--no-warn-ignored" to suppress this warning.'; + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + expectedMsg, + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should report on a file in a node_modules subfolder passed explicitly, even if ignored by default", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("cli-engine"), + }); + const results = await eslint.lintFiles([ + "nested_node_modules/subdir/node_modules/text.js", + ]); + const expectedMsg = + 'File ignored by default because it is located under the node_modules directory. Use ignore pattern "!**/node_modules/" to disable file ignore settings or use "--no-warn-ignored" to suppress this warning.'; + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + expectedMsg, + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it('should report on an ignored file with "node_modules" in its name', async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("cli-engine"), + ignorePatterns: ["*.js"], + }); + const results = await eslint.lintFiles([ + "node_modules_cleaner.js", + ]); + const expectedMsg = + 'File ignored because of a matching ignore pattern. Use "--no-ignore" to disable file ignore settings or use "--no-warn-ignored" to suppress this warning.'; + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + expectedMsg, + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should suppress the warning when a file in the node_modules folder passed explicitly and warnIgnored is false", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("cli-engine"), + warnIgnored: false, + }); + const results = await eslint.lintFiles([ + "node_modules/foo.js", + ]); + + assert.strictEqual(results.length, 0); + }); + + it("should report on globs with explicit inclusion of dotfiles", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("cli-engine"), + overrideConfigFile: true, + overrideConfig: { + rules: { + quotes: [2, "single"], + }, + }, + }); + const results = await eslint.lintFiles([ + "hidden/.hiddenfolder/*.js", + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should ignore node_modules files when using ignore file", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("cli-engine"), + overrideConfigFile: true, + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["node_modules"]); + }, /All files matched by 'node_modules' are ignored\./u); + }); + + // https://github.com/eslint/eslint/issues/5547 + it("should ignore node_modules files even with ignore: false", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("cli-engine"), + ignore: false, + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["node_modules"]); + }, /All files matched by 'node_modules' are ignored\./u); + }); + + it("should throw an error when all given files are ignored", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: getFixturePath( + "eslint.config-with-ignores.js", + ), + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["tests/fixtures/cli-engine/"]); + }, /All files matched by 'tests\/fixtures\/cli-engine\/' are ignored\./u); + }); + + it("should throw an error when all given files are ignored by a config object that has `name`", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: getFixturePath( + "eslint.config-with-ignores3.js", + ), + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["tests/fixtures/cli-engine/"]); + }, /All files matched by 'tests\/fixtures\/cli-engine\/' are ignored\./u); + }); + + it("should throw an error when all given files are ignored even with a `./` prefix", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: getFixturePath( + "eslint.config-with-ignores.js", + ), + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + "./tests/fixtures/cli-engine/", + ]); + }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); + }); + + // https://github.com/eslint/eslint/issues/3788 + it("should ignore one-level down node_modules by default", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { + quotes: [2, "double"], + }, + }, + cwd: getFixturePath( + "cli-engine", + "nested_node_modules", + ), + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + // https://github.com/eslint/eslint/issues/3812 + it("should ignore all files and throw an error when **/fixtures/** is in `ignores` in the config file", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: getFixturePath( + "cli-engine/eslint.config-with-ignores2.js", + ), + overrideConfig: { + rules: { + quotes: [2, "double"], + }, + }, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + "./tests/fixtures/cli-engine/", + ]); + }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); + }); + + it("should throw an error when all given files are ignored via ignorePatterns", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + ignorePatterns: ["tests/fixtures/single-quoted.js"], + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["tests/fixtures/*-quoted.js"]); + }, /All files matched by 'tests\/fixtures\/\*-quoted\.js' are ignored\./u); + }); + + it("should not throw an error when ignorePatterns is an empty array", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + ignorePatterns: [], + }); + + await assert.doesNotReject(async () => { + await eslint.lintFiles(["*.js"]); + }); + }); + + it("should return a warning when an explicitly given file is ignored", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: "eslint.config-with-ignores.js", + cwd: getFixturePath(), + }); + const filePath = getFixturePath("passing.js"); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + 'File ignored because of a matching ignore pattern. Use "--no-ignore" to disable file ignore settings or use "--no-warn-ignored" to suppress this warning.', + ); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return a warning when an explicitly given file has no matching config", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + cwd: getFixturePath(), + }); + const filePath = getFixturePath("files", "foo.js2"); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + "File ignored because no matching configuration was supplied.", + ); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return a warning when an explicitly given file is outside the base path", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + cwd: getFixturePath("files"), + }); + const filePath = getFixturePath("passing.js"); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + "File ignored because outside of base path.", + ); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should suppress the warning when an explicitly given file is ignored and warnIgnored is false", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: "eslint.config-with-ignores.js", + cwd: getFixturePath(), + warnIgnored: false, + }); + const filePath = getFixturePath("passing.js"); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 0); + }); + + it("should return a warning about matching ignore patterns when an explicitly given dotfile is ignored", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: "eslint.config-with-ignores.js", + cwd: getFixturePath(), + }); + const filePath = getFixturePath("dot-files/.a.js"); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + 'File ignored because of a matching ignore pattern. Use "--no-ignore" to disable file ignore settings or use "--no-warn-ignored" to suppress this warning.', + ); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return two messages when given a file in excluded files list while ignore is off", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(), + ignore: false, + overrideConfigFile: getFixturePath( + "eslint.config-with-ignores.js", + ), + overrideConfig: { + rules: { + "no-undef": 2, + }, + }, + }); + const filePath = fs.realpathSync( + getFixturePath("undef.js"), + ); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual( + results[0].messages[1].ruleId, + "no-undef", + ); + assert.strictEqual(results[0].messages[1].severity, 2); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return two messages when given a file in excluded files list by a config object that has `name` while ignore is off", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(), + ignore: false, + overrideConfigFile: getFixturePath( + "eslint.config-with-ignores3.js", + ), + overrideConfig: { + rules: { + "no-undef": 2, + }, + }, + }); + const filePath = fs.realpathSync( + getFixturePath("undef.js"), + ); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual( + results[0].messages[1].ruleId, + "no-undef", + ); + assert.strictEqual(results[0].messages[1].severity, 2); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/16300 + it("should process ignore patterns relative to basePath not cwd", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("ignores-relative/subdir"), + }); + const results = await eslint.lintFiles(["**/*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("ignores-relative/subdir/a.js"), + ); + }); + + // https://github.com/eslint/eslint/issues/16354 + it("should skip subdirectory files when ignore pattern matches deep subdirectory", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("ignores-directory"), + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir/**"]); + }, /All files matched by 'subdir\/\*\*' are ignored\./u); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir/subsubdir/**"]); + }, /All files matched by 'subdir\/subsubdir\/\*\*' are ignored\./u); + + const results = await eslint.lintFiles([ + "subdir/subsubdir/a.js", + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath( + "ignores-directory/subdir/subsubdir/a.js", + ), + ); + assert.strictEqual(results[0].warningCount, 1); + assert( + results[0].messages[0].message.startsWith( + "File ignored", + ), + "Should contain file ignored warning", + ); + }); + + // https://github.com/eslint/eslint/issues/16414 + it("should skip subdirectory files when ignore pattern matches subdirectory", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("ignores-subdirectory"), + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subdir/**/*.js"]); + }, /All files matched by 'subdir\/\*\*\/\*\.js' are ignored\./u); + + const results = await eslint.lintFiles([ + "subdir/subsubdir/a.js", + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath( + "ignores-subdirectory/subdir/subsubdir/a.js", + ), + ); + assert.strictEqual(results[0].warningCount, 1); + assert( + results[0].messages[0].message.startsWith( + "File ignored", + ), + "Should contain file ignored warning", + ); + + eslint = new ESLint({ + flags, + cwd: getFixturePath("ignores-subdirectory/subdir"), + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["subsubdir/**/*.js"]); + }, /All files matched by 'subsubdir\/\*\*\/\*\.js' are ignored\./u); + }); + + // https://github.com/eslint/eslint/issues/16340 + it("should lint files even when cwd directory name matches ignores pattern", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("ignores-self"), + }); + + const results = await eslint.lintFiles(["*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("ignores-self/eslint.config.js"), + ); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + }); + + // https://github.com/eslint/eslint/issues/16416 + it("should allow reignoring of previously ignored files", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("ignores-relative"), + overrideConfigFile: true, + overrideConfig: { + ignores: ["*.js", "!a*.js", "a.js"], + }, + }); + const results = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("ignores-relative/a.js"), + ); + }); + + // https://github.com/eslint/eslint/issues/16415 + it("should allow directories to be unignored", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("ignores-directory"), + overrideConfigFile: true, + overrideConfig: { + ignores: ["subdir/*", "!subdir/subsubdir"], + }, + }); + const results = await eslint.lintFiles(["subdir/**/*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual( + results[0].filePath, + getFixturePath( + "ignores-directory/subdir/subsubdir/a.js", + ), + ); + }); + + // https://github.com/eslint/eslint/issues/17964#issuecomment-1879840650 + it("should allow directories to be unignored without also unignoring all files in them", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("ignores-directory-deep"), + overrideConfigFile: true, + overrideConfig: { + ignores: [ + // ignore all files and directories + "tests/format/**/*", + + // unignore all directories + "!tests/format/**/*/", + + // unignore only specific files + "!tests/format/**/jsfmt.spec.js", + ], + }, + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual( + results[0].filePath, + getFixturePath( + "ignores-directory-deep/tests/format/jsfmt.spec.js", + ), + ); + assert.strictEqual(results[1].errorCount, 0); + assert.strictEqual(results[1].warningCount, 0); + assert.strictEqual( + results[1].filePath, + getFixturePath( + "ignores-directory-deep/tests/format/subdir/jsfmt.spec.js", + ), + ); + }); + + it("should allow only subdirectories to be ignored by a pattern ending with '/'", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("ignores-directory-deep"), + overrideConfigFile: true, + overrideConfig: { + ignores: ["tests/format/*/"], + }, + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual( + results[0].filePath, + getFixturePath( + "ignores-directory-deep/tests/format/foo.js", + ), + ); + assert.strictEqual(results[1].errorCount, 0); + assert.strictEqual(results[1].warningCount, 0); + assert.strictEqual( + results[1].filePath, + getFixturePath( + "ignores-directory-deep/tests/format/jsfmt.spec.js", + ), + ); + }); + + it("should allow only contents of a directory but not the directory itself to be ignored by a pattern ending with '**/*'", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("ignores-directory-deep"), + overrideConfigFile: true, + overrideConfig: { + ignores: [ + "tests/format/**/*", + "!tests/format/jsfmt.spec.js", + ], + }, + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual( + results[0].filePath, + getFixturePath( + "ignores-directory-deep/tests/format/jsfmt.spec.js", + ), + ); + }); + + it("should skip ignored files in an unignored directory", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("ignores-directory-deep"), + overrideConfigFile: true, + overrideConfig: { + ignores: [ + // ignore 'tests/format/' and all its contents + "tests/format/**", + + // unignore 'tests/format/', but its contents is still ignored + "!tests/format/", + ], + }, + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["."]); + }, /All files matched by '.' are ignored/u); + }); + + it("should skip files in an ignored directory even if they are matched by a negated pattern", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("ignores-directory-deep"), + overrideConfigFile: true, + overrideConfig: { + ignores: [ + // ignore 'tests/format/' and all its contents + "tests/format/**", + + // this patterns match some or all of its contents, but 'tests/format/' is still ignored + "!tests/format/jsfmt.spec.js", + "!tests/format/**/jsfmt.spec.js", + "!tests/format/*", + "!tests/format/**/*", + ], + }, + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["."]); + }, /All files matched by '.' are ignored/u); + }); + + // https://github.com/eslint/eslint/issues/18597 + it("should skip files ignored by a pattern with escape character '\\'", async () => { + eslint = new ESLint({ + cwd: getFixturePath(), + flags, + overrideConfigFile: true, + overrideConfig: [ + { + ignores: [ + "curly-files/\\{a,b}.js", // ignore file named `{a,b}.js`, not files named `a.js` or `b.js` + ], + }, + { + rules: { + "no-undef": "warn", + }, + }, + ], + }); + + const results = await eslint.lintFiles(["curly-files"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual( + results[0].filePath, + getFixturePath("curly-files", "a.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + assert.strictEqual( + results[0].messages[0].messageId, + "undef", + ); + assert.match(results[0].messages[0].message, /'bar'/u); + assert.strictEqual( + results[1].filePath, + getFixturePath("curly-files", "b.js"), + ); + assert.strictEqual(results[1].messages.length, 1); + assert.strictEqual(results[1].messages[0].severity, 1); + assert.strictEqual( + results[1].messages[0].ruleId, + "no-undef", + ); + assert.strictEqual( + results[1].messages[0].messageId, + "undef", + ); + assert.match(results[1].messages[0].message, /'baz'/u); + }); + + // https://github.com/eslint/eslint/issues/18706 + it("should disregard ignore pattern '/'", async () => { + eslint = new ESLint({ + cwd: getFixturePath("ignores-relative"), + flags, + overrideConfigFile: true, + overrideConfig: [ + { + ignores: ["/"], + }, + { + plugins: { + "test-plugin": { + rules: { + "no-program": { + create(context) { + return { + Program(node) { + context.report({ + node, + message: + "Program is disallowed.", + }); + }, + }; + }, + }, + }, + }, + }, + rules: { + "test-plugin/no-program": "warn", + }, + }, + ], + }); + + const results = await eslint.lintFiles(["**/a.js"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual( + results[0].filePath, + getFixturePath("ignores-relative", "a.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "test-plugin/no-program", + ); + assert.strictEqual( + results[0].messages[0].message, + "Program is disallowed.", + ); + assert.strictEqual( + results[1].filePath, + getFixturePath("ignores-relative", "subdir", "a.js"), + ); + assert.strictEqual(results[1].messages.length, 1); + assert.strictEqual(results[1].messages[0].severity, 1); + assert.strictEqual( + results[1].messages[0].ruleId, + "test-plugin/no-program", + ); + assert.strictEqual( + results[1].messages[0].message, + "Program is disallowed.", + ); + }); + + it("should not skip an unignored file in base path when all files are initially ignored by '**'", async () => { + eslint = new ESLint({ + cwd: getFixturePath("ignores-relative"), + flags, + overrideConfigFile: true, + overrideConfig: [ + { + ignores: ["**", "!a.js"], + }, + { + plugins: { + "test-plugin": { + rules: { + "no-program": { + create(context) { + return { + Program(node) { + context.report({ + node, + message: + "Program is disallowed.", + }); + }, + }; + }, + }, + }, + }, + }, + rules: { + "test-plugin/no-program": "warn", + }, + }, + ], + }); + + const results = await eslint.lintFiles(["**/a.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("ignores-relative", "a.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "test-plugin/no-program", + ); + assert.strictEqual( + results[0].messages[0].message, + "Program is disallowed.", + ); + }); + + // https://github.com/eslint/eslint/issues/18575 + describe("on Windows", () => { + if (os.platform() !== "win32") { + return; + } + + let otherDriveLetter; + const exec = util.promisify( + require("node:child_process").exec, + ); + + /* + * Map the fixture directory to a new virtual drive. + * Use the first drive letter available. + */ + before(async () => { + const substDir = getFixturePath(); + + for (const driveLetter of "ABCDEFGHIJKLMNOPQRSTUVWXYZ") { + try { + // More info on this command at https://en.wikipedia.org/wiki/SUBST + await exec( + `subst ${driveLetter}: "${substDir}"`, + ); + } catch { + continue; + } + otherDriveLetter = driveLetter; + break; + } + if (!otherDriveLetter) { + throw Error( + "Unable to assign a virtual drive letter.", + ); + } + }); + + /* + * Delete the virtual drive. + */ + after(async () => { + if (otherDriveLetter) { + try { + await exec(`subst /D ${otherDriveLetter}:`); + } catch ({ message }) { + throw new Error( + `Unable to unassign virtual drive letter ${otherDriveLetter}: - ${message}`, + ); + } + } + }); + + it("should return a warning when an explicitly given file is on a different drive", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + cwd: getFixturePath(), + }); + const filePath = `${otherDriveLetter}:\\passing.js`; + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + "File ignored because outside of base path.", + ); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual( + results[0].suppressedMessages.length, + 0, + ); + }); + + it("should not ignore an explicitly given file that is on the same drive as cwd", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + cwd: `${otherDriveLetter}:\\`, + }); + const filePath = `${otherDriveLetter}:\\passing.js`; + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual( + results[0].suppressedMessages.length, + 0, + ); + }); + + it("should not ignore a file on the same drive as cwd that matches a glob pattern", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + cwd: `${otherDriveLetter}:\\files`, + }); + const pattern = `${otherDriveLetter}:\\files\\???.*`; + const results = await eslint.lintFiles([pattern]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + `${otherDriveLetter}:\\files\\foo.js`, + ); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual( + results[0].suppressedMessages.length, + 0, + ); + }); + + it("should throw an error when a glob pattern matches only files on different drive", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + cwd: getFixturePath(), + }); + const pattern = `${otherDriveLetter}:\\pa**ng.*`; + + await assert.rejects( + eslint.lintFiles([pattern]), + `All files matched by '${otherDriveLetter}:\\pa**ng.*' are ignored.`, + ); + }); + }); + }); + + it("should report zero messages when given a pattern with a .js and a .js2 file", async () => { + eslint = new ESLint({ + flags, + overrideConfig: { files: ["**/*.js", "**/*.js2"] }, + ignore: false, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + }); + const results = await eslint.lintFiles([ + "fixtures/files/*.?s*", + ]); + + assert.strictEqual(results.length, 3); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + + it("should return one error message when given a config with rules with options and severity level set to error", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(), + overrideConfigFile: true, + overrideConfig: { + rules: { + quotes: ["error", "double"], + }, + }, + ignore: false, + }); + const results = await eslint.lintFiles([ + getFixturePath("single-quoted.js"), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return 5 results when given a config and a directory of 5 valid files", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + overrideConfig: { + rules: { + semi: 1, + strict: 0, + }, + }, + }); + + const formattersDir = getFixturePath("formatters"); + const results = await eslint.lintFiles([formattersDir]); + + assert.strictEqual(results.length, 5); + assert.strictEqual( + path.relative(formattersDir, results[0].filePath), + "async.js", + ); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + path.relative(formattersDir, results[1].filePath), + "broken.js", + ); + assert.strictEqual(results[1].errorCount, 0); + assert.strictEqual(results[1].warningCount, 0); + assert.strictEqual(results[1].fatalErrorCount, 0); + assert.strictEqual(results[1].fixableErrorCount, 0); + assert.strictEqual(results[1].fixableWarningCount, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual( + path.relative(formattersDir, results[2].filePath), + "cwd.js", + ); + assert.strictEqual(results[2].errorCount, 0); + assert.strictEqual(results[2].warningCount, 0); + assert.strictEqual(results[2].fatalErrorCount, 0); + assert.strictEqual(results[2].fixableErrorCount, 0); + assert.strictEqual(results[2].fixableWarningCount, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual(results[2].suppressedMessages.length, 0); + assert.strictEqual( + path.relative(formattersDir, results[3].filePath), + "simple.js", + ); + assert.strictEqual(results[3].errorCount, 0); + assert.strictEqual(results[3].warningCount, 0); + assert.strictEqual(results[3].fatalErrorCount, 0); + assert.strictEqual(results[3].fixableErrorCount, 0); + assert.strictEqual(results[3].fixableWarningCount, 0); + assert.strictEqual(results[3].messages.length, 0); + assert.strictEqual(results[3].suppressedMessages.length, 0); + assert.strictEqual( + path.relative(formattersDir, results[4].filePath), + path.join("test", "simple.js"), + ); + assert.strictEqual(results[4].errorCount, 0); + assert.strictEqual(results[4].warningCount, 0); + assert.strictEqual(results[4].fatalErrorCount, 0); + assert.strictEqual(results[4].fixableErrorCount, 0); + assert.strictEqual(results[4].fixableWarningCount, 0); + assert.strictEqual(results[4].messages.length, 0); + assert.strictEqual(results[4].suppressedMessages.length, 0); + }); + + it("should return zero messages when given a config with browser globals", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath( + "configurations", + "env-browser.js", + ), + }); + const results = await eslint.lintFiles([ + fs.realpathSync(getFixturePath("globals-browser.js")), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].messages.length, + 0, + "Should have no messages.", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return zero messages when given an option to add browser globals", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + overrideConfig: { + languageOptions: { + globals: { + window: false, + }, + }, + rules: { + "no-alert": 0, + "no-undef": 2, + }, + }, + }); + const results = await eslint.lintFiles([ + fs.realpathSync(getFixturePath("globals-browser.js")), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return zero messages when given a config with sourceType set to commonjs and Node.js globals", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath( + "configurations", + "env-node.js", + ), + }); + const results = await eslint.lintFiles([ + fs.realpathSync(getFixturePath("globals-node.js")), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].messages.length, + 0, + "Should have no messages.", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should not return results from previous call when calling more than once", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath("eslint.config.js"), + ignore: false, + overrideConfig: { + rules: { + semi: 2, + }, + }, + }); + const failFilePath = fs.realpathSync( + getFixturePath("missing-semicolon.js"), + ); + const passFilePath = fs.realpathSync( + getFixturePath("passing.js"), + ); + + let results = await eslint.lintFiles([failFilePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, failFilePath); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual(results[0].messages[0].severity, 2); + + results = await eslint.lintFiles([passFilePath]); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, passFilePath); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return zero messages when executing a file with a shebang", async () => { + eslint = new ESLint({ + flags, + ignore: false, + cwd: getFixturePath(), + overrideConfigFile: getFixturePath("eslint.config.js"), + }); + const results = await eslint.lintFiles([ + getFixturePath("shebang.js"), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].messages.length, + 0, + "Should have lint messages.", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return zero messages when executing without a config file", async () => { + eslint = new ESLint({ + flags, + cwd: getFixturePath(), + ignore: false, + overrideConfigFile: true, + }); + const filePath = fs.realpathSync( + getFixturePath("missing-semicolon.js"), + ); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + // working + describe("Deprecated Rules", () => { + it("should warn when deprecated rules are configured", async () => { + eslint = new ESLint({ + flags, + cwd: originalDir, + overrideConfigFile: true, + overrideConfig: { + plugins: { + test: { + rules: { + "deprecated-with-replacement": { + meta: { + deprecated: true, + replacedBy: ["replacement"], + }, + create: () => ({}), + }, + "deprecated-without-replacement": { + meta: { deprecated: true }, + create: () => ({}), + }, + }, + }, + }, + rules: { + "test/deprecated-with-replacement": "error", + "test/deprecated-without-replacement": "error", + }, + }, + }); + const results = await eslint.lintFiles(["lib/cli*.js"]); + + assert.deepStrictEqual(results[0].usedDeprecatedRules, [ + { + ruleId: "test/deprecated-with-replacement", + replacedBy: ["replacement"], + info: void 0, + }, + { + ruleId: "test/deprecated-without-replacement", + replacedBy: [], + info: void 0, + }, + ]); + }); + + it("should not warn when deprecated rules are not configured", async () => { + eslint = new ESLint({ + flags, + cwd: originalDir, + overrideConfigFile: true, + overrideConfig: { + rules: { eqeqeq: 1, "callback-return": 0 }, + }, + }); + const results = await eslint.lintFiles(["lib/cli*.js"]); + + assert.deepStrictEqual(results[0].usedDeprecatedRules, []); + }); + + it("should warn when deprecated rules are found in a config", async () => { + eslint = new ESLint({ + flags, + cwd: originalDir, + overrideConfigFile: + "tests/fixtures/cli-engine/deprecated-rule-config/eslint.config.js", + }); + const results = await eslint.lintFiles(["lib/cli*.js"]); + + assert.deepStrictEqual(results[0].usedDeprecatedRules, [ + { + ruleId: "indent-legacy", + replacedBy: ["@stylistic/indent"], + info: coreRules.get("indent-legacy").meta + .deprecated, + }, + ]); + }); + + it("should add the plugin name to the replacement if available", async () => { + const deprecated = { + message: "Deprecation", + url: "https://example.com", + replacedBy: [ + { + message: "Replacement", + plugin: { name: "plugin" }, + rule: { name: "name" }, + }, + ], + }; + + eslint = new ESLint({ + flags, + cwd: originalDir, + overrideConfigFile: true, + overrideConfig: { + plugins: { + test: { + rules: { + deprecated: { + meta: { deprecated }, + create: () => ({}), + }, + }, + }, + }, + rules: { + "test/deprecated": "error", + }, + }, + }); + const results = await eslint.lintFiles(["lib/cli*.js"]); + + assert.deepStrictEqual(results[0].usedDeprecatedRules, [ + { + ruleId: "test/deprecated", + replacedBy: ["plugin/name"], + info: deprecated, + }, + ]); + }); + }); + + // working + describe("Fix Mode", () => { + it("correctly autofixes semicolon-conflicting-fixes", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + }); + const inputPath = getFixturePath( + "autofix/semicolon-conflicting-fixes.js", + ); + const outputPath = getFixturePath( + "autofix/semicolon-conflicting-fixes.expected.js", + ); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("correctly autofixes return-conflicting-fixes", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + }); + const inputPath = getFixturePath( + "autofix/return-conflicting-fixes.js", + ); + const outputPath = getFixturePath( + "autofix/return-conflicting-fixes.expected.js", + ); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should return fixed text on multiple files when in fix mode", async () => { + /** + * Converts CRLF to LF in output. + * This is a workaround for git's autocrlf option on Windows. + * @param {Object} result A result object to convert. + * @returns {void} + */ + function convertCRLF(result) { + if (result && result.output) { + result.output = result.output.replace( + /\r\n/gu, + "\n", + ); + } + } + + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + overrideConfig: { + rules: { + semi: 2, + quotes: [2, "double"], + eqeqeq: 2, + "no-undef": 2, + "space-infix-ops": 2, + }, + }, + }); + const results = await eslint.lintFiles([ + path.resolve(fixtureDir, `${fixtureDir}/fixmode`), + ]); + + results.forEach(convertCRLF); + assert.deepStrictEqual(results, [ + { + filePath: fs.realpathSync( + path.resolve( + fixtureDir, + "fixmode/multipass.js", + ), + ), + messages: [], + suppressedMessages: [], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: 'true ? "yes" : "no";\n', + usedDeprecatedRules: [ + { + ruleId: "semi", + replacedBy: ["@stylistic/semi"], + info: coreRules.get("semi").meta.deprecated, + }, + { + ruleId: "quotes", + replacedBy: ["@stylistic/quotes"], + info: coreRules.get("quotes").meta + .deprecated, + }, + { + ruleId: "space-infix-ops", + replacedBy: ["@stylistic/space-infix-ops"], + info: coreRules.get("space-infix-ops").meta + .deprecated, + }, + ], + }, + { + filePath: fs.realpathSync( + path.resolve(fixtureDir, "fixmode/ok.js"), + ), + messages: [], + suppressedMessages: [], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + usedDeprecatedRules: [ + { + ruleId: "semi", + replacedBy: ["@stylistic/semi"], + info: coreRules.get("semi").meta.deprecated, + }, + { + ruleId: "quotes", + replacedBy: ["@stylistic/quotes"], + info: coreRules.get("quotes").meta + .deprecated, + }, + { + ruleId: "space-infix-ops", + replacedBy: ["@stylistic/space-infix-ops"], + info: coreRules.get("space-infix-ops").meta + .deprecated, + }, + ], + }, + { + filePath: fs.realpathSync( + path.resolve( + fixtureDir, + "fixmode/quotes-semi-eqeqeq.js", + ), + ), + messages: [ + { + column: 9, + line: 2, + endColumn: 11, + endLine: 2, + message: + "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + severity: 2, + suggestions: [ + { + data: { + actualOperator: "==", + expectedOperator: "===", + }, + desc: "Use '===' instead of '=='.", + fix: { + range: [24, 26], + text: "===", + }, + messageId: "replaceOperator", + }, + ], + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: 'var msg = "hi";\nif (msg == "hi") {\n\n}\n', + usedDeprecatedRules: [ + { + ruleId: "semi", + replacedBy: ["@stylistic/semi"], + info: coreRules.get("semi").meta.deprecated, + }, + { + ruleId: "quotes", + replacedBy: ["@stylistic/quotes"], + info: coreRules.get("quotes").meta + .deprecated, + }, + { + ruleId: "space-infix-ops", + replacedBy: ["@stylistic/space-infix-ops"], + info: coreRules.get("space-infix-ops").meta + .deprecated, + }, + ], + }, + { + filePath: fs.realpathSync( + path.resolve(fixtureDir, "fixmode/quotes.js"), + ), + messages: [ + { + column: 18, + line: 1, + endColumn: 21, + endLine: 1, + messageId: "undef", + message: "'foo' is not defined.", + nodeType: "Identifier", + ruleId: "no-undef", + severity: 2, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: 'var msg = "hi" + foo;\n', + usedDeprecatedRules: [ + { + ruleId: "semi", + replacedBy: ["@stylistic/semi"], + info: coreRules.get("semi").meta.deprecated, + }, + { + ruleId: "quotes", + replacedBy: ["@stylistic/quotes"], + info: coreRules.get("quotes").meta + .deprecated, + }, + { + ruleId: "space-infix-ops", + replacedBy: ["@stylistic/space-infix-ops"], + info: coreRules.get("space-infix-ops").meta + .deprecated, + }, + ], + }, + ]); + }); + + // Cannot be run properly until cache is implemented + it("should run autofix even if files are cached without autofix results", async () => { + const baseOptions = { + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + overrideConfig: { + rules: { + semi: 2, + quotes: [2, "double"], + eqeqeq: 2, + "no-undef": 2, + "space-infix-ops": 2, + }, + }, + }; + + eslint = new ESLint( + Object.assign({}, baseOptions, { + cache: true, + fix: false, + }), + ); + + // Do initial lint run and populate the cache file + await eslint.lintFiles([ + path.resolve(fixtureDir, `${fixtureDir}/fixmode`), + ]); + + eslint = new ESLint( + Object.assign({}, baseOptions, { + cache: true, + fix: true, + }), + ); + const results = await eslint.lintFiles([ + path.resolve(fixtureDir, `${fixtureDir}/fixmode`), + ]); + + assert(results.some(result => result.output)); + }); + }); + + describe("plugins", () => { + it("should return two messages when executing with config file that specifies a plugin", async () => { + eslint = eslintWithPlugins({ + flags, + cwd: path.resolve(fixtureDir, ".."), + overrideConfigFile: getFixturePath( + "configurations", + "plugins-with-prefix.js", + ), + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test/test-custom-rule.js"), + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].messages.length, + 2, + "Expected two messages.", + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "example/example-rule", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return two messages when executing with cli option that specifies a plugin", async () => { + eslint = eslintWithPlugins({ + flags, + cwd: path.resolve(fixtureDir, ".."), + overrideConfigFile: true, + overrideConfig: { + rules: { "example/example-rule": 1 }, + }, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "rules", + "test", + "test-custom-rule.js", + ), + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "example/example-rule", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should return two messages when executing with cli option that specifies preloaded plugin", async () => { + eslint = new ESLint({ + flags, + cwd: path.resolve(fixtureDir, ".."), + overrideConfigFile: true, + overrideConfig: { + rules: { "test/example-rule": 1 }, + }, + plugins: { + "eslint-plugin-test": { + rules: { + "example-rule": require("../../fixtures/rules/custom-rule"), + }, + }, + }, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "rules", + "test", + "test-custom-rule.js", + ), + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "test/example-rule", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + }); + + describe("processors", () => { + it("should return two messages when executing with config file that specifies preloaded processor", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: [ + { + plugins: { + test: { + processors: { + txt: { + preprocess(text) { + return [text]; + }, + postprocess(messages) { + return messages[0]; + }, + }, + }, + }, + }, + processor: "test/txt", + rules: { + "no-console": 2, + "no-unused-vars": 2, + }, + }, + { + files: ["**/*.txt", "**/*.txt/*.txt"], + }, + ], + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "processors", + "test", + "test-processor.txt", + ), + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should run processors when calling lintFiles with config file that specifies preloaded processor", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: [ + { + plugins: { + test: { + processors: { + txt: { + preprocess(text) { + return [ + text.replace( + "a()", + "b()", + ), + ]; + }, + postprocess(messages) { + messages[0][0].ruleId = + "post-processed"; + return messages[0]; + }, + }, + }, + }, + }, + processor: "test/txt", + rules: { + "no-console": 2, + "no-unused-vars": 2, + }, + }, + { + files: ["**/*.txt", "**/*.txt/*.txt"], + }, + ], + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles([ + getFixturePath( + "processors", + "test", + "test-processor.txt", + ), + ]); + + assert.strictEqual( + results[0].messages[0].message, + "'b' is defined but never used.", + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "post-processed", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should run processors when calling lintText with config file that specifies preloaded processor", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: [ + { + plugins: { + test: { + processors: { + txt: { + preprocess(text) { + return [ + text.replace( + "a()", + "b()", + ), + ]; + }, + postprocess(messages) { + messages[0][0].ruleId = + "post-processed"; + return messages[0]; + }, + }, + }, + }, + }, + processor: "test/txt", + rules: { + "no-console": 2, + "no-unused-vars": 2, + }, + }, + { + files: ["**/*.txt", "**/*.txt/*.txt"], + }, + ], + ignore: false, + }); + const results = await eslint.lintText( + 'function a() {console.log("Test");}', + { + filePath: + "tests/fixtures/processors/test/test-processor.txt", + }, + ); + + assert.strictEqual( + results[0].messages[0].message, + "'b' is defined but never used.", + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "post-processed", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should run processors when calling lintText with processor resolves same extension but different content correctly", async () => { + let count = 0; + + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: [ + { + plugins: { + test: { + processors: { + txt: { + preprocess(text) { + count++; + return [ + { + // it will be run twice, and text will be as-is at the second time, then it will not run third time + text: text.replace( + "a()", + "b()", + ), + filename: ".txt", + }, + ]; + }, + postprocess(messages) { + messages[0][0].ruleId = + "post-processed"; + return messages[0]; + }, + }, + }, + }, + }, + processor: "test/txt", + }, + { + files: ["**/*.txt/*.txt"], + rules: { + "no-console": 2, + "no-unused-vars": 2, + }, + }, + { + files: ["**/*.txt"], + }, + ], + ignore: false, + }); + const results = await eslint.lintText( + 'function a() {console.log("Test");}', + { + filePath: + "tests/fixtures/processors/test/test-processor.txt", + }, + ); + + assert.strictEqual(count, 2); + assert.strictEqual( + results[0].messages[0].message, + "'b' is defined but never used.", + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "post-processed", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + // https://github.com/eslint/markdown/blob/main/rfcs/configure-file-name-from-block-meta.md#name-uniqueness + it("should allow processors to return filenames with a slash and treat them as subpaths", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: [ + { + plugins: { + test: { + processors: { + txt: { + preprocess(input) { + return input + .split(" ") + .map((text, index) => ({ + filename: `example-${index}/a.js`, + text, + })); + }, + postprocess(messagesList) { + return messagesList.flat(); + }, + }, + }, + rules: { + "test-rule": { + meta: {}, + create(context) { + return { + Identifier(node) { + context.report({ + node, + message: `filename: ${context.filename} physicalFilename: ${context.physicalFilename} identifier: ${node.name}`, + }); + }, + }; + }, + }, + }, + }, + }, + }, + { + files: ["**/*.txt"], + processor: "test/txt", + }, + { + files: ["**/a.js"], + rules: { + "test/test-rule": "error", + }, + }, + ], + cwd: path.join(fixtureDir, ".."), + }); + const filename = getFixturePath( + "processors", + "test", + "test-subpath.txt", + ); + const [result] = await eslint.lintFiles([filename]); + + assert.strictEqual(result.messages.length, 3); + + assert.strictEqual( + result.messages[0].ruleId, + "test/test-rule", + ); + assert.strictEqual( + result.messages[0].message, + `filename: ${path.join(filename, "0_example-0", "a.js")} physicalFilename: ${filename} identifier: foo`, + ); + assert.strictEqual( + result.messages[1].ruleId, + "test/test-rule", + ); + assert.strictEqual( + result.messages[1].message, + `filename: ${path.join(filename, "1_example-1", "a.js")} physicalFilename: ${filename} identifier: bar`, + ); + assert.strictEqual( + result.messages[2].ruleId, + "test/test-rule", + ); + assert.strictEqual( + result.messages[2].message, + `filename: ${path.join(filename, "2_example-2", "a.js")} physicalFilename: ${filename} identifier: baz`, + ); + + assert.strictEqual(result.suppressedMessages.length, 0); + }); + + describe("autofixing with processors", () => { + const HTML_PROCESSOR = Object.freeze({ + preprocess(text) { + return [ + text + .replace(/^", + { filePath: "foo.html" }, + ); + + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual( + results[0].suppressedMessages.length, + 0, + ); + assert.strictEqual( + results[0].output, + "", + ); + }); + + it("should not run in autofix mode when using a processor that does not support autofixing", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + files: ["**/*.html"], + plugins: { + test: { + processors: { html: HTML_PROCESSOR }, + }, + }, + processor: "test/html", + rules: { + semi: 2, + }, + }, + ignore: false, + fix: true, + }); + const results = await eslint.lintText( + "", + { filePath: "foo.html" }, + ); + + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].suppressedMessages.length, + 0, + ); + assert(!Object.hasOwn(results[0], "output")); + }); + + it("should not run in autofix mode when `fix: true` is not provided, even if the processor supports autofixing", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: [ + { + files: ["**/*.html"], + plugins: { + test: { + processors: { + html: Object.assign( + { supportsAutofix: true }, + HTML_PROCESSOR, + ), + }, + }, + }, + processor: "test/html", + rules: { + semi: 2, + }, + }, + { + files: ["**/*.txt"], + }, + ], + ignore: false, + }); + const results = await eslint.lintText( + "", + { filePath: "foo.html" }, + ); + + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].suppressedMessages.length, + 0, + ); + assert(!Object.hasOwn(results[0], "output")); + }); + }); + + describe("matching and ignoring code blocks", () => { + const pluginConfig = { + files: ["**/*.md"], + plugins: { + markdown: exampleMarkdownPlugin, + }, + processor: "markdown/markdown", + }; + const text = unIndent` + \`\`\`js + foo_js + \`\`\` + + \`\`\`ts + foo_ts + \`\`\` + + \`\`\`cjs + foo_cjs + \`\`\` + + \`\`\`mjs + foo_mjs + \`\`\` + `; + + it("should by default lint only .js, .mjs, and .cjs virtual files", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: [ + pluginConfig, + { + rules: { + "no-undef": 2, + }, + }, + ], + }); + const [result] = await eslint.lintText(text, { + filePath: "foo.md", + }); + + assert.strictEqual(result.messages.length, 3); + assert.strictEqual( + result.messages[0].ruleId, + "no-undef", + ); + assert.match(result.messages[0].message, /foo_js/u); + assert.strictEqual(result.messages[0].line, 2); + assert.strictEqual( + result.messages[1].ruleId, + "no-undef", + ); + assert.match(result.messages[1].message, /foo_cjs/u); + assert.strictEqual(result.messages[1].line, 10); + assert.strictEqual( + result.messages[2].ruleId, + "no-undef", + ); + assert.match(result.messages[2].message, /foo_mjs/u); + assert.strictEqual(result.messages[2].line, 14); + }); + + it("should lint additional virtual files that match non-universal patterns", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: [ + pluginConfig, + { + rules: { + "no-undef": 2, + }, + }, + { + files: ["**/*.ts"], + }, + ], + }); + const [result] = await eslint.lintText(text, { + filePath: "foo.md", + }); + + assert.strictEqual(result.messages.length, 4); + assert.strictEqual( + result.messages[0].ruleId, + "no-undef", + ); + assert.match(result.messages[0].message, /foo_js/u); + assert.strictEqual(result.messages[0].line, 2); + assert.strictEqual( + result.messages[1].ruleId, + "no-undef", + ); + assert.match(result.messages[1].message, /foo_ts/u); + assert.strictEqual(result.messages[1].line, 6); + assert.strictEqual( + result.messages[2].ruleId, + "no-undef", + ); + assert.match(result.messages[2].message, /foo_cjs/u); + assert.strictEqual(result.messages[2].line, 10); + assert.strictEqual( + result.messages[3].ruleId, + "no-undef", + ); + assert.match(result.messages[3].message, /foo_mjs/u); + assert.strictEqual(result.messages[3].line, 14); + }); + + // https://github.com/eslint/eslint/issues/18493 + it("should silently skip virtual files that match only universal patterns", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: [ + pluginConfig, + { + files: ["**/*"], + rules: { + "no-undef": 2, + }, + }, + ], + }); + const [result] = await eslint.lintText(text, { + filePath: "foo.md", + }); + + assert.strictEqual(result.messages.length, 3); + assert.strictEqual( + result.messages[0].ruleId, + "no-undef", + ); + assert.match(result.messages[0].message, /foo_js/u); + assert.strictEqual(result.messages[0].line, 2); + assert.strictEqual( + result.messages[1].ruleId, + "no-undef", + ); + assert.match(result.messages[1].message, /foo_cjs/u); + assert.strictEqual(result.messages[1].line, 10); + assert.strictEqual( + result.messages[2].ruleId, + "no-undef", + ); + assert.match(result.messages[2].message, /foo_mjs/u); + assert.strictEqual(result.messages[2].line, 14); + }); + + it("should silently skip virtual files that are ignored by global ignores", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: [ + pluginConfig, + { + rules: { + "no-undef": 2, + }, + }, + { + ignores: ["**/*.cjs"], + }, + ], + }); + const [result] = await eslint.lintText(text, { + filePath: "foo.md", + }); + + assert.strictEqual(result.messages.length, 2); + assert.strictEqual( + result.messages[0].ruleId, + "no-undef", + ); + assert.match(result.messages[0].message, /foo_js/u); + assert.strictEqual(result.messages[0].line, 2); + assert.strictEqual( + result.messages[1].ruleId, + "no-undef", + ); + assert.match(result.messages[1].message, /foo_mjs/u); + assert.strictEqual(result.messages[1].line, 14); + }); + + // https://github.com/eslint/eslint/issues/15949 + it("should silently skip virtual files that are ignored by global ignores even if they match non-universal patterns", async () => { + eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: [ + pluginConfig, + { + rules: { + "no-undef": 2, + }, + }, + { + files: ["**/*.ts"], + }, + { + ignores: ["**/*.md/*.ts"], + }, + ], + }); + const [result] = await eslint.lintText(text, { + filePath: "foo.md", + }); + + assert.strictEqual(result.messages.length, 3); + assert.strictEqual( + result.messages[0].ruleId, + "no-undef", + ); + assert.match(result.messages[0].message, /foo_js/u); + assert.strictEqual(result.messages[0].line, 2); + assert.strictEqual( + result.messages[1].ruleId, + "no-undef", + ); + assert.match(result.messages[1].message, /foo_cjs/u); + assert.strictEqual(result.messages[1].line, 10); + assert.strictEqual( + result.messages[2].ruleId, + "no-undef", + ); + assert.match(result.messages[2].message, /foo_mjs/u); + assert.strictEqual(result.messages[2].line, 14); + }); + }); + }); + + describe("Patterns which match no file should throw errors.", () => { + beforeEach(() => { + eslint = new ESLint({ + flags, + cwd: getFixturePath("cli-engine"), + overrideConfigFile: true, + }); + }); + + it("one file", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["non-exist.js"]); + }, /No files matching 'non-exist\.js' were found\./u); + }); + + it("should throw if the directory exists and is empty", async () => { + ensureDirectoryExists(getFixturePath("cli-engine/empty")); + await assert.rejects(async () => { + await eslint.lintFiles(["empty"]); + }, /No files matching 'empty' were found\./u); + }); + + it("one glob pattern", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["non-exist/**/*.js"]); + }, /No files matching 'non-exist\/\*\*\/\*\.js' were found\./u); + }); + + it("two files", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["aaa.js", "bbb.js"]); + }, /No files matching 'aaa\.js' were found\./u); + }); + + it("a mix of an existing file and a non-existing file", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["console.js", "non-exist.js"]); + }, /No files matching 'non-exist\.js' were found\./u); + }); + + // https://github.com/eslint/eslint/issues/16275 + it("a mix of an existing glob pattern and a non-existing glob pattern", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["*.js", "non-exist/*.js"]); + }, /No files matching 'non-exist\/\*\.js' were found\./u); + }); + }); + + describe("multiple processors", () => { + const root = path.join( + os.tmpdir(), + "eslint/eslint/multiple-processors", + ); + const commonFiles = { + "node_modules/pattern-processor/index.js": fs.readFileSync( + require.resolve( + "../../fixtures/processors/pattern-processor", + ), + "utf8", + ), + "node_modules/eslint-plugin-markdown/index.js": ` + const { defineProcessor } = require("pattern-processor"); + const processor = defineProcessor(${/```(\w+)\n([\s\S]+?)\n```/gu}); + exports.processors = { + "markdown": { ...processor, supportsAutofix: true }, + "non-fixable": processor + }; + `, + "node_modules/eslint-plugin-html/index.js": ` + const { defineProcessor } = require("pattern-processor"); + const processor = defineProcessor(${/ + + \`\`\` + `, + }; + + // unique directory for each test to avoid quirky disk-cleanup errors + let id; + + beforeEach(() => (id = Date.now().toString())); + + /* + * `fs.rmdir(path, { recursive: true })` is deprecated and will be removed. + * Use `fs.rm(path, { recursive: true })` instead. + * When supporting Node.js 14.14.0+, the compatibility condition can be removed for `fs.rmdir`. + */ + if (typeof fsp.rm === "function") { + afterEach(async () => + fsp.rm(root, { recursive: true, force: true }), + ); + } else { + afterEach(async () => + fsp.rmdir(root, { recursive: true, force: true }), + ); + } + + it("should lint only JavaScript blocks.", async () => { + const teardown = createCustomTeardown({ + cwd: path.join(root, id), + files: { + ...commonFiles, + "eslint.config.js": `module.exports = [ + { + plugins: { + markdown: require("eslint-plugin-markdown"), + html: require("eslint-plugin-html") } + }, + { + files: ["**/*.js"], + rules: { semi: "error" } + }, + { + files: ["**/*.md"], + processor: "markdown/markdown" } - } - } - } - }); - const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); - - assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); - assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); - }); - - it("should run processors when calling lintText with processor resolves same extension but different content correctly", async () => { - let count = 0; - - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - plugins: ["test-processor"], - overrides: [{ - files: ["**/*.txt/*.txt"], - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }] - }, - extensions: ["txt"], - ignore: false, - plugins: { - "test-processor": { - processors: { - ".txt": { - preprocess(text) { - count++; - return [ - { - - // it will be run twice, and text will be as-is at the second time, then it will not run third time - text: text.replace("a()", "b()"), - filename: ".txt" - } - ]; - }, - postprocess(messages) { - messages[0][0].ruleId = "post-processed"; - return messages[0]; + ];`, + }, + }); + + await teardown.prepare(); + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual( + results.length, + 1, + "Should have one result.", + ); + assert.strictEqual( + results[0].messages.length, + 1, + "Should have one message.", + ); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual( + results[0].messages[0].line, + 2, + "Message should be on line 2.", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should lint HTML blocks as well with multiple processors if represented in config.", async () => { + const teardown = createCustomTeardown({ + cwd: path.join(root, id), + files: { + ...commonFiles, + "eslint.config.js": `module.exports = [ + { + plugins: { + markdown: require("eslint-plugin-markdown"), + html: require("eslint-plugin-html") } + }, + { + files: ["**/*.js"], + rules: { semi: "error" } + }, + { + files: ["**/*.md"], + processor: "markdown/markdown" + }, + { + files: ["**/*.html"], + processor: "html/html" } - } - } - } - }); - const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); - - assert.strictEqual(count, 2); - assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); - assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); - }); - - describe("autofixing with processors", () => { - const HTML_PROCESSOR = Object.freeze({ - preprocess(text) { - return [text.replace(/^", { filePath: "foo.html" }); - - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].output, ""); - }); - - it("should not run in autofix mode when using a processor that does not support autofixing", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - plugins: ["test-processor"], - rules: { - semi: 2 - } - }, - extensions: ["js", "txt"], - ignore: false, - fix: true, - plugins: { - "test-processor": { processors: { ".html": HTML_PROCESSOR } } - } - }); - const results = await eslint.lintText("", { filePath: "foo.html" }); - - assert.strictEqual(results[0].messages.length, 1); - assert(!Object.prototype.hasOwnProperty.call(results[0], "output")); - }); - - it("should not run in autofix mode when `fix: true` is not provided, even if the processor supports autofixing", async () => { - eslint = new ESLint({ - useEslintrc: false, - overrideConfig: { - plugins: ["test-processor"], - rules: { - semi: 2 - } - }, - extensions: ["js", "txt"], - ignore: false, - plugins: { - "test-processor": { - processors: { - ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) - } - } - } - }); - const results = await eslint.lintText("", { filePath: "foo.html" }); - - assert.strictEqual(results[0].messages.length, 1); - assert(!Object.prototype.hasOwnProperty.call(results[0], "output")); - }); - }); - }); - - describe("Patterns which match no file should throw errors.", () => { - beforeEach(() => { - eslint = new ESLint({ - cwd: getFixturePath("cli-engine"), - useEslintrc: false - }); - }); - - it("one file", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["non-exist.js"]); - }, /No files matching 'non-exist\.js' were found\./u); - }); - - it("should throw if the directory exists and is empty", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["empty"]); - }, /No files matching 'empty' were found\./u); - }); - - it("one glob pattern", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["non-exist/**/*.js"]); - }, /No files matching 'non-exist\/\*\*\/\*\.js' were found\./u); - }); - - it("two files", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["aaa.js", "bbb.js"]); - }, /No files matching 'aaa\.js' were found\./u); - }); - - it("a mix of an existing file and a non-existing file", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["console.js", "non-exist.js"]); - }, /No files matching 'non-exist\.js' were found\./u); - }); - }); - - describe("overrides", () => { - beforeEach(() => { - eslint = new ESLint({ - cwd: getFixturePath("cli-engine/overrides-with-dot"), - ignore: false - }); - }); - - it("should recognize dotfiles", async () => { - const ret = await eslint.lintFiles([".test-target.js"]); - - assert.strictEqual(ret.length, 1); - assert.strictEqual(ret[0].messages.length, 1); - assert.strictEqual(ret[0].messages[0].ruleId, "no-unused-vars"); - }); - }); - - describe("a config file setting should have higher priority than a shareable config file's settings always; https://github.com/eslint/eslint/issues/11510", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: path.join(os.tmpdir(), "eslint/11510"), - files: { - "no-console-error-in-overrides.json": JSON.stringify({ - overrides: [{ - files: ["*.js"], - rules: { "no-console": "error" } - }] - }), - ".eslintrc.json": JSON.stringify({ - extends: "./no-console-error-in-overrides.json", - rules: { "no-console": "off" } - }), - "a.js": "console.log();" - } - }); - - beforeEach(() => { - eslint = new ESLint({ cwd: getPath() }); - return prepare(); - }); - - afterEach(cleanup); - - it("should not report 'no-console' error.", async () => { - const results = await eslint.lintFiles("a.js"); - - assert.strictEqual(results.length, 1); - assert.deepStrictEqual(results[0].messages, []); - }); - }); - - describe("configs of plugin rules should be validated even if 'plugins' key doesn't exist; https://github.com/eslint/eslint/issues/11559", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: path.join(os.tmpdir(), "eslint/11559"), - files: { - "node_modules/eslint-plugin-test/index.js": ` - exports.configs = { - recommended: { plugins: ["test"] } - }; - exports.rules = { - foo: { - meta: { schema: [{ type: "number" }] }, - create() { return {}; } - } - }; - `, - ".eslintrc.json": JSON.stringify({ - - // Import via the recommended config. - extends: "plugin:test/recommended", - - // Has invalid option. - rules: { "test/foo": ["error", "invalid-option"] } - }), - "a.js": "console.log();" - } - }); - - beforeEach(() => { - eslint = new ESLint({ cwd: getPath() }); - return prepare(); - }); - - afterEach(cleanup); - - - it("should throw fatal error.", async () => { - await assert.rejects(async () => { - await eslint.lintFiles("a.js"); - }, /invalid-option/u); - }); - }); - - describe("'--fix-type' should not crash even if plugin rules exist; https://github.com/eslint/eslint/issues/11586", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: path.join(os.tmpdir(), "cli-engine/11586"), - files: { - "node_modules/eslint-plugin-test/index.js": ` - exports.rules = { - "no-example": { - meta: { type: "problem", fixable: "code" }, - create(context) { - return { - Identifier(node) { - if (node.name === "example") { - context.report({ - node, - message: "fix", - fix: fixer => fixer.replaceText(node, "fixed") - }) - } - } - }; + ];`, + }, + }); + + await teardown.prepare(); + eslint = new ESLint({ + flags, + cwd: teardown.getPath(), + overrideConfig: { files: ["**/*.html"] }, + }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual( + results.length, + 1, + "Should have one result.", + ); + assert.strictEqual( + results[0].messages.length, + 2, + "Should have two messages.", + ); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block + assert.strictEqual( + results[0].messages[0].line, + 2, + "First error should be on line 2", + ); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block + assert.strictEqual( + results[0].messages[1].line, + 7, + "Second error should be on line 7.", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should fix HTML blocks as well with multiple processors if represented in config.", async () => { + const teardown = createCustomTeardown({ + cwd: path.join(root, id), + files: { + ...commonFiles, + "eslint.config.js": `module.exports = [ + { + plugins: { + markdown: require("eslint-plugin-markdown"), + html: require("eslint-plugin-html") } - } - }; - `, - ".eslintrc.json": { - plugins: ["test"], - rules: { "test/no-example": "error" } - }, - "a.js": "example;" - } - }); - - beforeEach(() => { - eslint = new ESLint({ - cwd: getPath(), - fix: true, - fixTypes: ["problem"] - }); - - return prepare(); - }); - - afterEach(cleanup); - - it("should not crash.", async () => { - const results = await eslint.lintFiles("a.js"); - - assert.strictEqual(results.length, 1); - assert.deepStrictEqual(results[0].messages, []); - assert.deepStrictEqual(results[0].output, "fixed;"); - }); - }); - - describe("multiple processors", () => { - const root = path.join(os.tmpdir(), "eslint/eslint/multiple-processors"); - const commonFiles = { - "node_modules/pattern-processor/index.js": fs.readFileSync( - require.resolve("../../fixtures/processors/pattern-processor"), - "utf8" - ), - "node_modules/eslint-plugin-markdown/index.js": ` - const { defineProcessor } = require("pattern-processor"); - const processor = defineProcessor(${/```(\w+)\n([\s\S]+?)\n```/gu}); - exports.processors = { - ".md": { ...processor, supportsAutofix: true }, - "non-fixable": processor - }; - `, - "node_modules/eslint-plugin-html/index.js": ` - const { defineProcessor } = require("pattern-processor"); - const processor = defineProcessor(${/ - - \`\`\` - ` - }; - - let cleanup; - - beforeEach(() => { - cleanup = () => { }; - }); - - afterEach(() => cleanup()); - - it("should lint only JavaScript blocks if '--ext' was not given.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" } - } - } - }); - - cleanup = teardown.cleanup; - await teardown.prepare(); - eslint = new ESLint({ cwd: teardown.getPath() }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].messages[0].line, 2); - }); - - it("should fix only JavaScript blocks if '--ext' was not given.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" } - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath(), fix: true }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].output, unIndent` - \`\`\`js - console.log("hello");${/* ← fixed */""} - \`\`\` - \`\`\`html -
Hello
- - - \`\`\` - `); - }); - - it("should lint HTML blocks as well with multiple processors if '--ext' option was given.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" } - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block - assert.strictEqual(results[0].messages[0].line, 2); - assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block - assert.strictEqual(results[0].messages[1].line, 7); - }); - - it("should fix HTML blocks as well with multiple processors if '--ext' option was given.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" } - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"], fix: true }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].output, unIndent` - \`\`\`js - console.log("hello");${/* ← fixed */""} - \`\`\` - \`\`\`html -
Hello
- - - \`\`\` - `); - }); - - it("should use overridden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" }, - overrides: [ + }, + { + files: ["**/*.js"], + rules: { semi: "error" } + }, { - files: "*.html", - processor: "html/non-fixable" // supportsAutofix: false + files: ["**/*.md"], + processor: "markdown/markdown" + }, + { + files: ["**/*.html"], + processor: "html/html" } - ] - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"], fix: true }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS Block in HTML Block - assert.strictEqual(results[0].messages[0].line, 7); - assert.strictEqual(results[0].messages[0].fix, void 0); - assert.strictEqual(results[0].output, unIndent` - \`\`\`js - console.log("hello");${/* ← fixed */""} - \`\`\` - \`\`\`html -
Hello
- - - \`\`\` - `); - }); - - it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" }, - overrides: [ + ];`, + }, + }); + + await teardown.prepare(); + eslint = new ESLint({ + flags, + cwd: teardown.getPath(), + overrideConfig: { files: ["**/*.html"] }, + fix: true, + }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + results[0].output, + unIndent` + \`\`\`js + console.log("hello");${/* ← fixed */ ""} + \`\`\` + \`\`\`html +
Hello
+ + + \`\`\` + `, + ); + }); + + it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", async () => { + const teardown = createCustomTeardown({ + cwd: path.join(root, id), + files: { + ...commonFiles, + "eslint.config.js": `module.exports = [ { - files: "*.html", - - // this rules are not used because ESLint re-resolve configs if a code block had a different file extension. - rules: { - semi: "error", - "no-console": "off" + plugins: { + markdown: require("eslint-plugin-markdown"), + html: require("eslint-plugin-html") } }, { - files: "**/*.html/*.js", + files: ["**/*.js"], + rules: { semi: "error" } + }, + { + files: ["**/*.md"], + processor: "markdown/markdown" + }, + { + files: ["**/*.html"], + processor: "html/html" + }, + { + files: ["**/*.html/*.js"], rules: { semi: "off", "no-console": "error" } } - ] - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].messages[0].line, 2); - assert.strictEqual(results[0].messages[1].ruleId, "no-console"); - assert.strictEqual(results[0].messages[1].line, 7); - }); - - it("should use the same config as one which has 'processor' property in order to lint blocks in HTML if the processor was legacy style.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" }, - overrides: [ + + ];`, + }, + }); + + await teardown.prepare(); + eslint = new ESLint({ + flags, + cwd: teardown.getPath(), + overrideConfig: { files: ["**/*.html"] }, + }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual( + results[0].messages[1].ruleId, + "no-console", + ); + assert.strictEqual(results[0].messages[1].line, 7); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should use the same config as one which has 'processor' property in order to lint blocks in HTML if the processor was legacy style.", async () => { + const teardown = createCustomTeardown({ + cwd: path.join(root, id), + files: { + ...commonFiles, + "eslint.config.js": `module.exports = [ + { + plugins: { + markdown: require("eslint-plugin-markdown"), + html: require("eslint-plugin-html") + }, + rules: { semi: "error" } + }, { - files: "*.html", - processor: "html/legacy", // this processor returns strings rather than `{text, filename}` + files: ["**/*.md"], + processor: "markdown/markdown" + }, + { + files: ["**/*.html"], + processor: "html/legacy", // this processor returns strings rather than '{ text, filename }' rules: { semi: "off", "no-console": "error" } }, { - files: "**/*.html/*.js", + files: ["**/*.html/*.js"], rules: { semi: "error", "no-console": "off" } } - ] - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath(), extensions: ["js", "html"] }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 3); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].messages[0].line, 2); - assert.strictEqual(results[0].messages[1].ruleId, "no-console"); - assert.strictEqual(results[0].messages[1].line, 7); - assert.strictEqual(results[0].messages[2].ruleId, "no-console"); - assert.strictEqual(results[0].messages[2].line, 10); - }); - - it("should throw an error if invalid processor was specified.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - processor: "markdown/unknown" - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath() }); - - await assert.rejects(async () => { - await eslint.lintFiles(["test.md"]); - }, /ESLint configuration of processor in '\.eslintrc\.json' is invalid: 'markdown\/unknown' was not found\./u); - }); - - it("should lint HTML blocks as well with multiple processors if 'overrides[].files' is present.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ...commonFiles, - ".eslintrc.json": { - plugins: ["markdown", "html"], - rules: { semi: "error" }, - overrides: [ + + ];`, + }, + }); + + await teardown.prepare(); + eslint = new ESLint({ + flags, + cwd: teardown.getPath(), + overrideConfig: { files: ["**/*.html"] }, + }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 3); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual( + results[0].messages[1].ruleId, + "no-console", + ); + assert.strictEqual(results[0].messages[1].line, 7); + assert.strictEqual( + results[0].messages[2].ruleId, + "no-console", + ); + assert.strictEqual(results[0].messages[2].line, 10); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should throw an error if invalid processor was specified.", async () => { + const teardown = createCustomTeardown({ + cwd: path.join(root, id), + files: { + ...commonFiles, + "eslint.config.js": `module.exports = [ { - files: "*.html", - processor: "html/.html" + plugins: { + markdown: require("eslint-plugin-markdown"), + html: require("eslint-plugin-html") + } }, { - files: "*.md", - processor: "markdown/.md" + files: ["**/*.md"], + processor: "markdown/unknown" + } + + ];`, + }, + }); + + await teardown.prepare(); + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + + await assert.rejects(async () => { + await eslint.lintFiles(["test.md"]); + }, /Key "processor": Could not find "unknown" in plugin "markdown"/u); + }); + }); + + describe("glob pattern '[ab].js'", () => { + const root = getFixturePath("cli-engine/unmatched-glob"); + + let cleanup; + + beforeEach(() => { + cleanup = () => {}; + }); + + afterEach(() => cleanup()); + + it("should match '[ab].js' if existed.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "a.js": "", + "b.js": "", + "ab.js": "", + "[ab].js": "", + "eslint.config.js": "module.exports = [{}];", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + const results = await eslint.lintFiles(["[ab].js"]); + const filenames = results.map(r => + path.basename(r.filePath), + ); + + assert.deepStrictEqual(filenames, ["[ab].js"]); + }); + + it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "a.js": "", + "b.js": "", + "ab.js": "", + "eslint.config.js": "module.exports = [{}];", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + const results = await eslint.lintFiles(["[ab].js"]); + const filenames = results.map(r => + path.basename(r.filePath), + ); + + assert.deepStrictEqual(filenames, ["a.js", "b.js"]); + }); + }); + + describe("with 'noInlineConfig' setting", () => { + const root = getFixturePath("cli-engine/noInlineConfig"); + + let cleanup; + + beforeEach(() => { + cleanup = () => {}; + }); + + afterEach(() => cleanup()); + + it("should warn directive comments if 'noInlineConfig' was given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "test.js": "/* globals foo */", + "eslint.config.js": + "module.exports = [{ linterOptions: { noInlineConfig: true } }];", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "'/* globals foo */' has no effect because you have 'noInlineConfig' setting in your config.", + ); + }); + }); + + describe("with 'reportUnusedDisableDirectives' setting", () => { + const root = getFixturePath( + "cli-engine/reportUnusedDisableDirectives", + ); + + let cleanup; + let i = 0; + + beforeEach(() => { + cleanup = () => {}; + i++; + }); + + afterEach(() => cleanup()); + + it("should error unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = error'.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": + "module.exports = { linterOptions: { reportUnusedDisableDirectives: 'error' } }", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + "Unused eslint-disable directive (no problems were reported from 'eqeqeq').", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should error unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = 2'.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": + "module.exports = { linterOptions: { reportUnusedDisableDirectives: 2 } }", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + "Unused eslint-disable directive (no problems were reported from 'eqeqeq').", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = warn'.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": + "module.exports = { linterOptions: { reportUnusedDisableDirectives: 'warn' } }", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual( + messages[0].message, + "Unused eslint-disable directive (no problems were reported from 'eqeqeq').", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = 1'.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": + "module.exports = { linterOptions: { reportUnusedDisableDirectives: 1 } }", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual( + messages[0].message, + "Unused eslint-disable directive (no problems were reported from 'eqeqeq').", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = true'.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": + "module.exports = { linterOptions: { reportUnusedDisableDirectives: true } }", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual( + messages[0].message, + "Unused eslint-disable directive (no problems were reported from 'eqeqeq').", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = false'.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": + "module.exports = { linterOptions: { reportUnusedDisableDirectives: false } }", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 0); + }); + + it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = off'.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": + "module.exports = { linterOptions: { reportUnusedDisableDirectives: 'off' } }", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 0); + }); + + it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = 0'.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": + "module.exports = { linterOptions: { reportUnusedDisableDirectives: 0 } }", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 0); + }); + + describe("the runtime option overrides config files.", () => { + it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": + "module.exports = [{ linterOptions: { reportUnusedDisableDirectives: true } }]", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new ESLint({ + flags, + cwd: teardown.getPath(), + overrideConfig: { + linterOptions: { + reportUnusedDisableDirectives: "off", + }, + }, + }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 0); + }); + + it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "test.js": "/* eslint-disable eqeqeq */", + "eslint.config.js": + "module.exports = [{ linterOptions: { reportUnusedDisableDirectives: true } }]", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new ESLint({ + flags, + cwd: teardown.getPath(), + overrideConfig: { + linterOptions: { + reportUnusedDisableDirectives: "error", + }, + }, + }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + "Unused eslint-disable directive (no problems were reported from 'eqeqeq').", + ); + assert.strictEqual( + results[0].suppressedMessages.length, + 0, + ); + }); + }); + }); + + it("should throw if an invalid value is given to 'patterns' argument", async () => { + eslint = new ESLint({ flags }); + await assert.rejects( + () => eslint.lintFiles(777), + /'patterns' must be a non-empty string or an array of non-empty strings/u, + ); + await assert.rejects( + () => eslint.lintFiles([null]), + /'patterns' must be a non-empty string or an array of non-empty strings/u, + ); + }); + + describe("Alternate config files", () => { + it("should find eslint.config.mjs when present", async () => { + const cwd = getFixturePath("mjs-config"); + + eslint = new ESLint({ + flags, + cwd, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should find eslint.config.cjs when present", async () => { + const cwd = getFixturePath("cjs-config"); + + eslint = new ESLint({ + flags, + cwd, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should favor eslint.config.js when eslint.config.mjs and eslint.config.cjs are present", async () => { + const cwd = getFixturePath("js-mjs-cjs-config"); + + eslint = new ESLint({ + flags, + cwd, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should favor eslint.config.mjs when eslint.config.cjs is present", async () => { + const cwd = getFixturePath("mjs-cjs-config"); + + eslint = new ESLint({ + flags, + cwd, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + }); + + describe("TypeScript config files", () => { + const typeModule = JSON.stringify({ type: "module" }, null, 2); + + const typeCommonJS = JSON.stringify( + { type: "commonjs" }, + null, + 2, + ); + + JITI_VERSIONS.forEach(jitiVersion => { + describe(`Loading TypeScript config files with ${jitiVersion}`, () => { + if (jitiVersion !== "jiti") { + beforeEach(() => { + sinon + .stub(ConfigLoader, "loadJiti") + .callsFake(() => + Promise.resolve({ + createJiti: + require(jitiVersion).createJiti, + version: require( + `${jitiVersion}/package.json`, + ).version, + }), + ); + }); + } + + it("should find and load eslint.config.ts when present", async () => { + const cwd = getFixturePath("ts-config-files", "ts"); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts when we have "type": "commonjs" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-commonjs", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts when we have "type": "module" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-module", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with ESM syntax and "type": "commonjs" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-commonjs", + "ESM-syntax", + ); + + const configFileContent = `import type { FlatConfig } from "../../../helper";\nexport default ${JSON.stringify( + [{ rules: { "no-undef": 2 } }], + null, + 2, + )} satisfies FlatConfig[];`; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeCommonJS, + "eslint.config.ts": configFileContent, + "foo.js": "foo;", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS syntax and "type": "module" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-module", + "CJS-syntax", + ); + + const configFileContent = `import type { FlatConfig } from "../../../helper";\nmodule.exports = ${JSON.stringify( + [{ rules: { "no-undef": 2 } }], + null, + 2, + )} satisfies FlatConfig[];`; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeModule, + "eslint.config.ts": configFileContent, + "foo.js": "foo;", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS syntax and "type": "commonjs" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-commonjs", + "CJS-syntax", + ); + + const configFileContent = `import type { FlatConfig } from "../../../helper";\nmodule.exports = ${JSON.stringify( + [{ rules: { "no-undef": 2 } }], + null, + 2, + )} satisfies FlatConfig[];`; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeCommonJS, + "eslint.config.ts": configFileContent, + "foo.js": "foo;", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS syntax, "type": "module" in nearest `package.json` and top-level await syntax', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-module", + "CJS-syntax", + "top-level-await", + ); + + const configFileContent = `import type { FlatConfig } from "../../../../helper";\nmodule.exports = await Promise.resolve(${JSON.stringify( + [{ rules: { "no-undef": 2 } }], + null, + 2, + )}) satisfies FlatConfig[];`; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeModule, + "eslint.config.ts": configFileContent, + "foo.js": "foo;", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS syntax, "type": "commonjs" in nearest `package.json` and top-level await syntax', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-commonjs", + "CJS-syntax", + "top-level-await", + ); + + const configFileContent = `import type { FlatConfig } from "../../../../helper";\nmodule.exports = await Promise.resolve(${JSON.stringify( + [{ rules: { "no-undef": 2 } }], + null, + 2, + )}) satisfies FlatConfig[];`; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeCommonJS, + "eslint.config.ts": configFileContent, + "foo.js": "foo;", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS syntax, "type": "module" in nearest `package.json` and top-level await syntax (named import)', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-module", + "top-level-await", + "named-import", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../../helper";\nconst { rules } = await import("./rules");\nmodule.exports = [{ rules }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": `export const rules = ${JSON.stringify( + { + "no-undef": 2, + }, + null, + 2, + )};`, + "package.json": typeModule, + "eslint.config.ts": configFileContent, + "foo.js": "foo;", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS syntax, "type": "commonjs" in nearest `package.json` and top-level await syntax (named import)', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-commonjs", + "top-level-await", + "named-import", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../../helper";\nconst { rules } = await import("./rules");\nmodule.exports = [{ rules }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": `export const rules = ${JSON.stringify( + { + "no-undef": 2, + }, + null, + 2, + )};`, + "package.json": typeCommonJS, + "eslint.config.ts": configFileContent, + "foo.js": "foo;", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS syntax, "type": "module" in nearest `package.json` and top-level await syntax (import default)', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-module", + "top-level-await", + "import-default", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../../helper";\nconst { default: rules } = await import("./rules");\nmodule.exports = [{ rules }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": `export default ${JSON.stringify( + { + "no-undef": 2, + }, + null, + 2, + )};`, + "package.json": typeModule, + "eslint.config.ts": configFileContent, + "foo.js": "foo;", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS syntax, "type": "commonjs" in nearest `package.json` and top-level await syntax (import default)', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-commonjs", + "top-level-await", + "import-default", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../../helper";\nconst { default: rules } = await import("./rules");\nmodule.exports = [{ rules }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": `export default ${JSON.stringify( + { + "no-undef": 2, + }, + null, + 2, + )};`, + "package.json": typeCommonJS, + "eslint.config.ts": configFileContent, + "foo.js": "foo;", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS syntax, "type": "module" in nearest `package.json` and top-level await syntax (default and named imports)', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-module", + "top-level-await", + "import-default-and-named", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../../helper";\nconst { default: rules, Level } = await import("./rules");\n\nmodule.exports = [{ rules: { ...rules, semi: Level.Error } }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": `import type { RulesRecord } from "../../../../helper";\nexport const enum Level {\nError = 2,\nWarn = 1,\nOff = 0,\n};\nexport default ${JSON.stringify( + { + "no-undef": 2, + }, + null, + 2, + )} satisfies RulesRecord;`, + "package.json": typeModule, + "eslint.config.ts": configFileContent, + "foo.js": "foo", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[1].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with TypeScript\'s CJS syntax (import and export assignment), "type": "module" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-module", + "import-and-export-assignment", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../helper";\nimport rulesModule = require("./rules");\nconst { rules, Level } = rulesModule;\nexport = [{ rules: { ...rules, semi: Level.Error } }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": + 'import type { RulesRecord } from "../../../helper";\nimport { Severity } from "../../../helper";\nconst enum Level {\nError = 2,\nWarn = 1,\nOff = 0,\n};\nexport = { rules: { "no-undef": Severity.Error }, Level } satisfies RulesRecord;', + "package.json": typeModule, + "eslint.config.ts": configFileContent, + "foo.js": "foo", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[1].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with TypeScript\'s CJS syntax (import and export assignment), "type": "commonjs" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-commonjs", + "import-and-export-assignment", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../helper";\nimport rulesModule = require("./rules");\nconst { rules, Level } = rulesModule;\nexport = [{ rules: { ...rules, semi: Level.Error } }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": + 'import type { RulesRecord } from "../../../helper";\nimport { Severity } from "../../../helper";\nconst enum Level {\nError = 2,\nWarn = 1,\nOff = 0,\n};\nexport = { rules: { "no-undef": Severity.Error }, Level } satisfies RulesRecord;', + "package.json": typeCommonJS, + "eslint.config.ts": configFileContent, + "foo.js": "foo", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[1].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with wildcard imports, "type": "module" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-module", + "wildcard-imports", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../helper";\nimport * as rulesModule from "./rules";\nconst { default: rules ,Level } = rulesModule;\nexport = [{ rules: { ...rules, semi: Level.Error } }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": + 'import type { RulesRecord } from "../../../helper";\nimport { Severity } from "../../../helper";\nexport const enum Level {\nError = 2,\nWarn = 1,\nOff = 0,\n};\nexport default { "no-undef": Severity.Error } satisfies RulesRecord;', + "package.json": typeModule, + "eslint.config.ts": configFileContent, + "foo.js": "foo", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[1].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with wildcard imports, "type": "commonjs" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-commonjs", + "wildcard-imports", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../helper";\nimport * as rulesModule from "./rules";\nconst { default: rules ,Level } = rulesModule;\nexport = [{ rules: { ...rules, semi: Level.Error } }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": + 'import type { RulesRecord } from "../../../helper";\nimport { Severity } from "../../../helper";\nexport const enum Level {\nError = 2,\nWarn = 1,\nOff = 0,\n};\nexport default { "no-undef": Severity.Error } satisfies RulesRecord;', + "package.json": typeCommonJS, + "eslint.config.ts": configFileContent, + "foo.js": "foo", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[1].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS-ESM mixed syntax (import and module.exports), "type": "module" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-module", + "CJS-ESM-mixed-syntax", + "import-and-module-exports", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../../helper";\nimport rules, { Level } from "./rules";\nmodule.exports = [{ rules: { ...rules, semi: Level.Error } }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": `import type { RulesRecord } from "../../../../helper";\nexport const enum Level {\nError = 2,\nWarn = 1,\nOff = 0,\n};\nexport default ${JSON.stringify( + { + "no-undef": 2, + }, + null, + 2, + )} satisfies RulesRecord;`, + "package.json": typeModule, + "eslint.config.ts": configFileContent, + "foo.js": "foo", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[1].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS-ESM mixed syntax (import and module.exports), "type": "commonjs" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-commonjs", + "CJS-ESM-mixed-syntax", + "import-and-module-exports", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../../helper";\nimport rules, { Level } from "./rules";\nmodule.exports = [{ rules: { ...rules, semi: Level.Error } }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": `import type { RulesRecord } from "../../../../helper";\nexport const enum Level {\nError = 2,\nWarn = 1,\nOff = 0,\n};\nexport default ${JSON.stringify( + { + "no-undef": 2, + }, + null, + 2, + )} satisfies RulesRecord;`, + "package.json": typeCommonJS, + "eslint.config.ts": configFileContent, + "foo.js": "foo", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[1].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS-ESM mixed syntax (require and export default), "type": "module" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-module", + "CJS-ESM-mixed-syntax", + "require-and-export-default", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../../helper";\nconst { default: rules, Level } = require("./rules");\nexport default [{ rules: { ...rules, semi: Level.Error } }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": + 'import type { RulesRecord } from "../../../../helper";\nimport { Severity } from "../../../../helper";\nexport const enum Level {\nError = 2,\nWarn = 1,\nOff = 0,\n};\nexport default { "no-undef": Severity.Error } satisfies RulesRecord;', + "package.json": typeModule, + "eslint.config.ts": configFileContent, + "foo.js": "foo", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[1].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS-ESM mixed syntax (require and export default), "type": "commonjs" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-commonjs", + "CJS-ESM-mixed-syntax", + "require-and-export-default", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../../helper";\nconst { default: rules, Level } = require("./rules");\nexport default [{ rules: { ...rules, semi: Level.Error } }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": + 'import type { RulesRecord } from "../../../../helper";\nimport { Severity } from "../../../../helper";\nexport const enum Level {\nError = 2,\nWarn = 1,\nOff = 0,\n};\nexport default { "no-undef": Severity.Error } satisfies RulesRecord;', + "package.json": typeCommonJS, + "eslint.config.ts": configFileContent, + "foo.js": "foo", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[1].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS-ESM mixed syntax (import assignment and export default), "type": "module" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-module", + "CJS-ESM-mixed-syntax", + "import-assignment-and-export-default", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../../helper";\nimport rulesModule = require("./rules");\nconst { default: rules, Level } = rulesModule;\nexport default [{ rules: { ...rules, semi: Level.Error } }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": + 'import type { RulesRecord } from "../../../../helper";\nimport { Severity } from "../../../../helper";\nexport const enum Level {\nError = 2,\nWarn = 1,\nOff = 0,\n};\nexport default { "no-undef": Severity.Error } satisfies RulesRecord;', + "package.json": typeModule, + "eslint.config.ts": configFileContent, + "foo.js": "foo", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[1].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS-ESM mixed syntax (import assignment and export default), "type": "commonjs" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-commonjs", + "CJS-ESM-mixed-syntax", + "import-assignment-and-export-default", + ); + + const configFileContent = + 'import type { FlatConfig } from "../../../../helper";\nimport rulesModule = require("./rules");\nconst { default: rules, Level } = rulesModule;\nexport default [{ rules: { ...rules, semi: Level.Error } }] satisfies FlatConfig[];'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": + 'import type { RulesRecord } from "../../../../helper";\nimport { Severity } from "../../../../helper";\nexport const enum Level {\nError = 2,\nWarn = 1,\nOff = 0,\n};\nexport default { "no-undef": Severity.Error } satisfies RulesRecord;', + "package.json": typeCommonJS, + "eslint.config.ts": configFileContent, + "foo.js": "foo", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[1].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS-ESM mixed syntax (import and export assignment), "type": "module" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-module", + "CJS-ESM-mixed-syntax", + "import-and-export-assignment", + ); + + const configFileContent = + 'import helpers = require("../../../../helper");\nimport rulesModule = require("./rules");\nconst { default: rules, Level } = rulesModule;\nconst allExports = [{ rules: { ...rules, semi: Level.Error } }] satisfies helpers.FlatConfig[];\nexport = allExports;'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": + 'import helpers = require("../../../../helper");\nconst enum Level {\nError = 2,\nWarn = 1,\nOff = 0,\n};\nconst rules = { "no-undef": helpers.Severity.Error } satisfies helpers.RulesRecord;\nconst allExports = { default: rules, Level };\nexport = allExports;', + "package.json": typeModule, + "eslint.config.ts": configFileContent, + "foo.js": "foo", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[1].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.ts with CJS-ESM mixed syntax (import and export assignment), "type": "commonjs" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "with-type-commonjs", + "CJS-ESM-mixed-syntax", + "import-and-export-assignment", + ); + + const configFileContent = + 'import helpers = require("../../../../helper");\nimport rulesModule = require("./rules");\nconst { default: rules, Level } = rulesModule;\nconst allExports = [{ rules: { ...rules, semi: Level.Error } }] satisfies helpers.FlatConfig[];\nexport = allExports;'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "rules.ts": + 'import helpers = require("../../../../helper");\nconst enum Level {\nError = 2,\nWarn = 1,\nOff = 0,\n};\nconst rules = { "no-undef": helpers.Severity.Error } satisfies helpers.RulesRecord;\nconst allExports = { default: rules, Level };\nexport = allExports;', + "package.json": typeCommonJS, + "eslint.config.ts": configFileContent, + "foo.js": "foo", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[1].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should load eslint.config.ts with const enums", async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "const-enums", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should load eslint.config.ts with local namespace", async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "local-namespace", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should allow passing a TS config file to `overrideConfigFile`", async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "custom-config", + ); + + const overrideConfigFile = path.join( + cwd, + "eslint.custom.config.ts", + ); + + eslint = new ESLint({ + cwd, + flags, + overrideConfigFile, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual( + await eslint.findConfigFile(), + overrideConfigFile, + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should find and load eslint.config.mts when present", async () => { + const cwd = getFixturePath( + "ts-config-files", + "mts", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.mts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.mts when we have "type": "commonjs" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "mts", + "with-type-commonjs", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.mts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.mts config file when we have "type": "module" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "mts", + "with-type-module", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.mts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should find and load eslint.config.cts when present", async () => { + const cwd = getFixturePath( + "ts-config-files", + "cts", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.cts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load eslint.config.cts config file when we have "type": "commonjs" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "cts", + "with-type-commonjs", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.cts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('should load .cts config file when we have "type": "module" in nearest `package.json`', async () => { + const cwd = getFixturePath( + "ts-config-files", + "cts", + "with-type-module", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.cts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should not load extensions other than .ts, .mts or .cts", async () => { + const cwd = getFixturePath( + "ts-config-files", + "wrong-extension", + ); + + const configFileContent = `import type { FlatConfig } from "../../helper";\nexport default ${JSON.stringify( + [{ rules: { "no-undef": 2 } }], + null, + 2, + )} satisfies FlatConfig[];`; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeCommonJS, + "eslint.config.mcts": configFileContent, + "foo.js": "foo;", + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile: "eslint.config.mcts", + flags, + }); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.mcts"), + ); + await assert.rejects(() => + eslint.lintFiles(["foo.js"]), + ); + }); + + it("should successfully load a TS config file that exports a promise", async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "exports-promise", + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles(["foo*.js"]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, "eslint.config.ts"), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(cwd, "foo.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("should load a CommonJS TS config file that exports undefined with a helpful warning message", async () => { + sinon.restore(); + + const cwd = getFixturePath("ts-config-files", "ts"); + const processStub = sinon.stub( + process, + "emitWarning", + ); + + eslint = new ESLint({ + cwd, + flags, + overrideConfigFile: + "eslint.undefined.config.ts", + }); + + await eslint.lintFiles("foo.js"); + + assert.strictEqual( + processStub.callCount, + 1, + "calls `process.emitWarning()` once", + ); + assert.strictEqual( + processStub.getCall(0).args[1], + "ESLintEmptyConfigWarning", + ); + }); + }); + }); + + it("should fail to load a TS config file if jiti is not installed", async () => { + sinon.stub(ConfigLoader, "loadJiti").rejects(); + + const cwd = getFixturePath("ts-config-files", "ts"); + + eslint = new ESLint({ + cwd, + flags, + }); + + await assert.rejects(eslint.lintFiles("foo.js"), { + message: + "The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.", + }); + }); + + it("should fail to load a TS config file if an outdated version of jiti is installed", async () => { + sinon + .stub(ConfigLoader, "loadJiti") + .resolves({ createJiti: void 0, version: "1.21.7" }); + + const cwd = getFixturePath("ts-config-files", "ts"); + + eslint = new ESLint({ + cwd, + flags, + }); + + await assert.rejects(eslint.lintFiles("foo.js"), { + message: + "You are using an outdated version of the 'jiti' library. Please update to the latest version of 'jiti' to ensure compatibility and access to the latest features.", + }); + }); + + it("should handle jiti interopDefault edge cases", async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "jiti-interopDefault", + ); + + await fsp.writeFile( + path.join(cwd, "eslint.config.ts"), + ` + import plugin from "./plugin"; + + export default plugin.configs.recommended; + + // Autogenerated on ${new Date().toISOString()}.`, + ); + + eslint = new ESLint({ + cwd, + flags, + }); + + const results = await eslint.lintFiles("foo.js"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + // eslint-disable-next-line n/no-unsupported-features/node-builtins -- it's still an experimental feature. + (typeof process.features.typescript === "string" + ? describe + : describe.skip)( + "Loading TypeScript config files natively", + () => { + beforeEach(() => { + sinon.stub(ConfigLoader, "loadJiti").rejects(); + }); + + describe("should load a TS config file when --experimental-strip-types is enabled", () => { + it('with "type": "commonjs" in `package.json` and CJS syntax', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "native", + "with-type-commonjs", + "CJS-syntax", + ); + + const configFileContent = + 'import type { FlatConfig, Severity } from "./helper.ts";\n\nconst eslintConfig = [\n { rules: { "no-undef": 2 satisfies Severity.Error } },\n] satisfies FlatConfig[];\n\nmodule.exports = eslintConfig;\n'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeCommonJS, + [eslintConfigFiles.ts]: + configFileContent, + "foo.js": "foo;", + "helper.ts": + 'export type * from "../../../../helper.ts";\n', + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile: eslintConfigFiles.ts, + flags: nativeTSConfigFileFlags, + }); + + const results = await eslint.lintFiles([ + "foo*.js", + ]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, eslintConfigFiles.ts), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(cwd, "foo.js"), + ); + assert.strictEqual( + results[0].messages.length, + 1, + ); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('with "type": "commonjs" in `package.json` and ESM syntax', async () => { + const cwd = getFixturePath( + "ts-config-files", + "mts", + "native", + "with-type-commonjs", + "ESM-syntax", + ); + + const configFileContent = + 'import type { FlatConfig, Severity } from "./helper.ts";\n\nconst eslintConfig = [\n { rules: { "no-undef": 2 satisfies Severity.Error } },\n] satisfies FlatConfig[];\n\nexport default eslintConfig;\n'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeCommonJS, + [eslintConfigFiles.mts]: + configFileContent, + "foo.js": "foo;", + "helper.ts": + 'export type * from "../../../../helper.ts";\n', + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile: eslintConfigFiles.mts, + flags: nativeTSConfigFileFlags, + }); + + const results = await eslint.lintFiles([ + "foo*.js", + ]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, eslintConfigFiles.mts), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(cwd, "foo.js"), + ); + assert.strictEqual( + results[0].messages.length, + 1, + ); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('with "type": "module" in `package.json` and CJS syntax', async () => { + const cwd = getFixturePath( + "ts-config-files", + "cts", + "native", + "with-type-module", + "CJS-syntax", + ); + + const configFileContent = + 'import type { FlatConfig, Severity } from "./helper.cts";\n\nconst eslintConfig = [\n { rules: { "no-undef": 2 satisfies Severity.Error } },\n] satisfies FlatConfig[];\n\nmodule.exports = eslintConfig;\n'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeModule, + [eslintConfigFiles.cts]: + configFileContent, + "foo.js": "foo;", + "helper.cts": + 'export type * from "../../../../helper.ts";\n', + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile: eslintConfigFiles.cts, + flags: nativeTSConfigFileFlags, + }); + + const results = await eslint.lintFiles([ + "foo*.js", + ]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, eslintConfigFiles.cts), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(cwd, "foo.js"), + ); + assert.strictEqual( + results[0].messages.length, + 1, + ); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('with "type": "module" in `package.json` and ESM syntax', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "native", + "with-type-module", + "ESM-syntax", + ); + + const configFileContent = + 'import type { FlatConfig, Severity } from "./helper.cts";\n\nconst eslintConfig = [\n { rules: { "no-undef": 2 satisfies Severity.Error } },\n] satisfies FlatConfig[];\n\nexport default eslintConfig;\n'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeModule, + [eslintConfigFiles.ts]: + configFileContent, + "foo.js": "foo;", + "helper.ts": + 'export type * from "../../../../helper.ts";\n', + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile: eslintConfigFiles.ts, + flags: nativeTSConfigFileFlags, + }); + + const results = await eslint.lintFiles([ + "foo*.js", + ]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, eslintConfigFiles.ts), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(cwd, "foo.js"), + ); + assert.strictEqual( + results[0].messages.length, + 1, + ); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + }); + + // eslint-disable-next-line n/no-unsupported-features/node-builtins -- it's still an experimental feature. + (process.features.typescript === "transform" + ? describe + : describe.skip)( + "should load a TS config file when --experimental-transform-types is enabled", + () => { + it('with "type": "commonjs" in `package.json` and CJS syntax', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "native", + "with-type-commonjs", + "CJS-syntax", + ); + + const configFileContent = + 'import ESLintNameSpace = require("./helper.ts");\n\nconst eslintConfig = [ { rules: { "no-undef": ESLintNameSpace.StringSeverity.Error } }]\n\nexport = eslintConfig;\n'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeCommonJS, + [eslintConfigFiles.ts]: + configFileContent, + "foo.js": "foo;", + "helper.ts": + 'namespace ESLintNameSpace {\n export const enum StringSeverity {\n "Off" = "off",\n "Warn" = "warn",\n "Error" = "error",\n }\n}\n\nexport = ESLintNameSpace\n', + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile: + eslintConfigFiles.ts, + flags: nativeTSConfigFileFlags, + }); + + const results = await eslint.lintFiles([ + "foo*.js", + ]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, eslintConfigFiles.ts), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(cwd, "foo.js"), + ); + assert.strictEqual( + results[0].messages.length, + 1, + ); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('with "type": "commonjs" in `package.json` and ESM syntax', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "native", + "with-type-commonjs", + "ESM-syntax", + ); + + const configFileContent = + 'import ESLintNameSpace from "./helper.ts";\n\nconst eslintConfig = [ { rules: { "no-undef": ESLintNameSpace.StringSeverity.Error } }]\n\nexport default eslintConfig;\n'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeCommonJS, + [eslintConfigFiles.mts]: + configFileContent, + "foo.js": "foo;", + "helper.ts": + 'namespace ESLintNameSpace {\n export const enum StringSeverity {\n "Off" = "off",\n "Warn" = "warn",\n "Error" = "error",\n }\n}\n\nexport = ESLintNameSpace\n', + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile: + eslintConfigFiles.mts, + flags: nativeTSConfigFileFlags, + }); + + const results = await eslint.lintFiles([ + "foo*.js", + ]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, eslintConfigFiles.mts), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(cwd, "foo.js"), + ); + assert.strictEqual( + results[0].messages.length, + 1, + ); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('with "type": "module" in `package.json` and CJS syntax', async () => { + const cwd = getFixturePath( + "ts-config-files", + "cts", + "native", + "with-type-module", + "CJS-syntax", + ); + + const configFileContent = + 'import ESLintNameSpace = require("./helper.cts");\n\nconst eslintConfig = [ { rules: { "no-undef": ESLintNameSpace.StringSeverity.Error } }]\n\nexport = eslintConfig;\n'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeModule, + [eslintConfigFiles.cts]: + configFileContent, + "foo.js": "foo;", + "helper.cts": + 'namespace ESLintNameSpace {\n export const enum StringSeverity {\n "Off" = "off",\n "Warn" = "warn",\n "Error" = "error",\n }\n}\n\nexport = ESLintNameSpace\n', + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile: + eslintConfigFiles.cts, + flags: nativeTSConfigFileFlags, + }); + + const results = await eslint.lintFiles([ + "foo*.js", + ]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, eslintConfigFiles.cts), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(cwd, "foo.js"), + ); + assert.strictEqual( + results[0].messages.length, + 1, + ); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it('with "type": "module" in `package.json` and ESM syntax', async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "native", + "with-type-module", + "ESM-syntax", + ); + + const configFileContent = + 'import ESLintNameSpace from "./helper.ts";\n\nconst eslintConfig = [ { rules: { "no-undef": ESLintNameSpace.StringSeverity.Error } }]\n\nexport default eslintConfig;\n'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeModule, + [eslintConfigFiles.ts]: + configFileContent, + "foo.js": "foo;", + "helper.ts": + 'namespace ESLintNameSpace {\n export const enum StringSeverity {\n "Off" = "off",\n "Warn" = "warn",\n "Error" = "error",\n }\n}\n\nexport default ESLintNameSpace\n', + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile: + eslintConfigFiles.ts, + flags: nativeTSConfigFileFlags, + }); + + const results = await eslint.lintFiles([ + "foo*.js", + ]); + + assert.strictEqual( + await eslint.findConfigFile(), + path.join(cwd, eslintConfigFiles.ts), + ); + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(cwd, "foo.js"), + ); + assert.strictEqual( + results[0].messages.length, + 1, + ); + assert.strictEqual( + results[0].messages[0].severity, + 2, + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + }); + + it("fails without unstable_native_nodejs_ts_config if jiti is not installed", async () => { + sinon.restore(); + + const loadJitiStub = sinon + .stub(ConfigLoader, "loadJiti") + .rejects(); + + const cwd = getFixturePath( + "ts-config-files", + "ts", + "native", + "edge-case-1", + ); + + const configFileContent = + 'import ESLintNameSpace from "./helper.ts";\n\nconst eslintConfig = [ { rules: { "no-undef": ESLintNameSpace.StringSeverity.Error } }]\n\nexport default eslintConfig;\n'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeModule, + [eslintConfigFiles.ts]: + configFileContent, + "foo.js": "foo;", + "helper.ts": + 'namespace ESLintNameSpace {\n export const enum StringSeverity {\n "Off" = "off",\n "Warn" = "warn",\n "Error" = "error",\n }\n}\n\nexport default ESLintNameSpace\n', + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile: + eslintConfigFiles.ts, + flags, + }); + + await assert.rejects( + eslint.lintFiles(["foo*.js"]), + { + message: + "The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.", + }, + ); + + loadJitiStub.restore(); + }); + + it("fails without unstable_native_nodejs_ts_config if jiti is outdated", async () => { + sinon.restore(); + + const loadJitiStub = sinon + .stub(ConfigLoader, "loadJiti") + .resolves({ + createJiti: void 0, + version: "1.21.7", + }); + + const cwd = getFixturePath( + "ts-config-files", + "ts", + "native", + "edge-case-2", + ); + + const configFileContent = + 'import ESLintNameSpace from "./helper.ts";\n\nconst eslintConfig = [ { rules: { "no-undef": ESLintNameSpace.StringSeverity.Error } }]\n\nexport default eslintConfig;\n'; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeModule, + [eslintConfigFiles.ts]: + configFileContent, + "foo.js": "foo;", + "helper.ts": + 'namespace ESLintNameSpace {\n export const enum StringSeverity {\n "Off" = "off",\n "Warn" = "warn",\n "Error" = "error",\n }\n}\n\nexport default ESLintNameSpace\n', + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile: + eslintConfigFiles.ts, + flags, + }); + + await assert.rejects( + eslint.lintFiles(["foo*.js"]), + { + message: + "You are using an outdated version of the 'jiti' library. Please update to the latest version of 'jiti' to ensure compatibility and access to the latest features.", + }, + ); + + loadJitiStub.restore(); + }); + }, + ); + }, + ); + + // eslint-disable-next-line n/no-unsupported-features/node-builtins -- it's still an experimental feature. + (typeof process.features.typescript === "undefined" + ? it + : it.skip)( + "should throw an error if unstable_native_nodejs_ts_config is set but --experimental-strip-types is not enabled and process.features.typescript is undefined", + async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "native", + ); + + const configFileContent = `import type { FlatConfig } from "./helper.ts";\nexport default ${JSON.stringify( + [{ rules: { "no-undef": 2 } }], + null, + 2, + )} satisfies FlatConfig[];`; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeModule, + [eslintConfigFiles.ts]: configFileContent, + "foo.js": "foo;", + "helper.ts": + 'import type { Linter } from "eslint";\nexport type FlatConfig = Linter.Config;\n', + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile: eslintConfigFiles.ts, + flags: nativeTSConfigFileFlags, + }); + + await assert.rejects(eslint.lintFiles(["foo*.js"]), { + message: + "The unstable_native_nodejs_ts_config flag is not supported in older versions of Node.js.", + }); + }, + ); + + // eslint-disable-next-line n/no-unsupported-features/node-builtins -- it's still an experimental feature. + (process.features.typescript === false ? it : it.skip)( + "should throw an error if unstable_native_nodejs_ts_config is set but --experimental-strip-types is not enabled and process.features.typescript is false", + async () => { + const cwd = getFixturePath( + "ts-config-files", + "ts", + "native", + ); + + const configFileContent = `import type { FlatConfig } from "./helper.ts";\nexport default ${JSON.stringify( + [{ rules: { "no-undef": 2 } }], + null, + 2, + )} satisfies FlatConfig[];`; + + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": typeModule, + [eslintConfigFiles.ts]: configFileContent, + "foo.js": "foo;", + "helper.ts": + 'import type { Linter } from "eslint";\nexport type FlatConfig = Linter.Config;\n', + }, + }); + + await teardown.prepare(); + + eslint = new ESLint({ + cwd, + overrideConfigFile: eslintConfigFiles.ts, + flags: nativeTSConfigFileFlags, + }); + + await assert.rejects(eslint.lintFiles(["foo*.js"]), { + message: + "The unstable_native_nodejs_ts_config flag is enabled, but native TypeScript support is not enabled in the current Node.js process. You need to either enable native TypeScript support by passing --experimental-strip-types or remove the unstable_native_nodejs_ts_config flag.", + }); + }, + ); + }); + + it("should stop linting files if a rule crashes", async () => { + const cwd = getFixturePath("files"); + let createCallCount = 0; + + eslint = new ESLint({ + flags, + cwd, + plugins: { + boom: { + rules: { + boom: { + create() { + createCallCount++; + throw Error("Boom!"); + }, + }, + }, + }, + }, + baseConfig: { + rules: { + "boom/boom": "error", + }, + }, + }); + + await assert.rejects(eslint.lintFiles("*.js")); + + // Wait until all files have been closed. + // eslint-disable-next-line n/no-unsupported-features/node-builtins -- it's still an experimental feature. + while (process.getActiveResourcesInfo().includes("CloseReq")) { + await timers.setImmediate(); + } + assert.strictEqual(createCallCount, 1); + }); + + // https://github.com/eslint/eslint/issues/19243 + it("should not exit the process unexpectedly after a rule crashes", async () => { + const cwd = getFixturePath(); + + /* + * Mocha attaches `unhandledRejection` event handlers to the current process. + * To test without global handlers, we must launch a new process. + */ + const teardown = createCustomTeardown({ + cwd, + files: { + "test.js": ` + const { ESLint } = require(${JSON.stringify(require.resolve("eslint"))}); + + const eslint = new ESLint({ + flags: ${JSON.stringify(flags)}, + overrideConfigFile: true, + plugins: { + boom: { + rules: { + boom: { + create: () => ({ + "*"() { + throw "Boom!"; + }, + }), + } + } } - ] - } - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath() }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block - assert.strictEqual(results[0].messages[0].line, 2); - assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block - assert.strictEqual(results[0].messages[1].line, 7); - }); - }); - - describe("MODULE_NOT_FOUND error handling", () => { - const cwd = getFixturePath("module-not-found"); - - beforeEach(() => { - eslint = new ESLint({ cwd }); - }); - - it("should throw an error with a message template when 'extends' property has a non-existence JavaScript config.", async () => { - try { - await eslint.lintText("test", { filePath: "extends-js/test.js" }); - } catch (err) { - assert.strictEqual(err.messageTemplate, "extend-config-missing"); - assert.deepStrictEqual(err.messageData, { - configName: "nonexistent-config", - importerName: getFixturePath("module-not-found", "extends-js", ".eslintrc.yml") - }); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with a message template when 'extends' property has a non-existence plugin config.", async () => { - try { - await eslint.lintText("test", { filePath: "extends-plugin/test.js" }); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, "plugin-missing"); - assert.deepStrictEqual(err.messageData, { - importerName: `extends-plugin${path.sep}.eslintrc.yml`, - pluginName: "eslint-plugin-nonexistent-plugin", - resolvePluginsRelativeTo: path.join(cwd, "extends-plugin") // the directory of the config file. - }); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with a message template when 'plugins' property has a non-existence plugin.", async () => { - try { - await eslint.lintText("test", { filePath: "plugins/test.js" }); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, "plugin-missing"); - assert.deepStrictEqual(err.messageData, { - importerName: `plugins${path.sep}.eslintrc.yml`, - pluginName: "eslint-plugin-nonexistent-plugin", - resolvePluginsRelativeTo: path.join(cwd, "plugins") // the directory of the config file. - }); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with no message template when a JavaScript config threw a 'MODULE_NOT_FOUND' error.", async () => { - try { - await eslint.lintText("test", { filePath: "throw-in-config-itself/test.js" }); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, void 0); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with no message template when 'extends' property has a JavaScript config that throws a 'MODULE_NOT_FOUND' error.", async () => { - try { - await eslint.lintText("test", { filePath: "throw-in-extends-js/test.js" }); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, void 0); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with no message template when 'extends' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", async () => { - try { - await eslint.lintText("test", { filePath: "throw-in-extends-plugin/test.js" }); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, void 0); - return; - } - assert.fail("Expected to throw an error"); - }); - - it("should throw an error with no message template when 'plugins' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", async () => { - try { - await eslint.lintText("test", { filePath: "throw-in-plugins/test.js" }); - } catch (err) { - assert.strictEqual(err.code, "MODULE_NOT_FOUND"); - assert.strictEqual(err.messageTemplate, void 0); - return; - } - assert.fail("Expected to throw an error"); - }); - }); - - describe("with '--rulesdir' option", () => { - - const rootPath = getFixturePath("cli-engine/with-rulesdir"); - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: rootPath, - files: { - "internal-rules/test.js": ` - module.exports = context => ({ - ExpressionStatement(node) { - context.report({ node, message: "ok" }) + }, + baseConfig: { + rules: { + "boom/boom": "error" } - }) + } + }); + + eslint.lintFiles("passing.js").catch(() => { }); `, - ".eslintrc.json": { - root: true, - rules: { test: "error" } - }, - "test.js": "console.log('hello')" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - - it("should use the configured rules which are defined by '--rulesdir' option.", async () => { - eslint = new ESLint({ - cwd: getPath(), - rulePaths: ["internal-rules"] - }); - const results = await eslint.lintFiles(["test.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].message, "ok"); - }); - }); - - describe("glob pattern '[ab].js'", () => { - const root = getFixturePath("cli-engine/unmatched-glob"); - - let cleanup; - - beforeEach(() => { - cleanup = () => { }; - }); - - afterEach(() => cleanup()); - - it("should match '[ab].js' if existed.", async () => { - - const teardown = createCustomTeardown({ - cwd: root, - files: { - "a.js": "", - "b.js": "", - "ab.js": "", - "[ab].js": "", - ".eslintrc.yml": "root: true" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - eslint = new ESLint({ cwd: teardown.getPath() }); - const results = await eslint.lintFiles(["[ab].js"]); - const filenames = results.map(r => path.basename(r.filePath)); - - assert.deepStrictEqual(filenames, ["[ab].js"]); - }); - - it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "a.js": "", - "b.js": "", - "ab.js": "", - ".eslintrc.yml": "root: true" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath() }); - const results = await eslint.lintFiles(["[ab].js"]); - const filenames = results.map(r => path.basename(r.filePath)); - - assert.deepStrictEqual(filenames, ["a.js", "b.js"]); - }); - }); - - describe("with 'noInlineConfig' setting", () => { - const root = getFixturePath("cli-engine/noInlineConfig"); - - let cleanup; - - beforeEach(() => { - cleanup = () => { }; - }); - - afterEach(() => cleanup()); - - it("should warn directive comments if 'noInlineConfig' was given.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "test.js": "/* globals foo */", - ".eslintrc.yml": "noInlineConfig: true" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml)."); - }); - - it("should show the config file what the 'noInlineConfig' came from.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-config-foo/index.js": "module.exports = {noInlineConfig: true}", - "test.js": "/* globals foo */", - ".eslintrc.yml": "extends: foo" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml Âģ eslint-config-foo)."); - }); - }); - - describe("with 'reportUnusedDisableDirectives' setting", () => { - const root = getFixturePath("cli-engine/reportUnusedDisableDirectives"); - - let cleanup; - - beforeEach(() => { - cleanup = () => { }; - }); - - afterEach(() => cleanup()); - - it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives' was given.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "test.js": "/* eslint-disable eqeqeq */", - ".eslintrc.yml": "reportUnusedDisableDirectives: true" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - }); - - describe("the runtime option overrides config files.", () => { - it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "test.js": "/* eslint-disable eqeqeq */", - ".eslintrc.yml": "reportUnusedDisableDirectives: true" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - eslint = new ESLint({ - cwd: teardown.getPath(), - reportUnusedDisableDirectives: "off" - }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 0); - }); - - it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "test.js": "/* eslint-disable eqeqeq */", - ".eslintrc.yml": "reportUnusedDisableDirectives: true" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - eslint = new ESLint({ - cwd: teardown.getPath(), - reportUnusedDisableDirectives: "error" - }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - }); - }); - }); - - describe("with 'overrides[*].extends' setting on deep locations", () => { - const root = getFixturePath("cli-engine/deeply-overrides-i-extends"); - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - overrides: [{ files: ["*test*"], extends: "two" }] - })}`, - "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({ - overrides: [{ files: ["*.js"], extends: "three" }] - })}`, - "node_modules/eslint-config-three/index.js": `module.exports = ${JSON.stringify({ - rules: { "no-console": "error" } - })}`, - "test.js": "console.log('hello')", - ".eslintrc.yml": "extends: one" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("should not throw.", async () => { - eslint = new ESLint({ cwd: getPath() }); - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - }); - }); - - describe("don't ignore the entry directory.", () => { - const root = getFixturePath("cli-engine/dont-ignore-entry-dir"); - - let cleanup; - - beforeEach(() => { - cleanup = () => { }; - }); - - afterEach(async () => { - await cleanup(); - - const configFilePath = path.resolve(root, "../.eslintrc.json"); - - if (shell.test("-e", configFilePath)) { - shell.rm(configFilePath); - } - }); - - it("'lintFiles(\".\")' should not load config files from outside of \".\".", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "../.eslintrc.json": "BROKEN FILE", - ".eslintrc.json": JSON.stringify({ root: true }), - "index.js": "console.log(\"hello\")" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath() }); - - // Don't throw "failed to load config file" error. - await eslint.lintFiles("."); - }); - - it("'lintFiles(\".\")' should not ignore '.' even if 'ignorePatterns' contains it.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "../.eslintrc.json": { ignorePatterns: ["/dont-ignore-entry-dir"] }, - ".eslintrc.json": { root: true }, - "index.js": "console.log(\"hello\")" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath() }); - - // Don't throw "file not found" error. - await eslint.lintFiles("."); - }); - - it("'lintFiles(\"subdir\")' should not ignore './subdir' even if 'ignorePatterns' contains it.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": { ignorePatterns: ["/subdir"] }, - "subdir/.eslintrc.json": { root: true }, - "subdir/index.js": "console.log(\"hello\")" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new ESLint({ cwd: teardown.getPath() }); - - // Don't throw "file not found" error. - await eslint.lintFiles("subdir"); - }); - }); - - it("should throw if non-boolean value is given to 'options.warnIgnored' option", async () => { - eslint = new ESLint(); - await assert.rejects(() => eslint.lintFiles(777), /'patterns' must be a non-empty string or an array of non-empty strings/u); - await assert.rejects(() => eslint.lintFiles([null]), /'patterns' must be a non-empty string or an array of non-empty strings/u); - }); - }); - - describe("calculateConfigForFile", () => { - it("should return the info from Config#getConfig when called", async () => { - const options = { - overrideConfigFile: getFixturePath("configurations", "quotes-error.json") - }; - const engine = new ESLint(options); - const filePath = getFixturePath("single-quoted.js"); - const actualConfig = await engine.calculateConfigForFile(filePath); - const expectedConfig = new CascadingConfigArrayFactory({ specificConfigPath: options.overrideConfigFile }) - .getConfigArrayForFile(filePath) - .extractConfig(filePath) - .toCompatibleObjectAsConfigFileContent(); - - assert.deepStrictEqual(actualConfig, expectedConfig); - }); - - it("should return the config for a file that doesn't exist", async () => { - const engine = new ESLint(); - const filePath = getFixturePath("does_not_exist.js"); - const existingSiblingFilePath = getFixturePath("single-quoted.js"); - const actualConfig = await engine.calculateConfigForFile(filePath); - const expectedConfig = await engine.calculateConfigForFile(existingSiblingFilePath); - - assert.deepStrictEqual(actualConfig, expectedConfig); - }); - - it("should return the config for a virtual file that is a child of an existing file", async () => { - const engine = new ESLint(); - const parentFileName = "single-quoted.js"; - const filePath = getFixturePath(parentFileName, "virtual.js"); // single-quoted.js/virtual.js - const parentFilePath = getFixturePath(parentFileName); - const actualConfig = await engine.calculateConfigForFile(filePath); - const expectedConfig = await engine.calculateConfigForFile(parentFilePath); - - assert.deepStrictEqual(actualConfig, expectedConfig); - }); - - it("should return the config when run from within a subdir", async () => { - const options = { - cwd: getFixturePath("config-hierarchy", "root-true", "parent", "root", "subdir") - }; - const engine = new ESLint(options); - const filePath = getFixturePath("config-hierarchy", "root-true", "parent", "root", ".eslintrc"); - const actualConfig = await engine.calculateConfigForFile("./.eslintrc"); - const expectedConfig = new CascadingConfigArrayFactory(options) - .getConfigArrayForFile(filePath) - .extractConfig(filePath) - .toCompatibleObjectAsConfigFileContent(); - - assert.deepStrictEqual(actualConfig, expectedConfig); - }); - - it("should throw an error if a directory path was given.", async () => { - const engine = new ESLint(); - - try { - await engine.calculateConfigForFile("."); - } catch (error) { - assert.strictEqual(error.messageTemplate, "print-config-with-directory-path"); - return; - } - assert.fail("should throw an error"); - }); - - it("should throw if non-string value is given to 'filePath' parameter", async () => { - const eslint = new ESLint(); - - await assert.rejects(() => eslint.calculateConfigForFile(null), /'filePath' must be a non-empty string/u); - }); - - // https://github.com/eslint/eslint/issues/13793 - it("should throw with an invalid built-in rule config", async () => { - const options = { - baseConfig: { - rules: { - "no-alert": ["error", { - thisDoesNotExist: true - }] - } - } - }; - const engine = new ESLint(options); - const filePath = getFixturePath("single-quoted.js"); - - await assert.rejects( - () => engine.calculateConfigForFile(filePath), - /Configuration for rule "no-alert" is invalid:/u - ); - }); - }); - - describe("isPathIgnored", () => { - it("should check if the given path is ignored", async () => { - const engine = new ESLint({ - ignorePath: getFixturePath(".eslintignore2"), - cwd: getFixturePath() - }); - - assert(await engine.isPathIgnored("undef.js")); - assert(!await engine.isPathIgnored("passing.js")); - }); - - it("should return false if ignoring is disabled", async () => { - const engine = new ESLint({ - ignore: false, - ignorePath: getFixturePath(".eslintignore2"), - cwd: getFixturePath() - }); - - assert(!await engine.isPathIgnored("undef.js")); - }); - - // https://github.com/eslint/eslint/issues/5547 - it("should return true for default ignores even if ignoring is disabled", async () => { - const engine = new ESLint({ - ignore: false, - cwd: getFixturePath("cli-engine") - }); - - assert(await engine.isPathIgnored("node_modules/foo.js")); - }); - - describe("about the default ignore patterns", () => { - it("should always apply defaultPatterns if ignore option is true", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); - }); - - it("should still apply defaultPatterns if ignore option is is false", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ ignore: false, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); - }); - - it("should allow subfolders of defaultPatterns to be unignored by ignorePattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ - cwd, - overrideConfig: { - ignorePatterns: "!/node_modules/package" - } - }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); - }); - - it("should allow subfolders of defaultPatterns to be unignored by ignorePath", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ cwd, ignorePath: getFixturePath("ignored-paths", ".eslintignoreWithUnignoredDefaults") }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); - }); - - it("should ignore dotfiles", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".foo"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar"))); - }); - - it("should ignore directories beginning with a dot", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".foo/bar"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar/baz"))); - }); - - it("should still ignore dotfiles when ignore option disabled", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ ignore: false, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".foo"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar"))); - }); - - it("should still ignore directories beginning with a dot when ignore option disabled", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ ignore: false, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".foo/bar"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/.bar/baz"))); - }); - - it("should not ignore absolute paths containing '..'", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ cwd }); - - assert(!await engine.isPathIgnored(`${getFixturePath("ignored-paths", "foo")}/../unignored.js`)); - }); - - it("should ignore /node_modules/ relative to .eslintignore when loaded", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ ignorePath: getFixturePath("ignored-paths", ".eslintignore"), cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "existing.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo", "node_modules", "existing.js"))); - }); - - it("should ignore /node_modules/ relative to cwd without an .eslintignore", async () => { - const cwd = getFixturePath("ignored-paths", "no-ignore-file"); - const engine = new ESLint({ cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "node_modules", "existing.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "foo", "node_modules", "existing.js"))); - }); - }); - - describe("with no .eslintignore file", () => { - it("should not travel to parent directories to find .eslintignore when it's missing and cwd is provided", async () => { - const cwd = getFixturePath("ignored-paths", "configurations"); - const engine = new ESLint({ cwd }); - - // an .eslintignore in parent directories includes `*.js`, but don't load it. - assert(!await engine.isPathIgnored("foo.js")); - assert(await engine.isPathIgnored("node_modules/foo.js")); - }); - - it("should return false for files outside of the cwd (with no ignore file provided)", async () => { - - // Default ignore patterns should not inadvertently ignore files in parent directories - const engine = new ESLint({ cwd: getFixturePath("ignored-paths", "no-ignore-file") }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - }); - - describe("with .eslintignore file or package.json file", () => { - it("should load .eslintignore from cwd when explicitly passed", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ cwd }); - - // `${cwd}/.eslintignore` includes `sampleignorepattern`. - assert(await engine.isPathIgnored("sampleignorepattern")); - }); - - it("should use package.json's eslintIgnore files if no specified .eslintignore file", async () => { - const cwd = getFixturePath("ignored-paths", "package-json-ignore"); - const engine = new ESLint({ cwd }); - - assert(await engine.isPathIgnored("hello.js")); - assert(await engine.isPathIgnored("world.js")); - }); - - it("should use correct message template if failed to parse package.json", () => { - const cwd = getFixturePath("ignored-paths", "broken-package-json"); - - assert.throws(() => { - try { - // eslint-disable-next-line no-new -- Check for error - new ESLint({ cwd }); - } catch (error) { - assert.strictEqual(error.messageTemplate, "failed-to-read-json"); - throw error; - } - }); - }); - - it("should not use package.json's eslintIgnore files if specified .eslintignore file", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ cwd }); - - /* - * package.json includes `hello.js` and `world.js`. - * .eslintignore includes `sampleignorepattern`. - */ - assert(!await engine.isPathIgnored("hello.js")); - assert(!await engine.isPathIgnored("world.js")); - assert(await engine.isPathIgnored("sampleignorepattern")); - }); - - it("should error if package.json's eslintIgnore is not an array of file paths", () => { - const cwd = getFixturePath("ignored-paths", "bad-package-json-ignore"); - - assert.throws(() => { - // eslint-disable-next-line no-new -- Check for throwing - new ESLint({ cwd }); - }, /Package\.json eslintIgnore property requires an array of paths/u); - }); - }); - - describe("with --ignore-pattern option", () => { - it("should accept a string for options.ignorePattern", async () => { - const cwd = getFixturePath("ignored-paths", "ignore-pattern"); - const engine = new ESLint({ - overrideConfig: { - ignorePatterns: "ignore-me.txt" - }, - cwd - }); - - assert(await engine.isPathIgnored("ignore-me.txt")); - }); - - it("should accept an array for options.ignorePattern", async () => { - const engine = new ESLint({ - overrideConfig: { - ignorePatterns: ["a", "b"] - }, - useEslintrc: false - }); - - assert(await engine.isPathIgnored("a")); - assert(await engine.isPathIgnored("b")); - assert(!await engine.isPathIgnored("c")); - }); - - it("should return true for files which match an ignorePattern even if they do not exist on the filesystem", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ - overrideConfig: { - ignorePatterns: "not-a-file" - }, - cwd - }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "not-a-file"))); - }); - - it("should return true for file matching an ignore pattern exactly", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ overrideConfig: { ignorePatterns: "undef.js" }, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - - it("should return false for file matching an invalid ignore pattern with leading './'", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ overrideConfig: { ignorePatterns: "./undef.js" }, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - - it("should return false for file in subfolder of cwd matching an ignore pattern with leading '/'", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ overrideConfig: { ignorePatterns: "/undef.js" }, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir", "undef.js"))); - }); - - it("should return true for file matching a child of an ignore pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ overrideConfig: { ignorePatterns: "ignore-pattern" }, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "ignore-me.txt"))); - }); - - it("should return true for file matching a grandchild of an ignore pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ overrideConfig: { ignorePatterns: "ignore-pattern" }, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "subdir", "ignore-me.txt"))); - }); - - it("should return false for file not matching any ignore pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ overrideConfig: { ignorePatterns: "failing.js" }, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "unignored.js"))); - }); - - it("two globstar '**' ignore pattern should ignore files in nested directories", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ overrideConfig: { ignorePatterns: "**/*.js" }, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.js"))); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.j2"))); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.j2"))); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.j2"))); - }); - }); - - describe("with --ignore-path option", () => { - it("initialization with ignorePath should work when cwd is a parent directory", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new ESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored("custom-name/foo.js")); - }); - - it("initialization with ignorePath should work when the file is in the cwd", async () => { - const cwd = getFixturePath("ignored-paths", "custom-name"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new ESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored("foo.js")); - }); - - it("initialization with ignorePath should work when cwd is a subdirectory", async () => { - const cwd = getFixturePath("ignored-paths", "custom-name", "subdirectory"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new ESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored("../custom-name/foo.js")); - }); - - it("initialization with invalid file should throw error", () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "not-a-directory", ".foobaz"); - - assert.throws(() => { - // eslint-disable-next-line no-new -- Check for throwing - new ESLint({ ignorePath, cwd }); - }, /Cannot read \.eslintignore file/u); - }); - - it("should return false for files outside of ignorePath's directory", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "custom-name", "ignore-file"); - const engine = new ESLint({ ignorePath, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - - it("should resolve relative paths from CWD", async () => { - const cwd = getFixturePath("ignored-paths", "subdir"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreForDifferentCwd"); - const engine = new ESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/undef.js"))); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - - it("should resolve relative paths from CWD when it's in a child directory", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new ESLint({ ignorePath, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/undef.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/foo.js"))); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/bar.js"))); - }); - - it("should resolve relative paths from CWD when it contains negated globs", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new ESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored("subdir/blah.txt")); - assert(await engine.isPathIgnored("blah.txt")); - assert(await engine.isPathIgnored("subdir/bar.txt")); - assert(!await engine.isPathIgnored("bar.txt")); - assert(!await engine.isPathIgnored("subdir/baz.txt")); - assert(!await engine.isPathIgnored("baz.txt")); - }); - - it("should resolve default ignore patterns from the CWD even when the ignorePath is in a subdirectory", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "subdir/.eslintignoreInChildDir"); - const engine = new ESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored("node_modules/blah.js")); - }); - - it("should resolve default ignore patterns from the CWD even when the ignorePath is in a parent directory", async () => { - const cwd = getFixturePath("ignored-paths", "subdir"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreForDifferentCwd"); - const engine = new ESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored("node_modules/blah.js")); - }); - - it("should handle .eslintignore which contains CRLF correctly.", async () => { - const ignoreFileContent = fs.readFileSync(getFixturePath("ignored-paths", "crlf/.eslintignore"), "utf8"); - - assert(ignoreFileContent.includes("\r"), "crlf/.eslintignore should contains CR."); - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", "crlf/.eslintignore"); - const engine = new ESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide1/a.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide2/a.js"))); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "crlf/hide3/a.js"))); - }); - - it("should not include comments in ignore rules", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithComments"); - const engine = new ESLint({ ignorePath, cwd }); - - assert(!await engine.isPathIgnored("# should be ignored")); - assert(await engine.isPathIgnored("this_one_not")); - }); - - it("should ignore a non-negated pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithNegation"); - const engine = new ESLint({ ignorePath, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "negation", "ignore.js"))); - }); - - it("should not ignore a negated pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithNegation"); - const engine = new ESLint({ ignorePath, cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "negation", "unignore.js"))); - }); - - // https://github.com/eslint/eslint/issues/15642 - it("should correctly handle patterns with escaped brackets", async () => { - const cwd = getFixturePath("ignored-paths"); - const ignorePath = getFixturePath("ignored-paths", ".eslintignoreWithEscapedBrackets"); - const engine = new ESLint({ ignorePath, cwd }); - - const subdir = "brackets"; - - assert( - !await engine.isPathIgnored(getFixturePath("ignored-paths", subdir, "index.js")), - `'${subdir}/index.js' should not be ignored` - ); - - for (const filename of ["[index.js", "index].js", "[index].js"]) { - assert( - await engine.isPathIgnored(getFixturePath("ignored-paths", subdir, filename)), - `'${subdir}/${filename}' should be ignored` - ); - } - - }); - }); - - describe("with --ignore-path option and --ignore-pattern option", () => { - it("should return false for ignored file when unignored with ignore pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new ESLint({ - ignorePath: getFixturePath("ignored-paths", ".eslintignore"), - overrideConfig: { - ignorePatterns: "!sampleignorepattern" - }, - cwd - }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "sampleignorepattern"))); - }); - }); - - it("should throw if non-string value is given to 'filePath' parameter", async () => { - const eslint = new ESLint(); - - await assert.rejects(() => eslint.isPathIgnored(null), /'filePath' must be a non-empty string/u); - }); - }); - - describe("loadFormatter()", () => { - it("should return a formatter object when a bundled formatter is requested", async () => { - const engine = new ESLint(); - const formatter = await engine.loadFormatter("compact"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when no argument is passed", async () => { - const engine = new ESLint(); - const formatter = await engine.loadFormatter(); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a custom formatter is requested", async () => { - const engine = new ESLint(); - const formatter = await engine.loadFormatter(getFixturePath("formatters", "simple.js")); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a custom formatter is requested, also if the path has backslashes", async () => { - const engine = new ESLint({ - cwd: path.join(fixtureDir, "..") - }); - const formatter = await engine.loadFormatter(".\\fixtures\\formatters\\simple.js"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a formatter prefixed with eslint-formatter is requested", async () => { - const engine = new ESLint({ - cwd: getFixturePath("cli-engine") - }); - const formatter = await engine.loadFormatter("bar"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a formatter is requested, also when the eslint-formatter prefix is included in the format argument", async () => { - const engine = new ESLint({ - cwd: getFixturePath("cli-engine") - }); - const formatter = await engine.loadFormatter("eslint-formatter-bar"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a formatter is requested within a scoped npm package", async () => { - const engine = new ESLint({ - cwd: getFixturePath("cli-engine") - }); - const formatter = await engine.loadFormatter("@somenamespace/foo"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a formatter is requested within a scoped npm package, also when the eslint-formatter prefix is included in the format argument", async () => { - const engine = new ESLint({ - cwd: getFixturePath("cli-engine") - }); - const formatter = await engine.loadFormatter("@somenamespace/eslint-formatter-foo"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should throw if a custom formatter doesn't exist", async () => { - const engine = new ESLint(); - const formatterPath = getFixturePath("formatters", "doesntexist.js"); - const fullFormatterPath = path.resolve(formatterPath); - - await assert.rejects(async () => { - await engine.loadFormatter(formatterPath); - }, new RegExp(escapeStringRegExp(`There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`), "u")); - }); - - it("should throw if a built-in formatter doesn't exist", async () => { - const engine = new ESLint(); - const fullFormatterPath = path.resolve(__dirname, "../../../lib/cli-engine/formatters/special"); - - await assert.rejects(async () => { - await engine.loadFormatter("special"); - }, new RegExp(escapeStringRegExp(`There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`), "u")); - }); - - it("should throw if the required formatter exists but has an error", async () => { - const engine = new ESLint(); - const formatterPath = getFixturePath("formatters", "broken.js"); - - await assert.rejects(async () => { - await engine.loadFormatter(formatterPath); - }, new RegExp(escapeStringRegExp(`There was a problem loading formatter: ${formatterPath}\nError: Cannot find module 'this-module-does-not-exist'`), "u")); - }); - - it("should throw if a non-string formatter name is passed", async () => { - const engine = new ESLint(); - - await assert.rejects(async () => { - await engine.loadFormatter(5); - }, /'name' must be a string/u); - }); - - it("should pass cwd to the `cwd` property of the second argument.", async () => { - const cwd = getFixturePath(); - const engine = new ESLint({ cwd }); - const formatterPath = getFixturePath("formatters", "cwd.js"); - const formatter = await engine.loadFormatter(formatterPath); - - assert.strictEqual(formatter.format([]), cwd); - }); - }); - - describe("getErrorResults()", () => { - it("should report 5 error messages when looking for errors only", async () => { - process.chdir(originalDir); - const engine = new ESLint({ - useEslintrc: false, - overrideConfig: { - rules: { - quotes: 2, - "no-var": 2, - "eol-last": 2, - strict: [2, "global"], - "no-unused-vars": 2 - }, - env: { - node: true - } - } - }); - const results = await engine.lintText("var foo = 'bar';"); - const errorResults = ESLint.getErrorResults(results); - - assert.strictEqual(errorResults[0].messages.length, 5); - assert.strictEqual(errorResults[0].errorCount, 5); - assert.strictEqual(errorResults[0].fixableErrorCount, 3); - assert.strictEqual(errorResults[0].fixableWarningCount, 0); - assert.strictEqual(errorResults[0].messages[0].ruleId, "strict"); - assert.strictEqual(errorResults[0].messages[0].severity, 2); - assert.strictEqual(errorResults[0].messages[1].ruleId, "no-var"); - assert.strictEqual(errorResults[0].messages[1].severity, 2); - assert.strictEqual(errorResults[0].messages[2].ruleId, "no-unused-vars"); - assert.strictEqual(errorResults[0].messages[2].severity, 2); - assert.strictEqual(errorResults[0].messages[3].ruleId, "quotes"); - assert.strictEqual(errorResults[0].messages[3].severity, 2); - assert.strictEqual(errorResults[0].messages[4].ruleId, "eol-last"); - assert.strictEqual(errorResults[0].messages[4].severity, 2); - }); - - it("should not mutate passed report parameter", async () => { - process.chdir(originalDir); - const engine = new ESLint({ - useEslintrc: false, - overrideConfig: { - rules: { - quotes: [1, "double"], - "no-var": 2 - } - } - }); - const results = await engine.lintText("var foo = 'bar';"); - const reportResultsLength = results[0].messages.length; - - assert.strictEqual(results[0].messages.length, 2); - - ESLint.getErrorResults(results); - - assert.strictEqual(results[0].messages.length, reportResultsLength); - }); - - it("should report a warningCount of 0 when looking for errors only", async () => { - process.chdir(originalDir); - const engine = new ESLint({ - useEslintrc: false, - overrideConfig: { - rules: { - quotes: 2, - "no-var": 2, - "eol-last": 2, - strict: [2, "global"], - "no-unused-vars": 2 - }, - env: { - node: true - } - } - }); - const results = await engine.lintText("var foo = 'bar';"); - const errorResults = ESLint.getErrorResults(results); - - assert.strictEqual(errorResults[0].warningCount, 0); - assert.strictEqual(errorResults[0].fixableWarningCount, 0); - }); - - it("should return 0 error or warning messages even when the file has warnings", async () => { - const engine = new ESLint({ - ignorePath: path.join(fixtureDir, ".eslintignore"), - cwd: path.join(fixtureDir, "..") - }); - const options = { - filePath: "fixtures/passing.js", - warnIgnored: true - }; - const results = await engine.lintText("var bar = foo;", options); - const errorReport = ESLint.getErrorResults(results); - - assert.strictEqual(errorReport.length, 0); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - }); - - it("should return source code of file in the `source` property", async () => { - process.chdir(originalDir); - const engine = new ESLint({ - useEslintrc: false, - overrideConfig: { - rules: { quotes: [2, "double"] } - } - }); - const results = await engine.lintText("var foo = 'bar';"); - const errorResults = ESLint.getErrorResults(results); - - assert.strictEqual(errorResults[0].messages.length, 1); - assert.strictEqual(errorResults[0].source, "var foo = 'bar';"); - }); - - it("should contain `output` property after fixes", async () => { - process.chdir(originalDir); - const engine = new ESLint({ - useEslintrc: false, - fix: true, - overrideConfig: { - rules: { - semi: 2, - "no-console": 2 - } - } - }); - const results = await engine.lintText("console.log('foo')"); - const errorResults = ESLint.getErrorResults(results); - - assert.strictEqual(errorResults[0].messages.length, 1); - assert.strictEqual(errorResults[0].output, "console.log('foo');"); - }); - }); - - describe("getRulesMetaForResults()", () => { - it("should return empty object when there are no linting errors", async () => { - const engine = new ESLint({ - useEslintrc: false - }); - - const rulesMeta = engine.getRulesMetaForResults([]); - - assert.deepStrictEqual(rulesMeta, {}); - }); - - it("should return one rule meta when there is a linting error", async () => { - const engine = new ESLint({ - useEslintrc: false, - overrideConfig: { - rules: { - semi: 2 - } - } - }); - - const results = await engine.lintText("a"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(Object.keys(rulesMeta).length, 1); - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - }); - - it("should return one rule meta when there is a suppressed linting error", async () => { - const engine = new ESLint({ - useEslintrc: false, - overrideConfig: { - rules: { - semi: 2 - } - } - }); - - const results = await engine.lintText("a // eslint-disable-line semi"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(Object.keys(rulesMeta).length, 1); - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - }); - - it("should return multiple rule meta when there are multiple linting errors", async () => { - const engine = new ESLint({ - useEslintrc: false, - overrideConfig: { - rules: { - semi: 2, - quotes: [2, "double"] - } - } - }); - - const results = await engine.lintText("'a'"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); - }); - - it("should return multiple rule meta when there are multiple linting errors from a plugin", async () => { - const customPlugin = { - rules: { - "no-var": require("../../../lib/rules/no-var") - } - }; - - const engine = new ESLint({ - useEslintrc: false, - plugins: { - "custom-plugin": customPlugin - }, - overrideConfig: { - plugins: ["custom-plugin"], - rules: { - "custom-plugin/no-var": 2, - semi: 2, - quotes: [2, "double"] - } - } - }); - - const results = await engine.lintText("var foo = 0; var bar = '1'"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); - assert.strictEqual( - rulesMeta["custom-plugin/no-var"], - customPlugin.rules["no-var"].meta - ); - }); - - it("should ignore messages not related to a rule", async () => { - const engine = new ESLint({ - useEslintrc: false, - overrideConfig: { - ignorePatterns: "ignored.js", - rules: { - "no-var": "warn" - } - }, - reportUnusedDisableDirectives: "warn" - }); - - { - const results = await engine.lintText("syntax error"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, {}); - } - { - const results = await engine.lintText("// eslint-disable-line no-var"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, {}); - } - { - const results = await engine.lintText("", { filePath: "ignored.js", warnIgnored: true }); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, {}); - } - }); - - it("should return a non-empty value if some of the messages are related to a rule", async () => { - const engine = new ESLint({ - useEslintrc: false, - overrideConfig: { rules: { "no-var": "warn" } }, - reportUnusedDisableDirectives: "warn" - }); - - const results = await engine.lintText("// eslint-disable-line no-var\nvar foo;"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); - }); - }); - - describe("outputFixes()", () => { - afterEach(() => { - sinon.verifyAndRestore(); - }); - - it("should call fs.writeFile() for each result with output", async () => { - const fakeFS = { - writeFile: sinon.spy(callLastArgument) - }; - const spy = fakeFS.writeFile; - const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", { - fs: fakeFS - }); - - const results = [ - { - filePath: path.resolve("foo.js"), - output: "bar" - }, - { - filePath: path.resolve("bar.js"), - output: "baz" - } - ]; - - await localESLint.outputFixes(results); - - assert.strictEqual(spy.callCount, 2); - assert(spy.firstCall.calledWithExactly(path.resolve("foo.js"), "bar", sinon.match.func), "First call was incorrect."); - assert(spy.secondCall.calledWithExactly(path.resolve("bar.js"), "baz", sinon.match.func), "Second call was incorrect."); - }); - - it("should call fs.writeFile() for each result with output and not at all for a result without output", async () => { - const fakeFS = { - writeFile: sinon.spy(callLastArgument) - }; - const spy = fakeFS.writeFile; - const { ESLint: localESLint } = proxyquire("../../../lib/eslint/eslint", { - fs: fakeFS - }); - const results = [ - { - filePath: path.resolve("foo.js"), - output: "bar" - }, - { - filePath: path.resolve("abc.js") - }, - { - filePath: path.resolve("bar.js"), - output: "baz" - } - ]; - - await localESLint.outputFixes(results); - - assert.strictEqual(spy.callCount, 2); - assert(spy.firstCall.calledWithExactly(path.resolve("foo.js"), "bar", sinon.match.func), "First call was incorrect."); - assert(spy.secondCall.calledWithExactly(path.resolve("bar.js"), "baz", sinon.match.func), "Second call was incorrect."); - }); - - it("should throw if non object array is given to 'results' parameter", async () => { - await assert.rejects(() => ESLint.outputFixes(null), /'results' must be an array/u); - await assert.rejects(() => ESLint.outputFixes([null]), /'results' must include only objects/u); - }); - }); - - describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { - it("should report a violation for disabling rules", async () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert" - ].join("\n"); - const config = { - ignore: true, - useEslintrc: false, - allowInlineConfig: false, - overrideConfig: { - env: { browser: true }, - rules: { - "eol-last": 0, - "no-alert": 1, - "no-trailing-spaces": 0, - strict: 0, - quotes: 0 - } - } - }; - const eslintCLI = new ESLint(config); - const results = await eslintCLI.lintText(code); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - }); - - it("should not report a violation by default", async () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert" - ].join("\n"); - const config = { - ignore: true, - useEslintrc: false, - allowInlineConfig: true, - overrideConfig: { - env: { browser: true }, - rules: { - "eol-last": 0, - "no-alert": 1, - "no-trailing-spaces": 0, - strict: 0, - quotes: 0 - } - } - }; - const eslintCLI = new ESLint(config); - const results = await eslintCLI.lintText(code); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 0); - }); - }); - - describe("when evaluating code when reportUnusedDisableDirectives is enabled", () => { - it("should report problems for unused eslint-disable directives", async () => { - const eslint = new ESLint({ useEslintrc: false, reportUnusedDisableDirectives: "error" }); - - assert.deepStrictEqual( - await eslint.lintText("/* eslint-disable */"), - [ - { - filePath: "", - messages: [ + }, + }); + + await teardown.prepare(); + const execFile = util.promisify( + require("node:child_process").execFile, + ); + + await execFile(process.execPath, ["test.js"], { cwd }); + }); + + describe("Error while globbing", () => { + it("should throw an error with a glob pattern if an invalid config was provided", async () => { + const cwd = getFixturePath("files"); + + eslint = new ESLint({ + flags, + cwd, + overrideConfig: [{ invalid: "foobar" }], + }); + + await assert.rejects(eslint.lintFiles("*.js")); + }); + }); + + describe("patterns with './' prefix", () => { + const root = getFixturePath( + "cli-engine/patterns-with-dot-prefix", + ); + + let cleanup; + let i = 0; + + beforeEach(() => { + cleanup = () => {}; + i++; + }); + + afterEach(() => cleanup()); + + it("should match patterns with './' prefix in `files` patterns", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "src/foo.js": "undefinedVariable;", + "eslint.config.js": `module.exports = [{ + files: ["./src/*.js"], + rules: { "no-undef": "error" } + }];`, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + const results = await eslint.lintFiles("src/**/*.js"); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(teardown.getPath(), "src/foo.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + + it("should match patterns with './' prefix in `ignores` patterns", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "src/foo.js": "undefinedVariable;", + "eslint.config.js": `module.exports = [{ + files: ["**/*.js"], + ignores: ["./src/*.js"], + rules: { "no-undef": "error" } + }];`, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + const results = await eslint.lintFiles("src/**/*.js"); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(teardown.getPath(), "src/foo.js"), + ); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should match patterns with './' prefix in global `ignores` patterns", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "src/foo.js": "undefinedVariable;", + "eslint.config.js": `module.exports = [ + { + files: ["**/*.js"], + rules: { "no-undef": "error" } + }, + { + ignores: ["./src/*.js"] + } + ];`, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + + await assert.rejects(async () => { + await eslint.lintFiles("src/**/*.js"); + }, /All files matched by 'src\/\*\*\/\*\.js' are ignored\./u); + }); + + it("should match negated `files` patterns with './' prefix", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "src/foo.js": "undefinedVariable;", + "eslint.config.js": `module.exports = [{ + files: ["!./src/*.js"], + rules: { "no-undef": "error" } + }];`, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + const results = await eslint.lintFiles("src/**/*.js"); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(teardown.getPath(), "src/foo.js"), + ); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should match negated `ignores` patterns with './' prefix", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "src/foo.js": "undefinedVariable;", + "eslint.config.js": `module.exports = [{ + files: ["**/*.js"], + ignores: ["**/*.js", "!./src/foo.js"], + rules: { "no-undef": "error" } + }];`, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + const results = await eslint.lintFiles("src/**/*.js"); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(teardown.getPath(), "src/foo.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + + it("should match negated global `ignores` patterns with './' prefix", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "src/foo.js": "undefinedVariable;", + "eslint.config.js": `module.exports = [ + { + files: ["**/*.js"], + rules: { "no-undef": "error" } + }, + { + ignores: ["**/*.js", "!./src/*.js"] + } + ];`, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + const results = await eslint.lintFiles("src/**/*.js"); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(teardown.getPath(), "src/foo.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + + it("should match nested `files` patterns with './' prefix", async () => { + const teardown = createCustomTeardown({ + cwd: `${root}${i}`, + files: { + "src/foo.js": "undefinedVariable;", + "eslint.config.js": `module.exports = [{ + files: [["./src/*.js"]], + rules: { "no-undef": "error" } + }];`, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new ESLint({ flags, cwd: teardown.getPath() }); + const results = await eslint.lintFiles("src/**/*.js"); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(teardown.getPath(), "src/foo.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-undef", + ); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + }); + + describe("Config objects with `basePath` property", () => { + const cwd = getFixturePath("config-base-path"); + + it("should only be applied to files inside the config's base path when no `files` or `ignores` are specified", async () => { + eslint = new ESLint({ + flags, + cwd, + overrideConfigFile: true, + overrideConfig: [ + { + basePath: "subdir", + rules: { + "no-unused-vars": "warn", + }, + }, + ], + }); + + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 4); + + assert.strictEqual( + results[0].filePath, + path.resolve(cwd, "a.js"), + ); + assert.strictEqual(results[0].messages.length, 0); + + assert.strictEqual( + results[1].filePath, + path.resolve(cwd, "b.js"), + ); + assert.strictEqual(results[1].messages.length, 0); + + assert.strictEqual( + results[2].filePath, + path.resolve(cwd, "subdir/a.js"), + ); + assert.strictEqual(results[2].messages.length, 1); + assert.strictEqual( + results[2].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[2].messages[0].severity, 1); + + assert.strictEqual( + results[3].filePath, + path.resolve(cwd, "subdir/b.js"), + ); + assert.strictEqual(results[3].messages.length, 1); + assert.strictEqual( + results[3].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[3].messages[0].severity, 1); + }); + + it("should only be applied to files inside the config's base path when `files` are specified", async () => { + eslint = new ESLint({ + flags, + cwd, + overrideConfigFile: true, + overrideConfig: [ + { + basePath: "subdir", + files: ["a.js"], + rules: { + "no-unused-vars": "warn", + }, + }, + ], + }); + + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 4); + + assert.strictEqual( + results[0].filePath, + path.resolve(cwd, "a.js"), + ); + assert.strictEqual(results[0].messages.length, 0); + + assert.strictEqual( + results[1].filePath, + path.resolve(cwd, "b.js"), + ); + assert.strictEqual(results[1].messages.length, 0); + + assert.strictEqual( + results[2].filePath, + path.resolve(cwd, "subdir/a.js"), + ); + assert.strictEqual(results[2].messages.length, 1); + assert.strictEqual( + results[2].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[2].messages[0].severity, 1); + + assert.strictEqual( + results[3].filePath, + path.resolve(cwd, "subdir/b.js"), + ); + assert.strictEqual(results[3].messages.length, 0); + }); + + it("should only be applied to files inside the config's base path when non-global `ignores` are specified", async () => { + eslint = new ESLint({ + flags, + cwd, + overrideConfigFile: true, + overrideConfig: [ + { + basePath: "subdir", + ignores: ["a.js"], + rules: { + "no-unused-vars": "warn", + }, + }, + ], + }); + + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 4); + + assert.strictEqual( + results[0].filePath, + path.resolve(cwd, "a.js"), + ); + assert.strictEqual(results[0].messages.length, 0); + + assert.strictEqual( + results[1].filePath, + path.resolve(cwd, "b.js"), + ); + assert.strictEqual(results[1].messages.length, 0); + + assert.strictEqual( + results[2].filePath, + path.resolve(cwd, "subdir/a.js"), + ); + assert.strictEqual(results[2].messages.length, 0); + + assert.strictEqual( + results[3].filePath, + path.resolve(cwd, "subdir/b.js"), + ); + assert.strictEqual(results[3].messages.length, 1); + assert.strictEqual( + results[3].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[3].messages[0].severity, 1); + }); + + it("should only be applied to files inside the config's base path when both `files` and `ignores` are specified", async () => { + eslint = new ESLint({ + flags, + cwd, + overrideConfigFile: true, + overrideConfig: [ + { + basePath: "subdir", + files: ["**/*.js"], + ignores: ["a.js"], + rules: { + "no-unused-vars": "warn", + }, + }, + ], + }); + + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 4); + + assert.strictEqual( + results[0].filePath, + path.resolve(cwd, "a.js"), + ); + assert.strictEqual(results[0].messages.length, 0); + + assert.strictEqual( + results[1].filePath, + path.resolve(cwd, "b.js"), + ); + assert.strictEqual(results[1].messages.length, 0); + + assert.strictEqual( + results[2].filePath, + path.resolve(cwd, "subdir/a.js"), + ); + assert.strictEqual(results[2].messages.length, 0); + + assert.strictEqual( + results[3].filePath, + path.resolve(cwd, "subdir/b.js"), + ); + assert.strictEqual(results[3].messages.length, 1); + assert.strictEqual( + results[3].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[3].messages[0].severity, 1); + }); + + it("should interpret `basePath` as relative to the config file location", async () => { + eslint = new ESLint({ + flags, + cwd, + overrideConfig: [ + { + basePath: "config-base-path/subdir", // config file is in the parent's parent directory + rules: { + "no-unused-vars": "warn", + }, + }, + ], + }); + + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 4); + + assert.strictEqual( + results[0].filePath, + path.resolve(cwd, "a.js"), + ); + assert.strictEqual(results[0].messages.length, 0); + + assert.strictEqual( + results[1].filePath, + path.resolve(cwd, "b.js"), + ); + assert.strictEqual(results[1].messages.length, 0); + + assert.strictEqual( + results[2].filePath, + path.resolve(cwd, "subdir/a.js"), + ); + assert.strictEqual(results[2].messages.length, 1); + assert.strictEqual( + results[2].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[2].messages[0].severity, 1); + + assert.strictEqual( + results[3].filePath, + path.resolve(cwd, "subdir/b.js"), + ); + assert.strictEqual(results[3].messages.length, 1); + assert.strictEqual( + results[3].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[3].messages[0].severity, 1); + }); + + it("should interpret global ignores as relative to `basePath` when ignoring files", async () => { + eslint = new ESLint({ + flags, + cwd, + overrideConfigFile: true, + overrideConfig: [ + { + basePath: "subdir", + ignores: ["a.js"], + }, + ], + }); + + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 3); + + assert.strictEqual( + results[0].filePath, + path.resolve(cwd, "a.js"), + ); + assert.strictEqual(results[0].messages.length, 0); + + assert.strictEqual( + results[1].filePath, + path.resolve(cwd, "b.js"), + ); + assert.strictEqual(results[1].messages.length, 0); + + assert.strictEqual( + results[2].filePath, + path.resolve(cwd, "subdir/b.js"), + ); + assert.strictEqual(results[2].messages.length, 0); + }); + + it("should interpret global ignores as relative to `basePath` when ignoring directories", async () => { + eslint = new ESLint({ + flags, + cwd, + overrideConfig: [ + { + basePath: "config-base-path", // config file is in the parent directory + ignores: ["subdir"], + }, + ], + }); + + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 2); + + assert.strictEqual( + results[0].filePath, + path.resolve(cwd, "a.js"), + ); + assert.strictEqual(results[0].messages.length, 0); + + assert.strictEqual( + results[1].filePath, + path.resolve(cwd, "b.js"), + ); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should not apply global ignores when the `ignore` option is `false`", async () => { + eslint = new ESLint({ + flags, + cwd, + overrideConfigFile: true, + overrideConfig: [ + { + basePath: "subdir", + ignores: ["a.js"], + }, + ], + ignore: false, + }); + + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 4); + + assert.strictEqual( + results[0].filePath, + path.resolve(cwd, "a.js"), + ); + assert.strictEqual(results[0].messages.length, 0); + + assert.strictEqual( + results[1].filePath, + path.resolve(cwd, "b.js"), + ); + assert.strictEqual(results[1].messages.length, 0); + + assert.strictEqual( + results[2].filePath, + path.resolve(cwd, "subdir/a.js"), + ); + assert.strictEqual(results[2].messages.length, 0); + + assert.strictEqual( + results[3].filePath, + path.resolve(cwd, "subdir/b.js"), + ); + assert.strictEqual(results[3].messages.length, 0); + }); + }); + }); + + describe("Fix Types", () => { + /** @type {InstanceType} */ + let eslint; + + describe("fixTypes values validation", () => { + it("should throw an error when an invalid fix type is specified", () => { + assert.throws(() => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + fixTypes: ["layou"], + }); + }, /'fixTypes' must be an array of any of "directive", "problem", "suggestion", and "layout"\./iu); + }); + }); + + describe("with lintFiles", () => { + it("should not fix any rules when fixTypes is used without fix", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: false, + fixTypes: ["layout"], + }); + const inputPath = getFixturePath( + "fix-types/fix-only-semi.js", + ); + const results = await eslint.lintFiles([inputPath]); + + assert.strictEqual(results[0].output, void 0); + }); + + it("should not fix non-style rules when fixTypes has only 'layout'", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + fixTypes: ["layout"], + }); + const inputPath = getFixturePath( + "fix-types/fix-only-semi.js", + ); + const outputPath = getFixturePath( + "fix-types/fix-only-semi.expected.js", + ); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should not fix style or problem rules when fixTypes has only 'suggestion'", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + fixTypes: ["suggestion"], + }); + const inputPath = getFixturePath( + "fix-types/fix-only-prefer-arrow-callback.js", + ); + const outputPath = getFixturePath( + "fix-types/fix-only-prefer-arrow-callback.expected.js", + ); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should fix both style and problem rules when fixTypes has 'suggestion' and 'layout'", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + fixTypes: ["suggestion", "layout"], + }); + const inputPath = getFixturePath( + "fix-types/fix-both-semi-and-prefer-arrow-callback.js", + ); + const outputPath = getFixturePath( + "fix-types/fix-both-semi-and-prefer-arrow-callback.expected.js", + ); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + }); + + describe("with lintText", () => { + it("should not fix any rules when fixTypes is used without fix", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: false, + fixTypes: ["layout"], + }); + const inputPath = getFixturePath( + "fix-types/fix-only-semi.js", + ); + const content = fs.readFileSync(inputPath, "utf8"); + const results = await eslint.lintText(content, { + filePath: inputPath, + }); + + assert.strictEqual(results[0].output, void 0); + }); + + it("should not fix non-style rules when fixTypes has only 'layout'", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + fixTypes: ["layout"], + }); + const inputPath = getFixturePath( + "fix-types/fix-only-semi.js", + ); + const outputPath = getFixturePath( + "fix-types/fix-only-semi.expected.js", + ); + const content = fs.readFileSync(inputPath, "utf8"); + const results = await eslint.lintText(content, { + filePath: inputPath, + }); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should not fix style or problem rules when fixTypes has only 'suggestion'", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + fixTypes: ["suggestion"], + }); + const inputPath = getFixturePath( + "fix-types/fix-only-prefer-arrow-callback.js", + ); + const outputPath = getFixturePath( + "fix-types/fix-only-prefer-arrow-callback.expected.js", + ); + const content = fs.readFileSync(inputPath, "utf8"); + const results = await eslint.lintText(content, { + filePath: inputPath, + }); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should fix both style and problem rules when fixTypes has 'suggestion' and 'layout'", async () => { + eslint = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + fix: true, + fixTypes: ["suggestion", "layout"], + }); + const inputPath = getFixturePath( + "fix-types/fix-both-semi-and-prefer-arrow-callback.js", + ); + const outputPath = getFixturePath( + "fix-types/fix-both-semi-and-prefer-arrow-callback.expected.js", + ); + const content = fs.readFileSync(inputPath, "utf8"); + const results = await eslint.lintText(content, { + filePath: inputPath, + }); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + }); + }); + + describe("isPathIgnored", () => { + it("should check if the given path is ignored", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: getFixturePath( + "eslint.config-with-ignores2.js", + ), + cwd: getFixturePath(), + }); + + assert(await engine.isPathIgnored("undef.js")); + assert(!(await engine.isPathIgnored("passing.js"))); + }); + + it("should return false if ignoring is disabled", async () => { + const engine = new ESLint({ + flags, + ignore: false, + overrideConfigFile: getFixturePath( + "eslint.config-with-ignores2.js", + ), + cwd: getFixturePath(), + }); + + assert(!(await engine.isPathIgnored("undef.js"))); + }); + + // https://github.com/eslint/eslint/issues/5547 + it("should return true for default ignores even if ignoring is disabled", async () => { + const engine = new ESLint({ + flags, + ignore: false, + cwd: getFixturePath("cli-engine"), + }); + + assert(await engine.isPathIgnored("node_modules/foo.js")); + }); + + if (os.platform() === "win32") { + it("should return true for a file on a different drive on Windows", async () => { + const currentRoot = path.resolve("\\"); + const otherRoot = currentRoot === "A:\\" ? "B:\\" : "A:\\"; + const engine = new ESLint({ + flags, + overrideConfigFile: true, + cwd: currentRoot, + }); + + assert( + !(await engine.isPathIgnored(`${currentRoot}file.js`)), + ); + assert(await engine.isPathIgnored(`${otherRoot}file.js`)); + assert( + await engine.isPathIgnored("//SERVER//share//file.js"), + ); + }); + } + + describe("about the default ignore patterns", () => { + it("should always apply default ignore patterns if ignore option is true", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ flags, cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "node_modules/package/file.js", + ), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "subdir/node_modules/package/file.js", + ), + ), + ); + }); + + it("should still apply default ignore patterns if ignore option is false", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ flags, ignore: false, cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "node_modules/package/file.js", + ), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "subdir/node_modules/package/file.js", + ), + ), + ); + }); + + it("should allow subfolders of defaultPatterns to be unignored by ignorePattern constructor option", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ + flags, + cwd, + overrideConfigFile: true, + ignorePatterns: [ + "!node_modules/", + "node_modules/*", + "!node_modules/package/", + ], + }); + + const result = await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "node_modules", + "package", + "file.js", + ), + ); + + assert(!result, "File should not be ignored"); + }); + + it("should allow subfolders of defaultPatterns to be unignored by ignores in overrideConfig", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ + flags, + cwd, + overrideConfigFile: true, + overrideConfig: { + ignores: [ + "!node_modules/", + "node_modules/*", + "!node_modules/package/", + ], + }, + }); + + assert( + !(await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "node_modules", + "package", + "file.js", + ), + )), + ); + }); + + it("should ignore .git directory", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ flags, cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", ".git/bar"), + ), + ); + }); + + it("should still ignore .git directory when ignore option disabled", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ flags, ignore: false, cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", ".git/bar"), + ), + ); + }); + + it("should not ignore absolute paths containing '..'", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ flags, cwd }); + + assert( + !(await engine.isPathIgnored( + `${getFixturePath("ignored-paths", "foo")}/../unignored.js`, + )), + ); + }); + + it("should ignore /node_modules/ relative to cwd without any configured ignore patterns", async () => { + const cwd = getFixturePath( + "ignored-paths", + "no-ignore-file", + ); + const engine = new ESLint({ flags, cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "no-ignore-file", + "node_modules", + "existing.js", + ), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "no-ignore-file", + "foo", + "node_modules", + "existing.js", + ), + ), + ); + }); + + it("should not inadvertently ignore all files in parent directories", async () => { + const engine = new ESLint({ + flags, + cwd: getFixturePath("ignored-paths", "no-ignore-file"), + }); + + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + )), + ); + }); + }); + + describe("with ignorePatterns option", () => { + it("should accept a string for options.ignorePatterns", async () => { + const cwd = getFixturePath( + "ignored-paths", + "ignore-pattern", + ); + const engine = new ESLint({ + flags, + ignorePatterns: ["ignore-me.txt"], + cwd, + }); + + assert(await engine.isPathIgnored("ignore-me.txt")); + }); + + it("should accept an array for options.ignorePattern", async () => { + const engine = new ESLint({ + flags, + ignorePatterns: ["a.js", "b.js"], + overrideConfigFile: true, + }); + + assert( + await engine.isPathIgnored("a.js"), + "a.js should be ignored", + ); + assert( + await engine.isPathIgnored("b.js"), + "b.js should be ignored", + ); + assert( + !(await engine.isPathIgnored("c.js")), + "c.js should not be ignored", + ); + }); + + it("should interpret ignorePatterns as relative to cwd", async () => { + const cwd = getFixturePath("ignored-paths", "subdir"); + const engine = new ESLint({ + flags, + ignorePatterns: ["undef.js"], + cwd, // using ../../eslint.config.js + }); + + assert( + await engine.isPathIgnored(path.join(cwd, "undef.js")), + ); + }); + + it("should return true for files which match an ignorePattern even if they do not exist on the filesystem", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ + flags, + ignorePatterns: ["not-a-file"], + cwd, + }); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "not-a-file"), + ), + ); + }); + + it("should return true for file matching an ignore pattern exactly", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ + flags, + ignorePatterns: ["undef.js"], + cwd, + overrideConfigFile: true, + }); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + ), + ); + }); + + it("should return false for file in subfolder of cwd matching an ignore pattern with a base filename", async () => { + const cwd = getFixturePath("ignored-paths"); + const filePath = getFixturePath( + "ignored-paths", + "subdir", + "undef.js", + ); + const engine = new ESLint({ + flags, + ignorePatterns: ["undef.js"], + overrideConfigFile: true, + cwd, + }); + + assert(!(await engine.isPathIgnored(filePath))); + }); + + it("should return true for file matching a child of an ignore pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ + flags, + ignorePatterns: ["ignore-pattern"], + cwd, + }); + + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "ignore-pattern", + "ignore-me.txt", + ), + ), + ); + }); + + it("should return true for file matching a grandchild of a directory when the pattern is directory/**", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ + flags, + ignorePatterns: ["ignore-pattern/**"], + cwd, + }); + + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "ignore-pattern", + "subdir", + "ignore-me.js", + ), + ), + ); + }); + + it("should return false for file not matching any ignore pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ + flags, + ignorePatterns: ["failing.js"], + cwd, + }); + + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "unignored.js"), + )), + ); + }); + + it("two globstar '**' ignore pattern should ignore files in nested directories", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ + flags, + overrideConfigFile: true, + ignorePatterns: ["**/*.js"], + cwd, + }); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo.js"), + ), + "foo.js should be ignored", + ); + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/bar.js"), + ), + "foo/bar.js should be ignored", + ); + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/bar/baz.js"), + ), + "foo/bar/baz.js", + ); + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo.cjs"), + )), + "foo.cjs should not be ignored", + ); + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/bar.cjs"), + )), + "foo/bar.cjs should not be ignored", + ); + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/bar/baz.cjs"), + )), + "foo/bar/baz.cjs should not be ignored", + ); + }); + }); + + describe("with config ignores ignorePatterns option", () => { + it("should return false for ignored file when unignored with ignore pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new ESLint({ + flags, + overrideConfigFile: getFixturePath( + "eslint.config-with-ignores2.js", + ), + ignorePatterns: ["!undef.js"], + cwd, + }); + + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + )), + ); + }); + }); + + it("should throw if non-string value is given to 'filePath' parameter", async () => { + const eslint = new ESLint({ flags }); + + await assert.rejects( + () => eslint.isPathIgnored(null), + /'filePath' must be a non-empty string/u, + ); + }); + }); + + describe("loadFormatter()", () => { + it("should return a formatter object when a bundled formatter is requested", async () => { + const engine = new ESLint({ flags }); + const formatter = await engine.loadFormatter("json"); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when no argument is passed", async () => { + const engine = new ESLint({ flags }); + const formatter = await engine.loadFormatter(); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a custom formatter is requested", async () => { + const engine = new ESLint({ flags }); + const formatter = await engine.loadFormatter( + getFixturePath("formatters", "simple.js"), + ); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a custom formatter is requested, also if the path has backslashes", async () => { + const engine = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + }); + const formatter = await engine.loadFormatter( + ".\\fixtures\\formatters\\simple.js", + ); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a formatter prefixed with eslint-formatter is requested", async () => { + const engine = new ESLint({ + flags, + cwd: getFixturePath("cli-engine"), + }); + const formatter = await engine.loadFormatter("bar"); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a formatter is requested, also when the eslint-formatter prefix is included in the format argument", async () => { + const engine = new ESLint({ + flags, + cwd: getFixturePath("cli-engine"), + }); + const formatter = await engine.loadFormatter( + "eslint-formatter-bar", + ); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a formatter is requested within a scoped npm package", async () => { + const engine = new ESLint({ + flags, + cwd: getFixturePath("cli-engine"), + }); + const formatter = + await engine.loadFormatter("@somenamespace/foo"); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a formatter is requested within a scoped npm package, also when the eslint-formatter prefix is included in the format argument", async () => { + const engine = new ESLint({ + flags, + cwd: getFixturePath("cli-engine"), + }); + const formatter = await engine.loadFormatter( + "@somenamespace/eslint-formatter-foo", + ); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should throw if a custom formatter doesn't exist", async () => { + const engine = new ESLint({ flags }); + const formatterPath = getFixturePath( + "formatters", + "doesntexist.js", + ); + const fullFormatterPath = path.resolve(formatterPath); + + await assert.rejects( + async () => { + await engine.loadFormatter(formatterPath); + }, + new RegExp( + escapeStringRegExp( + `There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`, + ), + "u", + ), + ); + }); + + it("should throw if a built-in formatter doesn't exist", async () => { + const engine = new ESLint({ flags }); + const fullFormatterPath = path.resolve( + __dirname, + "../../../lib/cli-engine/formatters/special", + ); + + await assert.rejects( + async () => { + await engine.loadFormatter("special"); + }, + new RegExp( + escapeStringRegExp( + `There was a problem loading formatter: ${fullFormatterPath}.js\nError: Cannot find module '${fullFormatterPath}.js'`, + ), + "u", + ), + ); + }); + + it("should throw if the required formatter exists but has an error", async () => { + const engine = new ESLint({ flags }); + const formatterPath = getFixturePath("formatters", "broken.js"); + + await assert.rejects( + async () => { + await engine.loadFormatter(formatterPath); + + // for some reason, the error here contains multiple "there was a problem loading formatter" lines, so omitting + }, + new RegExp( + escapeStringRegExp( + "Error: Cannot find module 'this-module-does-not-exist'", + ), + "u", + ), + ); + }); + + it("should throw if a non-string formatter name is passed", async () => { + const engine = new ESLint({ flags }); + + await assert.rejects(async () => { + await engine.loadFormatter(5); + }, /'name' must be a string/u); + }); + }); + + describe("getErrorResults()", () => { + it("should report 5 error messages when looking for errors only", async () => { + process.chdir(originalDir); + const engine = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { + quotes: "error", + "no-var": "error", + "eol-last": "error", + "no-unused-vars": "error", + }, + }, + }); + const results = await engine.lintText("var foo = 'bar';"); + const errorResults = ESLint.getErrorResults(results); + + assert.strictEqual( + errorResults[0].messages.length, + 4, + "messages.length is wrong", + ); + assert.strictEqual( + errorResults[0].errorCount, + 4, + "errorCount is wrong", + ); + assert.strictEqual( + errorResults[0].fixableErrorCount, + 3, + "fixableErrorCount is wrong", + ); + assert.strictEqual( + errorResults[0].fixableWarningCount, + 0, + "fixableWarningCount is wrong", + ); + assert.strictEqual( + errorResults[0].messages[0].ruleId, + "no-var", + ); + assert.strictEqual(errorResults[0].messages[0].severity, 2); + assert.strictEqual( + errorResults[0].messages[1].ruleId, + "no-unused-vars", + ); + assert.strictEqual(errorResults[0].messages[1].severity, 2); + assert.strictEqual( + errorResults[0].messages[2].ruleId, + "quotes", + ); + assert.strictEqual(errorResults[0].messages[2].severity, 2); + assert.strictEqual( + errorResults[0].messages[3].ruleId, + "eol-last", + ); + assert.strictEqual(errorResults[0].messages[3].severity, 2); + }); + + it("should not mutate passed report parameter", async () => { + process.chdir(originalDir); + const engine = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { quotes: [1, "double"] }, + }, + }); + const results = await engine.lintText("var foo = 'bar';"); + const reportResultsLength = results[0].messages.length; + + ESLint.getErrorResults(results); + + assert.strictEqual( + results[0].messages.length, + reportResultsLength, + ); + }); + + it("should report a warningCount of 0 when looking for errors only", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { + strict: ["error", "global"], + quotes: "error", + "no-var": "error", + "eol-last": "error", + "no-unused-vars": "error", + }, + }, + }); + const lintResults = await engine.lintText("var foo = 'bar';"); + const errorResults = ESLint.getErrorResults(lintResults); + + assert.strictEqual(errorResults[0].warningCount, 0); + assert.strictEqual(errorResults[0].fixableWarningCount, 0); + }); + + it("should return 0 error or warning messages even when the file has warnings", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: getFixturePath( + "eslint.config-with-ignores.js", + ), + cwd: path.join(fixtureDir, ".."), + }); + const options = { + filePath: "fixtures/passing.js", + warnIgnored: true, + }; + const results = await engine.lintText( + "var bar = foo;", + options, + ); + const errorReport = ESLint.getErrorResults(results); + + assert.strictEqual(errorReport.length, 0); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + }); + + it("should return source code of file in the `source` property", async () => { + process.chdir(originalDir); + const engine = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { quotes: [2, "double"] }, + }, + }); + const results = await engine.lintText("var foo = 'bar';"); + const errorResults = ESLint.getErrorResults(results); + + assert.strictEqual(errorResults[0].messages.length, 1); + assert.strictEqual(errorResults[0].source, "var foo = 'bar';"); + }); + + it("should contain `output` property after fixes", async () => { + process.chdir(originalDir); + const engine = new ESLint({ + flags, + overrideConfigFile: true, + fix: true, + overrideConfig: { + rules: { + semi: 2, + "no-console": 2, + }, + }, + }); + const results = await engine.lintText("console.log('foo')"); + const errorResults = ESLint.getErrorResults(results); + + assert.strictEqual(errorResults[0].messages.length, 1); + assert.strictEqual( + errorResults[0].output, + "console.log('foo');", + ); + }); + }); + + describe("findConfigFile()", () => { + it("should return undefined when overrideConfigFile is true", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + }); + + assert.strictEqual(await engine.findConfigFile(), void 0); + }); + + it("should return undefined when a config file isn't found", async () => { + const engine = new ESLint({ + flags, + cwd: path.resolve(__dirname, "../../../../"), + }); + + assert.strictEqual(await engine.findConfigFile(), void 0); + }); + + it("should return custom config file path when overrideConfigFile is a nonempty string", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: "my-config.js", + }); + const configFilePath = path.resolve( + __dirname, + "../../../my-config.js", + ); + + assert.strictEqual( + await engine.findConfigFile(), + configFilePath, + ); + }); + + it("should return root level eslint.config.js when overrideConfigFile is null", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: null, + }); + const configFilePath = path.resolve( + __dirname, + "../../../eslint.config.js", + ); + + assert.strictEqual( + await engine.findConfigFile(), + configFilePath, + ); + }); + + it("should return root level eslint.config.js when overrideConfigFile is not specified", async () => { + const engine = new ESLint({ flags }); + const configFilePath = path.resolve( + __dirname, + "../../../eslint.config.js", + ); + + assert.strictEqual( + await engine.findConfigFile(), + configFilePath, + ); + }); + }); + + describe("Use stats option", () => { + /** + * Check if the given number is a number. + * @param {number} n The number to check. + * @returns {boolean} `true` if the number is a number, `false` otherwise. + */ + function isNumber(n) { + return typeof n === "number" && !Number.isNaN(n); + } + + it("should report stats", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { + "no-regex-spaces": "error", + }, + }, + cwd: getFixturePath("stats-example"), + stats: true, + }); + const results = await engine.lintFiles(["file-to-fix.js"]); + + assert.strictEqual(results[0].stats.fixPasses, 0); + assert.strictEqual(results[0].stats.times.passes.length, 1); + assert.strictEqual( + isNumber(results[0].stats.times.passes[0].parse.total), + true, + ); + assert.strictEqual( + isNumber( + results[0].stats.times.passes[0].rules[ + "no-regex-spaces" + ].total, + ), + true, + ); + assert.strictEqual( + isNumber( + results[0].stats.times.passes[0].rules["wrap-regex"] + .total, + ), + true, + ); + assert.strictEqual( + results[0].stats.times.passes[0].fix.total, + 0, + ); + assert.strictEqual( + isNumber(results[0].stats.times.passes[0].total), + true, + ); + }); + + it("should report stats with fix", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { + "no-regex-spaces": "error", + }, + }, + cwd: getFixturePath("stats-example"), + fix: true, + stats: true, + }); + const results = await engine.lintFiles(["file-to-fix.js"]); + + assert.strictEqual(results[0].stats.fixPasses, 2); + assert.strictEqual(results[0].stats.times.passes.length, 3); + assert.strictEqual( + isNumber(results[0].stats.times.passes[0].parse.total), + true, + ); + assert.strictEqual( + isNumber(results[0].stats.times.passes[1].parse.total), + true, + ); + assert.strictEqual( + isNumber(results[0].stats.times.passes[2].parse.total), + true, + ); + assert.strictEqual( + isNumber( + results[0].stats.times.passes[0].rules[ + "no-regex-spaces" + ].total, + ), + true, + ); + assert.strictEqual( + isNumber( + results[0].stats.times.passes[0].rules["wrap-regex"] + .total, + ), + true, + ); + assert.strictEqual( + isNumber( + results[0].stats.times.passes[1].rules[ + "no-regex-spaces" + ].total, + ), + true, + ); + assert.strictEqual( + isNumber( + results[0].stats.times.passes[1].rules["wrap-regex"] + .total, + ), + true, + ); + assert.strictEqual( + isNumber( + results[0].stats.times.passes[2].rules[ + "no-regex-spaces" + ].total, + ), + true, + ); + assert.strictEqual( + isNumber( + results[0].stats.times.passes[2].rules["wrap-regex"] + .total, + ), + true, + ); + assert.strictEqual( + isNumber(results[0].stats.times.passes[0].fix.total), + true, + ); + assert.strictEqual( + isNumber(results[0].stats.times.passes[1].fix.total), + true, + ); + assert.strictEqual( + results[0].stats.times.passes[2].fix.total, + 0, + ); + assert.strictEqual( + isNumber(results[0].stats.times.passes[0].total), + true, + ); + assert.strictEqual( + isNumber(results[0].stats.times.passes[1].total), + true, + ); + assert.strictEqual( + isNumber(results[0].stats.times.passes[2].total), + true, + ); + }); + }); + + describe("getRulesMetaForResults()", () => { + it("should throw an error when this instance did not lint any files", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + }); + + assert.throws( + () => { + engine.getRulesMetaForResults([ + { + filePath: "path/to/file.js", + messages: [ + { + ruleId: "curly", + severity: 2, + message: + "Expected { after 'if' condition.", + line: 2, + column: 1, + nodeType: "IfStatement", + }, + { + ruleId: "no-process-exit", + severity: 2, + message: + "Don't use process.exit(); throw an error instead.", + line: 3, + column: 1, + nodeType: "CallExpression", + }, + ], + suppressedMessages: [], + errorCount: 2, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "var err = doStuff();\nif (err) console.log('failed tests: ' + err);\nprocess.exit(1);\n", + }, + ]); + }, + { + constructor: TypeError, + message: + "Results object was not created from this ESLint instance.", + }, + ); + }); + + it("should throw an error when results were created from a different instance", async () => { + const engine1 = new ESLint({ + flags, + overrideConfigFile: true, + cwd: path.join(fixtureDir, "foo"), + overrideConfig: { + rules: { + semi: 2, + }, + }, + }); + const engine2 = new ESLint({ + flags, + overrideConfigFile: true, + cwd: path.join(fixtureDir, "bar"), + overrideConfig: { + rules: { + semi: 2, + }, + }, + }); + + const results1 = await engine1.lintText("1", { + filePath: "file.js", + }); + const results2 = await engine2.lintText("2", { + filePath: "file.js", + }); + + engine1.getRulesMetaForResults(results1); // should not throw an error + assert.throws( + () => { + engine1.getRulesMetaForResults(results2); + }, + { + constructor: TypeError, + message: + "Results object was not created from this ESLint instance.", + }, + ); + }); + + it("should treat a result without `filePath` as if the file was located in `cwd`", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + cwd: path.join(fixtureDir, "foo", "bar"), + ignorePatterns: ["*/**"], // ignore all subdirectories of `cwd` + overrideConfig: { + rules: { + eqeqeq: "warn", + }, + }, + }); + + const results = await engine.lintText("a==b"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual( + rulesMeta.eqeqeq, + coreRules.get("eqeqeq").meta, + ); + }); + + it("should not throw an error if a result without `filePath` contains an ignored file warning", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + cwd: path.join(fixtureDir, "foo", "bar"), + ignorePatterns: ["**"], + }); + + const results = await engine.lintText("", { + warnIgnored: true, + }); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + }); + + it("should not throw an error if results contain linted files and one ignored file", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + cwd: getFixturePath(), + ignorePatterns: ["passing*"], + overrideConfig: { + rules: { + "no-undef": 2, + semi: 1, + }, + }, + }); + + const results = await engine.lintFiles([ + "missing-semicolon.js", + "passing.js", + "undef.js", + ]); + + assert( + results.some(({ messages }) => + messages.some( + ({ message, ruleId }) => + !ruleId && message.startsWith("File ignored"), + ), + ), + "At least one file should be ignored but none is.", + ); + + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual( + rulesMeta["no-undef"], + coreRules.get("no-undef").meta, + ); + assert.deepStrictEqual( + rulesMeta.semi, + coreRules.get("semi").meta, + ); + }); + + it("should return empty object when there are no linting errors", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + }); + + const rulesMeta = engine.getRulesMetaForResults([]); + + assert.deepStrictEqual(rulesMeta, {}); + }); + + it("should return one rule meta when there is a linting error", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { + semi: 2, + }, + }, + }); + + const results = await engine.lintText("a", { + filePath: "foo.js", + }); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(Object.keys(rulesMeta).length, 1); + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + }); + + it("should return one rule meta when there is a suppressed linting error", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { + semi: 2, + }, + }, + }); + + const results = await engine.lintText( + "a // eslint-disable-line semi", + ); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(Object.keys(rulesMeta).length, 1); + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + }); + + it("should return multiple rule meta when there are multiple linting errors", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { + semi: 2, + quotes: [2, "double"], + }, + }, + }); + + const results = await engine.lintText("'a'"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + assert.strictEqual( + rulesMeta.quotes, + coreRules.get("quotes").meta, + ); + }); + + it("should return multiple rule meta when there are multiple linting errors from a plugin", async () => { + const customPlugin = { + rules: { + "no-var": require("../../../lib/rules/no-var"), + }, + }; + const engine = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + plugins: { + "custom-plugin": customPlugin, + }, + rules: { + "custom-plugin/no-var": 2, + semi: 2, + quotes: [2, "double"], + }, + }, + }); + + const results = await engine.lintText( + "var foo = 0; var bar = '1'", + ); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + assert.strictEqual( + rulesMeta.quotes, + coreRules.get("quotes").meta, + ); + assert.strictEqual( + rulesMeta["custom-plugin/no-var"], + customPlugin.rules["no-var"].meta, + ); + }); + + it("should ignore messages not related to a rule", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + ignorePatterns: ["ignored.js"], + overrideConfig: { + rules: { + "no-var": "warn", + }, + linterOptions: { + reportUnusedDisableDirectives: "warn", + }, + }, + }); + + { + const results = await engine.lintText("syntax error"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText( + "// eslint-disable-line no-var", + ); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText("", { + filePath: "ignored.js", + warnIgnored: true, + }); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + }); + + it("should return a non-empty value if some of the messages are related to a rule", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + rules: { "no-var": "warn" }, + linterOptions: { + reportUnusedDisableDirectives: "warn", + }, + }, + }); + + const results = await engine.lintText( + "// eslint-disable-line no-var\nvar foo;", + ); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, { + "no-var": coreRules.get("no-var").meta, + }); + }); + + it("should return empty object if all messages are related to unknown rules", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + }); + + const results = await engine.lintText( + "// eslint-disable-line foo, bar/baz, bar/baz/qux", + ); + + assert.strictEqual(results[0].messages.length, 3); + assert.strictEqual(results[0].messages[0].ruleId, "foo"); + assert.strictEqual(results[0].messages[1].ruleId, "bar/baz"); + assert.strictEqual( + results[0].messages[2].ruleId, + "bar/baz/qux", + ); + + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(Object.keys(rulesMeta).length, 0); + }); + + it("should return object with meta of known rules if some messages are related to unknown rules", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { rules: { "no-var": "warn" } }, + }); + + const results = await engine.lintText( + "// eslint-disable-line foo, bar/baz, bar/baz/qux\nvar x;", + ); + + assert.strictEqual(results[0].messages.length, 4); + assert.strictEqual(results[0].messages[0].ruleId, "foo"); + assert.strictEqual(results[0].messages[1].ruleId, "bar/baz"); + assert.strictEqual( + results[0].messages[2].ruleId, + "bar/baz/qux", + ); + assert.strictEqual(results[0].messages[3].ruleId, "no-var"); + + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, { + "no-var": coreRules.get("no-var").meta, + }); + }); + }); + + describe("outputFixes()", () => { + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it("should call fs.writeFile() for each result with output", async () => { + const spy = sinon.spy(() => Promise.resolve()); + const { ESLint: localESLint } = proxyquire( + "../../../lib/eslint/eslint", + { + "node:fs/promises": { + writeFile: spy, + }, + }, + ); + + const results = [ + { + filePath: path.resolve("foo.js"), + output: "bar", + }, + { + filePath: path.resolve("bar.js"), + output: "baz", + }, + ]; + + await localESLint.outputFixes(results); + + assert.strictEqual(spy.callCount, 2); + assert( + spy.firstCall.calledWithExactly( + path.resolve("foo.js"), + "bar", + ), + "First call was incorrect.", + ); + assert( + spy.secondCall.calledWithExactly( + path.resolve("bar.js"), + "baz", + ), + "Second call was incorrect.", + ); + }); + + it("should call fs.writeFile() for each result with output and not at all for a result without output", async () => { + const spy = sinon.spy(() => Promise.resolve()); + const { ESLint: localESLint } = proxyquire( + "../../../lib/eslint/eslint", + { + "node:fs/promises": { + writeFile: spy, + }, + }, + ); + + const results = [ + { + filePath: path.resolve("foo.js"), + output: "bar", + }, + { + filePath: path.resolve("abc.js"), + }, + { + filePath: path.resolve("bar.js"), + output: "baz", + }, + ]; + + await localESLint.outputFixes(results); + + assert.strictEqual(spy.callCount, 2, "Call count was wrong"); + assert( + spy.firstCall.calledWithExactly( + path.resolve("foo.js"), + "bar", + ), + "First call was incorrect.", + ); + assert( + spy.secondCall.calledWithExactly( + path.resolve("bar.js"), + "baz", + ), + "Second call was incorrect.", + ); + }); + + it("should throw if non object array is given to 'results' parameter", async () => { + await assert.rejects( + () => ESLint.outputFixes(null), + /'results' must be an array/u, + ); + await assert.rejects( + () => ESLint.outputFixes([null]), + /'results' must include only objects/u, + ); + }); + }); + + describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { + it("should report a violation for disabling rules", async () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + ].join("\n"); + const config = { + flags, + ignore: true, + overrideConfigFile: true, + allowInlineConfig: false, + overrideConfig: { + rules: { + "eol-last": 0, + "no-alert": 1, + "no-trailing-spaces": 0, + strict: 0, + quotes: 0, + }, + }, + }; + const eslintCLI = new ESLint(config); + const results = await eslintCLI.lintText(code); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should not report a violation by default", async () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + ].join("\n"); + const config = { + flags, + ignore: true, + overrideConfigFile: true, + allowInlineConfig: true, + overrideConfig: { + rules: { + "eol-last": 0, + "no-alert": 1, + "no-trailing-spaces": 0, + strict: 0, + quotes: 0, + }, + }, + }; + const eslintCLI = new ESLint(config); + const results = await eslintCLI.lintText(code); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 1); + assert.strictEqual( + results[0].suppressedMessages[0].ruleId, + "no-alert", + ); + }); + }); + + describe("when evaluating code when reportUnusedDisableDirectives is enabled", () => { + it("should report problems for unused eslint-disable directives", async () => { + const eslint = new ESLint({ + flags, + overrideConfigFile: true, + overrideConfig: { + linterOptions: { + reportUnusedDisableDirectives: "error", + }, + }, + }); + + assert.deepStrictEqual( + await eslint.lintText("/* eslint-disable */"), + [ + { + filePath: "", + messages: [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 0, + source: "/* eslint-disable */", + usedDeprecatedRules: [], + }, + ], + ); + }); + }); + + describe("when retrieving version number", () => { + it("should return current version number", () => { + const eslintCLI = require("../../../lib/eslint/eslint").ESLint; + const version = eslintCLI.version; + + assert.strictEqual(typeof version, "string"); + assert(parseInt(version[0], 10) >= 3); + }); + }); + + describe("mutability", () => { + describe("rules", () => { + it("Loading rules in one instance doesn't mutate to another instance", async () => { + const filePath = getFixturePath("single-quoted.js"); + const engine1 = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + overrideConfig: { + plugins: { + example: { + rules: { + "example-rule"() { + return {}; + }, + }, + }, + }, + rules: { "example/example-rule": 1 }, + }, + }); + const engine2 = new ESLint({ + flags, + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + }); + const fileConfig1 = + await engine1.calculateConfigForFile(filePath); + const fileConfig2 = + await engine2.calculateConfigForFile(filePath); + + // plugin + assert.deepStrictEqual( + fileConfig1.rules["example/example-rule"], + [1], + "example is present for engine 1", + ); + assert.strictEqual( + fileConfig2.rules, + void 0, + "example is not present for engine 2", + ); + }); + }); + }); + + describe("configs with 'ignores' and without 'files'", () => { + // https://github.com/eslint/eslint/issues/17103 + describe("config with ignores: ['error.js']", () => { + const cwd = getFixturePath("config-with-ignores-without-files"); + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd, + files: { + "eslint.config.js": `module.exports = [ { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " + rules: { + "no-unused-vars": "error", }, - severity: 2, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 1, - fixableWarningCount: 0, - source: "/* eslint-disable */", - usedDeprecatedRules: [] - } - ] - ); - }); - }); - - describe("when retrieving version number", () => { - it("should return current version number", () => { - const eslintCLI = require("../../../lib/eslint").ESLint; - const version = eslintCLI.version; - - assert.strictEqual(typeof version, "string"); - assert(parseInt(version[0], 10) >= 3); - }); - }); - - describe("mutability", () => { - describe("plugins", () => { - it("Loading plugin in one instance doesn't mutate to another instance", async () => { - const filePath = getFixturePath("single-quoted.js"); - const engine1 = eslintWithPlugins({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - overrideConfig: { - plugins: ["example"], - rules: { "example/example-rule": 1 } - } - }); - const engine2 = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false - }); - const fileConfig1 = await engine1.calculateConfigForFile(filePath); - const fileConfig2 = await engine2.calculateConfigForFile(filePath); - - // plugin - assert.deepStrictEqual(fileConfig1.plugins, ["example"], "Plugin is present for engine 1"); - assert.deepStrictEqual(fileConfig2.plugins, [], "Plugin is not present for engine 2"); - }); - }); - - describe("rules", () => { - it("Loading rules in one instance doesn't mutate to another instance", async () => { - const filePath = getFixturePath("single-quoted.js"); - const engine1 = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false, - overrideConfig: { rules: { "example/example-rule": 1 } } - }); - const engine2 = new ESLint({ - cwd: path.join(fixtureDir, ".."), - useEslintrc: false - }); - const fileConfig1 = await engine1.calculateConfigForFile(filePath); - const fileConfig2 = await engine2.calculateConfigForFile(filePath); - - // plugin - assert.deepStrictEqual(fileConfig1.rules["example/example-rule"], [1], "example is present for engine 1"); - assert.strictEqual(fileConfig2.rules["example/example-rule"], void 0, "example is not present for engine 2"); - }); - }); - }); - - describe("with ignorePatterns config", () => { - const root = getFixturePath("cli-engine/ignore-patterns"); - - describe("ignorePatterns can add an ignore pattern ('foo.js').", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": { - ignorePatterns: "foo.js" - }, - "foo.js": "", - "bar.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), false); - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false); - }); - - it("'lintFiles()' should not verify 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js"), - path.join(root, "subdir/bar.js") - ]); - }); - }); - - describe("ignorePatterns can add ignore patterns ('foo.js', '/bar.js').", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": { - ignorePatterns: ["foo.js", "/bar.js"] - }, - "foo.js": "", - "bar.js": "", - "baz.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "", - "subdir/baz.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); - }); - - it("'isPathIgnored()' should return 'true' for '/bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false); - }); - - it("'lintFiles()' should not verify 'foo.js' and '/bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "baz.js"), - path.join(root, "subdir/bar.js"), - path.join(root, "subdir/baz.js") - ]); - }); - }); - - describe("ignorePatterns can unignore '/node_modules/foo'.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": { - ignorePatterns: "!/node_modules/foo" - }, - "node_modules/foo/index.js": "", - "node_modules/foo/.dot.js": "", - "node_modules/bar/index.js": "", - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/foo/index.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for 'node_modules/foo/.dot.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/foo/.dot.js"), true); - }); - - it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/bar/index.js"), true); - }); - - it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "foo.js"), - path.join(root, "node_modules/foo/index.js") - ]); - }); - }); - - describe("ignorePatterns can unignore '.eslintrc.js'.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "!.eslintrc.js" - })}`, - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for '.eslintrc.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored(".eslintrc.js"), false); - }); - - it("'lintFiles()' should verify '.eslintrc.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, ".eslintrc.js"), - path.join(root, "foo.js") - ]); - }); - }); - - describe(".eslintignore can re-ignore files that are unignored by ignorePatterns.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "!.*" - })}`, - ".eslintignore": ".foo*", - ".foo.js": "", - ".bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored(".foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored(".bar.js"), false); - }); - - it("'lintFiles()' should not verify re-ignored '.foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, ".bar.js"), - path.join(root, ".eslintrc.js") - ]); - }); - }); - - describe(".eslintignore can unignore files that are ignored by ignorePatterns.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "*.js" - })}`, - ".eslintignore": "!foo.js", - "foo.js": "", - "bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), true); - }); - - it("'lintFiles()' should verify unignored 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "foo.js") - ]); - }); - }); - - describe("ignorePatterns in the config file in a child directory affects to only in the directory.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({ - ignorePatterns: "foo.js" - }), - "subdir/.eslintrc.json": JSON.stringify({ - ignorePatterns: "bar.js" - }), - "foo.js": "", - "bar.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "", - "subdir/subsubdir/foo.js": "", - "subdir/subsubdir/bar.js": "" - } - }); - - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/subsubdir/foo.js"), true); - }); - - it("'isPathIgnored()' should return 'true' for 'bar.js' in 'subdir'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/subsubdir/bar.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'bar.js' in the outside of 'subdir'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), false); - }); - - it("'lintFiles()' should verify 'bar.js' in the outside of 'subdir'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js") - ]); - }); - }); - - describe("ignorePatterns in the config file in a child directory can unignore the ignored files in the parent directory's config.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({ - ignorePatterns: "foo.js" - }), - "subdir/.eslintrc.json": JSON.stringify({ - ignorePatterns: "!foo.js" - }), - "foo.js": "", - "subdir/foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false); - }); - - it("'lintFiles()' should verify 'foo.js' in the child directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "subdir/foo.js") - ]); - }); - }); - - describe(".eslintignore can unignore files that are ignored by ignorePatterns in the config file in the child directory.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({}), - "subdir/.eslintrc.json": JSON.stringify({ - ignorePatterns: "*.js" - }), - ".eslintignore": "!foo.js", - "foo.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), false); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true); - }); - - it("'lintFiles()' should verify unignored 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "foo.js"), - path.join(root, "subdir/foo.js") - ]); - }); - }); - - describe("if the config in a child directory has 'root:true', ignorePatterns in the config file in the parent directory should not be used.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({ - ignorePatterns: "foo.js" - }), - "subdir/.eslintrc.json": JSON.stringify({ - root: true, - ignorePatterns: "bar.js" - }), - "foo.js": "", - "bar.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), false); - }); - - it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true); - }); - - it("'lintFiles()' should verify 'bar.js' in the root directory and 'foo.js' in the child directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js"), - path.join(root, "subdir/foo.js") - ]); - }); - }); - - describe("even if the config in a child directory has 'root:true', .eslintignore should be used.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({}), - "subdir/.eslintrc.json": JSON.stringify({ - root: true, - ignorePatterns: "bar.js" - }), - ".eslintignore": "foo.js", - "foo.js": "", - "bar.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), true); - }); - - it("'lintFiles()' should verify 'bar.js' in the root directory.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js") - ]); - }); - }); - - describe("ignorePatterns in the shareable config should be used.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "foo.js" - })}`, - ".eslintrc.json": JSON.stringify({ - extends: "one" - }), - "foo.js": "", - "bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), false); - }); - - it("'lintFiles()' should verify 'bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js") - ]); - }); - }); - - describe("ignorePatterns in the shareable config should be relative to the entry config file.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "/foo.js" - })}`, - ".eslintrc.json": JSON.stringify({ - extends: "one" - }), - "foo.js": "", - "subdir/foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'subdir/foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), false); - }); - - it("'lintFiles()' should verify 'subdir/foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "subdir/foo.js") - ]); - }); - }); - - describe("ignorePatterns in a config file can unignore the files which are ignored by ignorePatterns in the shareable config.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - ignorePatterns: "*.js" - })}`, - ".eslintrc.json": JSON.stringify({ - extends: "one", - ignorePatterns: "!bar.js" - }), - "foo.js": "", - "bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), false); - }); - - it("'lintFiles()' should verify 'bar.js'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js") - ]); - }); - }); - - describe("ignorePatterns in a config file should not be used if --no-ignore option was given.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({ - ignorePatterns: "*.js" - }), - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath(), ignore: false }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), false); - }); - - it("'lintFiles()' should verify 'foo.js'.", async () => { - const engine = new ESLint({ cwd: getPath(), ignore: false }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "foo.js") - ]); - }); - }); - - describe("ignorePatterns in overrides section is not allowed.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.js": `module.exports = ${JSON.stringify({ - overrides: [ + }, { - files: "*.js", - ignorePatterns: "foo.js" - } - ] - })}`, - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("should throw a configuration error.", async () => { - await assert.rejects(async () => { - const engine = new ESLint({ cwd: getPath() }); - - await engine.lintFiles("*.js"); - }, /Unexpected top-level property "overrides\[0\]\.ignorePatterns"/u); - }); - }); - }); - - describe("'overrides[].files' adds lint targets", () => { - const root = getFixturePath("cli-engine/additional-lint-targets"); - - - describe("if { files: 'foo/*.txt', excludedFiles: '**/ignore.txt' } is present,", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({ - overrides: [ + ignores: ["error.js"], + rules: { + "no-unused-vars": "warn", + }, + }, + ];`, + "error.js": "let unusedVar;", + "warn.js": "let unusedVar;", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should apply to all files except for 'error.js'", async () => { + const engine = new ESLint({ + flags, + cwd, + }); + + const results = await engine.lintFiles("{error,warn}.js"); + + assert.strictEqual(results.length, 2); + + const [errorResult, warnResult] = results; + + assert.strictEqual( + errorResult.filePath, + path.join(getPath(), "error.js"), + ); + assert.strictEqual(errorResult.messages.length, 1); + assert.strictEqual( + errorResult.messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(errorResult.messages[0].severity, 2); + + assert.strictEqual( + warnResult.filePath, + path.join(getPath(), "warn.js"), + ); + assert.strictEqual(warnResult.messages.length, 1); + assert.strictEqual( + warnResult.messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(warnResult.messages[0].severity, 1); + }); + + // https://github.com/eslint/eslint/issues/18261 + it("should apply to all files except for 'error.js' even with `ignore: false` option", async () => { + const engine = new ESLint({ + flags, + cwd, + ignore: false, + }); + + const results = await engine.lintFiles("{error,warn}.js"); + + assert.strictEqual(results.length, 2); + + const [errorResult, warnResult] = results; + + assert.strictEqual( + errorResult.filePath, + path.join(getPath(), "error.js"), + ); + assert.strictEqual(errorResult.messages.length, 1); + assert.strictEqual( + errorResult.messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(errorResult.messages[0].severity, 2); + + assert.strictEqual( + warnResult.filePath, + path.join(getPath(), "warn.js"), + ); + assert.strictEqual(warnResult.messages.length, 1); + assert.strictEqual( + warnResult.messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(warnResult.messages[0].severity, 1); + }); + }); + + describe("config with ignores: ['**/*.json']", () => { + const cwd = getFixturePath("config-with-ignores-without-files"); + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd, + files: { + "eslint.config.js": `module.exports = [ { - files: "foo/*.txt", - excludedFiles: "**/ignore.txt" - } - ] - }), - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "foo/ignore.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "bar/ignore.txt": "", - "test.js": "", - "test.txt": "", - "ignore.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with a directory path should contain 'foo/test.txt'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles(".")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/test.js"), - path.join(root, "foo/test.txt"), - path.join(root, "test.js") - ]); - }); - - it("'lintFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/test.js"), - path.join(root, "test.js") - ]); - }); - }); - - describe("if { files: 'foo/**/*.txt' } is present,", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({ - overrides: [ + rules: { + "no-undef": "error", + }, + }, { - files: "foo/**/*.txt" - } - ] - }), - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "test.js": "", - "test.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles(".")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/nested/test.txt"), - path.join(root, "foo/test.js"), - path.join(root, "foo/test.txt"), - path.join(root, "test.js") - ]); - }); - }); - - describe("if { files: 'foo/**/*' } is present,", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - ".eslintrc.json": JSON.stringify({ - overrides: [ + ignores: ["**/*.json"], + rules: { + "no-unused-vars": "error", + }, + }, + ];`, + "foo.js": "", + "foo.json": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should not add json files as lint targets", async () => { + const engine = new ESLint({ + flags, + cwd, + }); + + const results = await engine.lintFiles("foo*"); + + // should not lint `foo.json` + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(getPath(), "foo.js"), + ); + }); + }); + }); + + describe("with ignores config", () => { + const root = getFixturePath("cli-engine/ignore-patterns"); + + describe("ignores can add an ignore pattern ('foo.js').", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "eslint.config.js": `module.exports = { + ignores: ["**/foo.js"] + };`, + "foo.js": "", + "bar.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("foo.js"), + true, + ); + assert.strictEqual( + await engine.isPathIgnored("subdir/foo.js"), + true, + ); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("bar.js"), + false, + ); + assert.strictEqual( + await engine.isPathIgnored("subdir/bar.js"), + false, + ); + }); + + it("'lintFiles()' should not verify 'foo.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar.js"), + path.join(root, "eslint.config.js"), + path.join(root, "subdir/bar.js"), + ]); + }); + }); + + describe("ignores can add ignore patterns ('**/foo.js', '/bar.js').", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root + Date.now(), + files: { + "eslint.config.js": `module.exports = { + ignores: ["**/foo.js", "bar.js"] + };`, + "foo.js": "", + "bar.js": "", + "baz.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + "subdir/baz.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("foo.js"), + true, + ); + assert.strictEqual( + await engine.isPathIgnored("subdir/foo.js"), + true, + ); + }); + + it("'isPathIgnored()' should return 'true' for '/bar.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("bar.js"), + true, + ); + assert.strictEqual( + await engine.isPathIgnored("subdir/bar.js"), + false, + ); + }); + + it("'lintFiles()' should not verify 'foo.js' and '/bar.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(getPath(), "baz.js"), + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "subdir/bar.js"), + path.join(getPath(), "subdir/baz.js"), + ]); + }); + }); + + describe("ignores can unignore '/node_modules/foo' with patterns ['!node_modules/', 'node_modules/*', '!node_modules/foo/'].", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}-unignores`, + files: { + "eslint.config.js": `module.exports = { + ignores: ["!node_modules/", "node_modules/*", "!node_modules/foo/"] + };`, + "node_modules/foo/index.js": "", + "node_modules/foo/.dot.js": "", + "node_modules/bar/index.js": "", + "foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("node_modules/foo/index.js"), + false, + ); + }); + + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/.dot.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("node_modules/foo/.dot.js"), + false, + ); + }); + + it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("node_modules/bar/index.js"), + true, + ); + }); + + it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo.js"), + path.join(getPath(), "node_modules/foo/.dot.js"), + path.join(getPath(), "node_modules/foo/index.js"), + ]); + }); + }); + + describe("ignores can unignore '/node_modules/foo' with patterns ['!node_modules/', 'node_modules/*', '!node_modules/foo/**'].", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}-unignores`, + files: { + "eslint.config.js": `module.exports = { + ignores: ["!node_modules/", "node_modules/*", "!node_modules/foo/**"] + };`, + "node_modules/foo/index.js": "", + "node_modules/foo/.dot.js": "", + "node_modules/bar/index.js": "", + "foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("node_modules/foo/index.js"), + false, + ); + }); + + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/.dot.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("node_modules/foo/.dot.js"), + false, + ); + }); + + it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("node_modules/bar/index.js"), + true, + ); + }); + + it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + const result = await engine.lintFiles("**/*.js"); + + const filePaths = result.map(r => r.filePath).sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo.js"), + path.join(getPath(), "node_modules/foo/.dot.js"), + path.join(getPath(), "node_modules/foo/index.js"), + ]); + }); + }); + + describe("ignore pattern can re-ignore files that are unignored by a previous pattern.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}-reignore`, + files: { + "eslint.config.js": `module.exports = ${JSON.stringify({ + ignores: ["!.*", ".foo*"], + })}`, + ".foo.js": "", + ".bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored(".foo.js"), + true, + ); + }); + + it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored(".bar.js"), + false, + ); + }); + + it("'lintFiles()' should not lint re-ignored '.foo.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(getPath(), ".bar.js"), + path.join(getPath(), "eslint.config.js"), + ]); + }); + }); + + describe("ignore pattern can unignore files that are ignored by a previous pattern.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}-dignore`, + files: { + "eslint.config.js": `module.exports = ${JSON.stringify({ + ignores: ["**/*.js", "!foo.js"], + })}`, + "foo.js": "", + "bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("foo.js"), + false, + ); + }); + + it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("bar.js"), + true, + ); + }); + + it("'lintFiles()' should verify unignored 'foo.js'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(getPath(), "foo.js"), + ]); + }); + }); + + describe("ignores in a config file should not be used if ignore: false.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "eslint.config.js": `module.exports = { + ignores: ["*.js"] + }`, + "foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for 'foo.js'.", async () => { + const engine = new ESLint({ + flags, + cwd: getPath(), + ignore: false, + }); + + assert.strictEqual( + await engine.isPathIgnored("foo.js"), + false, + ); + }); + + it("'lintFiles()' should verify 'foo.js'.", async () => { + const engine = new ESLint({ + flags, + cwd: getPath(), + ignore: false, + }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "eslint.config.js"), + path.join(root, "foo.js"), + ]); + }); + }); + }); + + describe("config.files adds lint targets", () => { + const root = getFixturePath("cli-engine/additional-lint-targets"); + + describe("if { files: 'foo/*.txt', ignores: '**/ignore.txt' } is present,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root + 1, + files: { + "eslint.config.js": `module.exports = [{ + files: ["foo/*.txt"], + ignores: ["**/ignore.txt"] + }];`, + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "foo/ignore.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "bar/ignore.txt": "", + "test.js": "", + "test.txt": "", + "ignore.txt": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with a directory path should contain 'foo/test.txt'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + const filePaths = (await engine.lintFiles(".")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(getPath(), "bar/test.js"), + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo/test.js"), + path.join(getPath(), "foo/test.txt"), + path.join(getPath(), "test.js"), + ]); + }); + + it("'lintFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(getPath(), "bar/test.js"), + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo/test.js"), + path.join(getPath(), "test.js"), + ]); + }); + }); + + describe("if { files: 'foo/*.txt', ignores: '**/ignore.txt' } is present and subdirectory is passed,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root + 2, + files: { + "eslint.config.js": `module.exports = [{ + files: ["foo/*.txt"], + ignores: ["**/ignore.txt"] + }];`, + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "foo/ignore.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "bar/ignore.txt": "", + "test.js": "", + "test.txt": "", + "ignore.txt": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with a directory path should contain 'foo/test.txt'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + const filePaths = (await engine.lintFiles("foo")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(getPath(), "foo/test.js"), + path.join(getPath(), "foo/test.txt"), + ]); + }); + + it("'lintFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + const filePaths = (await engine.lintFiles("foo/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(getPath(), "foo/test.js"), + ]); + }); + }); + + describe("if { files: 'foo/**/*.txt' } is present,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root + 3, + files: { + "eslint.config.js": `module.exports = [ { - files: "foo/**/*" + files: ["foo/**/*.txt"] } - ] - }), - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "test.js": "", - "test.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles(".")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/test.js"), - path.join(root, "test.js") - ]); - }); - }); - - describe("if { files: 'foo/**/*.txt' } is present in a shareable config,", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-config-foo/index.js": `module.exports = ${JSON.stringify({ - overrides: [ + ]`, + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "test.js": "", + "test.txt": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + const filePaths = (await engine.lintFiles(".")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(getPath(), "bar/test.js"), + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo/nested/test.txt"), + path.join(getPath(), "foo/test.js"), + path.join(getPath(), "foo/test.txt"), + path.join(getPath(), "test.js"), + ]); + }); + }); + + describe("if { files: 'foo/**/*' } is present,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root + 4, + files: { + "eslint.config.js": `module.exports = [ { - files: "foo/**/*.txt" + files: ["foo/**/*"] } - ] - })}`, - ".eslintrc.json": JSON.stringify({ - extends: "foo" - }), - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "test.js": "", - "test.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles(".")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/nested/test.txt"), - path.join(root, "foo/test.js"), - path.join(root, "foo/test.txt"), - path.join(root, "test.js") - ]); - }); - }); - - describe("if { files: 'foo/**/*.txt' } is present in a plugin config,", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/eslint-plugin-foo/index.js": `exports.configs = ${JSON.stringify({ - bar: { - overrides: [ - { - files: "foo/**/*.txt" - } - ] - } - })}`, - ".eslintrc.json": JSON.stringify({ - extends: "plugin:foo/bar" - }), - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "test.js": "", - "test.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { - const engine = new ESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles(".")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar/test.js"), - path.join(root, "foo/nested/test.txt"), - path.join(root, "foo/test.js"), - path.join(root, "foo/test.txt"), - path.join(root, "test.js") - ]); - }); - }); - }); - - describe("'ignorePatterns', 'overrides[].files', and 'overrides[].excludedFiles' of the configuration that the '--config' option provided should be resolved from CWD.", () => { - const root = getFixturePath("cli-engine/config-and-overrides-files"); - - describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/myconf/.eslintrc.json": { - overrides: [ + ]`, + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "test.js": "", + "test.txt": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { + const engine = new ESLint({ flags, cwd: getPath() }); + const filePaths = (await engine.lintFiles(".")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(getPath(), "bar/test.js"), + path.join(getPath(), "eslint.config.js"), + path.join(getPath(), "foo/test.js"), + path.join(getPath(), "test.js"), + ]); + }); + }); + }); + + describe("'ignores', 'files' of the configuration that the '--config' option provided should be resolved from CWD.", () => { + const root = getFixturePath( + "cli-engine/config-and-overrides-files", + ); + + describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/eslint.config.js',", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}a1`, + files: { + "node_modules/myconf/eslint.config.js": `module.exports = [ { - files: "foo/*.js", + files: ["foo/*.js"], rules: { eqeqeq: "error" } } - ] - }, - "node_modules/myconf/foo/test.js": "a == b", - "foo/test.js": "a == b" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with 'foo/test.js' should use the override entry.", async () => { - const engine = new ESLint({ - overrideConfigFile: "node_modules/myconf/.eslintrc.json", - cwd: getPath(), - ignore: false, - useEslintrc: false - }); - const results = await engine.lintFiles("foo/test.js"); - - // Expected to be an 'eqeqeq' error because the file matches to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - errorCount: 1, - filePath: path.join(getPath(), "foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [ - { - column: 3, - endColumn: 5, - endLine: 1, - line: 1, - message: "Expected '===' and instead saw '=='.", - messageId: "unexpected", - nodeType: "BinaryExpression", - ruleId: "eqeqeq", - severity: 2 - } - ], - suppressedMessages: [], - source: "a == b", - usedDeprecatedRules: [], - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - - it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the override entry.", async () => { - const engine = new ESLint({ - overrideConfigFile: "node_modules/myconf/.eslintrc.json", - cwd: root, - ignore: false, - useEslintrc: false - }); - const results = await engine.lintFiles("node_modules/myconf/foo/test.js"); - - // Expected to be no errors because the file doesn't match to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - errorCount: 0, - filePath: path.join(getPath(), "node_modules/myconf/foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [], - suppressedMessages: [], - usedDeprecatedRules: [], - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - }); - - describe("if { files: '*', excludedFiles: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/myconf/.eslintrc.json": JSON.stringify({ - overrides: [ + ];`, + "node_modules/myconf/foo/test.js": "a == b", + "foo/test.js": "a == b", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with 'foo/test.js' should use the files entry.", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: + "node_modules/myconf/eslint.config.js", + cwd: getPath(), + ignore: false, + }); + const results = await engine.lintFiles("foo/test.js"); + + // Expected to be an 'eqeqeq' error because the file matches to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + suppressedMessages: [], + errorCount: 1, + filePath: path.join(getPath(), "foo/test.js"), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [ + { + column: 3, + endColumn: 5, + endLine: 1, + line: 1, + message: + "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + severity: 2, + suggestions: [ + { + data: { + actualOperator: "==", + expectedOperator: "===", + }, + desc: "Use '===' instead of '=='.", + fix: { + range: [2, 4], + text: "===", + }, + messageId: "replaceOperator", + }, + ], + }, + ], + source: "a == b", + usedDeprecatedRules: [], + warningCount: 0, + fatalErrorCount: 0, + }, + ]); + }); + + it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the files entry.", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: + "node_modules/myconf/eslint.config.js", + cwd: getPath(), + ignore: false, + }); + const results = await engine.lintFiles( + "node_modules/myconf/foo/test.js", + ); + + // Expected to be no errors because the file doesn't match to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + suppressedMessages: [], + errorCount: 0, + filePath: path.join( + getPath(), + "node_modules/myconf/foo/test.js", + ), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [ + { + ruleId: null, + fatal: false, + message: + 'File ignored by default because it is located under the node_modules directory. Use ignore pattern "!**/node_modules/" to disable file ignore settings or use "--no-warn-ignored" to suppress this warning.', + severity: 1, + nodeType: null, + }, + ], + usedDeprecatedRules: [], + warningCount: 1, + fatalErrorCount: 0, + }, + ]); + }); + }); + + describe("if { files: '*', ignores: 'foo/*.txt', ... } is present by '--config bar/myconf/eslint.config.js',", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}a2`, + files: { + "bar/myconf/eslint.config.js": `module.exports = [ { - files: "*", - excludedFiles: "foo/*.js", + files: ["**/*"], + ignores: ["foo/*.js"], rules: { eqeqeq: "error" } } - ] - }), - "node_modules/myconf/foo/test.js": "a == b", - "foo/test.js": "a == b" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with 'foo/test.js' should NOT use the override entry.", async () => { - const engine = new ESLint({ - overrideConfigFile: "node_modules/myconf/.eslintrc.json", - cwd: root, - ignore: false, - useEslintrc: false - }); - const results = await engine.lintFiles("foo/test.js"); - - // Expected to be no errors because the file matches to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - errorCount: 0, - filePath: path.join(getPath(), "foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [], - suppressedMessages: [], - usedDeprecatedRules: [], - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - - it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should use the override entry.", async () => { - const engine = new ESLint({ - overrideConfigFile: "node_modules/myconf/.eslintrc.json", - cwd: root, - ignore: false, - useEslintrc: false - }); - const results = await engine.lintFiles("node_modules/myconf/foo/test.js"); - - // Expected to be an 'eqeqeq' error because the file doesn't match to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - errorCount: 1, - filePath: path.join(getPath(), "node_modules/myconf/foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [ - { - column: 3, - endColumn: 5, - endLine: 1, - line: 1, - message: "Expected '===' and instead saw '=='.", - messageId: "unexpected", - nodeType: "BinaryExpression", - ruleId: "eqeqeq", - severity: 2 + ]`, + "bar/myconf/foo/test.js": "a == b", + "foo/test.js": "a == b", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with 'foo/test.js' should have no errors because no rules are enabled.", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: "bar/myconf/eslint.config.js", + cwd: getPath(), + ignore: false, + }); + const results = await engine.lintFiles("foo/test.js"); + + // Expected to be no errors because the file matches to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + suppressedMessages: [], + errorCount: 0, + filePath: path.join(getPath(), "foo/test.js"), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [], + usedDeprecatedRules: [], + warningCount: 0, + fatalErrorCount: 0, + }, + ]); + }); + + it("'lintFiles()' with 'bar/myconf/foo/test.js' should have an error because eqeqeq is enabled.", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: "bar/myconf/eslint.config.js", + cwd: getPath(), + ignore: false, + }); + const results = await engine.lintFiles( + "bar/myconf/foo/test.js", + ); + + // Expected to be an 'eqeqeq' error because the file doesn't match to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + suppressedMessages: [], + errorCount: 1, + filePath: path.join( + getPath(), + "bar/myconf/foo/test.js", + ), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [ + { + column: 3, + endColumn: 5, + endLine: 1, + line: 1, + message: + "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + severity: 2, + suggestions: [ + { + data: { + actualOperator: "==", + expectedOperator: "===", + }, + desc: "Use '===' instead of '=='.", + fix: { + range: [2, 4], + text: "===", + }, + messageId: "replaceOperator", + }, + ], + }, + ], + source: "a == b", + usedDeprecatedRules: [], + warningCount: 0, + fatalErrorCount: 0, + }, + ]); + }); + }); + + describe("if { ignores: 'foo/*.js', ... } is present by '--config node_modules/myconf/eslint.config.js',", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}a3`, + files: { + "node_modules/myconf/eslint.config.js": `module.exports = [{ + ignores: ["!node_modules", "node_modules/*", "!node_modules/myconf", "foo/*.js"], + }, { + rules: { + eqeqeq: "error" } - ], - suppressedMessages: [], - source: "a == b", - usedDeprecatedRules: [], - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - }); - - describe("if { ignorePatterns: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "node_modules/myconf/.eslintrc.json": JSON.stringify({ - ignorePatterns: ["!/node_modules/myconf", "foo/*.js"], - rules: { - eqeqeq: "error" - } - }), - "node_modules/myconf/foo/test.js": "a == b", - "foo/test.js": "a == b" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with '**/*.js' should iterate 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", async () => { - const engine = new ESLint({ - overrideConfigFile: "node_modules/myconf/.eslintrc.json", - cwd: getPath(), - useEslintrc: false - }); - const files = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(files, [ - path.join(root, "node_modules/myconf/foo/test.js") - ]); - }); - }); - }); - - describe("plugin conflicts", () => { - let uid = 0; - const root = getFixturePath("cli-engine/plugin-conflicts-"); - - /** - * Verify thrown errors. - * @param {() => Promise} f The function to run and throw. - * @param {Record} props The properties to verify. - * @returns {Promise} void - */ - async function assertThrows(f, props) { - try { - await f(); - } catch (error) { - for (const [key, value] of Object.entries(props)) { - assert.deepStrictEqual(error[key], value, key); - } - return; - } - - assert.fail("Function should throw an error, but not."); - } - - describe("between a config file and linear extendees.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - extends: ["two"], - plugins: ["foo"] - })}`, - "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({ - plugins: ["foo"] - })}`, - ".eslintrc.json": JSON.stringify({ - extends: ["one"], - plugins: ["foo"] - }), - "test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", async () => { - const engine = new ESLint({ cwd: getPath() }); - - await engine.lintFiles("test.js"); - }); - }); - - describe("between a config file and same-depth extendees.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify({ - plugins: ["foo"] - })}`, - "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify({ - plugins: ["foo"] - })}`, - ".eslintrc.json": JSON.stringify({ - extends: ["one", "two"], - plugins: ["foo"] - }), - "test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", async () => { - const engine = new ESLint({ cwd: getPath() }); - - await engine.lintFiles("test.js"); - }); - }); - - describe("between two config files in different directories, with single node_modules.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", async () => { - const engine = new ESLint({ cwd: getPath() }); - - await engine.lintFiles("subdir/test.js"); - }); - }); - - describe("between two config files in different directories, with multiple node_modules.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/node_modules/eslint-plugin-foo/index.js": "", - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", async () => { - const engine = new ESLint({ cwd: getPath() }); - - await assertThrows( - () => engine.lintFiles("subdir/test.js"), - { - message: `Plugin "foo" was conflicted between "subdir${path.sep}.eslintrc.json" and ".eslintrc.json".`, - messageTemplate: "plugin-conflict", - messageData: { - pluginId: "foo", - plugins: [ - { - filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"), - importerName: `subdir${path.sep}.eslintrc.json` - }, - { - filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"), - importerName: ".eslintrc.json" - } - ] - } - } - ); - }); - }); - - describe("between '--config' option and a regular config file, with single node_modules.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "node_modules/mine/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", async () => { - const engine = new ESLint({ - cwd: getPath(), - overrideConfigFile: "node_modules/mine/.eslintrc.json" - }); - - await engine.lintFiles("test.js"); - }); - }); - - describe("between '--config' option and a regular config file, with multiple node_modules.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "node_modules/mine/node_modules/eslint-plugin-foo/index.js": "", - "node_modules/mine/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", async () => { - const engine = new ESLint({ - cwd: getPath(), - overrideConfigFile: "node_modules/mine/.eslintrc.json" - }); - - await assertThrows( - () => engine.lintFiles("test.js"), - { - message: "Plugin \"foo\" was conflicted between \"--config\" and \".eslintrc.json\".", - messageTemplate: "plugin-conflict", - messageData: { - pluginId: "foo", - plugins: [ - { - filePath: path.join(getPath(), "node_modules/mine/node_modules/eslint-plugin-foo/index.js"), - importerName: "--config" - }, - { - filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"), - importerName: ".eslintrc.json" - } - ] - } - } - ); - }); - }); - - describe("between '--plugin' option and a regular config file, with single node_modules.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" - } - }); - - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file, but node_modules directory is unique.)", async () => { - const engine = new ESLint({ - cwd: getPath(), - overrideConfig: { plugins: ["foo"] } - }); - - await engine.lintFiles("subdir/test.js"); - }); - }); - - describe("between '--plugin' option and a regular config file, with multiple node_modules.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - "subdir/node_modules/eslint-plugin-foo/index.js": "", - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file.)", async () => { - const engine = new ESLint({ - cwd: getPath(), - overrideConfig: { plugins: ["foo"] } - }); - - await assertThrows( - () => engine.lintFiles("subdir/test.js"), - { - message: `Plugin "foo" was conflicted between "CLIOptions" and "subdir${path.sep}.eslintrc.json".`, - messageTemplate: "plugin-conflict", - messageData: { - pluginId: "foo", - plugins: [ - { - filePath: path.join(getPath(), "node_modules/eslint-plugin-foo/index.js"), - importerName: "CLIOptions" - }, - { - filePath: path.join(getPath(), "subdir/node_modules/eslint-plugin-foo/index.js"), - importerName: `subdir${path.sep}.eslintrc.json` - } - ] - } - } - ); - }); - }); - - describe("'--resolve-plugins-relative-to' option overrides the location that ESLint load plugins from.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "node_modules/eslint-plugin-foo/index.js": "", - ".eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/node_modules/eslint-plugin-foo/index.js": "", - "subdir/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "subdir/test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from '--resolve-plugins-relative-to'.)", async () => { - const engine = new ESLint({ - cwd: getPath(), - resolvePluginsRelativeTo: getPath() - }); - - await engine.lintFiles("subdir/test.js"); - }); - }); - - describe("between two config files with different target files.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}${++uid}`, - files: { - "one/node_modules/eslint-plugin-foo/index.js": "", - "one/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "one/test.js": "", - "two/node_modules/eslint-plugin-foo/index.js": "", - "two/.eslintrc.json": JSON.stringify({ - plugins: ["foo"] - }), - "two/test.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file for each target file. Not related to each other.)", async () => { - const engine = new ESLint({ cwd: getPath() }); - const results = await engine.lintFiles("*/test.js"); - - assert.strictEqual(results.length, 2); - }); - }); - }); - - describe("loading rules", () => { - it("should not load unused core rules", done => { - let calledDone = false; - - const cwd = getFixturePath("lazy-loading-rules"); - const pattern = "foo.js"; - const usedRules = ["semi"]; - - const forkedProcess = childProcess.fork( - path.join(__dirname, "../../_utils/test-lazy-loading-rules.js"), - [cwd, pattern, String(usedRules)] - ); - - // this is an error message - forkedProcess.on("message", ({ message, stack }) => { - if (calledDone) { - return; - } - calledDone = true; - - const error = new Error(message); - - error.stack = stack; - done(error); - }); - - forkedProcess.on("exit", exitCode => { - if (calledDone) { - return; - } - calledDone = true; - - if (exitCode === 0) { - done(); - } else { - done(new Error("Forked process exited with a non-zero exit code")); - } - }); - }); - }); - - // only works on a Windows machine - if (os.platform() === "win32") { - - // https://github.com/eslint/eslint/issues/17042 - describe("with cwd that is using forward slash on Windows", () => { - const cwd = getFixturePath("example-app3"); - const cwdForwardSlash = cwd.replace(/\\/gu, "/"); - - it("should correctly handle ignore patterns", async () => { - const engine = new ESLint({ cwd: cwdForwardSlash }); - const results = await engine.lintFiles(["./src"]); - - // src/dist/2.js should be ignored - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, path.join(cwd, "src\\1.js")); - }); - - it("should pass cwd with backslashes to rules", async () => { - const engine = new ESLint({ - cwd: cwdForwardSlash, - useEslintrc: false, - overrideConfig: { - plugins: ["test"], - rules: { - "test/report-cwd": "error" - } - } - }); - const results = await engine.lintText(""); - - assert.strictEqual(results[0].messages[0].ruleId, "test/report-cwd"); - assert.strictEqual(results[0].messages[0].message, cwd); - }); - - it("should pass cwd with backslashes to formatters", async () => { - const engine = new ESLint({ - cwd: cwdForwardSlash - }); - const results = await engine.lintText(""); - const formatter = await engine.loadFormatter("cwd"); - - assert.strictEqual(formatter.format(results), cwd); - }); - }); - } + }]`, + "node_modules/myconf/foo/test.js": "a == b", + "foo/test.js": "a == b", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with '**/*.js' should lint 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", async () => { + const engine = new ESLint({ + flags, + overrideConfigFile: + "node_modules/myconf/eslint.config.js", + cwd: getPath(), + }); + const files = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(files, [ + path.join( + getPath(), + "node_modules/myconf/eslint.config.js", + ), + path.join(getPath(), "node_modules/myconf/foo/test.js"), + ]); + }); + }); + }); + + describe("baseConfig", () => { + it("can be an object", async () => { + const eslint = new ESLint({ + flags, + overrideConfigFile: true, + baseConfig: { + rules: { + semi: 2, + }, + }, + }); + + const [{ messages }] = await eslint.lintText("foo"); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + }); + + it("can be an array", async () => { + const eslint = new ESLint({ + flags, + overrideConfigFile: true, + baseConfig: [ + { + rules: { + "no-var": 2, + }, + }, + { + rules: { + semi: 2, + }, + }, + ], + }); + + const [{ messages }] = await eslint.lintText("var foo"); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-var"); + assert.strictEqual(messages[1].ruleId, "semi"); + }); + + it("should be inserted after default configs", async () => { + const eslint = new ESLint({ + flags, + overrideConfigFile: true, + baseConfig: { + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, + }, + }); + + const [{ messages }] = await eslint.lintText("let x"); + + /* + * if baseConfig was inserted before default configs, + * `ecmaVersion: "latest"` from default configs would overwrite + * `ecmaVersion: 5` from baseConfig, so this wouldn't be a parsing error. + */ + + assert.strictEqual(messages.length, 1); + assert(messages[0].fatal, "Fatal error expected."); + }); + + it("should be inserted before configs from the config file", async () => { + const eslint = new ESLint({ + flags, + cwd: getFixturePath(), + baseConfig: { + rules: { + strict: ["error", "global"], + }, + languageOptions: { + sourceType: "script", + }, + }, + }); + + const [{ messages }] = await eslint.lintText("foo"); + + /* + * if baseConfig was inserted after configs from the config file, + * `strict: 0` from eslint.config.js wouldn't overwrite `strict: ["error", "global"]` + * from baseConfig, so there would be an error message from the `strict` rule. + */ + + assert.strictEqual(messages.length, 0); + }); + + it("should be inserted before overrideConfig", async () => { + const eslint = new ESLint({ + flags, + overrideConfigFile: true, + baseConfig: { + rules: { + semi: 2, + }, + }, + overrideConfig: { + rules: { + semi: 1, + }, + }, + }); + + const [{ messages }] = await eslint.lintText("foo"); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].severity, 1); + }); + + it("should be inserted before configs from the config file and overrideConfig", async () => { + const eslint = new ESLint({ + flags, + overrideConfigFile: getFixturePath( + "eslint.config-with-rules.js", + ), + baseConfig: { + rules: { + quotes: ["error", "double"], + semi: "error", + }, + }, + overrideConfig: { + rules: { + quotes: "warn", + }, + }, + }); + + const [{ messages }] = + await eslint.lintText('const foo = "bar"'); + + /* + * baseConfig: { quotes: ["error", "double"], semi: "error" } + * eslint.config-with-rules.js: { quotes: ["error", "single"] } + * overrideConfig: { quotes: "warn" } + * + * Merged config: { quotes: ["warn", "single"], semi: "error" } + */ + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "quotes"); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[1].ruleId, "semi"); + assert.strictEqual(messages[1].severity, 2); + }); + + it("when it has 'files' they should be interpreted as relative to the config file", async () => { + /* + * `fixtures/plugins` directory does not have a config file. + * It's parent directory `fixtures` does have a config file, so + * the base path will be `fixtures`, cwd will be `fixtures/plugins` + */ + const eslint = new ESLint({ + flags, + cwd: getFixturePath("plugins"), + baseConfig: { + files: ["plugins/a.js"], + rules: { + semi: 2, + }, + }, + }); + + const [{ messages }] = await eslint.lintText("foo", { + filePath: getFixturePath("plugins/a.js"), + }); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + }); + + it("when it has 'ignores' they should be interpreted as relative to the config file", async () => { + /* + * `fixtures/plugins` directory does not have a config file. + * It's parent directory `fixtures` does have a config file, so + * the base path will be `fixtures`, cwd will be `fixtures/plugins` + */ + const eslint = new ESLint({ + flags, + cwd: getFixturePath("plugins"), + baseConfig: { + ignores: ["plugins/a.js"], + }, + }); + + const [{ messages }] = await eslint.lintText("foo", { + filePath: getFixturePath("plugins/a.js"), + warnIgnored: true, + }); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.match(messages[0].message, /ignored/u); + }); + }); + + describe("config file", () => { + it("new instance of ESLint should use the latest version of the config file (ESM)", async () => { + const cwd = path.join( + getFixturePath(), + `config_file_${Date.now()}`, + ); + const configFileContent = + "export default [{ rules: { semi: ['error', 'always'] } }];"; + const teardown = createCustomTeardown({ + cwd, + files: { + "package.json": '{ "type": "module" }', + "eslint.config.js": configFileContent, + "a.js": "foo\nbar;", + }, + }); + + await teardown.prepare(); + + let eslint = new ESLint({ flags, cwd }); + let [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "missingSemi"); + assert.strictEqual(messages[0].line, 1); + + await sleep(100); + await fsp.writeFile( + path.join(cwd, "eslint.config.js"), + configFileContent.replace("always", "never"), + ); + + eslint = new ESLint({ flags, cwd }); + [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "extraSemi"); + assert.strictEqual(messages[0].line, 2); + }); + + it("new instance of ESLint should use the latest version of the config file (CJS)", async () => { + const cwd = path.join( + getFixturePath(), + `config_file_${Date.now()}`, + ); + const configFileContent = + "module.exports = [{ rules: { semi: ['error', 'always'] } }];"; + const teardown = createCustomTeardown({ + cwd, + files: { + "eslint.config.js": configFileContent, + "a.js": "foo\nbar;", + }, + }); + + await teardown.prepare(); + + let eslint = new ESLint({ flags, cwd }); + let [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "missingSemi"); + assert.strictEqual(messages[0].line, 1); + + await sleep(100); + await fsp.writeFile( + path.join(cwd, "eslint.config.js"), + configFileContent.replace("always", "never"), + ); + + eslint = new ESLint({ flags, cwd }); + [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "extraSemi"); + assert.strictEqual(messages[0].line, 2); + }); + + it("new instance of ESLint should use the latest version of the config file (TypeScript)", async () => { + const cwd = getFixturePath(`config_file_${Date.now()}`); + const configFileContent = + "export default [{ rules: { semi: ['error', 'always'] } }];"; + const teardown = createCustomTeardown({ + cwd, + files: { + "eslint.config.ts": configFileContent, + "a.js": "foo\nbar;", + }, + }); + + await teardown.prepare(); + + let eslint = new ESLint({ flags, cwd }); + let [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "missingSemi"); + assert.strictEqual(messages[0].line, 1); + + await sleep(100); + await fsp.writeFile( + path.join(cwd, "eslint.config.ts"), + configFileContent.replace("always", "never"), + ); + + eslint = new ESLint({ flags, cwd }); + [{ messages }] = await eslint.lintFiles(["a.js"]); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "semi"); + assert.strictEqual(messages[0].messageId, "extraSemi"); + assert.strictEqual(messages[0].line, 2); + }); + }); + + // only works on a Windows machine + if (os.platform() === "win32") { + // https://github.com/eslint/eslint/issues/17042 + describe("with cwd that is using forward slash on Windows", () => { + const cwd = getFixturePath("example-app3"); + const cwdForwardSlash = cwd.replace(/\\/gu, "/"); + + it("should correctly handle ignore patterns", async () => { + const engine = new ESLint({ flags, cwd: cwdForwardSlash }); + const results = await engine.lintFiles(["./src"]); + + // src/dist/2.js should be ignored + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(cwd, "src\\1.js"), + ); + }); + + it("should pass cwd with backslashes to rules", async () => { + const engine = new ESLint({ + flags, + cwd: cwdForwardSlash, + overrideConfigFile: true, + overrideConfig: { + plugins: { + test: require( + path.join( + cwd, + "node_modules", + "eslint-plugin-test", + ), + ), + }, + rules: { + "test/report-cwd": "error", + }, + }, + }); + const results = await engine.lintText(""); + + assert.strictEqual( + results[0].messages[0].ruleId, + "test/report-cwd", + ); + assert.strictEqual(results[0].messages[0].message, cwd); + }); + + it("should pass cwd with backslashes to formatters", async () => { + const engine = new ESLint({ + flags, + cwd: cwdForwardSlash, + }); + const results = await engine.lintText(""); + const formatter = await engine.loadFormatter("cwd"); + + assert.strictEqual(formatter.format(results), cwd); + }); + }); + } + + describe("config with circular references", () => { + it("in 'settings'", async () => { + let resolvedSettings = null; + + const circular = {}; + + circular.self = circular; + + const eslint = new ESLint({ + flags, + overrideConfigFile: true, + baseConfig: { + settings: { + sharedData: circular, + }, + rules: { + "test-plugin/test-rule": 1, + }, + }, + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create(context) { + resolvedSettings = context.settings; + return {}; + }, + }, + }, + }, + }, + }); + + await eslint.lintText("debugger;"); + + assert.deepStrictEqual(resolvedSettings.sharedData, circular); + }); + + it("in 'parserOptions'", async () => { + let resolvedParserOptions = null; + + const circular = {}; + + circular.self = circular; + + const eslint = new ESLint({ + flags, + overrideConfigFile: true, + baseConfig: { + languageOptions: { + parser: { + parse(text, parserOptions) { + resolvedParserOptions = parserOptions; + return espree.parse(text, parserOptions); + }, + }, + parserOptions: { + testOption: circular, + }, + }, + }, + }); + + await eslint.lintText("debugger;"); + + assert.deepStrictEqual( + resolvedParserOptions.testOption, + circular, + ); + }); + }); + }); + + describe("shouldUseFlatConfig", () => { + /** + * Check that `shouldUseFlatConfig` returns the expected value from a CWD + * with a flat config and one without a flat config. + * @param {boolean} expectedValueWithConfig the expected return value of + * `shouldUseFlatConfig` when in a directory with a flat config present + * @param {boolean} expectedValueWithoutConfig the expected return value of + * `shouldUseFlatConfig` when in a directory without any flat config present + * @returns {void} + */ + function testShouldUseFlatConfig( + expectedValueWithConfig, + expectedValueWithoutConfig, + ) { + describe("when there is a flat config file present", () => { + const originalCwd = process.cwd(); + + beforeEach(() => { + process.chdir(__dirname); + }); + + afterEach(() => { + process.chdir(originalCwd); + }); + + it(`is \`${expectedValueWithConfig}\``, async () => { + assert.strictEqual( + await shouldUseFlatConfig(), + expectedValueWithConfig, + ); + }); + }); + + describe("when there is no flat config file present", () => { + const originalCwd = process.cwd(); + + beforeEach(() => { + process.chdir(os.tmpdir()); + }); + + afterEach(() => { + process.chdir(originalCwd); + }); + + it(`is \`${expectedValueWithoutConfig}\``, async () => { + assert.strictEqual( + await shouldUseFlatConfig(), + expectedValueWithoutConfig, + ); + }); + }); + } + + describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is `'true'`", () => { + beforeEach(() => { + process.env.ESLINT_USE_FLAT_CONFIG = true; + }); + + afterEach(() => { + delete process.env.ESLINT_USE_FLAT_CONFIG; + }); + + testShouldUseFlatConfig(true, true); + }); + + describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is `'false'`", () => { + beforeEach(() => { + process.env.ESLINT_USE_FLAT_CONFIG = false; + }); + + afterEach(() => { + delete process.env.ESLINT_USE_FLAT_CONFIG; + }); + + testShouldUseFlatConfig(false, false); + }); + + describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is unset", () => { + testShouldUseFlatConfig(true, true); + }); + }); + + describe("cache", () => { + let eslint; + + /** + * helper method to delete a file without caring about exceptions + * @param {string} filePath The file path + * @returns {void} + */ + function doDelete(filePath) { + try { + fs.unlinkSync(filePath); + } catch { + /* + * we don't care if the file didn't exist, since our + * intention was to remove the file + */ + } + } + + let cacheFilePath; + + beforeEach(() => { + cacheFilePath = null; + }); + + afterEach(() => { + sinon.restore(); + if (cacheFilePath) { + doDelete(cacheFilePath); + } + }); + + describe("when cacheLocation is a directory or looks like a directory", () => { + const cwd = getFixturePath(); + + /** + * helper method to delete the directory used in testing + * @returns {void} + */ + function deleteCacheDir() { + try { + fs.rmSync(path.resolve(cwd, "tmp/.cacheFileDir/"), { + recursive: true, + force: true, + }); + } catch { + /* + * we don't care if the file didn't exist, since our + * intention was to remove the file + */ + } + } + beforeEach(() => { + deleteCacheDir(); + }); + + afterEach(() => { + deleteCacheDir(); + }); + + it("should create the directory and the cache file inside it when cacheLocation ends with a slash", async () => { + assert( + !shell.test( + "-d", + path.resolve(cwd, "./tmp/.cacheFileDir/"), + ), + "the cache directory already exists and wasn't successfully deleted", + ); + + eslint = new ESLint({ + overrideConfigFile: true, + cwd, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: "./tmp/.cacheFileDir/", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + ignore: false, + }); + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert( + shell.test( + "-f", + path.resolve( + cwd, + `./tmp/.cacheFileDir/.cache_${hash(cwd)}`, + ), + ), + "the cache for eslint should have been created", + ); + }); + + it("should create the cache file inside existing cacheLocation directory when cacheLocation ends with a slash", async () => { + assert( + !shell.test( + "-d", + path.resolve(cwd, "./tmp/.cacheFileDir/"), + ), + "the cache directory already exists and wasn't successfully deleted", + ); + + fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { + recursive: true, + }); + + eslint = new ESLint({ + overrideConfigFile: true, + cwd, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: "./tmp/.cacheFileDir/", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + ignore: false, + }); + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert( + shell.test( + "-f", + path.resolve( + cwd, + `./tmp/.cacheFileDir/.cache_${hash(cwd)}`, + ), + ), + "the cache for eslint should have been created", + ); + }); + + it("should create the cache file inside existing cacheLocation directory when cacheLocation doesn't end with a path separator", async () => { + assert( + !shell.test( + "-d", + path.resolve(cwd, "./tmp/.cacheFileDir/"), + ), + "the cache directory already exists and wasn't successfully deleted", + ); + + fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { + recursive: true, + }); + + eslint = new ESLint({ + overrideConfigFile: true, + cwd, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: "./tmp/.cacheFileDir", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + ignore: false, + }); + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert( + shell.test( + "-f", + path.resolve( + cwd, + `./tmp/.cacheFileDir/.cache_${hash(cwd)}`, + ), + ), + "the cache for eslint should have been created", + ); + }); + }); + + it("should create the cache file inside cwd when no cacheLocation provided", async () => { + const cwd = path.resolve(getFixturePath("cli-engine")); + + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new ESLint({ + overrideConfigFile: true, + cache: true, + cwd, + overrideConfig: { + rules: { + "no-console": 0, + }, + }, + ignore: false, + }); + const file = getFixturePath("cli-engine", "console.js"); + + await eslint.lintFiles([file]); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created at provided cwd", + ); + }); + + it("should invalidate the cache if the overrideConfig changed between executions", async () => { + const cwd = getFixturePath("cache/src"); + + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new ESLint({ + overrideConfigFile: true, + cwd, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + ignore: false, + }); + + let spy = sinon.spy(fs.promises, "readFile"); + + let file = path.join(cwd, "test-file.js"); + + file = fs.realpathSync(file); + const results = await eslint.lintFiles([file]); + + for (const { errorCount, warningCount } of results) { + assert.strictEqual( + errorCount + warningCount, + 0, + "the file should have passed linting without errors or warnings", + ); + } + + assert( + spy.calledWith(file), + "ESLint should have read the file because there was no cache file", + ); + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created", + ); + + // destroy the spy + sinon.restore(); + + eslint = new ESLint({ + overrideConfigFile: true, + cwd, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 2, + "no-unused-vars": 2, + }, + }, + ignore: false, + }); + + // create a new spy + spy = sinon.spy(fs.promises, "readFile"); + + const [newResult] = await eslint.lintFiles([file]); + + assert( + spy.calledWith(file), + "ESLint should have read the file again because it's considered changed because the config changed", + ); + assert.strictEqual( + newResult.errorCount, + 1, + "since configuration changed the cache should have not been used and one error should have been reported", + ); + assert.strictEqual(newResult.messages[0].ruleId, "no-console"); + assert( + shell.test("-f", cacheFilePath), + "The cache for ESLint should still exist", + ); + }); + + it("should remember the files from a previous run and do not operate on them if not changed", async () => { + const cwd = getFixturePath("cache/src"); + + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new ESLint({ + overrideConfigFile: true, + cwd, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + ignore: false, + }); + + let spy = sinon.spy(fs.promises, "readFile"); + + let file = getFixturePath("cache/src", "test-file.js"); + + file = fs.realpathSync(file); + + const result = await eslint.lintFiles([file]); + + assert( + spy.calledWith(file), + "ESLint should have read the file because there was no cache file", + ); + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created", + ); + + // destroy the spy + sinon.restore(); + + eslint = new ESLint({ + overrideConfigFile: true, + cwd, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + ignore: false, + }); + + // create a new spy + spy = sinon.spy(fs.promises, "readFile"); + + const cachedResult = await eslint.lintFiles([file]); + + assert.deepStrictEqual( + result, + cachedResult, + "the result should have been the same", + ); + + // assert the file was not processed because the cache was used + assert( + !spy.calledWith(file), + "the file should not have been reloaded", + ); + }); + + it("when `cacheLocation` is specified, should create the cache file with `cache:true` and then delete it with `cache:false`", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + const eslintOptions = { + overrideConfigFile: true, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + cwd: path.join(fixtureDir, ".."), + }; + + eslint = new ESLint(eslintOptions); + + let file = getFixturePath("cache/src", "test-file.js"); + + file = fs.realpathSync(file); + + await eslint.lintFiles([file]); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created", + ); + + eslintOptions.cache = false; + eslint = new ESLint(eslintOptions); + + await eslint.lintFiles([file]); + + assert( + !shell.test("-f", cacheFilePath), + "the cache for eslint should have been deleted since last run did not use the cache", + ); + }); + + it("should not attempt to delete the cache file if it does not exist", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + const spy = sinon.spy(fsp, "unlink"); + + const eslintOptions = { + overrideConfigFile: true, + cache: false, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + cwd: path.join(fixtureDir, ".."), + }; + + eslint = new ESLint(eslintOptions); + + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert( + spy.notCalled, + "Expected attempt to delete the cache was not made.", + ); + + spy.restore(); + }); + + it("should throw an error if the cache file to be deleted exist on a read-only file system", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + fs.writeFileSync(cacheFilePath, ""); + + // Simulate a read-only file system. + const unlinkStub = sinon.stub(fsp, "unlink").rejects( + Object.assign(new Error("read-only file system"), { + code: "EROFS", + }), + ); + + const eslintOptions = { + overrideConfigFile: true, + cache: false, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + cwd: path.join(fixtureDir, ".."), + }; + + eslint = new ESLint(eslintOptions); + + const file = getFixturePath("cache/src", "test-file.js"); + + await assert.rejects( + async () => await eslint.lintFiles([file]), + /read-only file system/u, + ); + + unlinkStub.restore(); + }); + + it("should not throw an error if deleting fails but cache file no longer exists", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + fs.writeFileSync(cacheFilePath, ""); + + const unlinkStub = sinon.stub(fsp, "unlink").callsFake(() => { + doDelete(cacheFilePath); + throw new Error("Failed to delete cache file"); + }); + + const eslintOptions = { + overrideConfigFile: true, + cache: false, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + cwd: path.join(fixtureDir, ".."), + }; + + eslint = new ESLint(eslintOptions); + + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert(unlinkStub.calledWithExactly(cacheFilePath)); + + unlinkStub.restore(); + }); + + it("should store in the cache a file that has lint messages and a file that doesn't have lint messages", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + }); + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + const result = await eslint.lintFiles([badFile, goodFile]); + const [badFileResult, goodFileResult] = result; + + assert.notStrictEqual( + badFileResult.errorCount + badFileResult.warningCount, + 0, + "the bad file should have some lint errors or warnings", + ); + assert.strictEqual( + goodFileResult.errorCount + badFileResult.warningCount, + 0, + "the good file should have passed linting without errors or warnings", + ); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created", + ); + + const fileCache = fCache.createFromFile(cacheFilePath); + const { cache } = fileCache; + + assert.strictEqual( + typeof cache.getKey(goodFile), + "object", + "the entry for the good file should have been in the cache", + ); + assert.strictEqual( + typeof cache.getKey(badFile), + "object", + "the entry for the bad file should have been in the cache", + ); + const cachedResult = await eslint.lintFiles([badFile, goodFile]); + + assert.deepStrictEqual( + result, + cachedResult, + "result should be the same with or without cache", + ); + }); + + it("should not contain in the cache a file that was deleted", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + }); + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + const toBeDeletedFile = fs.realpathSync( + getFixturePath("cache/src", "file-to-delete.js"), + ); + + await eslint.lintFiles([badFile, goodFile, toBeDeletedFile]); + const fileCache = fCache.createFromFile(cacheFilePath); + let { cache } = fileCache; + + assert.strictEqual( + typeof cache.getKey(toBeDeletedFile), + "object", + "the entry for the file to be deleted should have been in the cache", + ); + + // delete the file from the file system + fs.unlinkSync(toBeDeletedFile); + + /* + * file-entry-cache@2.0.0 will remove from the cache deleted files + * even when they were not part of the array of files to be analyzed + */ + await eslint.lintFiles([badFile, goodFile]); + + cache = JSON.parse(fs.readFileSync(cacheFilePath)); + + assert.strictEqual( + typeof cache[0][toBeDeletedFile], + "undefined", + "the entry for the file to be deleted should not have been in the cache", + ); + + // make sure that the previos assertion checks the right place + assert.notStrictEqual( + typeof cache[0][badFile], + "undefined", + "the entry for the bad file should have been in the cache", + ); + assert.notStrictEqual( + typeof cache[0][goodFile], + "undefined", + "the entry for the good file should have been in the cache", + ); + }); + + it("should contain files that were not visited in the cache provided they still exist", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + }); + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + const testFile2 = fs.realpathSync( + getFixturePath("cache/src", "test-file2.js"), + ); + + await eslint.lintFiles([badFile, goodFile, testFile2]); + + let fileCache = fCache.createFromFile(cacheFilePath); + let { cache } = fileCache; + + assert.strictEqual( + typeof cache.getKey(testFile2), + "object", + "the entry for the test-file2 should have been in the cache", + ); + + /* + * we pass a different set of files (minus test-file2) + * previous version of file-entry-cache would remove the non visited + * entries. 2.0.0 version will keep them unless they don't exist + */ + await eslint.lintFiles([badFile, goodFile]); + + fileCache = fCache.createFromFile(cacheFilePath); + cache = fileCache.cache; + + assert.strictEqual( + typeof cache.getKey(testFile2), + "object", + "the entry for the test-file2 should have been in the cache", + ); + }); + + it("should not delete cache when executing on text", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + }); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should exist", + ); + + await eslint.lintText("var foo = 'bar';"); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should still exist", + ); + }); + + it("should not delete cache when executing on text with a provided filename", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + }); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should exist", + ); + + await eslint.lintText("var bar = foo;", { + filePath: "fixtures/passing.js", + }); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should still exist", + ); + }); + + it("should not delete cache when executing on files with --cache flag", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + fs.writeFileSync(cacheFilePath, ""); + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + }); + const file = getFixturePath("cli-engine", "console.js"); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should exist", + ); + + await eslint.lintFiles([file]); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should still exist", + ); + }); + + it("should delete cache when executing on files without --cache flag", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + }); + const file = getFixturePath("cli-engine", "console.js"); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should exist", + ); + + await eslint.lintFiles([file]); + + assert( + !shell.test("-f", cacheFilePath), + "the cache for eslint should have been deleted", + ); + }); + + it("should use the specified cache file", async () => { + cacheFilePath = path.resolve(".cache/custom-cache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new ESLint({ + overrideConfigFile: true, + + // specify a custom cache file + cacheLocation: cacheFilePath, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + + cwd: path.join(fixtureDir, ".."), + }); + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + const result = await eslint.lintFiles([badFile, goodFile]); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created", + ); + + const fileCache = fCache.createFromFile(cacheFilePath); + const { cache } = fileCache; + + assert( + typeof cache.getKey(goodFile) === "object", + "the entry for the good file should have been in the cache", + ); + assert( + typeof cache.getKey(badFile) === "object", + "the entry for the bad file should have been in the cache", + ); + + const cachedResult = await eslint.lintFiles([badFile, goodFile]); + + assert.deepStrictEqual( + result, + cachedResult, + "result should be the same with or without cache", + ); + }); + + // https://github.com/eslint/eslint/issues/13507 + it("should not store `usedDeprecatedRules` in the cache file", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + const deprecatedRuleId = "space-in-parens"; + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + [deprecatedRuleId]: 2, + }, + }, + }); + + const filePath = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + + /* + * Run linting on the same file 3 times to cover multiple cases: + * Run 1: Lint result wasn't already cached. + * Run 2: Lint result was already cached. The cached lint result is used but the cache is reconciled before the run ends. + * Run 3: Lint result was already cached. The cached lint result was being used throughout the previous run, so possible + * mutations in the previous run that occured after the cache was reconciled may have side effects for this run. + */ + for (let i = 0; i < 3; i++) { + const [result] = await eslint.lintFiles([filePath]); + + assert( + result.usedDeprecatedRules && + result.usedDeprecatedRules.some( + rule => rule.ruleId === deprecatedRuleId, + ), + "the deprecated rule should have been in result.usedDeprecatedRules", + ); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created", + ); + + const fileCache = fCache.create(cacheFilePath); + const descriptor = fileCache.getFileDescriptor(filePath); + + assert( + typeof descriptor === "object", + "an entry for the file should have been in the cache file", + ); + assert( + typeof descriptor.meta.results === "object", + "lint result for the file should have been in its cache entry in the cache file", + ); + assert( + typeof descriptor.meta.results.usedDeprecatedRules === + "undefined", + "lint result in the cache file contains `usedDeprecatedRules`", + ); + } + }); + + // https://github.com/eslint/eslint/issues/13507 + it("should store `source` as `null` in the cache file if the lint result has `source` property", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-unused-vars": 2, + }, + }, + }); + + const filePath = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + + /* + * Run linting on the same file 3 times to cover multiple cases: + * Run 1: Lint result wasn't already cached. + * Run 2: Lint result was already cached. The cached lint result is used but the cache is reconciled before the run ends. + * Run 3: Lint result was already cached. The cached lint result was being used throughout the previous run, so possible + * mutations in the previous run that occured after the cache was reconciled may have side effects for this run. + */ + for (let i = 0; i < 3; i++) { + const [result] = await eslint.lintFiles([filePath]); + + assert( + typeof result.source === "string", + "the result should have contained the `source` property", + ); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created", + ); + + const fileCache = fCache.create(cacheFilePath); + const descriptor = fileCache.getFileDescriptor(filePath); + + assert( + typeof descriptor === "object", + "an entry for the file should have been in the cache file", + ); + assert( + typeof descriptor.meta.results === "object", + "lint result for the file should have been in its cache entry in the cache file", + ); + + // if the lint result contains `source`, it should be stored as `null` in the cache file + assert.strictEqual( + descriptor.meta.results.source, + null, + "lint result in the cache file contains non-null `source`", + ); + } + }); + + describe("cacheStrategy", () => { + it("should detect changes using a file's modification time when set to 'metadata'", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + cacheStrategy: "metadata", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + }); + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + + await eslint.lintFiles([badFile, goodFile]); + let fileCache = fCache.createFromFile(cacheFilePath); + const entries = fileCache.normalizeEntries([badFile, goodFile]); + + entries.forEach(entry => { + assert( + entry.changed === false, + `the entry for ${entry.key} should have been initially unchanged`, + ); + }); + + // this should result in a changed entry + shell.touch(goodFile); + fileCache = fCache.createFromFile(cacheFilePath); + assert( + fileCache.getFileDescriptor(badFile).changed === false, + `the entry for ${badFile} should have been unchanged`, + ); + assert( + fileCache.getFileDescriptor(goodFile).changed === true, + `the entry for ${goodFile} should have been changed`, + ); + }); + + it("should not detect changes using a file's modification time when set to 'content'", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + cacheStrategy: "content", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + }); + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + + await eslint.lintFiles([badFile, goodFile]); + let fileCache = fCache.createFromFile(cacheFilePath, true); + let entries = fileCache.normalizeEntries([badFile, goodFile]); + + entries.forEach(entry => { + assert( + entry.changed === false, + `the entry for ${entry.key} should have been initially unchanged`, + ); + }); + + // this should NOT result in a changed entry + shell.touch(goodFile); + fileCache = fCache.createFromFile(cacheFilePath, true); + entries = fileCache.normalizeEntries([badFile, goodFile]); + entries.forEach(entry => { + assert( + entry.changed === false, + `the entry for ${entry.key} should have remained unchanged`, + ); + }); + }); + + it("should detect changes using a file's contents when set to 'content'", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new ESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: true, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + cacheStrategy: "content", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + }); + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + const goodFileCopy = path.resolve( + `${path.dirname(goodFile)}`, + "test-file-copy.js", + ); + + shell.cp(goodFile, goodFileCopy); + + await eslint.lintFiles([badFile, goodFileCopy]); + let fileCache = fCache.createFromFile(cacheFilePath, true); + const entries = fileCache.normalizeEntries([ + badFile, + goodFileCopy, + ]); + + entries.forEach(entry => { + assert( + entry.changed === false, + `the entry for ${entry.key} should have been initially unchanged`, + ); + }); + + // this should result in a changed entry + shell.sed("-i", "abc", "xzy", goodFileCopy); + fileCache = fCache.createFromFile(cacheFilePath, true); + assert( + fileCache.getFileDescriptor(badFile).changed === false, + `the entry for ${badFile} should have been unchanged`, + ); + assert( + fileCache.getFileDescriptor(goodFileCopy).changed === true, + `the entry for ${goodFileCopy} should have been changed`, + ); + }); + }); + }); + + describe("v10_config_lookup_from_file", () => { + let eslint; + const flags = ["v10_config_lookup_from_file"]; + + it("should report zero messages when given a config file and a valid file", async () => { + /* + * This test ensures subdir/code.js is linted using the configuration in + * subdir/eslint.config.js and not from eslint.config.js in the parent + * directory. + */ + + eslint = new ESLint({ + flags, + cwd: getFixturePath("lookup-from-file"), + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 2); + assert.strictEqual( + results[0].filePath, + getFixturePath("lookup-from-file", "code.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-unused-vars"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].suppressedMessages.length, 0); + + assert.strictEqual( + results[1].filePath, + getFixturePath("lookup-from-file", "subdir", "code.js"), + ); + assert.strictEqual(results[1].messages.length, 1); + assert.strictEqual(results[1].messages[0].ruleId, "no-unused-vars"); + assert.strictEqual(results[1].messages[0].severity, 1); + assert.strictEqual(results[1].suppressedMessages.length, 0); + }); + + describe("Subdirectory Config File", () => { + const workDirName = "subdir-only-config"; + const tmpDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint"); + const workDir = path.join(tmpDir, workDirName); + + // copy into clean area so as not to get "infected" by other config files + before(() => { + shell.mkdir("-p", workDir); + shell.cp("-r", `./tests/fixtures/${workDirName}`, tmpDir); + }); + + after(() => { + shell.rm("-r", workDir); + }); + + it("should find config file when cwd doesn't have a config file", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir", "eslint.config.mjs"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + }); + + describe("Root config trying to ignore subdirectory pattern with config", () => { + const workDirName = "config-lookup-ignores"; + const tmpDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint"); + const workDir = path.join(tmpDir, workDirName); + + // copy into clean area so as not to get "infected" by other config files + before(() => { + shell.mkdir("-p", workDir); + shell.cp("-r", `./tests/fixtures/${workDirName}`, tmpDir); + }); + + after(() => { + shell.rm("-r", workDir); + }); + + it("should not traverse into subdir1 when parent config file specifies it as ignored and passing in .", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "eslint.config.cjs"), + ); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should not traverse into subdir1 when parent config file specifies it as ignored and passing in *", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles(["*"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "eslint.config.cjs"), + ); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should traverse into subdir1 when parent config file specifies it as ignored and passing in subdir1", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles(["subdir1"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir1", "eslint.config.mjs"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should traverse into subdir1 when parent config file specifies it as ignored and passing in subdir1/*.mjs", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles(["subdir1/*.mjs"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir1", "eslint.config.mjs"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should reject an error when parent config file specifies subdir1 as ignored and passing in sub*1/*.mjs", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + + return assert.rejects( + () => eslint.lintFiles(["sub*1/*.mjs"]), + /All files matched by 'sub\*1\/\*.mjs' are ignored\./u, + ); + }); + + it("should traverse into subdir1 when parent config file specifies it as ignored and passing in subdir1/eslint.config.mjs", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles([ + "subdir1/eslint.config.mjs", + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir1", "eslint.config.mjs"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should traverse into subdir1 when parent config file specifies it as ignored and passing in ../subdir1/eslint.config.mjs", async () => { + eslint = new ESLint({ + flags, + cwd: path.resolve(workDir, "subdir2"), + }); + const results = await eslint.lintFiles([ + "../subdir1/eslint.config.mjs", + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir1", "eslint.config.mjs"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should traverse into subdir1 when parent config file specifies it as ignored and passing in ../subdir1/*.mjs", async () => { + eslint = new ESLint({ + flags, + cwd: path.resolve(workDir, "subdir2"), + }); + const results = await eslint.lintFiles(["../subdir1/*.mjs"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir1", "eslint.config.mjs"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should traverse into subdir1 when parent config file specifies it as ignored and passing in ../subdir1", async () => { + eslint = new ESLint({ + flags, + cwd: path.resolve(workDir, "subdir2"), + }); + const results = await eslint.lintFiles(["../subdir1"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir1", "eslint.config.mjs"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should traverse into subdir3/subsubdir when parent config file specifies it as ignored and passing in subdir3/subsubdir", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles(["subdir3/subsubdir"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve( + workDir, + "subdir3", + "subsubdir", + "eslint.config.mjs", + ), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + }); + + describe("Root config trying to ignore specific subdirectory with config", () => { + const workDirName = "config-lookup-ignores-2"; + const tmpDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint"); + const workDir = path.join(tmpDir, workDirName); + + // copy into clean area so as not to get "infected" by other config files + before(() => { + shell.mkdir("-p", workDir); + shell.cp("-r", `./tests/fixtures/${workDirName}`, tmpDir); + }); + + after(() => { + shell.rm("-r", workDir); + }); + + it("should traverse into subdir1 and subdir2 but not subdir3 when parent config file specifies it as ignored and passing in .", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 3); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "eslint.config.cjs"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + results[1].filePath, + path.resolve(workDir, "subdir1/1.js"), + ); + assert.strictEqual(results[1].messages.length, 1); + assert.strictEqual( + results[1].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[1].messages[0].severity, 1); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual( + results[2].filePath, + path.resolve(workDir, "subdir2/2.js"), + ); + assert.strictEqual(results[2].messages.length, 1); + assert.strictEqual( + results[2].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[2].messages[0].severity, 1); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + + it("should not traverse into subdirectories when passing in *", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles(["*"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "eslint.config.cjs"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should traverse into subdir3 when parent config file specifies it as ignored and passing in subdir3", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles(["subdir3"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir3/3.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + results[1].filePath, + path.resolve(workDir, "subdir3/eslint.config.mjs"), + ); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + }); + + it("should traverse into subdir3 when parent config file specifies it as ignored and passing in subdir3/*.js", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles(["subdir3/*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir3/3.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should lint files in subdir3 and eslint.config.cjs when parent config file specifies subdir3 as ignored and passing in subdir3/*.js, **/*.cjs", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles([ + "subdir3/*.js", + "**/*.cjs", + ]); + + assert.strictEqual(results.length, 2); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "eslint.config.cjs"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + results[1].filePath, + path.resolve(workDir, "subdir3/3.js"), + ); + assert.strictEqual(results[1].messages.length, 1); + assert.strictEqual( + results[1].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[1].messages[0].severity, 2); + assert.strictEqual(results[1].suppressedMessages.length, 0); + }); + + it("should lint files in subdir3 and eslint.config.cjs when parent config file specifies subdir3 as ignored and passing in **/*.cjs, subdir3/*.js", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles([ + "**/*.cjs", + "subdir3/*.js", + ]); + + assert.strictEqual(results.length, 2); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "eslint.config.cjs"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + results[1].filePath, + path.resolve(workDir, "subdir3/3.js"), + ); + assert.strictEqual(results[1].messages.length, 1); + assert.strictEqual( + results[1].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[1].messages[0].severity, 2); + assert.strictEqual(results[1].suppressedMessages.length, 0); + }); + + it("should traverse into subdir1 and subdir2 but not subdir3 when parent config file specifies it as ignored and passing in sub*/*.js", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles(["sub*/*.js"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir1/1.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + results[1].filePath, + path.resolve(workDir, "subdir2/2.js"), + ); + assert.strictEqual(results[1].messages.length, 1); + assert.strictEqual( + results[1].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[1].messages[0].severity, 1); + assert.strictEqual(results[1].suppressedMessages.length, 0); + }); + + it("should reject an error when parent config file specifies subdir3 as ignored and passing in sub*3/*.mjs", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + + return assert.rejects( + () => eslint.lintFiles(["sub*3/*.mjs"]), + /All files matched by 'sub\*3\/\*\.mjs' are ignored\./u, + ); + }); + + it("should traverse into subdir1 and subdir2 but not subdir3 when parent config file specifies it as ignored and passing in sub*/*.js and **/*.cjs", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles([ + "sub*/*.js", + "**/*.cjs", + ]); + + assert.strictEqual(results.length, 3); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "eslint.config.cjs"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + results[1].filePath, + path.resolve(workDir, "subdir1/1.js"), + ); + assert.strictEqual(results[1].messages.length, 1); + assert.strictEqual( + results[1].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[1].messages[0].severity, 1); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual( + results[2].filePath, + path.resolve(workDir, "subdir2/2.js"), + ); + assert.strictEqual(results[2].messages.length, 1); + assert.strictEqual( + results[2].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[2].messages[0].severity, 1); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + + it("should traverse into subdir1 and subdir2 but not subdir3 when parent config file specifies it as ignored and passing in **/*.cjs and sub*/*.js", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles([ + "**/*.cjs", + "sub*/*.js", + ]); + + assert.strictEqual(results.length, 3); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "eslint.config.cjs"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + results[1].filePath, + path.resolve(workDir, "subdir1/1.js"), + ); + assert.strictEqual(results[1].messages.length, 1); + assert.strictEqual( + results[1].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[1].messages[0].severity, 1); + assert.strictEqual(results[1].suppressedMessages.length, 0); + assert.strictEqual( + results[2].filePath, + path.resolve(workDir, "subdir2/2.js"), + ); + assert.strictEqual(results[2].messages.length, 1); + assert.strictEqual( + results[2].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[2].messages[0].severity, 1); + assert.strictEqual(results[2].suppressedMessages.length, 0); + }); + + it("should traverse into subdir3 when parent config file specifies it as ignored and passing in subdir3/eslint.config.mjs", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + }); + const results = await eslint.lintFiles([ + "subdir3/eslint.config.mjs", + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir3", "eslint.config.mjs"), + ); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should traverse into subdir3 when parent config file specifies it as ignored and passing in ../subdir3/eslint.config.mjs", async () => { + eslint = new ESLint({ + flags, + cwd: path.resolve(workDir, "subdir2"), + }); + const results = await eslint.lintFiles([ + "../subdir3/eslint.config.mjs", + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir3", "eslint.config.mjs"), + ); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should traverse into subdir3 when parent config file specifies it as ignored and passing in ../subdir3/*.mjs", async () => { + eslint = new ESLint({ + flags, + cwd: path.resolve(workDir, "subdir2"), + }); + const results = await eslint.lintFiles(["../subdir3/*.mjs"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir3", "eslint.config.mjs"), + ); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].suppressedMessages.length, 0); + }); + + it("should traverse into subdir3 when parent config file specifies it as ignored and passing in ../subdir3", async () => { + eslint = new ESLint({ + flags, + cwd: path.resolve(workDir, "subdir2"), + }); + const results = await eslint.lintFiles(["../subdir3"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir3/3.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + results[1].filePath, + path.resolve(workDir, "subdir3/eslint.config.mjs"), + ); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + }); + }); + + describe("with `ignorePatterns`", () => { + const workDirName = "config-lookup-ignores-3"; + const tmpDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint"); + const workDir = path.join(tmpDir, workDirName); + + // copy into clean area so as not to get "infected" by other config files + before(() => { + shell.mkdir("-p", workDir); + shell.cp("-r", `./tests/fixtures/${workDirName}`, tmpDir); + }); + + after(() => { + shell.rm("-r", workDir); + }); + + // https://github.com/eslint/eslint/issues/18948 + it("should interpret `ignorePatterns` as relative to `cwd` when `cwd` is a parent directory.", async () => { + eslint = new ESLint({ + flags, + cwd: workDir, + ignorePatterns: ["subdir/b.js"], + }); + const results = await eslint.lintFiles(["subdir"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual( + results[0].filePath, + path.resolve(workDir, "subdir/a.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].suppressedMessages.length, 0); + assert.strictEqual( + results[1].filePath, + path.resolve(workDir, "subdir/eslint.config.mjs"), + ); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual(results[1].suppressedMessages.length, 0); + }); + }); + }); + + // A test copied from the `v10_config_lookup_from_file` tests to ensure the `unstable_config_lookup_from_file` flag still works + describe("unstable_config_lookup_from_file", () => { + let processStub; + + beforeEach(() => { + sinon.restore(); + processStub = sinon + .stub(process, "emitWarning") + .withArgs(sinon.match.any, sinon.match(/^ESLintInactiveFlag_/u)) + .returns(); + }); + + it("should report zero messages when given a config file and a valid file", async () => { + /* + * This test ensures subdir/code.js is linted using the configuration in + * subdir/eslint.config.js and not from eslint.config.js in the parent + * directory. + */ + + const eslint = new ESLint({ + flags: ["unstable_config_lookup_from_file"], + cwd: getFixturePath("lookup-from-file"), + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 2); + assert.strictEqual( + results[0].filePath, + getFixturePath("lookup-from-file", "code.js"), + ); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-unused-vars"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].suppressedMessages.length, 0); + + assert.strictEqual( + results[1].filePath, + getFixturePath("lookup-from-file", "subdir", "code.js"), + ); + assert.strictEqual(results[1].messages.length, 1); + assert.strictEqual(results[1].messages[0].ruleId, "no-unused-vars"); + assert.strictEqual(results[1].messages[0].severity, 1); + assert.strictEqual(results[1].suppressedMessages.length, 0); + + assert.strictEqual( + processStub.callCount, + 1, + "calls `process.emitWarning()` for flags once", + ); + assert.deepStrictEqual(processStub.getCall(0).args, [ + "The flag 'unstable_config_lookup_from_file' is inactive: This flag has been renamed 'v10_config_lookup_from_file' to reflect its stabilization. Please use 'v10_config_lookup_from_file' instead.", + "ESLintInactiveFlag_unstable_config_lookup_from_file", + ]); + }); + }); }); diff --git a/tests/lib/eslint/flat-eslint.js b/tests/lib/eslint/flat-eslint.js deleted file mode 100644 index d71da6936e94..000000000000 --- a/tests/lib/eslint/flat-eslint.js +++ /dev/null @@ -1,6399 +0,0 @@ -/** - * @fileoverview Tests for the ESLint class. - * @author Kai Cataldo - * @author Toru Nagashima - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("assert"); -const util = require("util"); -const fs = require("fs"); -const fsp = fs.promises; -const os = require("os"); -const path = require("path"); -const escapeStringRegExp = require("escape-string-regexp"); -const fCache = require("file-entry-cache"); -const sinon = require("sinon"); -const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); -const shell = require("shelljs"); -const hash = require("../../../lib/cli-engine/hash"); -const { unIndent, createCustomTeardown } = require("../../_utils"); -const { shouldUseFlatConfig } = require("../../../lib/eslint/flat-eslint"); -const coreRules = require("../../../lib/rules"); - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -/** - * Creates a directory if it doesn't already exist. - * @param {string} dirPath The path to the directory that should exist. - * @returns {void} - */ -function ensureDirectoryExists(dirPath) { - try { - fs.statSync(dirPath); - } catch { - fs.mkdirSync(dirPath); - } -} - -/** - * Does nothing for a given time. - * @param {number} time Time in ms. - * @returns {void} - */ -async function sleep(time) { - await util.promisify(setTimeout)(time); -} - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("FlatESLint", () => { - const examplePluginName = "eslint-plugin-example"; - const examplePluginNameWithNamespace = "@eslint/eslint-plugin-example"; - const examplePlugin = { - rules: { - "example-rule": require("../../fixtures/rules/custom-rule"), - "make-syntax-error": require("../../fixtures/rules/make-syntax-error-rule") - } - }; - const examplePreprocessorName = "eslint-plugin-processor"; - const originalDir = process.cwd(); - const fixtureDir = path.resolve(fs.realpathSync(os.tmpdir()), "eslint/fixtures"); - - /** @type {import("../../../lib/eslint/flat-eslint").FlatESLint} */ - let FlatESLint; - - /** - * Returns the path inside of the fixture directory. - * @param {...string} args file path segments. - * @returns {string} The path inside the fixture directory. - * @private - */ - function getFixturePath(...args) { - const filepath = path.join(fixtureDir, ...args); - - try { - return fs.realpathSync(filepath); - } catch { - return filepath; - } - } - - /** - * Create the ESLint object by mocking some of the plugins - * @param {Object} options options for ESLint - * @returns {ESLint} engine object - * @private - */ - function eslintWithPlugins(options) { - return new FlatESLint({ - ...options, - plugins: { - [examplePluginName]: examplePlugin, - [examplePluginNameWithNamespace]: examplePlugin, - [examplePreprocessorName]: require("../../fixtures/processors/custom-processor") - } - }); - } - - // copy into clean area so as not to get "infected" by this project's .eslintrc files - before(function() { - - /* - * GitHub Actions Windows and macOS runners occasionally exhibit - * extremely slow filesystem operations, during which copying fixtures - * exceeds the default test timeout, so raise it just for this hook. - * Mocha uses `this` to set timeouts on an individual hook level. - */ - this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API - shell.mkdir("-p", fixtureDir); - shell.cp("-r", "./tests/fixtures/.", fixtureDir); - }); - - beforeEach(() => { - ({ FlatESLint } = require("../../../lib/eslint/flat-eslint")); - }); - - after(() => { - shell.rm("-r", fixtureDir); - }); - - describe("ESLint constructor function", () => { - it("the default value of 'options.cwd' should be the current working directory.", async () => { - process.chdir(__dirname); - try { - const engine = new FlatESLint(); - const results = await engine.lintFiles("eslint.js"); - - assert.strictEqual(path.dirname(results[0].filePath), __dirname); - } finally { - process.chdir(originalDir); - } - }); - - it("should normalize 'options.cwd'.", async () => { - const cwd = getFixturePath("example-app3"); - const engine = new FlatESLint({ - cwd: `${cwd}${path.sep}foo${path.sep}..`, // `/foo/..` should be normalized to `` - overrideConfigFile: true, - overrideConfig: { - plugins: { - test: require(path.join(cwd, "node_modules", "eslint-plugin-test")) - }, - rules: { - "test/report-cwd": "error" - } - } - }); - const results = await engine.lintText(""); - - assert.strictEqual(results[0].messages[0].ruleId, "test/report-cwd"); - assert.strictEqual(results[0].messages[0].message, cwd); - - const formatter = await engine.loadFormatter("cwd"); - - assert.strictEqual(formatter.format(results), cwd); - }); - - // https://github.com/eslint/eslint/issues/2380 - it("should not modify baseConfig when format is specified", () => { - const customBaseConfig = { root: true }; - - new FlatESLint({ baseConfig: customBaseConfig }); // eslint-disable-line no-new -- Check for argument side effects - - assert.deepStrictEqual(customBaseConfig, { root: true }); - }); - - it("should throw readable messages if removed options are present", () => { - assert.throws( - () => new FlatESLint({ - cacheFile: "", - configFile: "", - envs: [], - globals: [], - ignorePath: ".gitignore", - ignorePattern: [], - parser: "", - parserOptions: {}, - rules: {}, - plugins: [], - reportUnusedDisableDirectives: "error" - }), - new RegExp(escapeStringRegExp([ - "Invalid Options:", - "- Unknown options: cacheFile, configFile, envs, globals, ignorePath, ignorePattern, parser, parserOptions, rules, reportUnusedDisableDirectives" - ].join("\n")), "u") - ); - }); - - it("should throw readable messages if wrong type values are given to options", () => { - assert.throws( - () => new FlatESLint({ - allowInlineConfig: "", - baseConfig: "", - cache: "", - cacheLocation: "", - cwd: "foo", - errorOnUnmatchedPattern: "", - fix: "", - fixTypes: ["xyz"], - globInputPaths: "", - ignore: "", - ignorePatterns: "", - overrideConfig: "", - overrideConfigFile: "", - plugins: "", - warnIgnored: "" - }), - new RegExp(escapeStringRegExp([ - "Invalid Options:", - "- 'allowInlineConfig' must be a boolean.", - "- 'baseConfig' must be an object or null.", - "- 'cache' must be a boolean.", - "- 'cacheLocation' must be a non-empty string.", - "- 'cwd' must be an absolute path.", - "- 'errorOnUnmatchedPattern' must be a boolean.", - "- 'fix' must be a boolean or a function.", - "- 'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".", - "- 'globInputPaths' must be a boolean.", - "- 'ignore' must be a boolean.", - "- 'ignorePatterns' must be an array of non-empty strings or null.", - "- 'overrideConfig' must be an object or null.", - "- 'overrideConfigFile' must be a non-empty string, null, or true.", - "- 'plugins' must be an object or null.", - "- 'warnIgnored' must be a boolean." - ].join("\n")), "u") - ); - }); - - it("should throw readable messages if 'ignorePatterns' is not an array of non-empty strings.", () => { - const invalidIgnorePatterns = [ - () => {}, - false, - {}, - "", - "foo", - [[]], - [() => {}], - [false], - [{}], - [""], - ["foo", ""], - ["foo", "", "bar"], - ["foo", false, "bar"] - ]; - - invalidIgnorePatterns.forEach(ignorePatterns => { - assert.throws( - () => new FlatESLint({ ignorePatterns }), - new RegExp(escapeStringRegExp([ - "Invalid Options:", - "- 'ignorePatterns' must be an array of non-empty strings or null." - ].join("\n")), "u") - ); - }); - }); - - it("should throw readable messages if 'plugins' option contains empty key", () => { - assert.throws( - () => new FlatESLint({ - plugins: { - "eslint-plugin-foo": {}, - "eslint-plugin-bar": {}, - "": {} - } - }), - new RegExp(escapeStringRegExp([ - "Invalid Options:", - "- 'plugins' must not include an empty string." - ].join("\n")), "u") - ); - }); - }); - - describe("lintText()", () => { - let eslint; - - it("should report the total and per file errors when using local cwd eslint.config.js", async () => { - eslint = new FlatESLint({ - cwd: __dirname - }); - - const results = await eslint.lintText("var foo = 'bar';"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 4); - assert.strictEqual(results[0].messages[0].ruleId, "no-var"); - assert.strictEqual(results[0].messages[1].ruleId, "no-unused-vars"); - assert.strictEqual(results[0].messages[2].ruleId, "quotes"); - assert.strictEqual(results[0].messages[3].ruleId, "eol-last"); - assert.strictEqual(results[0].fixableErrorCount, 3); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 2); - assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); - assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report the total and per file warnings when using local cwd .eslintrc", async () => { - eslint = new FlatESLint({ - overrideConfig: { - rules: { - quotes: 1, - "no-var": 1, - "eol-last": 1, - "no-unused-vars": 1 - } - }, - overrideConfigFile: true - }); - const results = await eslint.lintText("var foo = 'bar';"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 4); - assert.strictEqual(results[0].messages[0].ruleId, "no-var"); - assert.strictEqual(results[0].messages[1].ruleId, "no-unused-vars"); - assert.strictEqual(results[0].messages[2].ruleId, "quotes"); - assert.strictEqual(results[0].messages[3].ruleId, "eol-last"); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 3); - assert.strictEqual(results[0].usedDeprecatedRules.length, 2); - assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); - assert.strictEqual(results[0].usedDeprecatedRules[1].ruleId, "eol-last"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report one message when using specific config file", async () => { - eslint = new FlatESLint({ - overrideConfigFile: "fixtures/configurations/quotes-error.js", - cwd: getFixturePath("..") - }); - const results = await eslint.lintText("var foo = 'bar';"); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].output, void 0); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 1); - assert.strictEqual(results[0].usedDeprecatedRules[0].ruleId, "quotes"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report the filename when passed in", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "test.js" }; - const results = await eslint.lintText("var foo = 'bar';", options); - - assert.strictEqual(results[0].filePath, getFixturePath("test.js")); - }); - - it("should return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is true", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config_with_ignores.js" - }); - - const options = { filePath: "fixtures/passing.js", warnIgnored: true }; - const results = await eslint.lintText("var bar = foo;", options); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("passing.js")); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); - assert.strictEqual(results[0].messages[0].output, void 0); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return a warning when given a filename by --stdin-filename in excluded files list if constructor warnIgnored is false, but lintText warnIgnored is true", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config_with_ignores.js", - warnIgnored: false - }); - - const options = { filePath: "fixtures/passing.js", warnIgnored: true }; - const results = await eslint.lintText("var bar = foo;", options); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("passing.js")); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); - assert.strictEqual(results[0].messages[0].output, void 0); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].usedDeprecatedRules.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should not return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is false", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config_with_ignores.js" - }); - const options = { - filePath: "fixtures/passing.js", - warnIgnored: false - }; - - // intentional parsing error - const results = await eslint.lintText("va r bar = foo;", options); - - // should not report anything because the file is ignored - assert.strictEqual(results.length, 0); - }); - - it("should not return a warning when given a filename by --stdin-filename in excluded files list if constructor warnIgnored is false", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config_with_ignores.js", - warnIgnored: false - }); - const options = { filePath: "fixtures/passing.js" }; - const results = await eslint.lintText("var bar = foo;", options); - - // should not report anything because the warning is suppressed - assert.strictEqual(results.length, 0); - }); - - it("should show excluded file warnings by default", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/eslint.config_with_ignores.js" - }); - const options = { filePath: "fixtures/passing.js" }; - const results = await eslint.lintText("var bar = foo;", options); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); - }); - - it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - ignore: false, - overrideConfigFile: "fixtures/eslint.config_with_ignores.js", - overrideConfig: { - rules: { - "no-undef": 2 - } - } - }); - const options = { filePath: "fixtures/passing.js" }; - const results = await eslint.lintText("var bar = foo;", options); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("passing.js")); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[0].output, void 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return a message and fixed text when in fix mode", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - fix: true, - overrideConfig: { - rules: { - semi: 2 - } - }, - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "passing.js" }; - const results = await eslint.lintText("var bar = foo", options); - - assert.deepStrictEqual(results, [ - { - filePath: getFixturePath("passing.js"), - messages: [], - suppressedMessages: [], - errorCount: 0, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var bar = foo;", - usedDeprecatedRules: [ - { - ruleId: "semi", - replacedBy: [] - } - ] - } - ]); - }); - - it("should return a message and omit fixed text when in fix mode and fixes aren't done", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - fix: true, - overrideConfig: { - rules: { - "no-undef": 2 - } - }, - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "passing.js" }; - const results = await eslint.lintText("var bar = foo", options); - - assert.deepStrictEqual(results, [ - { - filePath: getFixturePath("passing.js"), - messages: [ - { - ruleId: "no-undef", - severity: 2, - messageId: "undef", - message: "'foo' is not defined.", - line: 1, - column: 11, - endLine: 1, - endColumn: 14, - nodeType: "Identifier" - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - source: "var bar = foo", - usedDeprecatedRules: [] - } - ]); - }); - - it("should not delete code if there is a syntax error after trying to autofix.", async () => { - eslint = eslintWithPlugins({ - overrideConfigFile: true, - fix: true, - overrideConfig: { - rules: { - "example/make-syntax-error": "error" - } - }, - ignore: false, - cwd: getFixturePath(".") - }); - const options = { filePath: "test.js" }; - const results = await eslint.lintText("var bar = foo", options); - - assert.deepStrictEqual(results, [ - { - filePath: getFixturePath("test.js"), - messages: [ - { - ruleId: null, - fatal: true, - severity: 2, - message: "Parsing error: Unexpected token is", - line: 1, - column: 19, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var bar = foothis is a syntax error.", - usedDeprecatedRules: [] - } - ]); - }); - - it("should not crash even if there are any syntax error since the first time.", async () => { - eslint = eslintWithPlugins({ - overrideConfigFile: true, - fix: true, - overrideConfig: { - rules: { - "example/make-syntax-error": "error" - } - }, - ignore: false, - cwd: getFixturePath() - }); - const options = { filePath: "test.js" }; - const results = await eslint.lintText("var bar =", options); - - assert.deepStrictEqual(results, [ - { - filePath: getFixturePath("test.js"), - messages: [ - { - ruleId: null, - fatal: true, - severity: 2, - message: "Parsing error: Unexpected token", - line: 1, - column: 10, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 0, - source: "var bar =", - usedDeprecatedRules: [] - } - ]); - }); - - it("should return source code of file in `source` property when errors are present", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { semi: 2 } - } - }); - const results = await eslint.lintText("var foo = 'bar'"); - - assert.strictEqual(results[0].source, "var foo = 'bar'"); - }); - - it("should return source code of file in `source` property when warnings are present", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { semi: 1 } - } - }); - const results = await eslint.lintText("var foo = 'bar'"); - - assert.strictEqual(results[0].source, "var foo = 'bar'"); - }); - - - it("should not return a `source` property when no errors or warnings are present", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { semi: 2 } - } - }); - const results = await eslint.lintText("var foo = 'bar';"); - - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].source, void 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should not return a `source` property when fixes are applied", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - fix: true, - overrideConfig: { - rules: { - semi: 2, - "no-unused-vars": 2 - } - } - }); - const results = await eslint.lintText("var msg = 'hi' + foo\n"); - - assert.strictEqual(results[0].source, void 0); - assert.strictEqual(results[0].output, "var msg = 'hi' + foo;\n"); - }); - - it("should return a `source` property when a parsing error has occurred", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { eqeqeq: 2 } - } - }); - const results = await eslint.lintText("var bar = foothis is a syntax error.\n return bar;"); - - assert.deepStrictEqual(results, [ - { - filePath: "", - messages: [ - { - ruleId: null, - fatal: true, - severity: 2, - message: "Parsing error: Unexpected token is", - line: 1, - column: 19, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 1, - fixableErrorCount: 0, - fixableWarningCount: 0, - source: "var bar = foothis is a syntax error.\n return bar;", - usedDeprecatedRules: [] - } - ]); - }); - - // https://github.com/eslint/eslint/issues/5547 - it("should respect default ignore rules (ignoring node_modules), even with --no-ignore", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(), - ignore: false - }); - const results = await eslint.lintText("var bar = foo;", { filePath: "node_modules/passing.js", warnIgnored: true }); - const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("node_modules/passing.js")); - assert.strictEqual(results[0].messages[0].message, expectedMsg); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should warn when deprecated rules are found in a config", async () => { - eslint = new FlatESLint({ - cwd: originalDir, - overrideConfigFile: "tests/fixtures/cli-engine/deprecated-rule-config/eslint.config.js" - }); - const [result] = await eslint.lintText("foo"); - - assert.deepStrictEqual( - result.usedDeprecatedRules, - [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] - ); - }); - - it("should throw if eslint.config.js file is not present", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("..") - }); - await assert.rejects(() => eslint.lintText("var foo = 'bar';"), /Could not find config file/u); - }); - - it("should not throw if eslint.config.js file is not present and overrideConfigFile is `true`", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: true - }); - await eslint.lintText("var foo = 'bar';"); - }); - - it("should not throw if eslint.config.js file is not present and overrideConfigFile is path to a config file", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/configurations/quotes-error.js" - }); - await eslint.lintText("var foo = 'bar';"); - }); - - it("should throw if overrideConfigFile is path to a file that doesn't exist", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(""), - overrideConfigFile: "does-not-exist.js" - }); - await assert.rejects(() => eslint.lintText("var foo = 'bar';"), { code: "ENOENT" }); - }); - - it("should throw if non-string value is given to 'code' parameter", async () => { - eslint = new FlatESLint(); - await assert.rejects(() => eslint.lintText(100), /'code' must be a string/u); - }); - - it("should throw if non-object value is given to 'options' parameter", async () => { - eslint = new FlatESLint(); - await assert.rejects(() => eslint.lintText("var a = 0", "foo.js"), /'options' must be an object, null, or undefined/u); - }); - - it("should throw if 'options' argument contains unknown key", async () => { - eslint = new FlatESLint(); - await assert.rejects(() => eslint.lintText("var a = 0", { filename: "foo.js" }), /'options' must not include the unknown option\(s\): filename/u); - }); - - it("should throw if non-string value is given to 'options.filePath' option", async () => { - eslint = new FlatESLint(); - await assert.rejects(() => eslint.lintText("var a = 0", { filePath: "" }), /'options.filePath' must be a non-empty string or undefined/u); - }); - - it("should throw if non-boolean value is given to 'options.warnIgnored' option", async () => { - eslint = new FlatESLint(); - await assert.rejects(() => eslint.lintText("var a = 0", { warnIgnored: "" }), /'options.warnIgnored' must be a boolean or undefined/u); - }); - - it("should work with config file that exports a promise", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("promise-config") - }); - const results = await eslint.lintText('var foo = "bar";'); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - }); - }); - - describe("lintFiles()", () => { - - /** @type {InstanceType} */ - let eslint; - - it("should use correct parser when custom parser is specified", async () => { - const filePath = path.resolve(__dirname, "../../fixtures/configurations/parser/custom.js"); - - eslint = new FlatESLint({ - cwd: originalDir, - ignore: false, - overrideConfigFile: true, - overrideConfig: { - languageOptions: { - parser: require(filePath) - } - } - }); - - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].message, "Parsing error: Boom!"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report zero messages when given a config file and a valid file", async () => { - eslint = new FlatESLint({ - cwd: originalDir, - overrideConfigFile: "tests/fixtures/simple-valid-project/eslint.config.js" - }); - const results = await eslint.lintFiles(["tests/fixtures/simple-valid-project/**/foo*.js"]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should handle multiple patterns with overlapping files", async () => { - eslint = new FlatESLint({ - cwd: originalDir, - overrideConfigFile: "tests/fixtures/simple-valid-project/eslint.config.js" - }); - const results = await eslint.lintFiles([ - "tests/fixtures/simple-valid-project/**/foo*.js", - "tests/fixtures/simple-valid-project/foo.?s", - "tests/fixtures/simple-valid-project/{foo,src/foobar}.js" - ]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report zero messages when given a config file and a valid file and espree as parser", async () => { - eslint = new FlatESLint({ - overrideConfig: { - languageOptions: { - parser: require("espree"), - parserOptions: { - ecmaVersion: 2021 - } - } - }, - overrideConfigFile: true - }); - const results = await eslint.lintFiles(["lib/cli.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report zero messages when given a config file and a valid file and esprima as parser", async () => { - eslint = new FlatESLint({ - overrideConfig: { - languageOptions: { - parser: require("esprima") - } - }, - overrideConfigFile: true, - ignore: false - }); - const results = await eslint.lintFiles(["tests/fixtures/passing.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should throw if eslint.config.js file is not present", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("..") - }); - await assert.rejects(() => eslint.lintFiles("fixtures/undef*.js"), /Could not find config file/u); - }); - - it("should not throw if eslint.config.js file is not present and overrideConfigFile is `true`", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: true - }); - await eslint.lintFiles("fixtures/undef*.js"); - }); - - it("should not throw if eslint.config.js file is not present and overrideConfigFile is path to a config file", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(".."), - overrideConfigFile: "fixtures/configurations/quotes-error.js" - }); - await eslint.lintFiles("fixtures/undef*.js"); - }); - - it("should throw if overrideConfigFile is path to a file that doesn't exist", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(), - overrideConfigFile: "does-not-exist.js" - }); - await assert.rejects(() => eslint.lintFiles("undef*.js"), { code: "ENOENT" }); - }); - - it("should throw an error when given a config file and a valid file and invalid parser", async () => { - eslint = new FlatESLint({ - overrideConfig: { - languageOptions: { - parser: "test11" - } - }, - overrideConfigFile: true - }); - - await assert.rejects(async () => await eslint.lintFiles(["lib/cli.js"]), /Expected object with parse\(\) or parseForESLint\(\) method/u); - }); - - it("should report zero messages when given a directory with a .js2 file", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("eslint.config.js"), - overrideConfig: { - files: ["**/*.js2"] - } - }); - const results = await eslint.lintFiles([getFixturePath("files/foo.js2")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report zero messages when given a directory with a .js and a .js2 file", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath(".."), - overrideConfig: { files: ["**/*.js", "**/*.js2"] }, - overrideConfigFile: getFixturePath("eslint.config.js") - }); - const results = await eslint.lintFiles(["fixtures/files/"]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - // https://github.com/eslint/eslint/issues/16413 - it("should find files and report zero messages when given a parent directory with a .js", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath("example-app/subdir") - }); - const results = await eslint.lintFiles(["../*.js"]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[1].suppressedMessages.length, 0); - }); - - // https://github.com/eslint/eslint/issues/16038 - it("should allow files patterns with '..' inside", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath("dots-in-files") - }); - const results = await eslint.lintFiles(["."]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].filePath, getFixturePath("dots-in-files/a..b.js")); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - - // https://github.com/eslint/eslint/issues/16299 - it("should only find files in the subdir1 directory when given a directory name", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath("example-app2") - }); - const results = await eslint.lintFiles(["subdir1"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].filePath, getFixturePath("example-app2/subdir1/a.js")); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - // https://github.com/eslint/eslint/issues/14742 - it("should run", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("{curly-path}", "server") - }); - const results = await eslint.lintFiles(["src/**/*.{js,json}"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "no-console"); - assert.strictEqual( - results[0].filePath, - getFixturePath("{curly-path}/server/src/two.js") - ); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should work with config file that exports a promise", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("promise-config") - }); - const results = await eslint.lintFiles(["a*.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("promise-config", "a.js")); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - }); - - // https://github.com/eslint/eslint/issues/16265 - describe("Dot files in searches", () => { - - it("should find dot files in current directory when a . pattern is used", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("dot-files") - }); - const results = await eslint.lintFiles(["."]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[1].filePath, getFixturePath("dot-files/.c.js")); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[2].filePath, getFixturePath("dot-files/b.js")); - assert.strictEqual(results[2].suppressedMessages.length, 0); - }); - - it("should find dot files in current directory when a *.js pattern is used", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("dot-files") - }); - const results = await eslint.lintFiles(["*.js"]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[1].filePath, getFixturePath("dot-files/.c.js")); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[2].filePath, getFixturePath("dot-files/b.js")); - assert.strictEqual(results[2].suppressedMessages.length, 0); - }); - - it("should find dot files in current directory when a .a.js pattern is used", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("dot-files") - }); - const results = await eslint.lintFiles([".a.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].filePath, getFixturePath("dot-files/.a.js")); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - }); - - // https://github.com/eslint/eslint/issues/16275 - describe("Glob patterns without matches", () => { - - it("should throw an error for a missing pattern when combined with a found pattern", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath("example-app2") - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir1", "doesnotexist/*.js"]); - }, /No files matching 'doesnotexist\/\*\.js' were found/u); - }); - - it("should throw an error for an ignored directory pattern when combined with a found pattern", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("example-app2"), - overrideConfig: { - ignores: ["subdir2"] - } - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); - }, /All files matched by 'subdir2\/\*\.js' are ignored/u); - }); - - it("should throw an error for an ignored file pattern when combined with a found pattern", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("example-app2"), - overrideConfig: { - ignores: ["subdir2/*.js"] - } - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); - }, /All files matched by 'subdir2\/\*\.js' are ignored/u); - }); - - it("should always throw an error for the first unmatched file pattern", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("example-app2"), - overrideConfig: { - ignores: ["subdir1/*.js", "subdir2/*.js"] - } - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["doesnotexist1/*.js", "doesnotexist2/*.js"]); - }, /No files matching 'doesnotexist1\/\*\.js' were found/u); - - await assert.rejects(async () => { - await eslint.lintFiles(["doesnotexist1/*.js", "subdir1/*.js"]); - }, /No files matching 'doesnotexist1\/\*\.js' were found/u); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir1/*.js", "doesnotexist1/*.js"]); - }, /All files matched by 'subdir1\/\*\.js' are ignored/u); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir1/*.js", "subdir2/*.js"]); - }, /All files matched by 'subdir1\/\*\.js' are ignored/u); - }); - - it("should not throw an error for an ignored file pattern when errorOnUnmatchedPattern is false", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("example-app2"), - overrideConfig: { - ignores: ["subdir2/*.js"] - }, - errorOnUnmatchedPattern: false - }); - - const results = await eslint.lintFiles(["subdir2/*.js"]); - - assert.strictEqual(results.length, 0); - }); - - it("should not throw an error for a non-existing file pattern when errorOnUnmatchedPattern is false", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("example-app2"), - errorOnUnmatchedPattern: false - }); - - const results = await eslint.lintFiles(["doesexist/*.js"]); - - assert.strictEqual(results.length, 0); - }); - }); - - // https://github.com/eslint/eslint/issues/16260 - describe("Globbing based on configs", () => { - it("should report zero messages when given a directory with a .js and config file specifying a subdirectory", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath("shallow-glob") - }); - const results = await eslint.lintFiles(["target-dir"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should glob for .jsx file in a subdirectory of the passed-in directory and not glob for any other patterns", async () => { - eslint = new FlatESLint({ - ignore: false, - overrideConfigFile: true, - overrideConfig: { - files: ["subdir/**/*.jsx", "target-dir/*.js"], - languageOptions: { - parserOptions: { - jsx: true - } - } - }, - cwd: getFixturePath("shallow-glob") - }); - const results = await eslint.lintFiles(["subdir/subsubdir"]); - - assert.strictEqual(results.length, 2); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("shallow-glob/subdir/subsubdir/broken.js")); - assert(results[0].messages[0].fatal, "Fatal error expected."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].filePath, getFixturePath("shallow-glob/subdir/subsubdir/plain.jsx")); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[1].suppressedMessages.length, 0); - }); - - it("should glob for all files in subdir when passed-in on the command line with a partial matching glob", async () => { - eslint = new FlatESLint({ - ignore: false, - overrideConfigFile: true, - overrideConfig: { - files: ["s*/subsubdir/*.jsx", "target-dir/*.js"], - languageOptions: { - parserOptions: { - jsx: true - } - } - }, - cwd: getFixturePath("shallow-glob") - }); - const results = await eslint.lintFiles(["subdir"]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 1); - assert(results[0].messages[0].fatal, "Fatal error expected."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].messages.length, 1); - assert(results[0].messages[0].fatal, "Fatal error expected."); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[2].suppressedMessages.length, 0); - }); - }); - - it("should report zero messages when given a '**' pattern with a .js and a .js2 file", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: path.join(fixtureDir, ".."), - overrideConfig: { files: ["**/*.js", "**/*.js2"] }, - overrideConfigFile: getFixturePath("eslint.config.js") - - }); - const results = await eslint.lintFiles(["fixtures/files/*"]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(results[2].suppressedMessages.length, 0); - }); - - it("should resolve globs when 'globInputPaths' option is true", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath(".."), - overrideConfig: { files: ["**/*.js", "**/*.js2"] }, - overrideConfigFile: getFixturePath("eslint.config.js") - - }); - const results = await eslint.lintFiles(["fixtures/files/*"]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(results[2].suppressedMessages.length, 0); - }); - - // only works on a Windows machine - if (os.platform() === "win32") { - - it("should resolve globs with Windows slashes when 'globInputPaths' option is true", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath(".."), - overrideConfig: { files: ["**/*.js", "**/*.js2"] }, - overrideConfigFile: getFixturePath("eslint.config.js") - - }); - const results = await eslint.lintFiles(["fixtures\\files\\*"]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(results[2].suppressedMessages.length, 0); - }); - - } - - - it("should not resolve globs when 'globInputPaths' option is false", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath(".."), - overrideConfig: { files: ["**/*.js", "**/*.js2"] }, - overrideConfigFile: true, - globInputPaths: false - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["fixtures/files/*"]); - }, /No files matching 'fixtures\/files\/\*' were found \(glob was disabled\)\./u); - }); - - describe("Ignoring Files", () => { - - it("should report on a file in the node_modules folder passed explicitly, even if ignored by default", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine") - }); - const results = await eslint.lintFiles(["node_modules/foo.js"]); - const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, expectedMsg); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report on a file in a node_modules subfolder passed explicitly, even if ignored by default", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine") - }); - const results = await eslint.lintFiles(["nested_node_modules/subdir/node_modules/text.js"]); - const expectedMsg = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, expectedMsg); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should report on an ignored file with \"node_modules\" in its name", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine"), - ignorePatterns: ["*.js"] - }); - const results = await eslint.lintFiles(["node_modules_cleaner.js"]); - const expectedMsg = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."; - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, expectedMsg); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should suppress the warning when a file in the node_modules folder passed explicitly and warnIgnored is false", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine"), - warnIgnored: false - }); - const results = await eslint.lintFiles(["node_modules/foo.js"]); - - assert.strictEqual(results.length, 0); - }); - - it("should report on globs with explicit inclusion of dotfiles", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine"), - overrideConfigFile: true, - overrideConfig: { - rules: { - quotes: [2, "single"] - } - } - }); - const results = await eslint.lintFiles(["hidden/.hiddenfolder/*.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); - }); - - it("should ignore node_modules files when using ignore file", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine"), - overrideConfigFile: true - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["node_modules"]); - }, /All files matched by 'node_modules' are ignored\./u); - }); - - // https://github.com/eslint/eslint/issues/5547 - it("should ignore node_modules files even with ignore: false", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine"), - ignore: false - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["node_modules"]); - }, /All files matched by 'node_modules' are ignored\./u); - }); - - it("should throw an error when all given files are ignored", async () => { - eslint = new FlatESLint({ - overrideConfigFile: getFixturePath("eslint.config_with_ignores.js") - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["tests/fixtures/cli-engine/"]); - }, /All files matched by 'tests\/fixtures\/cli-engine\/' are ignored\./u); - }); - - it("should throw an error when all given files are ignored even with a `./` prefix", async () => { - eslint = new FlatESLint({ - overrideConfigFile: getFixturePath("eslint.config_with_ignores.js") - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); - }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); - }); - - // https://github.com/eslint/eslint/issues/3788 - it("should ignore one-level down node_modules by default", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { - quotes: [2, "double"] - } - }, - cwd: getFixturePath("cli-engine", "nested_node_modules") - }); - const results = await eslint.lintFiles(["."]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - }); - - // https://github.com/eslint/eslint/issues/3812 - it("should ignore all files and throw an error when **/fixtures/** is in `ignores` in the config file", async () => { - eslint = new FlatESLint({ - overrideConfigFile: getFixturePath("cli-engine/eslint.config_with_ignores2.js"), - overrideConfig: { - rules: { - quotes: [2, "double"] - } - } - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); - }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); - }); - - it("should throw an error when all given files are ignored via ignorePatterns", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - ignorePatterns: ["tests/fixtures/single-quoted.js"] - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["tests/fixtures/*-quoted.js"]); - }, /All files matched by 'tests\/fixtures\/\*-quoted\.js' are ignored\./u); - }); - - it("should return a warning when an explicitly given file is ignored", async () => { - eslint = new FlatESLint({ - overrideConfigFile: "eslint.config_with_ignores.js", - cwd: getFixturePath() - }); - const filePath = getFixturePath("passing.js"); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should suppress the warning when an explicitly given file is ignored and warnIgnored is false", async () => { - eslint = new FlatESLint({ - overrideConfigFile: "eslint.config_with_ignores.js", - cwd: getFixturePath(), - warnIgnored: false - }); - const filePath = getFixturePath("passing.js"); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 0); - }); - - it("should return a warning about matching ignore patterns when an explicitly given dotfile is ignored", async () => { - eslint = new FlatESLint({ - overrideConfigFile: "eslint.config_with_ignores.js", - cwd: getFixturePath() - }); - const filePath = getFixturePath("dot-files/.a.js"); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages[0].severity, 1); - assert.strictEqual(results[0].messages[0].message, "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning."); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return two messages when given a file in excluded files list while ignore is off", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(), - ignore: false, - overrideConfigFile: getFixturePath("eslint.config_with_ignores.js"), - overrideConfig: { - rules: { - "no-undef": 2 - } - } - }); - const filePath = fs.realpathSync(getFixturePath("undef.js")); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].messages[1].ruleId, "no-undef"); - assert.strictEqual(results[0].messages[1].severity, 2); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - // https://github.com/eslint/eslint/issues/16300 - it("should process ignore patterns relative to basePath not cwd", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("ignores-relative/subdir") - }); - const results = await eslint.lintFiles(["**/*.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("ignores-relative/subdir/a.js")); - }); - - // https://github.com/eslint/eslint/issues/16354 - it("should skip subdirectory files when ignore pattern matches deep subdirectory", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("ignores-directory") - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir/**"]); - }, /All files matched by 'subdir\/\*\*' are ignored\./u); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir/subsubdir/**"]); - }, /All files matched by 'subdir\/subsubdir\/\*\*' are ignored\./u); - - const results = await eslint.lintFiles(["subdir/subsubdir/a.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("ignores-directory/subdir/subsubdir/a.js")); - assert.strictEqual(results[0].warningCount, 1); - assert(results[0].messages[0].message.startsWith("File ignored"), "Should contain file ignored warning"); - - }); - - // https://github.com/eslint/eslint/issues/16414 - it("should skip subdirectory files when ignore pattern matches subdirectory", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("ignores-subdirectory") - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["subdir/**/*.js"]); - }, /All files matched by 'subdir\/\*\*\/\*\.js' are ignored\./u); - - const results = await eslint.lintFiles(["subdir/subsubdir/a.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("ignores-subdirectory/subdir/subsubdir/a.js")); - assert.strictEqual(results[0].warningCount, 1); - assert(results[0].messages[0].message.startsWith("File ignored"), "Should contain file ignored warning"); - - eslint = new FlatESLint({ - cwd: getFixturePath("ignores-subdirectory/subdir") - }); - - await assert.rejects(async () => { - await eslint.lintFiles(["subsubdir/**/*.js"]); - }, /All files matched by 'subsubdir\/\*\*\/\*\.js' are ignored\./u); - - - }); - - // https://github.com/eslint/eslint/issues/16340 - it("should lint files even when cwd directory name matches ignores pattern", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("ignores-self") - }); - - const results = await eslint.lintFiles(["*.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, getFixturePath("ignores-self/eslint.config.js")); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 0); - - }); - - // https://github.com/eslint/eslint/issues/16416 - it("should allow reignoring of previously ignored files", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("ignores-relative"), - overrideConfigFile: true, - overrideConfig: { - ignores: [ - "*.js", - "!a*.js", - "a.js" - ] - } - }); - const results = await eslint.lintFiles(["a.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - assert.strictEqual(results[0].filePath, getFixturePath("ignores-relative/a.js")); - }); - - // https://github.com/eslint/eslint/issues/16415 - it("should allow directories to be unignored", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath("ignores-directory"), - overrideConfigFile: true, - overrideConfig: { - ignores: [ - "subdir/*", - "!subdir/subsubdir" - ] - } - }); - const results = await eslint.lintFiles(["subdir/**/*.js"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].filePath, getFixturePath("ignores-directory/subdir/subsubdir/a.js")); - }); - - - }); - - - it("should report zero messages when given a pattern with a .js and a .js2 file", async () => { - eslint = new FlatESLint({ - overrideConfig: { files: ["**/*.js", "**/*.js2"] }, - ignore: false, - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true - }); - const results = await eslint.lintFiles(["fixtures/files/*.?s*"]); - - assert.strictEqual(results.length, 3); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[2].suppressedMessages.length, 0); - }); - - it("should return one error message when given a config with rules with options and severity level set to error", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(), - overrideConfigFile: true, - overrideConfig: { - rules: { - quotes: ["error", "double"] - } - }, - ignore: false - }); - const results = await eslint.lintFiles([getFixturePath("single-quoted.js")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "quotes"); - assert.strictEqual(results[0].messages[0].severity, 2); - assert.strictEqual(results[0].errorCount, 1); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 1); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return 5 results when given a config and a directory of 5 valid files", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - overrideConfig: { - rules: { - semi: 1, - strict: 0 - } - } - }); - - const formattersDir = getFixturePath("formatters"); - const results = await eslint.lintFiles([formattersDir]); - - assert.strictEqual(results.length, 5); - assert.strictEqual(path.relative(formattersDir, results[0].filePath), "async.js"); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 0); - assert.strictEqual(results[0].fatalErrorCount, 0); - assert.strictEqual(results[0].fixableErrorCount, 0); - assert.strictEqual(results[0].fixableWarningCount, 0); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(path.relative(formattersDir, results[1].filePath), "broken.js"); - assert.strictEqual(results[1].errorCount, 0); - assert.strictEqual(results[1].warningCount, 0); - assert.strictEqual(results[1].fatalErrorCount, 0); - assert.strictEqual(results[1].fixableErrorCount, 0); - assert.strictEqual(results[1].fixableWarningCount, 0); - assert.strictEqual(results[1].messages.length, 0); - assert.strictEqual(results[1].suppressedMessages.length, 0); - assert.strictEqual(path.relative(formattersDir, results[2].filePath), "cwd.js"); - assert.strictEqual(results[2].errorCount, 0); - assert.strictEqual(results[2].warningCount, 0); - assert.strictEqual(results[2].fatalErrorCount, 0); - assert.strictEqual(results[2].fixableErrorCount, 0); - assert.strictEqual(results[2].fixableWarningCount, 0); - assert.strictEqual(results[2].messages.length, 0); - assert.strictEqual(results[2].suppressedMessages.length, 0); - assert.strictEqual(path.relative(formattersDir, results[3].filePath), "simple.js"); - assert.strictEqual(results[3].errorCount, 0); - assert.strictEqual(results[3].warningCount, 0); - assert.strictEqual(results[3].fatalErrorCount, 0); - assert.strictEqual(results[3].fixableErrorCount, 0); - assert.strictEqual(results[3].fixableWarningCount, 0); - assert.strictEqual(results[3].messages.length, 0); - assert.strictEqual(results[3].suppressedMessages.length, 0); - assert.strictEqual(path.relative(formattersDir, results[4].filePath), path.join("test", "simple.js")); - assert.strictEqual(results[4].errorCount, 0); - assert.strictEqual(results[4].warningCount, 0); - assert.strictEqual(results[4].fatalErrorCount, 0); - assert.strictEqual(results[4].fixableErrorCount, 0); - assert.strictEqual(results[4].fixableWarningCount, 0); - assert.strictEqual(results[4].messages.length, 0); - assert.strictEqual(results[4].suppressedMessages.length, 0); - }); - - it("should return zero messages when given a config with browser globals", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "env-browser.js") - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0, "Should have no messages."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return zero messages when given an option to add browser globals", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - overrideConfig: { - languageOptions: { - globals: { - window: false - } - }, - rules: { - "no-alert": 0, - "no-undef": 2 - } - } - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-browser.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return zero messages when given a config with sourceType set to commonjs and Node.js globals", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "env-node.js") - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("globals-node.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0, "Should have no messages."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should not return results from previous call when calling more than once", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: getFixturePath("eslint.config.js"), - ignore: false, - overrideConfig: { - rules: { - semi: 2 - } - } - }); - const failFilePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); - const passFilePath = fs.realpathSync(getFixturePath("passing.js")); - - let results = await eslint.lintFiles([failFilePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, failFilePath); - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[0].messages[0].severity, 2); - - results = await eslint.lintFiles([passFilePath]); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, passFilePath); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return zero messages when executing a file with a shebang", async () => { - eslint = new FlatESLint({ - ignore: false, - cwd: getFixturePath(), - overrideConfigFile: getFixturePath("eslint.config.js") - }); - const results = await eslint.lintFiles([getFixturePath("shebang.js")]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0, "Should have lint messages."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should return zero messages when executing without a config file", async () => { - eslint = new FlatESLint({ - cwd: getFixturePath(), - ignore: false, - overrideConfigFile: true - }); - const filePath = fs.realpathSync(getFixturePath("missing-semicolon.js")); - const results = await eslint.lintFiles([filePath]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, filePath); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - // working - describe("Deprecated Rules", () => { - - it("should warn when deprecated rules are configured", async () => { - eslint = new FlatESLint({ - cwd: originalDir, - overrideConfigFile: true, - overrideConfig: { - rules: { - "indent-legacy": 1, - "require-jsdoc": 1, - "valid-jsdoc": 1 - } - } - }); - const results = await eslint.lintFiles(["lib/cli*.js"]); - - assert.deepStrictEqual( - results[0].usedDeprecatedRules, - [ - { ruleId: "indent-legacy", replacedBy: ["indent"] }, - { ruleId: "require-jsdoc", replacedBy: [] }, - { ruleId: "valid-jsdoc", replacedBy: [] } - ] - ); - }); - - it("should not warn when deprecated rules are not configured", async () => { - eslint = new FlatESLint({ - cwd: originalDir, - overrideConfigFile: true, - overrideConfig: { - rules: { eqeqeq: 1, "valid-jsdoc": 0, "require-jsdoc": 0 } - } - }); - const results = await eslint.lintFiles(["lib/cli*.js"]); - - assert.deepStrictEqual(results[0].usedDeprecatedRules, []); - }); - - it("should warn when deprecated rules are found in a config", async () => { - eslint = new FlatESLint({ - cwd: originalDir, - overrideConfigFile: "tests/fixtures/cli-engine/deprecated-rule-config/eslint.config.js" - }); - const results = await eslint.lintFiles(["lib/cli*.js"]); - - assert.deepStrictEqual( - results[0].usedDeprecatedRules, - [{ ruleId: "indent-legacy", replacedBy: ["indent"] }] - ); - }); - }); - - // working - describe("Fix Mode", () => { - - it("correctly autofixes semicolon-conflicting-fixes", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: true - }); - const inputPath = getFixturePath("autofix/semicolon-conflicting-fixes.js"); - const outputPath = getFixturePath("autofix/semicolon-conflicting-fixes.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("correctly autofixes return-conflicting-fixes", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: true - }); - const inputPath = getFixturePath("autofix/return-conflicting-fixes.js"); - const outputPath = getFixturePath("autofix/return-conflicting-fixes.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should return fixed text on multiple files when in fix mode", async () => { - - /** - * Converts CRLF to LF in output. - * This is a workaround for git's autocrlf option on Windows. - * @param {Object} result A result object to convert. - * @returns {void} - */ - function convertCRLF(result) { - if (result && result.output) { - result.output = result.output.replace(/\r\n/gu, "\n"); - } - } - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: true, - overrideConfig: { - rules: { - semi: 2, - quotes: [2, "double"], - eqeqeq: 2, - "no-undef": 2, - "space-infix-ops": 2 - } - } - }); - const results = await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - - results.forEach(convertCRLF); - assert.deepStrictEqual(results, [ - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/multipass.js")), - messages: [], - suppressedMessages: [], - errorCount: 0, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "true ? \"yes\" : \"no\";\n", - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/ok.js")), - messages: [], - suppressedMessages: [], - errorCount: 0, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes-semi-eqeqeq.js")), - messages: [ - { - column: 9, - line: 2, - endColumn: 11, - endLine: 2, - message: "Expected '===' and instead saw '=='.", - messageId: "unexpected", - nodeType: "BinaryExpression", - ruleId: "eqeqeq", - severity: 2 - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n", - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - }, - { - filePath: fs.realpathSync(path.resolve(fixtureDir, "fixmode/quotes.js")), - messages: [ - { - column: 18, - line: 1, - endColumn: 21, - endLine: 1, - messageId: "undef", - message: "'foo' is not defined.", - nodeType: "Identifier", - ruleId: "no-undef", - severity: 2 - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - output: "var msg = \"hi\" + foo;\n", - usedDeprecatedRules: [ - { - replacedBy: [], - ruleId: "semi" - }, - { - replacedBy: [], - ruleId: "quotes" - }, - { - replacedBy: [], - ruleId: "space-infix-ops" - } - ] - } - ]); - }); - - // Cannot be run properly until cache is implemented - it("should run autofix even if files are cached without autofix results", async () => { - const baseOptions = { - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - overrideConfig: { - rules: { - semi: 2, - quotes: [2, "double"], - eqeqeq: 2, - "no-undef": 2, - "space-infix-ops": 2 - } - } - }; - - eslint = new FlatESLint(Object.assign({}, baseOptions, { cache: true, fix: false })); - - // Do initial lint run and populate the cache file - await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - - eslint = new FlatESLint(Object.assign({}, baseOptions, { cache: true, fix: true })); - const results = await eslint.lintFiles([path.resolve(fixtureDir, `${fixtureDir}/fixmode`)]); - - assert(results.some(result => result.output)); - }); - }); - - describe("plugins", () => { - it("should return two messages when executing with config file that specifies a plugin", async () => { - eslint = eslintWithPlugins({ - cwd: path.resolve(fixtureDir, ".."), - overrideConfigFile: getFixturePath("configurations", "plugins-with-prefix.js") - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test/test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2, "Expected two messages."); - assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - - it("should return two messages when executing with cli option that specifies a plugin", async () => { - eslint = eslintWithPlugins({ - cwd: path.resolve(fixtureDir, ".."), - overrideConfigFile: true, - overrideConfig: { - rules: { "example/example-rule": 1 } - } - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "example/example-rule"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - - it("should return two messages when executing with cli option that specifies preloaded plugin", async () => { - eslint = new FlatESLint({ - cwd: path.resolve(fixtureDir, ".."), - overrideConfigFile: true, - overrideConfig: { - rules: { "test/example-rule": 1 } - }, - plugins: { - "eslint-plugin-test": { rules: { "example-rule": require("../../fixtures/rules/custom-rule") } } - } - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("rules", "test", "test-custom-rule.js"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "test/example-rule"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - }); - - describe("cache", () => { - - /** - * helper method to delete a file without caring about exceptions - * @param {string} filePath The file path - * @returns {void} - */ - function doDelete(filePath) { - try { - fs.unlinkSync(filePath); - } catch { - - /* - * we don't care if the file didn't exist, since our - * intention was to remove the file - */ - } - } - - let cacheFilePath; - - beforeEach(() => { - cacheFilePath = null; - }); - - afterEach(() => { - sinon.restore(); - if (cacheFilePath) { - doDelete(cacheFilePath); - } - }); - - describe("when cacheLocation is a directory or looks like a directory", () => { - - const cwd = getFixturePath(); - - /** - * helper method to delete the directory used in testing - * @returns {void} - */ - function deleteCacheDir() { - try { - - /* - * `fs.rmdir(path, { recursive: true })` is deprecated and will be removed. - * Use `fs.rm(path, { recursive: true })` instead. - * When supporting Node.js 14.14.0+, the compatibility condition can be removed for `fs.rmdir`. - */ - // eslint-disable-next-line n/no-unsupported-features/node-builtins -- just checking if it exists - if (typeof fs.rm === "function") { - - // eslint-disable-next-line n/no-unsupported-features/node-builtins -- conditionally used - fs.rmSync(path.resolve(cwd, "tmp/.cacheFileDir/"), { recursive: true, force: true }); - } else { - fs.rmdirSync(path.resolve(cwd, "tmp/.cacheFileDir/"), { recursive: true, force: true }); - } - - } catch { - - /* - * we don't care if the file didn't exist, since our - * intention was to remove the file - */ - } - } - beforeEach(() => { - deleteCacheDir(); - }); - - afterEach(() => { - deleteCacheDir(); - }); - - it("should create the directory and the cache file inside it when cacheLocation ends with a slash", async () => { - assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cwd, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: "./tmp/.cacheFileDir/", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false - }); - const file = getFixturePath("cache/src", "test-file.js"); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); - }); - - it("should create the cache file inside existing cacheLocation directory when cacheLocation ends with a slash", async () => { - assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); - - fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { recursive: true }); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cwd, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: "./tmp/.cacheFileDir/", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false - }); - const file = getFixturePath("cache/src", "test-file.js"); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); - }); - - it("should create the cache file inside existing cacheLocation directory when cacheLocation doesn't end with a path separator", async () => { - assert(!shell.test("-d", path.resolve(cwd, "./tmp/.cacheFileDir/")), "the cache directory already exists and wasn't successfully deleted"); - - fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { recursive: true }); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cwd, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: "./tmp/.cacheFileDir", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false - }); - const file = getFixturePath("cache/src", "test-file.js"); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", path.resolve(cwd, `./tmp/.cacheFileDir/.cache_${hash(cwd)}`)), "the cache for eslint should have been created"); - }); - }); - - it("should create the cache file inside cwd when no cacheLocation provided", async () => { - const cwd = path.resolve(getFixturePath("cli-engine")); - - cacheFilePath = path.resolve(cwd, ".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cache: true, - cwd, - overrideConfig: { - rules: { - "no-console": 0 - } - }, - ignore: false - }); - const file = getFixturePath("cli-engine", "console.js"); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created at provided cwd"); - }); - - it("should invalidate the cache if the overrideConfig changed between executions", async () => { - const cwd = getFixturePath("cache/src"); - - cacheFilePath = path.resolve(cwd, ".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cwd, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false - }); - - let spy = sinon.spy(fs.promises, "readFile"); - - let file = path.join(cwd, "test-file.js"); - - file = fs.realpathSync(file); - const results = await eslint.lintFiles([file]); - - for (const { errorCount, warningCount } of results) { - assert.strictEqual(errorCount + warningCount, 0, "the file should have passed linting without errors or warnings"); - } - - assert(spy.calledWith(file), "ESLint should have read the file because there was no cache file"); - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - // destroy the spy - sinon.restore(); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cwd, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - ignore: false - }); - - // create a new spy - spy = sinon.spy(fs.promises, "readFile"); - - const [newResult] = await eslint.lintFiles([file]); - - assert(spy.calledWith(file), "ESLint should have read the file again because it's considered changed because the config changed"); - assert.strictEqual(newResult.errorCount, 1, "since configuration changed the cache should have not been used and one error should have been reported"); - assert.strictEqual(newResult.messages[0].ruleId, "no-console"); - assert(shell.test("-f", cacheFilePath), "The cache for ESLint should still exist"); - }); - - it("should remember the files from a previous run and do not operate on them if not changed", async () => { - const cwd = getFixturePath("cache/src"); - - cacheFilePath = path.resolve(cwd, ".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cwd, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false - }); - - let spy = sinon.spy(fs.promises, "readFile"); - - let file = getFixturePath("cache/src", "test-file.js"); - - file = fs.realpathSync(file); - - const result = await eslint.lintFiles([file]); - - assert(spy.calledWith(file), "ESLint should have read the file because there was no cache file"); - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - // destroy the spy - sinon.restore(); - - eslint = new FlatESLint({ - overrideConfigFile: true, - cwd, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - ignore: false - }); - - // create a new spy - spy = sinon.spy(fs.promises, "readFile"); - - const cachedResult = await eslint.lintFiles([file]); - - assert.deepStrictEqual(result, cachedResult, "the result should have been the same"); - - // assert the file was not processed because the cache was used - assert(!spy.calledWith(file), "the file should not have been reloaded"); - }); - - it("when `cacheLocation` is specified, should create the cache file with `cache:true` and then delete it with `cache:false`", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - const eslintOptions = { - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - cwd: path.join(fixtureDir, "..") - }; - - eslint = new FlatESLint(eslintOptions); - - let file = getFixturePath("cache/src", "test-file.js"); - - file = fs.realpathSync(file); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - eslintOptions.cache = false; - eslint = new FlatESLint(eslintOptions); - - await eslint.lintFiles([file]); - - assert(!shell.test("-f", cacheFilePath), "the cache for eslint should have been deleted since last run did not use the cache"); - }); - - it("should not throw an error if the cache file to be deleted does not exist on a read-only file system", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - // Simulate a read-only file system. - sinon.stub(fsp, "unlink").rejects( - Object.assign(new Error("read-only file system"), { code: "EROFS" }) - ); - - const eslintOptions = { - overrideConfigFile: true, - - // specifying cache false the cache will be deleted - cache: false, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - cwd: path.join(fixtureDir, "..") - }; - - eslint = new FlatESLint(eslintOptions); - - const file = getFixturePath("cache/src", "test-file.js"); - - await eslint.lintFiles([file]); - - assert(fsp.unlink.calledWithExactly(cacheFilePath), "Expected attempt to delete the cache was not made."); - }); - - it("should store in the cache a file that has lint messages and a file that doesn't have lint messages", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const result = await eslint.lintFiles([badFile, goodFile]); - const [badFileResult, goodFileResult] = result; - - assert.notStrictEqual(badFileResult.errorCount + badFileResult.warningCount, 0, "the bad file should have some lint errors or warnings"); - assert.strictEqual(goodFileResult.errorCount + badFileResult.warningCount, 0, "the good file should have passed linting without errors or warnings"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - const fileCache = fCache.createFromFile(cacheFilePath); - const { cache } = fileCache; - - assert.strictEqual(typeof cache.getKey(goodFile), "object", "the entry for the good file should have been in the cache"); - assert.strictEqual(typeof cache.getKey(badFile), "object", "the entry for the bad file should have been in the cache"); - const cachedResult = await eslint.lintFiles([badFile, goodFile]); - - assert.deepStrictEqual(result, cachedResult, "result should be the same with or without cache"); - }); - - it("should not contain in the cache a file that was deleted", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const toBeDeletedFile = fs.realpathSync(getFixturePath("cache/src", "file-to-delete.js")); - - await eslint.lintFiles([badFile, goodFile, toBeDeletedFile]); - const fileCache = fCache.createFromFile(cacheFilePath); - let { cache } = fileCache; - - assert.strictEqual(typeof cache.getKey(toBeDeletedFile), "object", "the entry for the file to be deleted should have been in the cache"); - - // delete the file from the file system - fs.unlinkSync(toBeDeletedFile); - - /* - * file-entry-cache@2.0.0 will remove from the cache deleted files - * even when they were not part of the array of files to be analyzed - */ - await eslint.lintFiles([badFile, goodFile]); - - cache = JSON.parse(fs.readFileSync(cacheFilePath)); - - assert.strictEqual(typeof cache[0][toBeDeletedFile], "undefined", "the entry for the file to be deleted should not have been in the cache"); - - // make sure that the previos assertion checks the right place - assert.notStrictEqual(typeof cache[0][badFile], "undefined", "the entry for the bad file should have been in the cache"); - assert.notStrictEqual(typeof cache[0][goodFile], "undefined", "the entry for the good file should have been in the cache"); - }); - - it("should contain files that were not visited in the cache provided they still exist", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const testFile2 = fs.realpathSync(getFixturePath("cache/src", "test-file2.js")); - - await eslint.lintFiles([badFile, goodFile, testFile2]); - - let fileCache = fCache.createFromFile(cacheFilePath); - let { cache } = fileCache; - - assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 should have been in the cache"); - - /* - * we pass a different set of files (minus test-file2) - * previous version of file-entry-cache would remove the non visited - * entries. 2.0.0 version will keep them unless they don't exist - */ - await eslint.lintFiles([badFile, goodFile]); - - fileCache = fCache.createFromFile(cacheFilePath); - cache = fileCache.cache; - - assert.strictEqual(typeof cache.getKey(testFile2), "object", "the entry for the test-file2 should have been in the cache"); - }); - - it("should not delete cache when executing on text", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - }); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); - - await eslint.lintText("var foo = 'bar';"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); - }); - - it("should not delete cache when executing on text with a provided filename", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - }); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); - - await eslint.lintText("var bar = foo;", { filePath: "fixtures/passing.js" }); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); - }); - - it("should not delete cache when executing on files with --cache flag", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - fs.writeFileSync(cacheFilePath, ""); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - }); - const file = getFixturePath("cli-engine", "console.js"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); - - await eslint.lintFiles([file]); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should still exist"); - }); - - it("should delete cache when executing on files without --cache flag", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - }); - const file = getFixturePath("cli-engine", "console.js"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should exist"); - - await eslint.lintFiles([file]); - - assert(!shell.test("-f", cacheFilePath), "the cache for eslint should have been deleted"); - }); - - it("should use the specified cache file", async () => { - cacheFilePath = path.resolve(".cache/custom-cache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - overrideConfigFile: true, - - // specify a custom cache file - cacheLocation: cacheFilePath, - - // specifying cache true the cache will be created - cache: true, - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - }, - - cwd: path.join(fixtureDir, "..") - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const result = await eslint.lintFiles([badFile, goodFile]); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - const fileCache = fCache.createFromFile(cacheFilePath); - const { cache } = fileCache; - - assert(typeof cache.getKey(goodFile) === "object", "the entry for the good file should have been in the cache"); - assert(typeof cache.getKey(badFile) === "object", "the entry for the bad file should have been in the cache"); - - const cachedResult = await eslint.lintFiles([badFile, goodFile]); - - assert.deepStrictEqual(result, cachedResult, "result should be the same with or without cache"); - }); - - // https://github.com/eslint/eslint/issues/13507 - it("should not store `usedDeprecatedRules` in the cache file", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - const deprecatedRuleId = "space-in-parens"; - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - [deprecatedRuleId]: 2 - } - } - }); - - const filePath = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - - /* - * Run linting on the same file 3 times to cover multiple cases: - * Run 1: Lint result wasn't already cached. - * Run 2: Lint result was already cached. The cached lint result is used but the cache is reconciled before the run ends. - * Run 3: Lint result was already cached. The cached lint result was being used throughout the previous run, so possible - * mutations in the previous run that occured after the cache was reconciled may have side effects for this run. - */ - for (let i = 0; i < 3; i++) { - const [result] = await eslint.lintFiles([filePath]); - - assert( - result.usedDeprecatedRules && result.usedDeprecatedRules.some(rule => rule.ruleId === deprecatedRuleId), - "the deprecated rule should have been in result.usedDeprecatedRules" - ); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - const fileCache = fCache.create(cacheFilePath); - const descriptor = fileCache.getFileDescriptor(filePath); - - assert(typeof descriptor === "object", "an entry for the file should have been in the cache file"); - assert(typeof descriptor.meta.results === "object", "lint result for the file should have been in its cache entry in the cache file"); - assert(typeof descriptor.meta.results.usedDeprecatedRules === "undefined", "lint result in the cache file contains `usedDeprecatedRules`"); - } - - }); - - // https://github.com/eslint/eslint/issues/13507 - it("should store `source` as `null` in the cache file if the lint result has `source` property", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - overrideConfig: { - rules: { - "no-unused-vars": 2 - } - } - }); - - const filePath = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - - /* - * Run linting on the same file 3 times to cover multiple cases: - * Run 1: Lint result wasn't already cached. - * Run 2: Lint result was already cached. The cached lint result is used but the cache is reconciled before the run ends. - * Run 3: Lint result was already cached. The cached lint result was being used throughout the previous run, so possible - * mutations in the previous run that occured after the cache was reconciled may have side effects for this run. - */ - for (let i = 0; i < 3; i++) { - const [result] = await eslint.lintFiles([filePath]); - - assert(typeof result.source === "string", "the result should have contained the `source` property"); - - assert(shell.test("-f", cacheFilePath), "the cache for eslint should have been created"); - - const fileCache = fCache.create(cacheFilePath); - const descriptor = fileCache.getFileDescriptor(filePath); - - assert(typeof descriptor === "object", "an entry for the file should have been in the cache file"); - assert(typeof descriptor.meta.results === "object", "lint result for the file should have been in its cache entry in the cache file"); - - // if the lint result contains `source`, it should be stored as `null` in the cache file - assert.strictEqual(descriptor.meta.results.source, null, "lint result in the cache file contains non-null `source`"); - } - - }); - - describe("cacheStrategy", () => { - it("should detect changes using a file's modification time when set to 'metadata'", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - cacheStrategy: "metadata", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - - await eslint.lintFiles([badFile, goodFile]); - let fileCache = fCache.createFromFile(cacheFilePath); - const entries = fileCache.normalizeEntries([badFile, goodFile]); - - entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); - }); - - // this should result in a changed entry - shell.touch(goodFile); - fileCache = fCache.createFromFile(cacheFilePath); - assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} should have been unchanged`); - assert(fileCache.getFileDescriptor(goodFile).changed === true, `the entry for ${goodFile} should have been changed`); - }); - - it("should not detect changes using a file's modification time when set to 'content'", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - cacheStrategy: "content", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - - await eslint.lintFiles([badFile, goodFile]); - let fileCache = fCache.createFromFile(cacheFilePath, true); - let entries = fileCache.normalizeEntries([badFile, goodFile]); - - entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); - }); - - // this should NOT result in a changed entry - shell.touch(goodFile); - fileCache = fCache.createFromFile(cacheFilePath, true); - entries = fileCache.normalizeEntries([badFile, goodFile]); - entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} should have remained unchanged`); - }); - }); - - it("should detect changes using a file's contents when set to 'content'", async () => { - cacheFilePath = getFixturePath(".eslintcache"); - doDelete(cacheFilePath); - assert(!shell.test("-f", cacheFilePath), "the cache file already exists and wasn't successfully deleted"); - - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - - // specifying cache true the cache will be created - cache: true, - cacheLocation: cacheFilePath, - cacheStrategy: "content", - overrideConfig: { - rules: { - "no-console": 0, - "no-unused-vars": 2 - } - } - - }); - const badFile = fs.realpathSync(getFixturePath("cache/src", "fail-file.js")); - const goodFile = fs.realpathSync(getFixturePath("cache/src", "test-file.js")); - const goodFileCopy = path.resolve(`${path.dirname(goodFile)}`, "test-file-copy.js"); - - shell.cp(goodFile, goodFileCopy); - - await eslint.lintFiles([badFile, goodFileCopy]); - let fileCache = fCache.createFromFile(cacheFilePath, true); - const entries = fileCache.normalizeEntries([badFile, goodFileCopy]); - - entries.forEach(entry => { - assert(entry.changed === false, `the entry for ${entry.key} should have been initially unchanged`); - }); - - // this should result in a changed entry - shell.sed("-i", "abc", "xzy", goodFileCopy); - fileCache = fCache.createFromFile(cacheFilePath, true); - assert(fileCache.getFileDescriptor(badFile).changed === false, `the entry for ${badFile} should have been unchanged`); - assert(fileCache.getFileDescriptor(goodFileCopy).changed === true, `the entry for ${goodFileCopy} should have been changed`); - }); - }); - }); - - describe("processors", () => { - - it("should return two messages when executing with config file that specifies preloaded processor", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: [ - { - plugins: { - test: { - processors: { - txt: { - preprocess(text) { - return [text]; - }, - postprocess(messages) { - return messages[0]; - } - } - } - } - }, - processor: "test/txt", - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - { - files: ["**/*.txt", "**/*.txt/*.txt"] - } - ], - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([fs.realpathSync(getFixturePath("processors", "test", "test-processor.txt"))]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - - it("should run processors when calling lintFiles with config file that specifies preloaded processor", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: [ - { - plugins: { - test: { - processors: { - txt: { - preprocess(text) { - return [text.replace("a()", "b()")]; - }, - postprocess(messages) { - messages[0][0].ruleId = "post-processed"; - return messages[0]; - } - } - } - } - }, - processor: "test/txt", - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - { - files: ["**/*.txt", "**/*.txt/*.txt"] - } - ], - cwd: path.join(fixtureDir, "..") - }); - const results = await eslint.lintFiles([getFixturePath("processors", "test", "test-processor.txt")]); - - assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); - assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - - it("should run processors when calling lintText with config file that specifies preloaded processor", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: [ - { - plugins: { - test: { - processors: { - txt: { - preprocess(text) { - return [text.replace("a()", "b()")]; - }, - postprocess(messages) { - messages[0][0].ruleId = "post-processed"; - return messages[0]; - } - } - } - } - }, - processor: "test/txt", - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - { - files: ["**/*.txt", "**/*.txt/*.txt"] - } - ], - ignore: false - }); - const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); - - assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); - assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should run processors when calling lintText with processor resolves same extension but different content correctly", async () => { - let count = 0; - - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: [ - { - plugins: { - test: { - processors: { - txt: { - preprocess(text) { - count++; - return [ - { - - // it will be run twice, and text will be as-is at the second time, then it will not run third time - text: text.replace("a()", "b()"), - filename: ".txt" - } - ]; - }, - postprocess(messages) { - messages[0][0].ruleId = "post-processed"; - return messages[0]; - } - } - } - } - }, - processor: "test/txt" - }, - { - files: ["**/*.txt/*.txt"], - rules: { - "no-console": 2, - "no-unused-vars": 2 - } - }, - { - files: ["**/*.txt"] - } - ], - ignore: false - }); - const results = await eslint.lintText("function a() {console.log(\"Test\");}", { filePath: "tests/fixtures/processors/test/test-processor.txt" }); - - assert.strictEqual(count, 2); - assert.strictEqual(results[0].messages[0].message, "'b' is defined but never used."); - assert.strictEqual(results[0].messages[0].ruleId, "post-processed"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - - describe("autofixing with processors", () => { - const HTML_PROCESSOR = Object.freeze({ - preprocess(text) { - return [text.replace(/^", { filePath: "foo.html" }); - - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[0].output, ""); - }); - - it("should not run in autofix mode when using a processor that does not support autofixing", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - files: ["**/*.html"], - plugins: { - test: { processors: { html: HTML_PROCESSOR } } - }, - processor: "test/html", - rules: { - semi: 2 - } - }, - ignore: false, - fix: true - }); - const results = await eslint.lintText("", { filePath: "foo.html" }); - - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert(!Object.prototype.hasOwnProperty.call(results[0], "output")); - }); - - it("should not run in autofix mode when `fix: true` is not provided, even if the processor supports autofixing", async () => { - eslint = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: [ - { - files: ["**/*.html"], - plugins: { - test: { processors: { html: Object.assign({ supportsAutofix: true }, HTML_PROCESSOR) } } - }, - processor: "test/html", - rules: { - semi: 2 - } - }, - { - files: ["**/*.txt"] - } - ], - ignore: false - }); - const results = await eslint.lintText("", { filePath: "foo.html" }); - - assert.strictEqual(results[0].messages.length, 1); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert(!Object.prototype.hasOwnProperty.call(results[0], "output")); - }); - }); - }); - - describe("Patterns which match no file should throw errors.", () => { - beforeEach(() => { - eslint = new FlatESLint({ - cwd: getFixturePath("cli-engine"), - overrideConfigFile: true - }); - }); - - it("one file", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["non-exist.js"]); - }, /No files matching 'non-exist\.js' were found\./u); - }); - - it("should throw if the directory exists and is empty", async () => { - ensureDirectoryExists(getFixturePath("cli-engine/empty")); - await assert.rejects(async () => { - await eslint.lintFiles(["empty"]); - }, /No files matching 'empty' were found\./u); - }); - - it("one glob pattern", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["non-exist/**/*.js"]); - }, /No files matching 'non-exist\/\*\*\/\*\.js' were found\./u); - }); - - it("two files", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["aaa.js", "bbb.js"]); - }, /No files matching 'aaa\.js' were found\./u); - }); - - it("a mix of an existing file and a non-existing file", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["console.js", "non-exist.js"]); - }, /No files matching 'non-exist\.js' were found\./u); - }); - - // https://github.com/eslint/eslint/issues/16275 - it("a mix of an existing glob pattern and a non-existing glob pattern", async () => { - await assert.rejects(async () => { - await eslint.lintFiles(["*.js", "non-exist/*.js"]); - }, /No files matching 'non-exist\/\*\.js' were found\./u); - }); - }); - - describe("multiple processors", () => { - const root = path.join(os.tmpdir(), "eslint/eslint/multiple-processors"); - const commonFiles = { - "node_modules/pattern-processor/index.js": fs.readFileSync( - require.resolve("../../fixtures/processors/pattern-processor"), - "utf8" - ), - "node_modules/eslint-plugin-markdown/index.js": ` - const { defineProcessor } = require("pattern-processor"); - const processor = defineProcessor(${/```(\w+)\n([\s\S]+?)\n```/gu}); - exports.processors = { - "markdown": { ...processor, supportsAutofix: true }, - "non-fixable": processor - }; - `, - "node_modules/eslint-plugin-html/index.js": ` - const { defineProcessor } = require("pattern-processor"); - const processor = defineProcessor(${/ - - \`\`\` - ` - }; - - // unique directory for each test to avoid quirky disk-cleanup errors - let id; - - beforeEach(() => (id = Date.now().toString())); - - /* - * `fs.rmdir(path, { recursive: true })` is deprecated and will be removed. - * Use `fs.rm(path, { recursive: true })` instead. - * When supporting Node.js 14.14.0+, the compatibility condition can be removed for `fs.rmdir`. - */ - if (typeof fsp.rm === "function") { - afterEach(async () => fsp.rm(root, { recursive: true, force: true })); - } else { - afterEach(async () => fsp.rmdir(root, { recursive: true, force: true })); - } - - it("should lint only JavaScript blocks.", async () => { - const teardown = createCustomTeardown({ - cwd: path.join(root, id), - files: { - ...commonFiles, - "eslint.config.js": `module.exports = [ - { - plugins: { - markdown: require("eslint-plugin-markdown"), - html: require("eslint-plugin-html") - } - }, - { - files: ["**/*.js"], - rules: { semi: "error" } - }, - { - files: ["**/*.md"], - processor: "markdown/markdown" - } - ];` - } - }); - - await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath() }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1, "Should have one result."); - assert.strictEqual(results[0].messages.length, 1, "Should have one message."); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].messages[0].line, 2, "Message should be on line 2."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - - it("should lint HTML blocks as well with multiple processors if represented in config.", async () => { - const teardown = createCustomTeardown({ - cwd: path.join(root, id), - files: { - ...commonFiles, - "eslint.config.js": `module.exports = [ - { - plugins: { - markdown: require("eslint-plugin-markdown"), - html: require("eslint-plugin-html") - } - }, - { - files: ["**/*.js"], - rules: { semi: "error" } - }, - { - files: ["**/*.md"], - processor: "markdown/markdown" - }, - { - files: ["**/*.html"], - processor: "html/html" - } - ];` - } - }); - - await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] } }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1, "Should have one result."); - assert.strictEqual(results[0].messages.length, 2, "Should have two messages."); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block - assert.strictEqual(results[0].messages[0].line, 2, "First error should be on line 2"); - assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block - assert.strictEqual(results[0].messages[1].line, 7, "Second error should be on line 7."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should fix HTML blocks as well with multiple processors if represented in config.", async () => { - const teardown = createCustomTeardown({ - cwd: path.join(root, id), - files: { - ...commonFiles, - "eslint.config.js": `module.exports = [ - { - plugins: { - markdown: require("eslint-plugin-markdown"), - html: require("eslint-plugin-html") - } - }, - { - files: ["**/*.js"], - rules: { semi: "error" } - }, - { - files: ["**/*.md"], - processor: "markdown/markdown" - }, - { - files: ["**/*.html"], - processor: "html/html" - } - ];` - } - }); - - await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] }, fix: true }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 0); - assert.strictEqual(results[0].output, unIndent` - \`\`\`js - console.log("hello");${/* ← fixed */""} - \`\`\` - \`\`\`html -
Hello
- - - \`\`\` - `); - }); - - it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", async () => { - const teardown = createCustomTeardown({ - cwd: path.join(root, id), - files: { - ...commonFiles, - "eslint.config.js": `module.exports = [ - { - plugins: { - markdown: require("eslint-plugin-markdown"), - html: require("eslint-plugin-html") - } - }, - { - files: ["**/*.js"], - rules: { semi: "error" } - }, - { - files: ["**/*.md"], - processor: "markdown/markdown" - }, - { - files: ["**/*.html"], - processor: "html/html" - }, - { - files: ["**/*.html/*.js"], - rules: { - semi: "off", - "no-console": "error" - } - } - - ];` - - } - }); - - await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] } }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 2); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].messages[0].line, 2); - assert.strictEqual(results[0].messages[1].ruleId, "no-console"); - assert.strictEqual(results[0].messages[1].line, 7); - assert.strictEqual(results[0].suppressedMessages.length, 0); - - }); - - it("should use the same config as one which has 'processor' property in order to lint blocks in HTML if the processor was legacy style.", async () => { - const teardown = createCustomTeardown({ - cwd: path.join(root, id), - files: { - ...commonFiles, - "eslint.config.js": `module.exports = [ - { - plugins: { - markdown: require("eslint-plugin-markdown"), - html: require("eslint-plugin-html") - }, - rules: { semi: "error" } - }, - { - files: ["**/*.md"], - processor: "markdown/markdown" - }, - { - files: ["**/*.html"], - processor: "html/legacy", // this processor returns strings rather than '{ text, filename }' - rules: { - semi: "off", - "no-console": "error" - } - }, - { - files: ["**/*.html/*.js"], - rules: { - semi: "error", - "no-console": "off" - } - } - - ];` - } - }); - - await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath(), overrideConfig: { files: ["**/*.html"] } }); - const results = await eslint.lintFiles(["test.md"]); - - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].messages.length, 3); - assert.strictEqual(results[0].messages[0].ruleId, "semi"); - assert.strictEqual(results[0].messages[0].line, 2); - assert.strictEqual(results[0].messages[1].ruleId, "no-console"); - assert.strictEqual(results[0].messages[1].line, 7); - assert.strictEqual(results[0].messages[2].ruleId, "no-console"); - assert.strictEqual(results[0].messages[2].line, 10); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should throw an error if invalid processor was specified.", async () => { - const teardown = createCustomTeardown({ - cwd: path.join(root, id), - files: { - ...commonFiles, - "eslint.config.js": `module.exports = [ - { - plugins: { - markdown: require("eslint-plugin-markdown"), - html: require("eslint-plugin-html") - } - }, - { - files: ["**/*.md"], - processor: "markdown/unknown" - } - - ];` - } - }); - - await teardown.prepare(); - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - await assert.rejects(async () => { - await eslint.lintFiles(["test.md"]); - }, /Key "processor": Could not find "unknown" in plugin "markdown"/u); - }); - - }); - - describe("glob pattern '[ab].js'", () => { - const root = getFixturePath("cli-engine/unmatched-glob"); - - let cleanup; - - beforeEach(() => { - cleanup = () => { }; - }); - - afterEach(() => cleanup()); - - it("should match '[ab].js' if existed.", async () => { - - const teardown = createCustomTeardown({ - cwd: root, - files: { - "a.js": "", - "b.js": "", - "ab.js": "", - "[ab].js": "", - "eslint.config.js": "module.exports = [];" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - eslint = new FlatESLint({ cwd: teardown.getPath() }); - const results = await eslint.lintFiles(["[ab].js"]); - const filenames = results.map(r => path.basename(r.filePath)); - - assert.deepStrictEqual(filenames, ["[ab].js"]); - }); - - it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "a.js": "", - "b.js": "", - "ab.js": "", - "eslint.config.js": "module.exports = [];" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - const results = await eslint.lintFiles(["[ab].js"]); - const filenames = results.map(r => path.basename(r.filePath)); - - assert.deepStrictEqual(filenames, ["a.js", "b.js"]); - }); - }); - - describe("with 'noInlineConfig' setting", () => { - const root = getFixturePath("cli-engine/noInlineConfig"); - - let cleanup; - - beforeEach(() => { - cleanup = () => { }; - }); - - afterEach(() => cleanup()); - - it("should warn directive comments if 'noInlineConfig' was given.", async () => { - const teardown = createCustomTeardown({ - cwd: root, - files: { - "test.js": "/* globals foo */", - "eslint.config.js": "module.exports = [{ linterOptions: { noInlineConfig: true } }];" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "'/* globals foo */' has no effect because you have 'noInlineConfig' setting in your config."); - }); - - }); - - describe("with 'reportUnusedDisableDirectives' setting", () => { - const root = getFixturePath("cli-engine/reportUnusedDisableDirectives"); - - let cleanup; - let i = 0; - - beforeEach(() => { - cleanup = () => { }; - i++; - }); - - afterEach(() => cleanup()); - - it("should error unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = error'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 'error' } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should error unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = 2'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 2 } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = warn'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 'warn' } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = 1'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 1 } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = true'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: true } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = false'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: false } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 0); - }); - - it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = off'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 'off' } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 0); - }); - - it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives = 0'.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = { linterOptions: { reportUnusedDisableDirectives: 0 } }" - } - }); - - - await teardown.prepare(); - cleanup = teardown.cleanup; - eslint = new FlatESLint({ cwd: teardown.getPath() }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 0); - }); - - describe("the runtime option overrides config files.", () => { - it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = [{ linterOptions: { reportUnusedDisableDirectives: true } }]" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - eslint = new FlatESLint({ - cwd: teardown.getPath(), - overrideConfig: { - linterOptions: { reportUnusedDisableDirectives: "off" } - } - }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 0); - }); - - it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => { - const teardown = createCustomTeardown({ - cwd: `${root}${i}`, - files: { - "test.js": "/* eslint-disable eqeqeq */", - "eslint.config.js": "module.exports = [{ linterOptions: { reportUnusedDisableDirectives: true } }]" - } - }); - - await teardown.prepare(); - cleanup = teardown.cleanup; - - eslint = new FlatESLint({ - cwd: teardown.getPath(), - overrideConfig: { - linterOptions: { reportUnusedDisableDirectives: "error" } - } - }); - - const results = await eslint.lintFiles(["test.js"]); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, "Unused eslint-disable directive (no problems were reported from 'eqeqeq')."); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - }); - }); - - it("should throw if non-boolean value is given to 'options.warnIgnored' option", async () => { - eslint = new FlatESLint(); - await assert.rejects(() => eslint.lintFiles(777), /'patterns' must be a non-empty string or an array of non-empty strings/u); - await assert.rejects(() => eslint.lintFiles([null]), /'patterns' must be a non-empty string or an array of non-empty strings/u); - }); - }); - - describe("Fix Types", () => { - - let eslint; - - it("should throw an error when an invalid fix type is specified", () => { - assert.throws(() => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: true, - fixTypes: ["layou"] - }); - }, /'fixTypes' must be an array of any of "directive", "problem", "suggestion", and "layout"\./iu); - }); - - it("should not fix any rules when fixTypes is used without fix", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: false, - fixTypes: ["layout"] - }); - const inputPath = getFixturePath("fix-types/fix-only-semi.js"); - const results = await eslint.lintFiles([inputPath]); - - assert.strictEqual(results[0].output, void 0); - }); - - it("should not fix non-style rules when fixTypes has only 'layout'", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: true, - fixTypes: ["layout"] - }); - const inputPath = getFixturePath("fix-types/fix-only-semi.js"); - const outputPath = getFixturePath("fix-types/fix-only-semi.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should not fix style or problem rules when fixTypes has only 'suggestion'", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: true, - fixTypes: ["suggestion"] - }); - const inputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.js"); - const outputPath = getFixturePath("fix-types/fix-only-prefer-arrow-callback.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - it("should fix both style and problem rules when fixTypes has 'suggestion' and 'layout'", async () => { - eslint = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - fix: true, - fixTypes: ["suggestion", "layout"] - }); - const inputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.js"); - const outputPath = getFixturePath("fix-types/fix-both-semi-and-prefer-arrow-callback.expected.js"); - const results = await eslint.lintFiles([inputPath]); - const expectedOutput = fs.readFileSync(outputPath, "utf8"); - - assert.strictEqual(results[0].output, expectedOutput); - }); - - }); - - describe("isPathIgnored", () => { - it("should check if the given path is ignored", async () => { - const engine = new FlatESLint({ - overrideConfigFile: getFixturePath("eslint.config_with_ignores2.js"), - cwd: getFixturePath() - }); - - assert(await engine.isPathIgnored("undef.js")); - assert(!await engine.isPathIgnored("passing.js")); - }); - - it("should return false if ignoring is disabled", async () => { - const engine = new FlatESLint({ - ignore: false, - overrideConfigFile: getFixturePath("eslint.config_with_ignores2.js"), - cwd: getFixturePath() - }); - - assert(!await engine.isPathIgnored("undef.js")); - }); - - // https://github.com/eslint/eslint/issues/5547 - it("should return true for default ignores even if ignoring is disabled", async () => { - const engine = new FlatESLint({ - ignore: false, - cwd: getFixturePath("cli-engine") - }); - - assert(await engine.isPathIgnored("node_modules/foo.js")); - }); - - describe("about the default ignore patterns", () => { - it("should always apply default ignore patterns if ignore option is true", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); - }); - - it("should still apply default ignore patterns if ignore option is is false", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignore: false, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules/package/file.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "subdir/node_modules/package/file.js"))); - }); - - it("should allow subfolders of defaultPatterns to be unignored by ignorePattern constructor option", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ - cwd, - overrideConfigFile: true, - ignorePatterns: ["!node_modules/", "node_modules/*", "!node_modules/package/"] - }); - - const result = await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js")); - - assert(!result, "File should not be ignored"); - }); - - it("should allow subfolders of defaultPatterns to be unignored by ignores in overrideConfig", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ - cwd, - overrideConfigFile: true, - overrideConfig: { - ignores: ["!node_modules/", "node_modules/*", "!node_modules/package/"] - } - }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "node_modules", "package", "file.js"))); - }); - - it("should ignore .git directory", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".git/bar"))); - }); - - it("should still ignore .git directory when ignore option disabled", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignore: false, cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", ".git/bar"))); - }); - - it("should not ignore absolute paths containing '..'", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ cwd }); - - assert(!await engine.isPathIgnored(`${getFixturePath("ignored-paths", "foo")}/../unignored.js`)); - }); - - it("should ignore /node_modules/ relative to cwd without any configured ignore patterns", async () => { - const cwd = getFixturePath("ignored-paths", "no-ignore-file"); - const engine = new FlatESLint({ cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "node_modules", "existing.js"))); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "no-ignore-file", "foo", "node_modules", "existing.js"))); - }); - - it("should not inadvertently ignore all files in parent directories", async () => { - const engine = new FlatESLint({ cwd: getFixturePath("ignored-paths", "no-ignore-file") }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - }); - - describe("with ignorePatterns option", () => { - it("should accept a string for options.ignorePatterns", async () => { - const cwd = getFixturePath("ignored-paths", "ignore-pattern"); - const engine = new FlatESLint({ - ignorePatterns: ["ignore-me.txt"], - cwd - }); - - assert(await engine.isPathIgnored("ignore-me.txt")); - }); - - it("should accept an array for options.ignorePattern", async () => { - const engine = new FlatESLint({ - ignorePatterns: ["a.js", "b.js"], - overrideConfigFile: true - }); - - assert(await engine.isPathIgnored("a.js"), "a.js should be ignored"); - assert(await engine.isPathIgnored("b.js"), "b.js should be ignored"); - assert(!await engine.isPathIgnored("c.js"), "c.js should not be ignored"); - }); - - it("should return true for files which match an ignorePattern even if they do not exist on the filesystem", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ - ignorePatterns: ["not-a-file"], - cwd - }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "not-a-file"))); - }); - - it("should return true for file matching an ignore pattern exactly", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ - ignorePatterns: ["undef.js"], - cwd, - overrideConfigFile: true - }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - - it("should return false for file in subfolder of cwd matching an ignore pattern with a base filename", async () => { - const cwd = getFixturePath("ignored-paths"); - const filePath = getFixturePath("ignored-paths", "subdir", "undef.js"); - const engine = new FlatESLint({ - ignorePatterns: ["undef.js"], - overrideConfigFile: true, - cwd - }); - - assert(!await engine.isPathIgnored(filePath)); - }); - - it("should return true for file matching a child of an ignore pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignorePatterns: ["ignore-pattern"], cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "ignore-me.txt"))); - }); - - it("should return true for file matching a grandchild of a directory when the pattern is directory/**", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignorePatterns: ["ignore-pattern/**"], cwd }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "ignore-pattern", "subdir", "ignore-me.js"))); - }); - - it("should return false for file not matching any ignore pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ ignorePatterns: ["failing.js"], cwd }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "unignored.js"))); - }); - - it("two globstar '**' ignore pattern should ignore files in nested directories", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ - overrideConfigFile: true, - ignorePatterns: ["**/*.js"], - cwd - }); - - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.js")), "foo.js should be ignored"); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.js")), "foo/bar.js should be ignored"); - assert(await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.js")), "foo/bar/baz.js"); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo.cjs")), "foo.cjs should not be ignored"); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar.cjs")), "foo/bar.cjs should not be ignored"); - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "foo/bar/baz.cjs")), "foo/bar/baz.cjs should not be ignored"); - }); - }); - - describe("with config ignores ignorePatterns option", () => { - it("should return false for ignored file when unignored with ignore pattern", async () => { - const cwd = getFixturePath("ignored-paths"); - const engine = new FlatESLint({ - overrideConfigFile: getFixturePath("eslint.config_with_ignores2.js"), - ignorePatterns: ["!undef.js"], - cwd - }); - - assert(!await engine.isPathIgnored(getFixturePath("ignored-paths", "undef.js"))); - }); - }); - - it("should throw if non-string value is given to 'filePath' parameter", async () => { - const eslint = new FlatESLint(); - - await assert.rejects(() => eslint.isPathIgnored(null), /'filePath' must be a non-empty string/u); - }); - }); - - describe("loadFormatter()", () => { - it("should return a formatter object when a bundled formatter is requested", async () => { - const engine = new FlatESLint(); - const formatter = await engine.loadFormatter("compact"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when no argument is passed", async () => { - const engine = new FlatESLint(); - const formatter = await engine.loadFormatter(); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a custom formatter is requested", async () => { - const engine = new FlatESLint(); - const formatter = await engine.loadFormatter(getFixturePath("formatters", "simple.js")); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a custom formatter is requested, also if the path has backslashes", async () => { - const engine = new FlatESLint({ - cwd: path.join(fixtureDir, "..") - }); - const formatter = await engine.loadFormatter(".\\fixtures\\formatters\\simple.js"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a formatter prefixed with eslint-formatter is requested", async () => { - const engine = new FlatESLint({ - cwd: getFixturePath("cli-engine") - }); - const formatter = await engine.loadFormatter("bar"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a formatter is requested, also when the eslint-formatter prefix is included in the format argument", async () => { - const engine = new FlatESLint({ - cwd: getFixturePath("cli-engine") - }); - const formatter = await engine.loadFormatter("eslint-formatter-bar"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a formatter is requested within a scoped npm package", async () => { - const engine = new FlatESLint({ - cwd: getFixturePath("cli-engine") - }); - const formatter = await engine.loadFormatter("@somenamespace/foo"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should return a formatter object when a formatter is requested within a scoped npm package, also when the eslint-formatter prefix is included in the format argument", async () => { - const engine = new FlatESLint({ - cwd: getFixturePath("cli-engine") - }); - const formatter = await engine.loadFormatter("@somenamespace/eslint-formatter-foo"); - - assert.strictEqual(typeof formatter, "object"); - assert.strictEqual(typeof formatter.format, "function"); - }); - - it("should throw if a custom formatter doesn't exist", async () => { - const engine = new FlatESLint(); - const formatterPath = getFixturePath("formatters", "doesntexist.js"); - const fullFormatterPath = path.resolve(formatterPath); - - await assert.rejects(async () => { - await engine.loadFormatter(formatterPath); - }, new RegExp(escapeStringRegExp(`There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`), "u")); - }); - - it("should throw if a built-in formatter doesn't exist", async () => { - const engine = new FlatESLint(); - const fullFormatterPath = path.resolve(__dirname, "../../../lib/cli-engine/formatters/special"); - - await assert.rejects(async () => { - await engine.loadFormatter("special"); - }, new RegExp(escapeStringRegExp(`There was a problem loading formatter: ${fullFormatterPath}.js\nError: Cannot find module '${fullFormatterPath}.js'`), "u")); - }); - - it("should throw if the required formatter exists but has an error", async () => { - const engine = new FlatESLint(); - const formatterPath = getFixturePath("formatters", "broken.js"); - - await assert.rejects(async () => { - await engine.loadFormatter(formatterPath); - - // for some reason, the error here contains multiple "there was a problem loading formatter" lines, so omitting - }, new RegExp(escapeStringRegExp("Error: Cannot find module 'this-module-does-not-exist'"), "u")); - }); - - it("should throw if a non-string formatter name is passed", async () => { - const engine = new FlatESLint(); - - await assert.rejects(async () => { - await engine.loadFormatter(5); - }, /'name' must be a string/u); - }); - }); - - describe("getErrorResults()", () => { - - it("should report 5 error messages when looking for errors only", async () => { - process.chdir(originalDir); - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { - quotes: "error", - "no-var": "error", - "eol-last": "error", - "no-unused-vars": "error" - } - } - }); - const results = await engine.lintText("var foo = 'bar';"); - const errorResults = FlatESLint.getErrorResults(results); - - assert.strictEqual(errorResults[0].messages.length, 4, "messages.length is wrong"); - assert.strictEqual(errorResults[0].errorCount, 4, "errorCount is wrong"); - assert.strictEqual(errorResults[0].fixableErrorCount, 3, "fixableErrorCount is wrong"); - assert.strictEqual(errorResults[0].fixableWarningCount, 0, "fixableWarningCount is wrong"); - assert.strictEqual(errorResults[0].messages[0].ruleId, "no-var"); - assert.strictEqual(errorResults[0].messages[0].severity, 2); - assert.strictEqual(errorResults[0].messages[1].ruleId, "no-unused-vars"); - assert.strictEqual(errorResults[0].messages[1].severity, 2); - assert.strictEqual(errorResults[0].messages[2].ruleId, "quotes"); - assert.strictEqual(errorResults[0].messages[2].severity, 2); - assert.strictEqual(errorResults[0].messages[3].ruleId, "eol-last"); - assert.strictEqual(errorResults[0].messages[3].severity, 2); - }); - - it("should not mutate passed report parameter", async () => { - process.chdir(originalDir); - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { quotes: [1, "double"] } - } - }); - const results = await engine.lintText("var foo = 'bar';"); - const reportResultsLength = results[0].messages.length; - - FlatESLint.getErrorResults(results); - - assert.strictEqual(results[0].messages.length, reportResultsLength); - }); - - it("should report a warningCount of 0 when looking for errors only", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { - strict: ["error", "global"], - quotes: "error", - "no-var": "error", - "eol-last": "error", - "no-unused-vars": "error" - } - } - }); - const lintResults = await engine.lintText("var foo = 'bar';"); - const errorResults = FlatESLint.getErrorResults(lintResults); - - assert.strictEqual(errorResults[0].warningCount, 0); - assert.strictEqual(errorResults[0].fixableWarningCount, 0); - }); - - it("should return 0 error or warning messages even when the file has warnings", async () => { - const engine = new FlatESLint({ - overrideConfigFile: getFixturePath("eslint.config_with_ignores.js"), - cwd: path.join(fixtureDir, "..") - }); - const options = { - filePath: "fixtures/passing.js", - warnIgnored: true - }; - const results = await engine.lintText("var bar = foo;", options); - const errorReport = FlatESLint.getErrorResults(results); - - assert.strictEqual(errorReport.length, 0); - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].errorCount, 0); - assert.strictEqual(results[0].warningCount, 1); - }); - - it("should return source code of file in the `source` property", async () => { - process.chdir(originalDir); - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { quotes: [2, "double"] } - } - }); - const results = await engine.lintText("var foo = 'bar';"); - const errorResults = FlatESLint.getErrorResults(results); - - assert.strictEqual(errorResults[0].messages.length, 1); - assert.strictEqual(errorResults[0].source, "var foo = 'bar';"); - }); - - it("should contain `output` property after fixes", async () => { - process.chdir(originalDir); - const engine = new FlatESLint({ - overrideConfigFile: true, - fix: true, - overrideConfig: { - rules: { - semi: 2, - "no-console": 2 - } - } - }); - const results = await engine.lintText("console.log('foo')"); - const errorResults = FlatESLint.getErrorResults(results); - - assert.strictEqual(errorResults[0].messages.length, 1); - assert.strictEqual(errorResults[0].output, "console.log('foo');"); - }); - }); - - describe("findConfigFile()", () => { - - it("should return undefined when overrideConfigFile is true", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true - }); - - assert.strictEqual(await engine.findConfigFile(), void 0); - }); - - it("should return undefined when a config file isn't found", async () => { - const engine = new FlatESLint({ - cwd: path.resolve(__dirname, "../../../../") - }); - - assert.strictEqual(await engine.findConfigFile(), void 0); - }); - - it("should return custom config file path when overrideConfigFile is a nonempty string", async () => { - const engine = new FlatESLint({ - overrideConfigFile: "my-config.js" - }); - const configFilePath = path.resolve(__dirname, "../../../my-config.js"); - - assert.strictEqual(await engine.findConfigFile(), configFilePath); - }); - - it("should return root level eslint.config.js when overrideConfigFile is null", async () => { - const engine = new FlatESLint({ - overrideConfigFile: null - }); - const configFilePath = path.resolve(__dirname, "../../../eslint.config.js"); - - assert.strictEqual(await engine.findConfigFile(), configFilePath); - }); - - it("should return root level eslint.config.js when overrideConfigFile is not specified", async () => { - const engine = new FlatESLint(); - const configFilePath = path.resolve(__dirname, "../../../eslint.config.js"); - - assert.strictEqual(await engine.findConfigFile(), configFilePath); - }); - - }); - - describe("getRulesMetaForResults()", () => { - - it("should throw an error when this instance did not lint any files", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true - }); - - assert.throws(() => { - engine.getRulesMetaForResults([ - { - filePath: "path/to/file.js", - messages: [ - { - ruleId: "curly", - severity: 2, - message: "Expected { after 'if' condition.", - line: 2, - column: 1, - nodeType: "IfStatement" - }, - { - ruleId: "no-process-exit", - severity: 2, - message: "Don't use process.exit(); throw an error instead.", - line: 3, - column: 1, - nodeType: "CallExpression" - } - ], - suppressedMessages: [], - errorCount: 2, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 0, - fixableWarningCount: 0, - source: - "var err = doStuff();\nif (err) console.log('failed tests: ' + err);\nprocess.exit(1);\n" - } - ]); - }, { - constructor: TypeError, - message: "Results object was not created from this ESLint instance." - }); - }); - - it("should throw an error when results were created from a different instance", async () => { - const engine1 = new FlatESLint({ - overrideConfigFile: true, - cwd: path.join(fixtureDir, "foo"), - overrideConfig: { - rules: { - semi: 2 - } - } - }); - const engine2 = new FlatESLint({ - overrideConfigFile: true, - cwd: path.join(fixtureDir, "bar"), - overrideConfig: { - rules: { - semi: 2 - } - } - }); - - const results1 = await engine1.lintText("1", { filePath: "file.js" }); - const results2 = await engine2.lintText("2", { filePath: "file.js" }); - - engine1.getRulesMetaForResults(results1); // should not throw an error - assert.throws(() => { - engine1.getRulesMetaForResults(results2); - }, { - constructor: TypeError, - message: "Results object was not created from this ESLint instance." - }); - }); - - it("should treat a result without `filePath` as if the file was located in `cwd`", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - cwd: path.join(fixtureDir, "foo", "bar"), - ignorePatterns: ["*/**"], // ignore all subdirectories of `cwd` - overrideConfig: { - rules: { - eqeqeq: "warn" - } - } - }); - - const results = await engine.lintText("a==b"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta.eqeqeq, coreRules.get("eqeqeq").meta); - }); - - it("should not throw an error if a result without `filePath` contains an ignored file warning", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - cwd: path.join(fixtureDir, "foo", "bar"), - ignorePatterns: ["**"] - }); - - const results = await engine.lintText("", { warnIgnored: true }); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, {}); - }); - - it("should not throw an error if results contain linted files and one ignored file", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - cwd: getFixturePath(), - ignorePatterns: ["passing*"], - overrideConfig: { - rules: { - "no-undef": 2, - semi: 1 - } - } - }); - - const results = await engine.lintFiles(["missing-semicolon.js", "passing.js", "undef.js"]); - - assert( - results.some(({ messages }) => messages.some(({ message, ruleId }) => !ruleId && message.startsWith("File ignored"))), - "At least one file should be ignored but none is." - ); - - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta["no-undef"], coreRules.get("no-undef").meta); - assert.deepStrictEqual(rulesMeta.semi, coreRules.get("semi").meta); - }); - - it("should return empty object when there are no linting errors", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true - }); - - const rulesMeta = engine.getRulesMetaForResults([]); - - assert.deepStrictEqual(rulesMeta, {}); - }); - - it("should return one rule meta when there is a linting error", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { - semi: 2 - } - } - }); - - const results = await engine.lintText("a", { filePath: "foo.js" }); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(Object.keys(rulesMeta).length, 1); - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - }); - - it("should return one rule meta when there is a suppressed linting error", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { - semi: 2 - } - } - }); - - const results = await engine.lintText("a // eslint-disable-line semi"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(Object.keys(rulesMeta).length, 1); - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - }); - - it("should return multiple rule meta when there are multiple linting errors", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - rules: { - semi: 2, - quotes: [2, "double"] - } - } - }); - - const results = await engine.lintText("'a'"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); - }); - - it("should return multiple rule meta when there are multiple linting errors from a plugin", async () => { - const customPlugin = { - rules: { - "no-var": require("../../../lib/rules/no-var") - } - }; - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { - plugins: { - "custom-plugin": customPlugin - }, - rules: { - "custom-plugin/no-var": 2, - semi: 2, - quotes: [2, "double"] - } - } - }); - - const results = await engine.lintText("var foo = 0; var bar = '1'"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); - assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); - assert.strictEqual( - rulesMeta["custom-plugin/no-var"], - customPlugin.rules["no-var"].meta - ); - }); - - it("should ignore messages not related to a rule", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - ignorePatterns: ["ignored.js"], - overrideConfig: { - rules: { - "no-var": "warn" - }, - linterOptions: { - reportUnusedDisableDirectives: "warn" - } - } - }); - - { - const results = await engine.lintText("syntax error"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, {}); - } - { - const results = await engine.lintText("// eslint-disable-line no-var"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, {}); - } - { - const results = await engine.lintText("", { filePath: "ignored.js", warnIgnored: true }); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, {}); - } - }); - - it("should return a non-empty value if some of the messages are related to a rule", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { rules: { "no-var": "warn" }, linterOptions: { reportUnusedDisableDirectives: "warn" } } - }); - - const results = await engine.lintText("// eslint-disable-line no-var\nvar foo;"); - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); - }); - - it("should return empty object if all messages are related to unknown rules", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true - }); - - const results = await engine.lintText("// eslint-disable-line foo, bar/baz, bar/baz/qux"); - - assert.strictEqual(results[0].messages.length, 3); - assert.strictEqual(results[0].messages[0].ruleId, "foo"); - assert.strictEqual(results[0].messages[1].ruleId, "bar/baz"); - assert.strictEqual(results[0].messages[2].ruleId, "bar/baz/qux"); - - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.strictEqual(Object.keys(rulesMeta).length, 0); - }); - - it("should return object with meta of known rules if some messages are related to unknown rules", async () => { - const engine = new FlatESLint({ - overrideConfigFile: true, - overrideConfig: { rules: { "no-var": "warn" } } - }); - - const results = await engine.lintText("// eslint-disable-line foo, bar/baz, bar/baz/qux\nvar x;"); - - assert.strictEqual(results[0].messages.length, 4); - assert.strictEqual(results[0].messages[0].ruleId, "foo"); - assert.strictEqual(results[0].messages[1].ruleId, "bar/baz"); - assert.strictEqual(results[0].messages[2].ruleId, "bar/baz/qux"); - assert.strictEqual(results[0].messages[3].ruleId, "no-var"); - - const rulesMeta = engine.getRulesMetaForResults(results); - - assert.deepStrictEqual(rulesMeta, { "no-var": coreRules.get("no-var").meta }); - }); - }); - - describe("outputFixes()", () => { - afterEach(() => { - sinon.verifyAndRestore(); - }); - - it("should call fs.writeFile() for each result with output", async () => { - const fakeFS = { - writeFile: sinon.spy(() => Promise.resolve()) - }; - const spy = fakeFS.writeFile; - const { FlatESLint: localESLint } = proxyquire("../../../lib/eslint/flat-eslint", { - fs: { - promises: fakeFS - } - }); - - const results = [ - { - filePath: path.resolve("foo.js"), - output: "bar" - }, - { - filePath: path.resolve("bar.js"), - output: "baz" - } - ]; - - await localESLint.outputFixes(results); - - assert.strictEqual(spy.callCount, 2); - assert(spy.firstCall.calledWithExactly(path.resolve("foo.js"), "bar"), "First call was incorrect."); - assert(spy.secondCall.calledWithExactly(path.resolve("bar.js"), "baz"), "Second call was incorrect."); - }); - - it("should call fs.writeFile() for each result with output and not at all for a result without output", async () => { - const fakeFS = { - writeFile: sinon.spy(() => Promise.resolve()) - }; - const spy = fakeFS.writeFile; - const { FlatESLint: localESLint } = proxyquire("../../../lib/eslint/flat-eslint", { - fs: { - promises: fakeFS - } - }); - const results = [ - { - filePath: path.resolve("foo.js"), - output: "bar" - }, - { - filePath: path.resolve("abc.js") - }, - { - filePath: path.resolve("bar.js"), - output: "baz" - } - ]; - - await localESLint.outputFixes(results); - - assert.strictEqual(spy.callCount, 2, "Call count was wrong"); - assert(spy.firstCall.calledWithExactly(path.resolve("foo.js"), "bar"), "First call was incorrect."); - assert(spy.secondCall.calledWithExactly(path.resolve("bar.js"), "baz"), "Second call was incorrect."); - }); - - it("should throw if non object array is given to 'results' parameter", async () => { - await assert.rejects(() => FlatESLint.outputFixes(null), /'results' must be an array/u); - await assert.rejects(() => FlatESLint.outputFixes([null]), /'results' must include only objects/u); - }); - }); - - describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { - it("should report a violation for disabling rules", async () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert" - ].join("\n"); - const config = { - ignore: true, - overrideConfigFile: true, - allowInlineConfig: false, - overrideConfig: { - rules: { - "eol-last": 0, - "no-alert": 1, - "no-trailing-spaces": 0, - strict: 0, - quotes: 0 - } - } - }; - const eslintCLI = new FlatESLint(config); - const results = await eslintCLI.lintText(code); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(results[0].suppressedMessages.length, 0); - }); - - it("should not report a violation by default", async () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert" - ].join("\n"); - const config = { - ignore: true, - overrideConfigFile: true, - allowInlineConfig: true, - overrideConfig: { - rules: { - "eol-last": 0, - "no-alert": 1, - "no-trailing-spaces": 0, - strict: 0, - quotes: 0 - } - } - }; - const eslintCLI = new FlatESLint(config); - const results = await eslintCLI.lintText(code); - const messages = results[0].messages; - - assert.strictEqual(messages.length, 0); - assert.strictEqual(results[0].suppressedMessages.length, 1); - assert.strictEqual(results[0].suppressedMessages[0].ruleId, "no-alert"); - }); - }); - - describe("when evaluating code when reportUnusedDisableDirectives is enabled", () => { - it("should report problems for unused eslint-disable directives", async () => { - const eslint = new FlatESLint({ overrideConfigFile: true, overrideConfig: { linterOptions: { reportUnusedDisableDirectives: "error" } } }); - - assert.deepStrictEqual( - await eslint.lintText("/* eslint-disable */"), - [ - { - filePath: "", - messages: [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 2, - nodeType: null - } - ], - suppressedMessages: [], - errorCount: 1, - warningCount: 0, - fatalErrorCount: 0, - fixableErrorCount: 1, - fixableWarningCount: 0, - source: "/* eslint-disable */", - usedDeprecatedRules: [] - } - ] - ); - }); - }); - - describe("when retrieving version number", () => { - it("should return current version number", () => { - const eslintCLI = require("../../../lib/eslint/flat-eslint").FlatESLint; - const version = eslintCLI.version; - - assert.strictEqual(typeof version, "string"); - assert(parseInt(version[0], 10) >= 3); - }); - }); - - describe("mutability", () => { - - describe("rules", () => { - it("Loading rules in one instance doesn't mutate to another instance", async () => { - const filePath = getFixturePath("single-quoted.js"); - const engine1 = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true, - overrideConfig: { - plugins: { - example: { - rules: { - "example-rule"() { - return {}; - } - } - } - }, - rules: { "example/example-rule": 1 } - } - }); - const engine2 = new FlatESLint({ - cwd: path.join(fixtureDir, ".."), - overrideConfigFile: true - }); - const fileConfig1 = await engine1.calculateConfigForFile(filePath); - const fileConfig2 = await engine2.calculateConfigForFile(filePath); - - // plugin - assert.deepStrictEqual(fileConfig1.rules["example/example-rule"], [1], "example is present for engine 1"); - assert.strictEqual(fileConfig2.rules, void 0, "example is not present for engine 2"); - }); - }); - }); - - describe("configs with 'ignores' and without 'files'", () => { - - // https://github.com/eslint/eslint/issues/17103 - describe("config with ignores: ['error.js']", () => { - const cwd = getFixturePath("config-with-ignores-without-files"); - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd, - files: { - "eslint.config.js": `module.exports = [ - { - rules: { - "no-unused-vars": "error", - }, - }, - { - ignores: ["error.js"], - rules: { - "no-unused-vars": "warn", - }, - }, - ];`, - "error.js": "let unusedVar;", - "warn.js": "let unusedVar;" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("should apply to all files except for 'error.js'", async () => { - const engine = new FlatESLint({ - cwd - }); - - const results = await engine.lintFiles("{error,warn}.js"); - - assert.strictEqual(results.length, 2); - - const [errorResult, warnResult] = results; - - assert.strictEqual(errorResult.filePath, path.join(getPath(), "error.js")); - assert.strictEqual(errorResult.messages.length, 1); - assert.strictEqual(errorResult.messages[0].ruleId, "no-unused-vars"); - assert.strictEqual(errorResult.messages[0].severity, 2); - - assert.strictEqual(warnResult.filePath, path.join(getPath(), "warn.js")); - assert.strictEqual(warnResult.messages.length, 1); - assert.strictEqual(warnResult.messages[0].ruleId, "no-unused-vars"); - assert.strictEqual(warnResult.messages[0].severity, 1); - }); - }); - - describe("config with ignores: ['**/*.json']", () => { - const cwd = getFixturePath("config-with-ignores-without-files"); - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd, - files: { - "eslint.config.js": `module.exports = [ - { - rules: { - "no-undef": "error", - }, - }, - { - ignores: ["**/*.json"], - rules: { - "no-unused-vars": "error", - }, - }, - ];`, - "foo.js": "", - "foo.json": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("should not add json files as lint targets", async () => { - const engine = new FlatESLint({ - cwd - }); - - const results = await engine.lintFiles("foo*"); - - // should not lint `foo.json` - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, path.join(getPath(), "foo.js")); - }); - }); - - }); - - describe("with ignores config", () => { - const root = getFixturePath("cli-engine/ignore-patterns"); - - describe("ignores can add an ignore pattern ('foo.js').", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "eslint.config.js": `module.exports = { - ignores: ["**/foo.js"] - };`, - "foo.js": "", - "bar.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), false); - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false); - }); - - it("'lintFiles()' should not verify 'foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "bar.js"), - path.join(root, "eslint.config.js"), - path.join(root, "subdir/bar.js") - ]); - }); - }); - - describe("ignores can add ignore patterns ('**/foo.js', '/bar.js').", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root + Date.now(), - files: { - "eslint.config.js": `module.exports = { - ignores: ["**/foo.js", "bar.js"] - };`, - "foo.js": "", - "bar.js": "", - "baz.js": "", - "subdir/foo.js": "", - "subdir/bar.js": "", - "subdir/baz.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/foo.js"), true); - }); - - it("'isPathIgnored()' should return 'true' for '/bar.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), true); - assert.strictEqual(await engine.isPathIgnored("subdir/bar.js"), false); - }); - - it("'lintFiles()' should not verify 'foo.js' and '/bar.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "baz.js"), - path.join(getPath(), "eslint.config.js"), - path.join(getPath(), "subdir/bar.js"), - path.join(getPath(), "subdir/baz.js") - ]); - }); - }); - - - describe("ignores can unignore '/node_modules/foo' with patterns ['!node_modules/', 'node_modules/*', '!node_modules/foo/'].", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}-unignores`, - files: { - "eslint.config.js": `module.exports = { - ignores: ["!node_modules/", "node_modules/*", "!node_modules/foo/"] - };`, - "node_modules/foo/index.js": "", - "node_modules/foo/.dot.js": "", - "node_modules/bar/index.js": "", - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/foo/index.js"), false); - }); - - it("'isPathIgnored()' should return 'false' for 'node_modules/foo/.dot.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/foo/.dot.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/bar/index.js"), true); - }); - - it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "eslint.config.js"), - path.join(getPath(), "foo.js"), - path.join(getPath(), "node_modules/foo/.dot.js"), - path.join(getPath(), "node_modules/foo/index.js") - ]); - }); - }); - - describe("ignores can unignore '/node_modules/foo' with patterns ['!node_modules/', 'node_modules/*', '!node_modules/foo/**'].", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}-unignores`, - files: { - "eslint.config.js": `module.exports = { - ignores: ["!node_modules/", "node_modules/*", "!node_modules/foo/**"] - };`, - "node_modules/foo/index.js": "", - "node_modules/foo/.dot.js": "", - "node_modules/bar/index.js": "", - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/foo/index.js"), false); - }); - - it("'isPathIgnored()' should return 'false' for 'node_modules/foo/.dot.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/foo/.dot.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("node_modules/bar/index.js"), true); - }); - - it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const result = (await engine.lintFiles("**/*.js")); - - const filePaths = result - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "eslint.config.js"), - path.join(getPath(), "foo.js"), - path.join(getPath(), "node_modules/foo/.dot.js"), - path.join(getPath(), "node_modules/foo/index.js") - ]); - }); - }); - - describe("ignore pattern can re-ignore files that are unignored by a previous pattern.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}-reignore`, - files: { - "eslint.config.js": `module.exports = ${JSON.stringify({ - ignores: ["!.*", ".foo*"] - })}`, - ".foo.js": "", - ".bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored(".foo.js"), true); - }); - - it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored(".bar.js"), false); - }); - - it("'lintFiles()' should not lint re-ignored '.foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), ".bar.js"), - path.join(getPath(), "eslint.config.js") - ]); - }); - }); - - describe("ignore pattern can unignore files that are ignored by a previous pattern.", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}-dignore`, - files: { - "eslint.config.js": `module.exports = ${JSON.stringify({ - ignores: ["**/*.js", "!foo.js"] - })}`, - "foo.js": "", - "bar.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), false); - }); - - it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - - assert.strictEqual(await engine.isPathIgnored("bar.js"), true); - }); - - it("'lintFiles()' should verify unignored 'foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "foo.js") - ]); - }); - }); - - describe("ignores in a config file should not be used if ignore: false.", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root, - files: { - "eslint.config.js": `module.exports = { - ignores: ["*.js"] - }`, - "foo.js": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'isPathIgnored()' should return 'false' for 'foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath(), ignore: false }); - - assert.strictEqual(await engine.isPathIgnored("foo.js"), false); - }); - - it("'lintFiles()' should verify 'foo.js'.", async () => { - const engine = new FlatESLint({ cwd: getPath(), ignore: false }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(root, "eslint.config.js"), - path.join(root, "foo.js") - ]); - }); - }); - - }); - - describe("config.files adds lint targets", () => { - const root = getFixturePath("cli-engine/additional-lint-targets"); - - - describe("if { files: 'foo/*.txt', ignores: '**/ignore.txt' } is present,", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root + 1, - files: { - "eslint.config.js": `module.exports = [{ - files: ["foo/*.txt"], - ignores: ["**/ignore.txt"] - }];`, - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "foo/ignore.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "bar/ignore.txt": "", - "test.js": "", - "test.txt": "", - "ignore.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with a directory path should contain 'foo/test.txt'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles(".")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "bar/test.js"), - path.join(getPath(), "eslint.config.js"), - path.join(getPath(), "foo/test.js"), - path.join(getPath(), "foo/test.txt"), - path.join(getPath(), "test.js") - ]); - }); - - it("'lintFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "bar/test.js"), - path.join(getPath(), "eslint.config.js"), - path.join(getPath(), "foo/test.js"), - path.join(getPath(), "test.js") - ]); - }); - }); - - describe("if { files: 'foo/*.txt', ignores: '**/ignore.txt' } is present and subdirectory is passed,", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root + 2, - files: { - "eslint.config.js": `module.exports = [{ - files: ["foo/*.txt"], - ignores: ["**/ignore.txt"] - }];`, - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "foo/ignore.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "bar/ignore.txt": "", - "test.js": "", - "test.txt": "", - "ignore.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with a directory path should contain 'foo/test.txt'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("foo")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "foo/test.js"), - path.join(getPath(), "foo/test.txt") - ]); - }); - - it("'lintFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles("foo/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "foo/test.js") - ]); - }); - }); - - describe("if { files: 'foo/**/*.txt' } is present,", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root + 3, - files: { - "eslint.config.js": `module.exports = [ - { - files: ["foo/**/*.txt"] - } - ]`, - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "test.js": "", - "test.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles(".")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "bar/test.js"), - path.join(getPath(), "eslint.config.js"), - path.join(getPath(), "foo/nested/test.txt"), - path.join(getPath(), "foo/test.js"), - path.join(getPath(), "foo/test.txt"), - path.join(getPath(), "test.js") - ]); - }); - }); - - describe("if { files: 'foo/**/*' } is present,", () => { - - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: root + 4, - files: { - "eslint.config.js": `module.exports = [ - { - files: ["foo/**/*"] - } - ]`, - "foo/nested/test.txt": "", - "foo/test.js": "", - "foo/test.txt": "", - "bar/test.js": "", - "bar/test.txt": "", - "test.js": "", - "test.txt": "" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { - const engine = new FlatESLint({ cwd: getPath() }); - const filePaths = (await engine.lintFiles(".")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(filePaths, [ - path.join(getPath(), "bar/test.js"), - path.join(getPath(), "eslint.config.js"), - path.join(getPath(), "foo/test.js"), - path.join(getPath(), "test.js") - ]); - }); - }); - - }); - - describe("'ignores', 'files' of the configuration that the '--config' option provided should be resolved from CWD.", () => { - const root = getFixturePath("cli-engine/config-and-overrides-files"); - - describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/eslint.config.js',", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}a1`, - files: { - "node_modules/myconf/eslint.config.js": `module.exports = [ - { - files: ["foo/*.js"], - rules: { - eqeqeq: "error" - } - } - ];`, - "node_modules/myconf/foo/test.js": "a == b", - "foo/test.js": "a == b" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with 'foo/test.js' should use the files entry.", async () => { - const engine = new FlatESLint({ - overrideConfigFile: "node_modules/myconf/eslint.config.js", - cwd: getPath(), - ignore: false - }); - const results = await engine.lintFiles("foo/test.js"); - - // Expected to be an 'eqeqeq' error because the file matches to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - suppressedMessages: [], - errorCount: 1, - filePath: path.join(getPath(), "foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [ - { - column: 3, - endColumn: 5, - endLine: 1, - line: 1, - message: "Expected '===' and instead saw '=='.", - messageId: "unexpected", - nodeType: "BinaryExpression", - ruleId: "eqeqeq", - severity: 2 - } - ], - source: "a == b", - usedDeprecatedRules: [], - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - - it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the files entry.", async () => { - const engine = new FlatESLint({ - overrideConfigFile: "node_modules/myconf/eslint.config.js", - cwd: getPath(), - ignore: false - }); - const results = await engine.lintFiles("node_modules/myconf/foo/test.js"); - - // Expected to be no errors because the file doesn't match to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - suppressedMessages: [], - errorCount: 0, - filePath: path.join(getPath(), "node_modules/myconf/foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [ - { - ruleId: null, - fatal: false, - message: "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.", - severity: 1, - nodeType: null - } - ], - usedDeprecatedRules: [], - warningCount: 1, - fatalErrorCount: 0 - } - ]); - }); - }); - - describe("if { files: '*', ignores: 'foo/*.txt', ... } is present by '--config bar/myconf/eslint.config.js',", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}a2`, - files: { - "bar/myconf/eslint.config.js": `module.exports = [ - { - files: ["**/*"], - ignores: ["foo/*.js"], - rules: { - eqeqeq: "error" - } - } - ]`, - "bar/myconf/foo/test.js": "a == b", - "foo/test.js": "a == b" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with 'foo/test.js' should have no errors because no rules are enabled.", async () => { - const engine = new FlatESLint({ - overrideConfigFile: "bar/myconf/eslint.config.js", - cwd: getPath(), - ignore: false - }); - const results = await engine.lintFiles("foo/test.js"); - - // Expected to be no errors because the file matches to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - suppressedMessages: [], - errorCount: 0, - filePath: path.join(getPath(), "foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [], - usedDeprecatedRules: [], - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - - it("'lintFiles()' with 'bar/myconf/foo/test.js' should have an error because eqeqeq is enabled.", async () => { - const engine = new FlatESLint({ - overrideConfigFile: "bar/myconf/eslint.config.js", - cwd: getPath(), - ignore: false - }); - const results = await engine.lintFiles("bar/myconf/foo/test.js"); - - // Expected to be an 'eqeqeq' error because the file doesn't match to `$CWD/foo/*.js`. - assert.deepStrictEqual(results, [ - { - suppressedMessages: [], - errorCount: 1, - filePath: path.join(getPath(), "bar/myconf/foo/test.js"), - fixableErrorCount: 0, - fixableWarningCount: 0, - messages: [ - { - column: 3, - endColumn: 5, - endLine: 1, - line: 1, - message: "Expected '===' and instead saw '=='.", - messageId: "unexpected", - nodeType: "BinaryExpression", - ruleId: "eqeqeq", - severity: 2 - } - ], - source: "a == b", - usedDeprecatedRules: [], - warningCount: 0, - fatalErrorCount: 0 - } - ]); - }); - }); - - describe("if { ignores: 'foo/*.js', ... } is present by '--config node_modules/myconf/eslint.config.js',", () => { - const { prepare, cleanup, getPath } = createCustomTeardown({ - cwd: `${root}a3`, - files: { - "node_modules/myconf/eslint.config.js": `module.exports = [{ - ignores: ["!node_modules", "node_modules/*", "!node_modules/myconf", "foo/*.js"], - }, { - rules: { - eqeqeq: "error" - } - }]`, - "node_modules/myconf/foo/test.js": "a == b", - "foo/test.js": "a == b" - } - }); - - beforeEach(prepare); - afterEach(cleanup); - - it("'lintFiles()' with '**/*.js' should lint 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", async () => { - const engine = new FlatESLint({ - overrideConfigFile: "node_modules/myconf/eslint.config.js", - cwd: getPath() - }); - const files = (await engine.lintFiles("**/*.js")) - .map(r => r.filePath) - .sort(); - - assert.deepStrictEqual(files, [ - path.join(getPath(), "node_modules/myconf/eslint.config.js"), - path.join(getPath(), "node_modules/myconf/foo/test.js") - ]); - }); - }); - }); - - describe("baseConfig", () => { - it("can be an object", async () => { - const eslint = new FlatESLint({ - overrideConfigFile: true, - baseConfig: { - rules: { - semi: 2 - } - } - }); - - const [{ messages }] = await eslint.lintText("foo"); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "semi"); - }); - - it("can be an array", async () => { - const eslint = new FlatESLint({ - overrideConfigFile: true, - baseConfig: [ - { - rules: { - "no-var": 2 - } - }, - { - rules: { - semi: 2 - } - } - ] - }); - - const [{ messages }] = await eslint.lintText("var foo"); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-var"); - assert.strictEqual(messages[1].ruleId, "semi"); - }); - - it("should be inserted after default configs", async () => { - const eslint = new FlatESLint({ - overrideConfigFile: true, - baseConfig: { - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } - } - }); - - const [{ messages }] = await eslint.lintText("let x"); - - /* - * if baseConfig was inserted before default configs, - * `ecmaVersion: "latest"` from default configs would overwrite - * `ecmaVersion: 5` from baseConfig, so this wouldn't be a parsing error. - */ - - assert.strictEqual(messages.length, 1); - assert(messages[0].fatal, "Fatal error expected."); - }); - - it("should be inserted before configs from the config file", async () => { - const eslint = new FlatESLint({ - cwd: getFixturePath(), - baseConfig: { - rules: { - strict: ["error", "global"] - }, - languageOptions: { - sourceType: "script" - } - } - }); - - const [{ messages }] = await eslint.lintText("foo"); - - /* - * if baseConfig was inserted after configs from the config file, - * `strict: 0` from eslint.config.js wouldn't overwrite `strict: ["error", "global"]` - * from baseConfig, so there would be an error message from the `strict` rule. - */ - - assert.strictEqual(messages.length, 0); - }); - - it("should be inserted before overrideConfig", async () => { - const eslint = new FlatESLint({ - overrideConfigFile: true, - baseConfig: { - rules: { - semi: 2 - } - }, - overrideConfig: { - rules: { - semi: 1 - } - } - }); - - const [{ messages }] = await eslint.lintText("foo"); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "semi"); - assert.strictEqual(messages[0].severity, 1); - }); - - it("should be inserted before configs from the config file and overrideConfig", async () => { - const eslint = new FlatESLint({ - overrideConfigFile: getFixturePath("eslint.config_with_rules.js"), - baseConfig: { - rules: { - quotes: ["error", "double"], - semi: "error" - } - }, - overrideConfig: { - rules: { - quotes: "warn" - } - } - }); - - const [{ messages }] = await eslint.lintText('const foo = "bar"'); - - /* - * baseConfig: { quotes: ["error", "double"], semi: "error" } - * eslint.config_with_rules.js: { quotes: ["error", "single"] } - * overrideConfig: { quotes: "warn" } - * - * Merged config: { quotes: ["warn", "single"], semi: "error" } - */ - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "quotes"); - assert.strictEqual(messages[0].severity, 1); - assert.strictEqual(messages[1].ruleId, "semi"); - assert.strictEqual(messages[1].severity, 2); - }); - - it("when it has 'files' they should be interpreted as relative to the config file", async () => { - - /* - * `fixtures/plugins` directory does not have a config file. - * It's parent directory `fixtures` does have a config file, so - * the base path will be `fixtures`, cwd will be `fixtures/plugins` - */ - const eslint = new FlatESLint({ - cwd: getFixturePath("plugins"), - baseConfig: { - files: ["plugins/a.js"], - rules: { - semi: 2 - } - } - }); - - const [{ messages }] = await eslint.lintText("foo", { filePath: getFixturePath("plugins/a.js") }); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "semi"); - }); - - it("when it has 'ignores' they should be interpreted as relative to the config file", async () => { - - /* - * `fixtures/plugins` directory does not have a config file. - * It's parent directory `fixtures` does have a config file, so - * the base path will be `fixtures`, cwd will be `fixtures/plugins` - */ - const eslint = new FlatESLint({ - cwd: getFixturePath("plugins"), - baseConfig: { - ignores: ["plugins/a.js"] - } - }); - - const [{ messages }] = await eslint.lintText("foo", { filePath: getFixturePath("plugins/a.js"), warnIgnored: true }); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); - assert.match(messages[0].message, /ignored/u); - }); - }); - - describe("config file", () => { - - it("new instance of FlatESLint should use the latest version of the config file (ESM)", async () => { - const cwd = path.join(getFixturePath(), `config_file_${Date.now()}`); - const configFileContent = "export default [{ rules: { semi: ['error', 'always'] } }];"; - const teardown = createCustomTeardown({ - cwd, - files: { - "package.json": '{ "type": "module" }', - "eslint.config.js": configFileContent, - "a.js": "foo\nbar;" - } - }); - - await teardown.prepare(); - - let eslint = new FlatESLint({ cwd }); - let [{ messages }] = await eslint.lintFiles(["a.js"]); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "semi"); - assert.strictEqual(messages[0].messageId, "missingSemi"); - assert.strictEqual(messages[0].line, 1); - - await sleep(100); - await fsp.writeFile(path.join(cwd, "eslint.config.js"), configFileContent.replace("always", "never")); - - eslint = new FlatESLint({ cwd }); - [{ messages }] = await eslint.lintFiles(["a.js"]); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "semi"); - assert.strictEqual(messages[0].messageId, "extraSemi"); - assert.strictEqual(messages[0].line, 2); - }); - - it("new instance of FlatESLint should use the latest version of the config file (CJS)", async () => { - const cwd = path.join(getFixturePath(), `config_file_${Date.now()}`); - const configFileContent = "module.exports = [{ rules: { semi: ['error', 'always'] } }];"; - const teardown = createCustomTeardown({ - cwd, - files: { - "eslint.config.js": configFileContent, - "a.js": "foo\nbar;" - } - }); - - await teardown.prepare(); - - let eslint = new FlatESLint({ cwd }); - let [{ messages }] = await eslint.lintFiles(["a.js"]); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "semi"); - assert.strictEqual(messages[0].messageId, "missingSemi"); - assert.strictEqual(messages[0].line, 1); - - await sleep(100); - await fsp.writeFile(path.join(cwd, "eslint.config.js"), configFileContent.replace("always", "never")); - - eslint = new FlatESLint({ cwd }); - [{ messages }] = await eslint.lintFiles(["a.js"]); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "semi"); - assert.strictEqual(messages[0].messageId, "extraSemi"); - assert.strictEqual(messages[0].line, 2); - }); - }); - - // only works on a Windows machine - if (os.platform() === "win32") { - - // https://github.com/eslint/eslint/issues/17042 - describe("with cwd that is using forward slash on Windows", () => { - const cwd = getFixturePath("example-app3"); - const cwdForwardSlash = cwd.replace(/\\/gu, "/"); - - it("should correctly handle ignore patterns", async () => { - const engine = new FlatESLint({ cwd: cwdForwardSlash }); - const results = await engine.lintFiles(["./src"]); - - // src/dist/2.js should be ignored - assert.strictEqual(results.length, 1); - assert.strictEqual(results[0].filePath, path.join(cwd, "src\\1.js")); - }); - - it("should pass cwd with backslashes to rules", async () => { - const engine = new FlatESLint({ - cwd: cwdForwardSlash, - overrideConfigFile: true, - overrideConfig: { - plugins: { - test: require(path.join(cwd, "node_modules", "eslint-plugin-test")) - }, - rules: { - "test/report-cwd": "error" - } - } - }); - const results = await engine.lintText(""); - - assert.strictEqual(results[0].messages[0].ruleId, "test/report-cwd"); - assert.strictEqual(results[0].messages[0].message, cwd); - }); - - it("should pass cwd with backslashes to formatters", async () => { - const engine = new FlatESLint({ - cwd: cwdForwardSlash - }); - const results = await engine.lintText(""); - const formatter = await engine.loadFormatter("cwd"); - - assert.strictEqual(formatter.format(results), cwd); - }); - }); - } - -}); - -describe("shouldUseFlatConfig", () => { - - /** - * Check that `shouldUseFlatConfig` returns the expected value from a CWD - * with a flat config and one without a flat config. - * @param {boolean} expectedValueWithConfig the expected return value of - * `shouldUseFlatConfig` when in a directory with a flat config present - * @param {boolean} expectedValueWithoutConfig the expected return value of - * `shouldUseFlatConfig` when in a directory without any flat config present - * @returns {void} - */ - function testShouldUseFlatConfig(expectedValueWithConfig, expectedValueWithoutConfig) { - describe("when there is a flat config file present", () => { - const originalDir = process.cwd(); - - beforeEach(() => { - process.chdir(__dirname); - }); - - afterEach(() => { - process.chdir(originalDir); - }); - - it(`is \`${expectedValueWithConfig}\``, async () => { - assert.strictEqual(await shouldUseFlatConfig(), expectedValueWithConfig); - }); - }); - - describe("when there is no flat config file present", () => { - const originalDir = process.cwd(); - - beforeEach(() => { - process.chdir(os.tmpdir()); - }); - - afterEach(() => { - process.chdir(originalDir); - }); - - it(`is \`${expectedValueWithoutConfig}\``, async () => { - assert.strictEqual(await shouldUseFlatConfig(), expectedValueWithoutConfig); - }); - }); - } - - describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is `'true'`", () => { - beforeEach(() => { - process.env.ESLINT_USE_FLAT_CONFIG = true; - }); - - afterEach(() => { - delete process.env.ESLINT_USE_FLAT_CONFIG; - }); - - testShouldUseFlatConfig(true, true); - }); - - describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is `'false'`", () => { - beforeEach(() => { - process.env.ESLINT_USE_FLAT_CONFIG = false; - }); - - afterEach(() => { - delete process.env.ESLINT_USE_FLAT_CONFIG; - }); - - testShouldUseFlatConfig(false, false); - }); - - describe("when the env variable `ESLINT_USE_FLAT_CONFIG` is unset", () => { - testShouldUseFlatConfig(true, false); - }); -}); diff --git a/tests/lib/eslint/legacy-eslint.js b/tests/lib/eslint/legacy-eslint.js new file mode 100644 index 000000000000..480b45d17a67 --- /dev/null +++ b/tests/lib/eslint/legacy-eslint.js @@ -0,0 +1,9451 @@ +/** + * @fileoverview Tests for the ESLint class. + * @author Kai Cataldo + * @author Toru Nagashima + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("node:assert"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const escapeStringRegExp = require("escape-string-regexp"); +const fCache = require("file-entry-cache"); +const sinon = require("sinon"); +const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); +const shell = require("shelljs"); +const { + Legacy: { CascadingConfigArrayFactory }, +} = require("@eslint/eslintrc"); +const hash = require("../../../lib/cli-engine/hash"); +const { unIndent, createCustomTeardown } = require("../../_utils"); +const coreRules = require("../../../lib/rules"); +const childProcess = require("node:child_process"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("LegacyESLint", () => { + const examplePluginName = "eslint-plugin-example"; + const examplePluginNameWithNamespace = "@eslint/eslint-plugin-example"; + const examplePlugin = { + rules: { + "example-rule": require("../../fixtures/rules/custom-rule"), + "make-syntax-error": require("../../fixtures/rules/make-syntax-error-rule"), + }, + }; + const examplePreprocessorName = "eslint-plugin-processor"; + const originalDir = process.cwd(); + const fixtureDir = path.resolve( + fs.realpathSync(os.tmpdir()), + "eslint/fixtures", + ); + + /** @type {import("../../../lib/eslint/legacy-eslint").LegacyESLint} */ + let LegacyESLint; + + /** + * Returns the path inside of the fixture directory. + * @param {...string} args file path segments. + * @returns {string} The path inside the fixture directory. + * @private + */ + function getFixturePath(...args) { + const filepath = path.join(fixtureDir, ...args); + + try { + return fs.realpathSync(filepath); + } catch { + return filepath; + } + } + + /** + * Create the ESLint object by mocking some of the plugins + * @param {Object} options options for ESLint + * @returns {ESLint} engine object + * @private + */ + function eslintWithPlugins(options) { + return new LegacyESLint({ + ...options, + plugins: { + [examplePluginName]: examplePlugin, + [examplePluginNameWithNamespace]: examplePlugin, + [examplePreprocessorName]: require("../../fixtures/processors/custom-processor"), + }, + }); + } + + /** + * Call the last argument. + * @param {any[]} args Arguments + * @returns {void} + */ + function callLastArgument(...args) { + process.nextTick(args.at(-1), null); + } + + // copy into clean area so as not to get "infected" by this project's .eslintrc files + before(function () { + /* + * GitHub Actions Windows and macOS runners occasionally exhibit + * extremely slow filesystem operations, during which copying fixtures + * exceeds the default test timeout, so raise it just for this hook. + * Mocha uses `this` to set timeouts on an individual hook level. + */ + this.timeout(60 * 1000); // eslint-disable-line no-invalid-this -- Mocha API + shell.mkdir("-p", fixtureDir); + shell.cp("-r", "./tests/fixtures/.", fixtureDir); + }); + + beforeEach(() => { + ({ LegacyESLint } = require("../../../lib/eslint/legacy-eslint")); + }); + + after(() => { + shell.rm("-r", fixtureDir); + }); + + describe("ESLint constructor function", () => { + it("should have a static property indicating the configType being used", () => { + assert.strictEqual(LegacyESLint.configType, "eslintrc"); + }); + + it("the default value of 'options.cwd' should be the current working directory.", async () => { + process.chdir(__dirname); + try { + const engine = new LegacyESLint({ useEslintrc: false }); + const results = await engine.lintFiles("eslint.js"); + + assert.strictEqual( + path.dirname(results[0].filePath), + __dirname, + ); + } finally { + process.chdir(originalDir); + } + }); + + it("should normalize 'options.cwd'.", async () => { + const cwd = getFixturePath("example-app3"); + const engine = new LegacyESLint({ + cwd: `${cwd}${path.sep}foo${path.sep}..`, // `/foo/..` should be normalized to `` + useEslintrc: false, + overrideConfig: { + plugins: ["test"], + rules: { + "test/report-cwd": "error", + }, + }, + }); + const results = await engine.lintText(""); + + assert.strictEqual( + results[0].messages[0].ruleId, + "test/report-cwd", + ); + assert.strictEqual(results[0].messages[0].message, cwd); + + const formatter = await engine.loadFormatter("cwd"); + + assert.strictEqual(formatter.format(results), cwd); + }); + + it("should report one fatal message when given a path by --ignore-path that is not a file when ignore is true.", () => { + assert.throws( + () => { + // eslint-disable-next-line no-new -- Check for throwing + new LegacyESLint({ ignorePath: fixtureDir }); + }, + new RegExp( + escapeStringRegExp( + `Cannot read .eslintignore file: ${fixtureDir}\nError: EISDIR: illegal operation on a directory, read`, + ), + "u", + ), + ); + }); + + // https://github.com/eslint/eslint/issues/2380 + it("should not modify baseConfig when format is specified", () => { + const customBaseConfig = { root: true }; + + new LegacyESLint({ baseConfig: customBaseConfig }); // eslint-disable-line no-new -- Check for argument side effects + + assert.deepStrictEqual(customBaseConfig, { root: true }); + }); + + it("should throw readable messages if removed options are present", () => { + assert.throws( + () => + new LegacyESLint({ + cacheFile: "", + configFile: "", + envs: [], + globals: [], + ignorePattern: [], + parser: "", + parserOptions: {}, + rules: {}, + plugins: [], + }), + new RegExp( + escapeStringRegExp( + [ + "Invalid Options:", + "- Unknown options: cacheFile, configFile, envs, globals, ignorePattern, parser, parserOptions, rules", + "- 'cacheFile' has been removed. Please use the 'cacheLocation' option instead.", + "- 'configFile' has been removed. Please use the 'overrideConfigFile' option instead.", + "- 'envs' has been removed. Please use the 'overrideConfig.env' option instead.", + "- 'globals' has been removed. Please use the 'overrideConfig.globals' option instead.", + "- 'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.", + "- 'parser' has been removed. Please use the 'overrideConfig.parser' option instead.", + "- 'parserOptions' has been removed. Please use the 'overrideConfig.parserOptions' option instead.", + "- 'rules' has been removed. Please use the 'overrideConfig.rules' option instead.", + "- 'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.", + ].join("\n"), + ), + "u", + ), + ); + }); + + it("should throw readable messages if wrong type values are given to options", () => { + assert.throws( + () => + new LegacyESLint({ + allowInlineConfig: "", + baseConfig: "", + cache: "", + cacheLocation: "", + cwd: "foo", + errorOnUnmatchedPattern: "", + extensions: "", + fix: "", + fixTypes: ["xyz"], + globInputPaths: "", + ignore: "", + ignorePath: "", + overrideConfig: "", + overrideConfigFile: "", + plugins: "", + reportUnusedDisableDirectives: "", + resolvePluginsRelativeTo: "", + rulePaths: "", + useEslintrc: "", + }), + new RegExp( + escapeStringRegExp( + [ + "Invalid Options:", + "- 'allowInlineConfig' must be a boolean.", + "- 'baseConfig' must be an object or null.", + "- 'cache' must be a boolean.", + "- 'cacheLocation' must be a non-empty string.", + "- 'cwd' must be an absolute path.", + "- 'errorOnUnmatchedPattern' must be a boolean.", + "- 'extensions' must be an array of non-empty strings or null.", + "- 'fix' must be a boolean or a function.", + '- \'fixTypes\' must be an array of any of "directive", "problem", "suggestion", and "layout".', + "- 'globInputPaths' must be a boolean.", + "- 'ignore' must be a boolean.", + "- 'ignorePath' must be a non-empty string or null.", + "- 'overrideConfig' must be an object or null.", + "- 'overrideConfigFile' must be a non-empty string or null.", + "- 'plugins' must be an object or null.", + '- \'reportUnusedDisableDirectives\' must be any of "error", "warn", "off", and null.', + "- 'resolvePluginsRelativeTo' must be a non-empty string or null.", + "- 'rulePaths' must be an array of non-empty strings.", + "- 'useEslintrc' must be a boolean.", + ].join("\n"), + ), + "u", + ), + ); + }); + + it("should throw readable messages if 'plugins' option contains empty key", () => { + assert.throws( + () => + new LegacyESLint({ + plugins: { + "eslint-plugin-foo": {}, + "eslint-plugin-bar": {}, + "": {}, + }, + }), + new RegExp( + escapeStringRegExp( + [ + "Invalid Options:", + "- 'plugins' must not include an empty string.", + ].join("\n"), + ), + "u", + ), + ); + }); + }); + + describe("hasFlag", () => { + let eslint; + + it("should return false if the flag is present and active", () => { + eslint = new LegacyESLint({ + cwd: getFixturePath(), + flags: ["test_only"], + }); + + assert.strictEqual(eslint.hasFlag("test_only"), false); + }); + + it("should return false if the flag is present and inactive", () => { + eslint = new LegacyESLint({ + cwd: getFixturePath(), + flags: ["test_only_old"], + }); + + assert.strictEqual(eslint.hasFlag("test_only_old"), false); + }); + + it("should return false if the flag is not present", () => { + eslint = new LegacyESLint({ cwd: getFixturePath() }); + + assert.strictEqual(eslint.hasFlag("x_feature"), false); + }); + }); + + describe("lintText()", () => { + let eslint; + + describe("when using local cwd .eslintrc", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: path.join(os.tmpdir(), "eslint/multiple-rules-config"), + files: { + ".eslintrc.json": { + root: true, + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2, + }, + env: { + node: true, + }, + }, + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should report the total and per file errors", async () => { + eslint = new LegacyESLint({ cwd: getPath() }); + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 5); + assert.strictEqual(results[0].messages[0].ruleId, "strict"); + assert.strictEqual(results[0].messages[1].ruleId, "no-var"); + assert.strictEqual( + results[0].messages[2].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[3].ruleId, "quotes"); + assert.strictEqual(results[0].messages[4].ruleId, "eol-last"); + assert.strictEqual(results[0].fixableErrorCount, 3); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 2); + assert.strictEqual( + results[0].usedDeprecatedRules[0].ruleId, + "quotes", + ); + assert.strictEqual( + results[0].usedDeprecatedRules[1].ruleId, + "eol-last", + ); + }); + + it("should report the total and per file warnings", async () => { + eslint = new LegacyESLint({ + cwd: getPath(), + overrideConfig: { + rules: { + quotes: 1, + "no-var": 1, + "eol-last": 1, + strict: 1, + "no-unused-vars": 1, + }, + }, + }); + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 5); + assert.strictEqual(results[0].messages[0].ruleId, "strict"); + assert.strictEqual(results[0].messages[1].ruleId, "no-var"); + assert.strictEqual( + results[0].messages[2].ruleId, + "no-unused-vars", + ); + assert.strictEqual(results[0].messages[3].ruleId, "quotes"); + assert.strictEqual(results[0].messages[4].ruleId, "eol-last"); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 3); + assert.strictEqual(results[0].usedDeprecatedRules.length, 2); + assert.strictEqual( + results[0].usedDeprecatedRules[0].ruleId, + "quotes", + ); + assert.strictEqual( + results[0].usedDeprecatedRules[1].ruleId, + "eol-last", + ); + }); + }); + + it("should report one message when using specific config file", async () => { + eslint = new LegacyESLint({ + overrideConfigFile: "fixtures/configurations/quotes-error.json", + useEslintrc: false, + cwd: getFixturePath(".."), + }); + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 1); + assert.strictEqual( + results[0].usedDeprecatedRules[0].ruleId, + "quotes", + ); + }); + + it("should report the filename when passed in", async () => { + eslint = new LegacyESLint({ + ignore: false, + cwd: getFixturePath(), + }); + const options = { filePath: "test.js" }; + const results = await eslint.lintText("var foo = 'bar';", options); + + assert.strictEqual(results[0].filePath, getFixturePath("test.js")); + }); + + it("should return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is true", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath(".eslintignore"), + cwd: getFixturePath(".."), + }); + const options = { + filePath: "fixtures/passing.js", + warnIgnored: true, + }; + const results = await eslint.lintText("var bar = foo;", options); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("passing.js"), + ); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override.', + ); + assert.strictEqual(results[0].messages[0].output, void 0); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].usedDeprecatedRules.length, 0); + }); + + it("should not return a warning when given a filename by --stdin-filename in excluded files list if warnIgnored is false", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath(".eslintignore"), + cwd: getFixturePath(".."), + }); + const options = { + filePath: "fixtures/passing.js", + warnIgnored: false, + }; + + // intentional parsing error + const results = await eslint.lintText("va r bar = foo;", options); + + // should not report anything because the file is ignored + assert.strictEqual(results.length, 0); + }); + + it("should suppress excluded file warnings by default", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath(".eslintignore"), + cwd: getFixturePath(".."), + }); + const options = { filePath: "fixtures/passing.js" }; + const results = await eslint.lintText("var bar = foo;", options); + + // should not report anything because there are no errors + assert.strictEqual(results.length, 0); + }); + + it("should return a message when given a filename by --stdin-filename in excluded files list and ignore is off", async () => { + eslint = new LegacyESLint({ + ignorePath: "fixtures/.eslintignore", + cwd: getFixturePath(".."), + ignore: false, + useEslintrc: false, + overrideConfig: { + rules: { + "no-undef": 2, + }, + }, + }); + const options = { filePath: "fixtures/passing.js" }; + const results = await eslint.lintText("var bar = foo;", options); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("passing.js"), + ); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[0].output, void 0); + }); + + it("should return a message and fixed text when in fix mode", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + fix: true, + overrideConfig: { + rules: { + semi: 2, + }, + }, + ignore: false, + cwd: getFixturePath(), + }); + const options = { filePath: "passing.js" }; + const results = await eslint.lintText("var bar = foo", options); + + assert.deepStrictEqual(results, [ + { + filePath: getFixturePath("passing.js"), + messages: [], + suppressedMessages: [], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var bar = foo;", + usedDeprecatedRules: [ + { + ruleId: "semi", + replacedBy: [], + }, + ], + }, + ]); + }); + + it("should use eslint:recommended rules when eslint:recommended configuration is specified", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + extends: ["eslint:recommended"], + }, + ignore: false, + cwd: getFixturePath(), + }); + const options = { filePath: "file.js" }; + const results = await eslint.lintText("foo ()", options); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + + it("should use eslint:all rules when eslint:all configuration is specified", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + extends: ["eslint:all"], + }, + ignore: false, + cwd: getFixturePath(), + }); + const options = { filePath: "file.js" }; + const results = await eslint.lintText( + "if (true) { foo() }", + options, + ); + + assert.strictEqual(results.length, 1); + + const { messages } = results[0]; + + // Some rules that should report errors in the given code. Not all, as we don't want to update this test when we add new rules. + const expectedRules = ["no-undef", "no-constant-condition"]; + + expectedRules.forEach(ruleId => { + const messageFromRule = messages.find( + message => message.ruleId === ruleId, + ); + + assert.ok( + typeof messageFromRule === "object" && + messageFromRule !== null, // LintMessage object + `Expected a message from rule '${ruleId}'`, + ); + assert.strictEqual(messageFromRule.severity, 2); + }); + }); + + it("correctly autofixes semicolon-conflicting-fixes", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + }); + const inputPath = getFixturePath( + "autofix/semicolon-conflicting-fixes.js", + ); + const outputPath = getFixturePath( + "autofix/semicolon-conflicting-fixes.expected.js", + ); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("correctly autofixes return-conflicting-fixes", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + }); + const inputPath = getFixturePath( + "autofix/return-conflicting-fixes.js", + ); + const outputPath = getFixturePath( + "autofix/return-conflicting-fixes.expected.js", + ); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + describe("Fix Types", () => { + it("should throw an error when an invalid fix type is specified", () => { + assert.throws(() => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layou"], + }); + }, /'fixTypes' must be an array of any of "directive", "problem", "suggestion", and "layout"\./iu); + }); + + it("should not fix any rules when fixTypes is used without fix", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: false, + fixTypes: ["layout"], + }); + const inputPath = getFixturePath("fix-types/fix-only-semi.js"); + const results = await eslint.lintFiles([inputPath]); + + assert.strictEqual(results[0].output, void 0); + }); + + it("should not fix non-style rules when fixTypes has only 'layout'", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"], + }); + const inputPath = getFixturePath("fix-types/fix-only-semi.js"); + const outputPath = getFixturePath( + "fix-types/fix-only-semi.expected.js", + ); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should not fix style or problem rules when fixTypes has only 'suggestion'", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["suggestion"], + }); + const inputPath = getFixturePath( + "fix-types/fix-only-prefer-arrow-callback.js", + ); + const outputPath = getFixturePath( + "fix-types/fix-only-prefer-arrow-callback.expected.js", + ); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should fix both style and problem rules when fixTypes has 'suggestion' and 'layout'", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["suggestion", "layout"], + }); + const inputPath = getFixturePath( + "fix-types/fix-both-semi-and-prefer-arrow-callback.js", + ); + const outputPath = getFixturePath( + "fix-types/fix-both-semi-and-prefer-arrow-callback.expected.js", + ); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should not throw an error when a rule doesn't have a 'meta' property", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"], + rulePaths: [getFixturePath("rules", "fix-types-test")], + }); + const inputPath = getFixturePath( + "fix-types/ignore-missing-meta.js", + ); + const outputPath = getFixturePath( + "fix-types/ignore-missing-meta.expected.js", + ); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should not throw an error when a rule is loaded after initialization with lintFiles()", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"], + plugins: { + test: { + rules: { + "no-program": require( + getFixturePath( + "rules", + "fix-types-test", + "no-program.js", + ), + ), + }, + }, + }, + }); + const inputPath = getFixturePath( + "fix-types/ignore-missing-meta.js", + ); + const outputPath = getFixturePath( + "fix-types/ignore-missing-meta.expected.js", + ); + const results = await eslint.lintFiles([inputPath]); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + + it("should not throw an error when a rule is loaded after initialization with lintText()", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + fixTypes: ["layout"], + plugins: { + test: { + rules: { + "no-program": require( + getFixturePath( + "rules", + "fix-types-test", + "no-program.js", + ), + ), + }, + }, + }, + }); + const inputPath = getFixturePath( + "fix-types/ignore-missing-meta.js", + ); + const outputPath = getFixturePath( + "fix-types/ignore-missing-meta.expected.js", + ); + const results = await eslint.lintText( + fs.readFileSync(inputPath, { encoding: "utf8" }), + { filePath: inputPath }, + ); + const expectedOutput = fs.readFileSync(outputPath, "utf8"); + + assert.strictEqual(results[0].output, expectedOutput); + }); + }); + + it("should return a message and omit fixed text when in fix mode and fixes aren't done", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + fix: true, + overrideConfig: { + rules: { + "no-undef": 2, + }, + }, + ignore: false, + cwd: getFixturePath(), + }); + const options = { filePath: "passing.js" }; + const results = await eslint.lintText("var bar = foo", options); + + assert.deepStrictEqual(results, [ + { + filePath: getFixturePath("passing.js"), + messages: [ + { + ruleId: "no-undef", + severity: 2, + messageId: "undef", + message: "'foo' is not defined.", + line: 1, + column: 11, + endLine: 1, + endColumn: 14, + nodeType: "Identifier", + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "var bar = foo", + usedDeprecatedRules: [], + }, + ]); + }); + + it("should not delete code if there is a syntax error after trying to autofix.", async () => { + eslint = eslintWithPlugins({ + useEslintrc: false, + fix: true, + overrideConfig: { + plugins: ["example"], + rules: { + "example/make-syntax-error": "error", + }, + }, + ignore: false, + cwd: getFixturePath(), + }); + const options = { filePath: "test.js" }; + const results = await eslint.lintText("var bar = foo", options); + + assert.deepStrictEqual(results, [ + { + filePath: getFixturePath("test.js"), + messages: [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Parsing error: Unexpected token is", + line: 1, + column: 19, + nodeType: null, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: "var bar = foothis is a syntax error.", + usedDeprecatedRules: [], + }, + ]); + }); + + it("should not crash even if there are any syntax error since the first time.", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + fix: true, + overrideConfig: { + rules: { + "example/make-syntax-error": "error", + }, + }, + ignore: false, + cwd: getFixturePath(), + }); + const options = { filePath: "test.js" }; + const results = await eslint.lintText("var bar =", options); + + assert.deepStrictEqual(results, [ + { + filePath: getFixturePath("test.js"), + messages: [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Parsing error: Unexpected token", + line: 1, + column: 10, + nodeType: null, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "var bar =", + usedDeprecatedRules: [], + }, + ]); + }); + + it("should return source code of file in `source` property when errors are present", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { semi: 2 }, + }, + }); + const results = await eslint.lintText("var foo = 'bar'"); + + assert.strictEqual(results[0].source, "var foo = 'bar'"); + }); + + it("should return source code of file in `source` property when warnings are present", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { semi: 1 }, + }, + }); + const results = await eslint.lintText("var foo = 'bar'"); + + assert.strictEqual(results[0].source, "var foo = 'bar'"); + }); + + it("should not return a `source` property when no errors or warnings are present", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { semi: 2 }, + }, + }); + const results = await eslint.lintText("var foo = 'bar';"); + + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[0].source, void 0); + }); + + it("should not return a `source` property when fixes are applied", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + fix: true, + overrideConfig: { + rules: { + semi: 2, + "no-unused-vars": 2, + }, + }, + }); + const results = await eslint.lintText("var msg = 'hi' + foo\n"); + + assert.strictEqual(results[0].source, void 0); + assert.strictEqual(results[0].output, "var msg = 'hi' + foo;\n"); + }); + + it("should return a `source` property when a parsing error has occurred", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { eqeqeq: 2 }, + }, + }); + const results = await eslint.lintText( + "var bar = foothis is a syntax error.\n return bar;", + ); + + assert.deepStrictEqual(results, [ + { + filePath: "", + messages: [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Parsing error: Unexpected token is", + line: 1, + column: 19, + nodeType: null, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 1, + fixableErrorCount: 0, + fixableWarningCount: 0, + source: "var bar = foothis is a syntax error.\n return bar;", + usedDeprecatedRules: [], + }, + ]); + }); + + // https://github.com/eslint/eslint/issues/5547 + it("should respect default ignore rules, even with --no-ignore", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath(), + ignore: false, + }); + const results = await eslint.lintText("var bar = foo;", { + filePath: "node_modules/passing.js", + warnIgnored: true, + }); + const expectedMsg = + "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("node_modules/passing.js"), + ); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + }); + + describe('plugin shorthand notation ("@scope" for "@scope/eslint-plugin")', () => { + const Module = require("node:module"); + let originalFindPath = null; + + /* eslint-disable no-underscore-dangle -- Override Node API */ + before(() => { + originalFindPath = Module._findPath; + Module._findPath = function (id, ...otherArgs) { + if (id === "@scope/eslint-plugin") { + return path.resolve( + __dirname, + "../../fixtures/plugin-shorthand/basic/node_modules/@scope/eslint-plugin/index.js", + ); + } + return originalFindPath.call(this, id, ...otherArgs); + }; + }); + after(() => { + Module._findPath = originalFindPath; + }); + /* eslint-enable no-underscore-dangle -- Override Node API */ + + it("should resolve 'plugins:[\"@scope\"]' to 'node_modules/@scope/eslint-plugin'.", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("plugin-shorthand/basic"), + }); + const [result] = await eslint.lintText("var x = 0", { + filePath: "index.js", + }); + + assert.strictEqual( + result.filePath, + getFixturePath("plugin-shorthand/basic/index.js"), + ); + assert.strictEqual(result.messages[0].ruleId, "@scope/rule"); + assert.strictEqual(result.messages[0].message, "OK"); + }); + + it("should resolve 'extends:[\"plugin:@scope/recommended\"]' to 'node_modules/@scope/eslint-plugin'.", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("plugin-shorthand/extends"), + }); + const [result] = await eslint.lintText("var x = 0", { + filePath: "index.js", + }); + + assert.strictEqual( + result.filePath, + getFixturePath("plugin-shorthand/extends/index.js"), + ); + assert.strictEqual(result.messages[0].ruleId, "@scope/rule"); + assert.strictEqual(result.messages[0].message, "OK"); + }); + }); + + it("should warn when deprecated rules are found in a config", async () => { + eslint = new LegacyESLint({ + cwd: originalDir, + useEslintrc: false, + overrideConfigFile: + "tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml", + }); + const [result] = await eslint.lintText("foo"); + + assert.deepStrictEqual(result.usedDeprecatedRules, [ + { ruleId: "indent-legacy", replacedBy: [] }, + ]); + }); + + it("should throw if non-string value is given to 'code' parameter", async () => { + eslint = new LegacyESLint(); + await assert.rejects( + () => eslint.lintText(100), + /'code' must be a string/u, + ); + }); + + it("should throw if non-object value is given to 'options' parameter", async () => { + eslint = new LegacyESLint(); + await assert.rejects( + () => eslint.lintText("var a = 0", "foo.js"), + /'options' must be an object, null, or undefined/u, + ); + }); + + it("should throw if 'options' argument contains unknown key", async () => { + eslint = new LegacyESLint(); + await assert.rejects( + () => eslint.lintText("var a = 0", { filename: "foo.js" }), + /'options' must not include the unknown option\(s\): filename/u, + ); + }); + + it("should throw if non-string value is given to 'options.filePath' option", async () => { + eslint = new LegacyESLint(); + await assert.rejects( + () => eslint.lintText("var a = 0", { filePath: "" }), + /'options.filePath' must be a non-empty string or undefined/u, + ); + }); + + it("should throw if non-boolean value is given to 'options.warnIgnored' option", async () => { + eslint = new LegacyESLint(); + await assert.rejects( + () => eslint.lintText("var a = 0", { warnIgnored: "" }), + /'options.warnIgnored' must be a boolean or undefined/u, + ); + }); + }); + + describe("lintFiles()", () => { + /** @type {InstanceType} */ + let eslint; + + it("should use correct parser when custom parser is specified", async () => { + eslint = new LegacyESLint({ + cwd: originalDir, + ignore: false, + }); + const filePath = path.resolve( + __dirname, + "../../fixtures/configurations/parser/custom.js", + ); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].message, + "Parsing error: Boom!", + ); + }); + + it("should report zero messages when given a config file and a valid file", async () => { + eslint = new LegacyESLint({ + cwd: originalDir, + useEslintrc: false, + ignore: false, + overrideConfigFile: + "tests/fixtures/simple-valid-project/.eslintrc.js", + }); + const results = await eslint.lintFiles([ + "tests/fixtures/simple-valid-project/**/foo*.js", + ]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should handle multiple patterns with overlapping files", async () => { + eslint = new LegacyESLint({ + cwd: originalDir, + useEslintrc: false, + ignore: false, + overrideConfigFile: + "tests/fixtures/simple-valid-project/.eslintrc.js", + }); + const results = await eslint.lintFiles([ + "tests/fixtures/simple-valid-project/**/foo*.js", + "tests/fixtures/simple-valid-project/foo.?s", + "tests/fixtures/simple-valid-project/{foo,src/foobar}.js", + ]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should report zero messages when given a config file and a valid file and espree as parser", async () => { + eslint = new LegacyESLint({ + overrideConfig: { + parser: "espree", + parserOptions: { + ecmaVersion: 2021, + }, + }, + useEslintrc: false, + }); + const results = await eslint.lintFiles(["lib/cli.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should report zero messages when given a config file and a valid file and esprima as parser", async () => { + eslint = new LegacyESLint({ + overrideConfig: { + parser: "esprima", + }, + useEslintrc: false, + ignore: false, + }); + const results = await eslint.lintFiles([ + "tests/fixtures/passing.js", + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should throw an error when given a config file and a valid file and invalid parser", async () => { + eslint = new LegacyESLint({ + overrideConfig: { + parser: "test11", + }, + useEslintrc: false, + }); + + await assert.rejects( + async () => await eslint.lintFiles(["lib/cli.js"]), + /Cannot find module 'test11'/u, + ); + }); + + describe("Invalid inputs", () => { + [ + ["an empty string", ""], + ["an empty array", []], + ["a string with a single space", " "], + ["an array with one empty string", [""]], + ["an array with two empty strings", ["", ""]], + ].forEach(([name, value]) => { + it(`should throw an error when passed ${name}`, async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + }); + + await assert.rejects( + async () => await eslint.lintFiles(value), + /'patterns' must be a non-empty string or an array of non-empty strings/u, + ); + }); + + if ( + value === "" || + (Array.isArray(value) && value.length === 0) + ) { + it(`should not throw an error when passed ${name} and passOnNoPatterns: true`, async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + passOnNoPatterns: true, + }); + + const results = await eslint.lintFiles(value); + + assert.strictEqual(results.length, 0); + }); + } + }); + }); + + it("should report zero messages when given a directory with a .js2 file", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + extensions: [".js2"], + }); + const results = await eslint.lintFiles([ + getFixturePath("files/foo.js2"), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should fall back to defaults when extensions is set to an empty array", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("configurations"), + overrideConfigFile: getFixturePath( + "configurations", + "quotes-error.json", + ), + extensions: [], + }); + const results = await eslint.lintFiles([ + getFixturePath("single-quoted.js"), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should report zero messages when given a directory with a .js and a .js2 file", async () => { + eslint = new LegacyESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: getFixturePath(".."), + }); + const results = await eslint.lintFiles(["fixtures/files/"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should report zero messages when given a '**' pattern with a .js and a .js2 file", async () => { + eslint = new LegacyESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles(["fixtures/files/*"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should resolve globs when 'globInputPaths' option is true", async () => { + eslint = new LegacyESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: getFixturePath(".."), + }); + const results = await eslint.lintFiles(["fixtures/files/*"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should not resolve globs when 'globInputPaths' option is false", async () => { + eslint = new LegacyESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: getFixturePath(".."), + globInputPaths: false, + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["fixtures/files/*"]); + }, /No files matching 'fixtures\/files\/\*' were found \(glob was disabled\)\./u); + }); + + it("should report on all files passed explicitly, even if ignored by default", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + }); + const results = await eslint.lintFiles(["node_modules/foo.js"]); + const expectedMsg = + "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override."; + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + }); + + it("should report on globs with explicit inclusion of dotfiles, even though ignored by default", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + overrideConfig: { + rules: { + quotes: [2, "single"], + }, + }, + }); + const results = await eslint.lintFiles([ + "hidden/.hiddenfolder/*.js", + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should not check default ignored files without --no-ignore flag", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["node_modules"]); + }, /All files matched by 'node_modules' are ignored\./u); + }); + + // https://github.com/eslint/eslint/issues/5547 + it("should not check node_modules files even with --no-ignore flag", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + ignore: false, + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["node_modules"]); + }, /All files matched by 'node_modules' are ignored\./u); + }); + + it("should not check .hidden files if they are passed explicitly without --no-ignore flag", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath(".."), + useEslintrc: false, + overrideConfig: { + rules: { + quotes: [2, "single"], + }, + }, + }); + const results = await eslint.lintFiles(["fixtures/files/.bar.js"]); + const expectedMsg = + "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + }); + + // https://github.com/eslint/eslint/issues/12873 + it("should not check files within a .hidden folder if they are passed explicitly without the --no-ignore flag", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + useEslintrc: false, + overrideConfig: { + rules: { + quotes: [2, "single"], + }, + }, + }); + const results = await eslint.lintFiles([ + "hidden/.hiddenfolder/double-quotes.js", + ]); + const expectedMsg = + "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!'\") to override."; + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].message, expectedMsg); + }); + + it("should check .hidden files if they are passed explicitly with --no-ignore flag", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath(".."), + ignore: false, + useEslintrc: false, + overrideConfig: { + rules: { + quotes: [2, "single"], + }, + }, + }); + const results = await eslint.lintFiles(["fixtures/files/.bar.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + }); + + it("should check .hidden files if they are unignored with an --ignore-pattern", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + ignore: true, + useEslintrc: false, + overrideConfig: { + ignorePatterns: "!.hidden*", + rules: { + quotes: [2, "single"], + }, + }, + }); + const results = await eslint.lintFiles(["hidden/"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + }); + + it("should report zero messages when given a pattern with a .js and a .js2 file", async () => { + eslint = new LegacyESLint({ + extensions: [".js", ".js2"], + ignore: false, + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles(["fixtures/files/*.?s*"]); + + assert.strictEqual(results.length, 2); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual(results[1].messages.length, 0); + }); + + it("should return one error message when given a config with rules with options and severity level set to error", async () => { + eslint = new LegacyESLint({ + cwd: getFixturePath("configurations"), + overrideConfigFile: getFixturePath( + "configurations", + "quotes-error.json", + ), + }); + const results = await eslint.lintFiles([ + getFixturePath("single-quoted.js"), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].errorCount, 1); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 1); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should return 3 messages when given a config file and a directory of 3 valid files", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath( + "configurations", + "semi-error.json", + ), + }); + const fixturePath = getFixturePath("formatters"); + const results = await eslint.lintFiles([fixturePath]); + + assert.strictEqual(results.length, 5); + assert.strictEqual( + path.relative(fixturePath, results[0].filePath), + "async.js", + ); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual( + path.relative(fixturePath, results[1].filePath), + "broken.js", + ); + assert.strictEqual(results[1].errorCount, 0); + assert.strictEqual(results[1].warningCount, 0); + assert.strictEqual(results[1].fixableErrorCount, 0); + assert.strictEqual(results[1].fixableWarningCount, 0); + assert.strictEqual(results[1].messages.length, 0); + assert.strictEqual( + path.relative(fixturePath, results[2].filePath), + "cwd.js", + ); + assert.strictEqual(results[2].errorCount, 0); + assert.strictEqual(results[2].warningCount, 0); + assert.strictEqual(results[2].fixableErrorCount, 0); + assert.strictEqual(results[2].fixableWarningCount, 0); + assert.strictEqual(results[2].messages.length, 0); + assert.strictEqual( + path.relative(fixturePath, results[3].filePath), + "simple.js", + ); + assert.strictEqual(results[3].errorCount, 0); + assert.strictEqual(results[3].warningCount, 0); + assert.strictEqual(results[3].fixableErrorCount, 0); + assert.strictEqual(results[3].fixableWarningCount, 0); + assert.strictEqual(results[3].messages.length, 0); + assert.strictEqual( + path.relative(fixturePath, results[4].filePath), + path.join("test", "simple.js"), + ); + assert.strictEqual(results[4].errorCount, 0); + assert.strictEqual(results[4].warningCount, 0); + assert.strictEqual(results[4].fixableErrorCount, 0); + assert.strictEqual(results[4].fixableWarningCount, 0); + assert.strictEqual(results[4].messages.length, 0); + }); + + it("should process when file is given by not specifying extensions", async () => { + eslint = new LegacyESLint({ + ignore: false, + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles(["fixtures/files/foo.js2"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages when given a config with environment set to browser", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath( + "configurations", + "env-browser.json", + ), + }); + const results = await eslint.lintFiles([ + fs.realpathSync(getFixturePath("globals-browser.js")), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages when given an option to set environment to browser", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfig: { + env: { browser: true }, + rules: { + "no-alert": 0, + "no-undef": 2, + }, + }, + }); + const results = await eslint.lintFiles([ + fs.realpathSync(getFixturePath("globals-browser.js")), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages when given a config with environment set to Node.js", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath( + "configurations", + "env-node.json", + ), + }); + const results = await eslint.lintFiles([ + fs.realpathSync(getFixturePath("globals-node.js")), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should not return results from previous call when calling more than once", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + ignore: false, + overrideConfig: { + rules: { + semi: 2, + }, + }, + }); + const failFilePath = fs.realpathSync( + getFixturePath("missing-semicolon.js"), + ); + const passFilePath = fs.realpathSync(getFixturePath("passing.js")); + + let results = await eslint.lintFiles([failFilePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, failFilePath); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].severity, 2); + + results = await eslint.lintFiles([passFilePath]); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, passFilePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should throw an error when given a directory with all eslint excluded files in the directory", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath(".eslintignore"), + }); + + await assert.rejects( + async () => { + await eslint.lintFiles([getFixturePath("./cli-engine/")]); + }, + new RegExp( + escapeStringRegExp( + `All files matched by '${getFixturePath("./cli-engine/")}' are ignored.`, + ), + "u", + ), + ); + }); + + it("should throw an error when all given files are ignored", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + ignorePath: getFixturePath(".eslintignore"), + }); + await assert.rejects(async () => { + await eslint.lintFiles(["tests/fixtures/cli-engine/"]); + }, /All files matched by 'tests\/fixtures\/cli-engine\/' are ignored\./u); + }); + + it("should throw an error when all given files are ignored even with a `./` prefix", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + ignorePath: getFixturePath(".eslintignore"), + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); + }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); + }); + + // https://github.com/eslint/eslint/issues/3788 + it("should ignore one-level down node_modules when ignore file has 'node_modules/' in it", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath( + "cli-engine", + "nested_node_modules", + ".eslintignore", + ), + useEslintrc: false, + overrideConfig: { + rules: { + quotes: [2, "double"], + }, + }, + cwd: getFixturePath("cli-engine", "nested_node_modules"), + }); + const results = await eslint.lintFiles(["."]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + // https://github.com/eslint/eslint/issues/3812 + it("should ignore all files and throw an error when tests/fixtures/ is in ignore file", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath("cli-engine/.eslintignore2"), + useEslintrc: false, + overrideConfig: { + rules: { + quotes: [2, "double"], + }, + }, + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["./tests/fixtures/cli-engine/"]); + }, /All files matched by '\.\/tests\/fixtures\/cli-engine\/' are ignored\./u); + }); + + // https://github.com/eslint/eslint/issues/15642 + it("should ignore files that are ignored by patterns with escaped brackets", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath( + "ignored-paths", + ".eslintignoreWithEscapedBrackets", + ), + useEslintrc: false, + cwd: getFixturePath("ignored-paths"), + }); + + // Only `brackets/index.js` should be linted. Other files in `brackets/` should be ignored. + const results = await eslint.lintFiles(["brackets/*.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + getFixturePath("ignored-paths", "brackets", "index.js"), + ); + }); + + it("should throw an error when all given files are ignored via ignore-pattern", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + ignorePatterns: "tests/fixtures/single-quoted.js", + }, + }); + + await assert.rejects(async () => { + await eslint.lintFiles(["tests/fixtures/*-quoted.js"]); + }, /All files matched by 'tests\/fixtures\/\*-quoted\.js' are ignored\./u); + }); + + it("should not throw an error when ignorePatterns is an empty array", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + ignorePatterns: [], + }, + }); + + await assert.doesNotReject(async () => { + await eslint.lintFiles(["*.js"]); + }); + }); + + it("should return a warning when an explicitly given file is ignored", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath(".eslintignore"), + cwd: getFixturePath(), + }); + const filePath = getFixturePath("passing.js"); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual( + results[0].messages[0].message, + 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override.', + ); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should return two messages when given a file in excluded files list while ignore is off", async () => { + eslint = new LegacyESLint({ + ignorePath: getFixturePath(".eslintignore"), + ignore: false, + overrideConfig: { + rules: { + "no-undef": 2, + }, + }, + }); + const filePath = fs.realpathSync(getFixturePath("undef.js")); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages[0].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-undef"); + assert.strictEqual(results[0].messages[1].severity, 2); + }); + + it("should return zero messages when executing a file with a shebang", async () => { + eslint = new LegacyESLint({ + ignore: false, + }); + const results = await eslint.lintFiles([ + getFixturePath("shebang.js"), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should give a warning when loading a custom rule that doesn't exist", async () => { + eslint = new LegacyESLint({ + ignore: false, + rulePaths: [getFixturePath("rules", "dir1")], + overrideConfigFile: getFixturePath( + "rules", + "missing-rule.json", + ), + }); + const results = await eslint.lintFiles([ + getFixturePath("rules", "test", "test-custom-rule.js"), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "missing-rule"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual( + results[0].messages[0].message, + "Definition for rule 'missing-rule' was not found.", + ); + }); + + it("should throw an error when loading a bad custom rule", async () => { + eslint = new LegacyESLint({ + ignore: false, + rulePaths: [getFixturePath("rules", "wrong")], + overrideConfigFile: getFixturePath("rules", "eslint.json"), + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + getFixturePath("rules", "test", "test-custom-rule.js"), + ]); + }, /Error while loading rule 'custom-rule'/u); + }); + + it("should throw an error when loading a function-style custom rule", async () => { + eslint = new LegacyESLint({ + ignore: false, + useEslintrc: false, + rulePaths: [getFixturePath("rules", "function-style")], + overrideConfig: { + rules: { + "no-strings": "error", + }, + }, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + getFixturePath("rules", "test", "test-custom-rule.js"), + ]); + }, /Error while loading rule 'no-strings': Rule must be an object with a `create` method/u); + }); + + it("should return one message when a custom rule matches a file", async () => { + eslint = new LegacyESLint({ + ignore: false, + useEslintrc: false, + rulePaths: [getFixturePath("rules/")], + overrideConfigFile: getFixturePath("rules", "eslint.json"), + }); + const filePath = fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "custom-rule"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + it("should load custom rule from the provided cwd", async () => { + const cwd = path.resolve(getFixturePath("rules")); + + eslint = new LegacyESLint({ + ignore: false, + cwd, + rulePaths: ["./"], + overrideConfigFile: "eslint.json", + }); + const filePath = fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "custom-rule"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + it("should return messages when multiple custom rules match a file", async () => { + eslint = new LegacyESLint({ + ignore: false, + rulePaths: [ + getFixturePath("rules", "dir1"), + getFixturePath("rules", "dir2"), + ], + overrideConfigFile: getFixturePath( + "rules", + "multi-rulesdirs.json", + ), + }); + const filePath = fs.realpathSync( + getFixturePath("rules", "test-multi-rulesdirs.js"), + ); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "no-literals"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-strings"); + assert.strictEqual(results[0].messages[1].severity, 2); + }); + + it("should return zero messages when executing without useEslintrc flag", async () => { + eslint = new LegacyESLint({ + ignore: false, + useEslintrc: false, + }); + const filePath = fs.realpathSync( + getFixturePath("missing-semicolon.js"), + ); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages when executing without useEslintrc flag in Node.js environment", async () => { + eslint = new LegacyESLint({ + ignore: false, + useEslintrc: false, + overrideConfig: { + env: { node: true }, + }, + }); + const filePath = fs.realpathSync(getFixturePath("process-exit.js")); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages and ignore .eslintrc files when executing with no-eslintrc flag", async () => { + eslint = new LegacyESLint({ + ignore: false, + useEslintrc: false, + overrideConfig: { + env: { node: true }, + }, + }); + const filePath = fs.realpathSync( + getFixturePath("eslintrc", "quotes.js"), + ); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should return zero messages and ignore package.json files when executing with no-eslintrc flag", async () => { + eslint = new LegacyESLint({ + ignore: false, + useEslintrc: false, + overrideConfig: { + env: { node: true }, + }, + }); + const filePath = fs.realpathSync( + getFixturePath("packagejson", "quotes.js"), + ); + const results = await eslint.lintFiles([filePath]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].filePath, filePath); + assert.strictEqual(results[0].messages.length, 0); + }); + + it("should warn when deprecated rules are configured", async () => { + eslint = new LegacyESLint({ + cwd: originalDir, + useEslintrc: false, + overrideConfig: { + rules: { + "indent-legacy": 1, + "callback-return": 1, + }, + }, + }); + const results = await eslint.lintFiles(["lib/cli*.js"]); + + assert.deepStrictEqual(results[0].usedDeprecatedRules, [ + { ruleId: "indent-legacy", replacedBy: [] }, + { ruleId: "callback-return", replacedBy: [] }, + ]); + }); + + it("should not warn when deprecated rules are not configured", async () => { + eslint = new LegacyESLint({ + cwd: originalDir, + useEslintrc: false, + overrideConfig: { + rules: { eqeqeq: 1, "callback-return": 0 }, + }, + }); + const results = await eslint.lintFiles(["lib/cli*.js"]); + + assert.deepStrictEqual(results[0].usedDeprecatedRules, []); + }); + + it("should warn when deprecated rules are found in a config", async () => { + eslint = new LegacyESLint({ + cwd: originalDir, + overrideConfigFile: + "tests/fixtures/cli-engine/deprecated-rule-config/.eslintrc.yml", + useEslintrc: false, + }); + const results = await eslint.lintFiles(["lib/cli*.js"]); + + assert.deepStrictEqual(results[0].usedDeprecatedRules, [ + { ruleId: "indent-legacy", replacedBy: [] }, + ]); + }); + + describe("Fix Mode", () => { + it("should return fixed text on multiple files when in fix mode", async () => { + /** + * Converts CRLF to LF in output. + * This is a workaround for git's autocrlf option on Windows. + * @param {Object} result A result object to convert. + * @returns {void} + */ + function convertCRLF(result) { + if (result && result.output) { + result.output = result.output.replace(/\r\n/gu, "\n"); + } + } + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + fix: true, + overrideConfig: { + rules: { + semi: 2, + quotes: [2, "double"], + eqeqeq: 2, + "no-undef": 2, + "space-infix-ops": 2, + }, + }, + }); + const results = await eslint.lintFiles([ + path.resolve(fixtureDir, `${fixtureDir}/fixmode`), + ]); + + results.forEach(convertCRLF); + assert.deepStrictEqual(results, [ + { + filePath: fs.realpathSync( + path.resolve(fixtureDir, "fixmode/multipass.js"), + ), + messages: [], + suppressedMessages: [], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: 'true ? "yes" : "no";\n', + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi", + }, + { + replacedBy: [], + ruleId: "quotes", + }, + { + replacedBy: [], + ruleId: "space-infix-ops", + }, + ], + }, + { + filePath: fs.realpathSync( + path.resolve(fixtureDir, "fixmode/ok.js"), + ), + messages: [], + suppressedMessages: [], + errorCount: 0, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi", + }, + { + replacedBy: [], + ruleId: "quotes", + }, + { + replacedBy: [], + ruleId: "space-infix-ops", + }, + ], + }, + { + filePath: fs.realpathSync( + path.resolve( + fixtureDir, + "fixmode/quotes-semi-eqeqeq.js", + ), + ), + messages: [ + { + column: 9, + line: 2, + endColumn: 11, + endLine: 2, + message: "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + severity: 2, + suggestions: [ + { + data: { + actualOperator: "==", + expectedOperator: "===", + }, + desc: "Use '===' instead of '=='.", + fix: { + range: [24, 26], + text: "===", + }, + messageId: "replaceOperator", + }, + ], + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: 'var msg = "hi";\nif (msg == "hi") {\n\n}\n', + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi", + }, + { + replacedBy: [], + ruleId: "quotes", + }, + { + replacedBy: [], + ruleId: "space-infix-ops", + }, + ], + }, + { + filePath: fs.realpathSync( + path.resolve(fixtureDir, "fixmode/quotes.js"), + ), + messages: [ + { + column: 18, + line: 1, + endColumn: 21, + endLine: 1, + messageId: "undef", + message: "'foo' is not defined.", + nodeType: "Identifier", + ruleId: "no-undef", + severity: 2, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 0, + fixableWarningCount: 0, + output: 'var msg = "hi" + foo;\n', + usedDeprecatedRules: [ + { + replacedBy: [], + ruleId: "semi", + }, + { + replacedBy: [], + ruleId: "quotes", + }, + { + replacedBy: [], + ruleId: "space-infix-ops", + }, + ], + }, + ]); + }); + + it("should run autofix even if files are cached without autofix results", async () => { + const baseOptions = { + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + overrideConfig: { + rules: { + semi: 2, + quotes: [2, "double"], + eqeqeq: 2, + "no-undef": 2, + "space-infix-ops": 2, + }, + }, + }; + + eslint = new LegacyESLint( + Object.assign({}, baseOptions, { cache: true, fix: false }), + ); + + // Do initial lint run and populate the cache file + await eslint.lintFiles([ + path.resolve(fixtureDir, `${fixtureDir}/fixmode`), + ]); + + eslint = new LegacyESLint( + Object.assign({}, baseOptions, { cache: true, fix: true }), + ); + const results = await eslint.lintFiles([ + path.resolve(fixtureDir, `${fixtureDir}/fixmode`), + ]); + + assert(results.some(result => result.output)); + }); + }); + + // These tests have to do with https://github.com/eslint/eslint/issues/963 + + describe("configuration hierarchy", () => { + // Default configuration - blank + it("should return zero messages when executing with no .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // No default configuration rules - conf/environments.js (/*eslint-env node*/) + it("should return zero messages when executing with no .eslintrc in the Node.js environment", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/console-wrong-quotes-node.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Project configuration - first level .eslintrc + it("should return zero messages when executing with .eslintrc in the Node.js environment", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/process-exit.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Project configuration - first level .eslintrc + it("should return zero messages when executing with .eslintrc in the Node.js environment", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/process-exit.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Project configuration - first level .eslintrc + it("should return one message when executing with .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + + // Project configuration - second level .eslintrc + it("should return one message when executing with local .eslintrc that overrides parent .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-console"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Project configuration - third level .eslintrc + it("should return one message when executing with local .eslintrc that overrides parent and grandparent .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/subbroken/subsubbroken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Project configuration - first level package.json + it("should return one message when executing with package.json", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/packagejson/subdir/wrong-quotes.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Project configuration - second level package.json + it("should return zero messages when executing with local package.json that overrides parent package.json", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/packagejson/subdir/subsubdir/wrong-quotes.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Project configuration - third level package.json + it("should return one message when executing with local package.json that overrides parent and grandparent package.json", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/packagejson/subdir/subsubdir/subsubsubdir/wrong-quotes.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + + // Project configuration - .eslintrc overrides package.json in same directory + it("should return one message when executing with .eslintrc that overrides a package.json in the same directory", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/packagejson/wrong-quotes.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + }); + + // Command line configuration - --config with first level .eslintrc + it("should return two messages when executing with config file that adds to local .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/add-conf.yaml`, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 2); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); + assert.strictEqual(results[0].messages[1].severity, 1); + }); + + // Command line configuration - --config with first level .eslintrc + it("should return no messages when executing with config file that overrides local .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/override-conf.yaml`, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Command line configuration - --config with second level .eslintrc + it("should return two messages when executing with config file that adds to local and parent .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/add-conf.yaml`, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "no-console"); + assert.strictEqual(results[0].messages[0].severity, 1); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); + assert.strictEqual(results[0].messages[1].severity, 1); + }); + + // Command line configuration - --config with second level .eslintrc + it("should return one message when executing with config file that overrides local and parent .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath( + "config-hierarchy/broken/override-conf.yaml", + ), + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/subbroken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "no-console"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Command line configuration - --config with first level .eslintrc + it("should return no messages when executing with config file that overrides local .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: `${fixtureDir}/config-hierarchy/broken/override-conf.yaml`, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + }); + + // Command line configuration - --rule with --config and first level .eslintrc + it("should return one message when executing with command line rule and config file that overrides local .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath( + "config-hierarchy/broken/override-conf.yaml", + ), + overrideConfig: { + rules: { + quotes: [1, "double"], + }, + }, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + `${fixtureDir}/config-hierarchy/broken/console-wrong-quotes.js`, + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + + // Command line configuration - --rule with --config and first level .eslintrc + it("should return one message when executing with command line rule and config file that overrides local .eslintrc", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath( + "/config-hierarchy/broken/override-conf.yaml", + ), + overrideConfig: { + rules: { + quotes: [1, "double"], + }, + }, + }); + const results = await eslint.lintFiles([ + getFixturePath( + "config-hierarchy/broken/console-wrong-quotes.js", + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "quotes"); + assert.strictEqual(results[0].messages[0].severity, 1); + }); + }); + + describe("plugins", () => { + it("should return two messages when executing with config file that specifies a plugin", async () => { + eslint = eslintWithPlugins({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath( + "configurations", + "plugins-with-prefix.json", + ), + useEslintrc: false, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test/test-custom-rule.js"), + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "example/example-rule", + ); + }); + + it("should return two messages when executing with config file that specifies a plugin with namespace", async () => { + eslint = eslintWithPlugins({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath( + "configurations", + "plugins-with-prefix-and-namespace.json", + ), + useEslintrc: false, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "@eslint/example/example-rule", + ); + }); + + it("should return two messages when executing with config file that specifies a plugin without prefix", async () => { + eslint = eslintWithPlugins({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath( + "configurations", + "plugins-without-prefix.json", + ), + useEslintrc: false, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "example/example-rule", + ); + }); + + it("should return two messages when executing with config file that specifies a plugin without prefix and with namespace", async () => { + eslint = eslintWithPlugins({ + cwd: path.join(fixtureDir, ".."), + overrideConfigFile: getFixturePath( + "configurations", + "plugins-without-prefix-with-namespace.json", + ), + useEslintrc: false, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "@eslint/example/example-rule", + ); + }); + + it("should return two messages when executing with cli option that specifies a plugin", async () => { + eslint = eslintWithPlugins({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + overrideConfig: { + plugins: ["example"], + rules: { "example/example-rule": 1 }, + }, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "example/example-rule", + ); + }); + + it("should return two messages when executing with cli option that specifies preloaded plugin", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + overrideConfig: { + plugins: ["test"], + rules: { "test/example-rule": 1 }, + }, + plugins: { + "eslint-plugin-test": { + rules: { + "example-rule": require("../../fixtures/rules/custom-rule"), + }, + }, + }, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "test/example-rule", + ); + }); + + it("should throw an error when executing with a function-style rule from a preloaded plugin", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + overrideConfig: { + plugins: ["test"], + rules: { "test/example-rule": 1 }, + }, + plugins: { + "eslint-plugin-test": { + rules: { "example-rule": () => ({}) }, + }, + }, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "rules", + "test", + "test-custom-rule.js", + ), + ), + ]); + }, /Error while loading rule 'test\/example-rule': Rule must be an object with a `create` method/u); + }); + + it("should return two messages when executing with `baseConfig` that extends preloaded plugin config", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + baseConfig: { + extends: ["plugin:test/preset"], + }, + plugins: { + test: { + rules: { + "example-rule": require("../../fixtures/rules/custom-rule"), + }, + configs: { + preset: { + rules: { + "test/example-rule": 1, + }, + plugins: ["test"], + }, + }, + }, + }, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual( + results[0].messages[0].ruleId, + "test/example-rule", + ); + }); + + it("should load plugins from the `loadPluginsRelativeTo` directory, if specified", async () => { + eslint = new LegacyESLint({ + resolvePluginsRelativeTo: getFixturePath("plugins"), + baseConfig: { + plugins: ["with-rules"], + rules: { "with-rules/rule1": "error" }, + }, + useEslintrc: false, + }); + const results = await eslint.lintText("foo"); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual( + results[0].messages[0].ruleId, + "with-rules/rule1", + ); + assert.strictEqual( + results[0].messages[0].message, + "Rule report from plugin", + ); + }); + + it("should throw an error when executing with a function-style rule from a plugin", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["with-function-style-rules"], + rules: { "with-function-style-rules/rule1": "error" }, + }, + useEslintrc: false, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "rules", + "test", + "test-custom-rule.js", + ), + ), + ]); + }, /Error while loading rule 'with-function-style-rules\/rule1': Rule must be an object with a `create` method/u); + }); + + it("should throw an error when executing with a rule with `schema:true` from a plugin", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-true"], + rules: { "schema-true/rule1": "error" }, + }, + useEslintrc: false, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "rules", + "test", + "test-custom-rule.js", + ), + ), + ]); + }, /Error while processing options validation schema of rule 'schema-true\/rule1': Rule's `meta.schema` must be an array or object/u); + }); + + it("should throw an error when executing with a rule with `schema:null` from a plugin", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-null"], + rules: { "schema-null/rule1": "error" }, + }, + useEslintrc: false, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "rules", + "test", + "test-custom-rule.js", + ), + ), + ]); + }, /Error while processing options validation schema of rule 'schema-null\/rule1': Rule's `meta.schema` must be an array or object/u); + }); + + it("should throw an error when executing with a rule with invalid JSON schema type from a plugin", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-invalid"], + rules: { "schema-invalid/rule1": "error" }, + }, + useEslintrc: false, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "rules", + "test", + "test-custom-rule.js", + ), + ), + ]); + }, /Error while processing options validation schema of rule 'schema-invalid\/rule1': minItems must be number/u); + }); + + it("should succesfully execute with a rule with `schema:false` from a plugin when no options were passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-false"], + rules: { "schema-false/rule1": "error" }, + }, + useEslintrc: false, + }); + + const [result] = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual( + result.messages[0].ruleId, + "schema-false/rule1", + ); + assert.strictEqual( + result.messages[0].message, + "No options were passed", + ); + }); + + it("should succesfully execute with a rule with `schema:false` from a plugin when an option is passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-false"], + rules: { "schema-false/rule1": ["error", "always"] }, + }, + useEslintrc: false, + }); + + const [result] = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual( + result.messages[0].ruleId, + "schema-false/rule1", + ); + assert.strictEqual( + result.messages[0].message, + "Option 'always' was passed", + ); + }); + + it("should succesfully execute with a rule with `schema:[]` from a plugin when no options were passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-empty-array"], + rules: { "schema-empty-array/rule1": "error" }, + }, + useEslintrc: false, + }); + + const [result] = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual( + result.messages[0].ruleId, + "schema-empty-array/rule1", + ); + assert.strictEqual(result.messages[0].message, "Hello"); + }); + + it("should throw when executing with a rule with `schema:[]` from a plugin when an option is passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-empty-array"], + rules: { + "schema-empty-array/rule1": ["error", "always"], + }, + }, + useEslintrc: false, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "rules", + "test", + "test-custom-rule.js", + ), + ), + ]); + }, /Configuration for rule "schema-empty-array\/rule1" is invalid.*should NOT have more than 0 items/su); + }); + + it("should succesfully execute with a rule with no schema from a plugin when no options were passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-missing"], + rules: { "schema-missing/rule1": "error" }, + }, + useEslintrc: false, + }); + + const [result] = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual( + result.messages[0].ruleId, + "schema-missing/rule1", + ); + assert.strictEqual(result.messages[0].message, "Hello"); + }); + + it("should throw when executing with a rule with no schema from a plugin when an option is passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-missing"], + rules: { "schema-missing/rule1": ["error", "always"] }, + }, + useEslintrc: false, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "rules", + "test", + "test-custom-rule.js", + ), + ), + ]); + }, /Configuration for rule "schema-missing\/rule1" is invalid.*should NOT have more than 0 items/su); + }); + + it("should succesfully execute with a rule with an array schema from a plugin when no options were passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-array"], + rules: { "schema-array/rule1": "error" }, + }, + useEslintrc: false, + }); + + const [result] = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual( + result.messages[0].ruleId, + "schema-array/rule1", + ); + assert.strictEqual( + result.messages[0].message, + "No options were passed", + ); + }); + + it("should succesfully execute with a rule with an array schema from a plugin when a correct option was passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-array"], + rules: { "schema-array/rule1": ["error", "always"] }, + }, + useEslintrc: false, + }); + + const [result] = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual( + result.messages[0].ruleId, + "schema-array/rule1", + ); + assert.strictEqual( + result.messages[0].message, + "Option 'always' was passed", + ); + }); + + it("should throw when executing with a rule with an array schema from a plugin when an incorrect option was passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-array"], + rules: { "schema-array/rule1": ["error", 5] }, + }, + useEslintrc: false, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "rules", + "test", + "test-custom-rule.js", + ), + ), + ]); + }, /Configuration for rule "schema-array\/rule1" is invalid.*Value 5 should be string/su); + }); + + it("should throw when executing with a rule with an array schema from a plugin when an extra option was passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-array"], + rules: { + "schema-array/rule1": ["error", "always", "never"], + }, + }, + useEslintrc: false, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "rules", + "test", + "test-custom-rule.js", + ), + ), + ]); + }, /Configuration for rule "schema-array\/rule1" is invalid.*should NOT have more than 1 items/su); + }); + + it("should succesfully execute with a rule with an object schema from a plugin when no options were passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-object"], + rules: { "schema-object/rule1": "error" }, + }, + useEslintrc: false, + }); + + const [result] = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual( + result.messages[0].ruleId, + "schema-object/rule1", + ); + assert.strictEqual( + result.messages[0].message, + "No options were passed", + ); + }); + + it("should succesfully execute with a rule with an object schema from a plugin when a correct option was passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-object"], + rules: { "schema-object/rule1": ["error", "always"] }, + }, + useEslintrc: false, + }); + + const [result] = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath("rules", "test", "test-custom-rule.js"), + ), + ]); + + assert.strictEqual(result.messages.length, 1); + assert.strictEqual( + result.messages[0].ruleId, + "schema-object/rule1", + ); + assert.strictEqual( + result.messages[0].message, + "Option 'always' was passed", + ); + }); + + it("should throw when executing with a rule with an object schema from a plugin when an incorrect option was passed", async () => { + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, "plugins"), + baseConfig: { + plugins: ["schema-object"], + rules: { "schema-object/rule1": ["error", 5] }, + }, + useEslintrc: false, + }); + + await assert.rejects(async () => { + await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "rules", + "test", + "test-custom-rule.js", + ), + ), + ]); + }, /Configuration for rule "schema-object\/rule1" is invalid.*Value 5 should be string/su); + }); + }); + + describe("cache", () => { + /** + * helper method to delete a file without caring about exceptions + * @param {string} filePath The file path + * @returns {void} + */ + function doDelete(filePath) { + try { + fs.unlinkSync(filePath); + } catch { + /* + * we don't care if the file didn't exist, since our + * intention was to remove the file + */ + } + } + + let cacheFilePath; + + beforeEach(() => { + cacheFilePath = null; + }); + + afterEach(() => { + sinon.restore(); + if (cacheFilePath) { + doDelete(cacheFilePath); + } + }); + + describe("when cacheLocation is a directory or looks like a directory", () => { + const cwd = getFixturePath(); + + /** + * helper method to delete the cache files created during testing + * @returns {void} + */ + function deleteCacheDir() { + try { + fs.rmSync(path.resolve(cwd, "tmp/.cacheFileDir/"), { + recursive: true, + force: true, + }); + } catch { + /* + * we don't care if the file didn't exist, since our + * intention was to remove the file + */ + } + } + beforeEach(() => { + deleteCacheDir(); + }); + + afterEach(() => { + deleteCacheDir(); + }); + + it("should create the directory and the cache file inside it when cacheLocation ends with a slash", async () => { + assert( + !shell.test( + "-d", + path.resolve(cwd, "./tmp/.cacheFileDir/"), + ), + "the cache directory already exists and wasn't successfully deleted", + ); + + eslint = new LegacyESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: "./tmp/.cacheFileDir/", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + ignore: false, + }); + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert( + shell.test( + "-f", + path.resolve( + cwd, + `./tmp/.cacheFileDir/.cache_${hash(cwd)}`, + ), + ), + "the cache for eslint should have been created", + ); + }); + + it("should create the cache file inside existing cacheLocation directory when cacheLocation ends with a slash", async () => { + assert( + !shell.test( + "-d", + path.resolve(cwd, "./tmp/.cacheFileDir/"), + ), + "the cache directory already exists and wasn't successfully deleted", + ); + + fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { + recursive: true, + }); + + eslint = new LegacyESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: "./tmp/.cacheFileDir/", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + ignore: false, + }); + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert( + shell.test( + "-f", + path.resolve( + cwd, + `./tmp/.cacheFileDir/.cache_${hash(cwd)}`, + ), + ), + "the cache for eslint should have been created", + ); + }); + + it("should create the cache file inside existing cacheLocation directory when cacheLocation doesn't end with a path separator", async () => { + assert( + !shell.test( + "-d", + path.resolve(cwd, "./tmp/.cacheFileDir/"), + ), + "the cache directory already exists and wasn't successfully deleted", + ); + + fs.mkdirSync(path.resolve(cwd, "./tmp/.cacheFileDir/"), { + recursive: true, + }); + + eslint = new LegacyESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: "./tmp/.cacheFileDir", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + ignore: false, + }); + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert( + shell.test( + "-f", + path.resolve( + cwd, + `./tmp/.cacheFileDir/.cache_${hash(cwd)}`, + ), + ), + "the cache for eslint should have been created", + ); + }); + }); + + it("should create the cache file inside cwd when no cacheLocation provided", async () => { + const cwd = path.resolve(getFixturePath("cli-engine")); + + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new LegacyESLint({ + useEslintrc: false, + cache: true, + cwd, + overrideConfig: { + rules: { + "no-console": 0, + }, + }, + extensions: ["js"], + ignore: false, + }); + const file = getFixturePath("cli-engine", "console.js"); + + await eslint.lintFiles([file]); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created at provided cwd", + ); + }); + + it("should invalidate the cache if the configuration changed between executions", async () => { + const cwd = getFixturePath("cache/src"); + + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new LegacyESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + ignore: false, + }); + + let spy = sinon.spy(fs, "readFileSync"); + + let file = getFixturePath("cache/src", "test-file.js"); + + file = fs.realpathSync(file); + const results = await eslint.lintFiles([file]); + + for (const { errorCount, warningCount } of results) { + assert.strictEqual( + errorCount + warningCount, + 0, + "the file should have passed linting without errors or warnings", + ); + } + assert( + spy.calledWith(file), + "ESLint should have read the file because there was no cache file", + ); + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created", + ); + + // destroy the spy + sinon.restore(); + + eslint = new LegacyESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 2, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + ignore: false, + }); + + // create a new spy + spy = sinon.spy(fs, "readFileSync"); + + const [newResult] = await eslint.lintFiles([file]); + + assert( + spy.calledWith(file), + "ESLint should have read the file again because it's considered changed because the config changed", + ); + assert.strictEqual( + newResult.errorCount, + 1, + "since configuration changed the cache should have not been used and one error should have been reported", + ); + assert.strictEqual(newResult.messages[0].ruleId, "no-console"); + assert( + shell.test("-f", cacheFilePath), + "The cache for ESLint should still exist", + ); + }); + + it("should remember the files from a previous run and do not operate on them if not changed", async () => { + const cwd = getFixturePath("cache/src"); + + cacheFilePath = path.resolve(cwd, ".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new LegacyESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + ignore: false, + }); + + let spy = sinon.spy(fs, "readFileSync"); + + let file = getFixturePath("cache/src", "test-file.js"); + + file = fs.realpathSync(file); + + const result = await eslint.lintFiles([file]); + + assert( + spy.calledWith(file), + "ESLint should have read the file because there was no cache file", + ); + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created", + ); + + // destroy the spy + sinon.restore(); + + eslint = new LegacyESLint({ + useEslintrc: false, + cwd, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + ignore: false, + }); + + // create a new spy + spy = sinon.spy(fs, "readFileSync"); + + const cachedResult = await eslint.lintFiles([file]); + + assert.deepStrictEqual( + result, + cachedResult, + "the result should have been the same", + ); + + // assert the file was not processed because the cache was used + assert( + !spy.calledWith(file), + "the file should not have been reloaded", + ); + }); + + it("when `cacheLocation` is specified, should create the cache file with `cache:true` and then delete it with `cache:false`", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + const eslintOptions = { + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + cwd: path.join(fixtureDir, ".."), + }; + + eslint = new LegacyESLint(eslintOptions); + + let file = getFixturePath("cache/src", "test-file.js"); + + file = fs.realpathSync(file); + + await eslint.lintFiles([file]); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created", + ); + + eslintOptions.cache = false; + eslint = new LegacyESLint(eslintOptions); + + await eslint.lintFiles([file]); + + assert( + !shell.test("-f", cacheFilePath), + "the cache for eslint should have been deleted since last run did not use the cache", + ); + }); + + it("should not throw an error if the cache file to be deleted does not exist on a read-only file system", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + // Simulate a read-only file system. + sinon.stub(fs, "unlinkSync").throws( + Object.assign(new Error("read-only file system"), { + code: "EROFS", + }), + ); + + const eslintOptions = { + useEslintrc: false, + + // specifying cache true the cache will be created + cache: false, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + cwd: path.join(fixtureDir, ".."), + }; + + eslint = new LegacyESLint(eslintOptions); + + const file = getFixturePath("cache/src", "test-file.js"); + + await eslint.lintFiles([file]); + + assert( + fs.unlinkSync.calledWithExactly(cacheFilePath), + "Expected attempt to delete the cache was not made.", + ); + }); + + it("should store in the cache a file that has lint messages and a file that doesn't have lint messages", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + }); + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + const result = await eslint.lintFiles([badFile, goodFile]); + const [badFileResult, goodFileResult] = result; + + assert.notStrictEqual( + badFileResult.errorCount + badFileResult.warningCount, + 0, + "the bad file should have some lint errors or warnings", + ); + assert.strictEqual( + goodFileResult.errorCount + badFileResult.warningCount, + 0, + "the good file should have passed linting without errors or warnings", + ); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created", + ); + + const fileCache = fCache.createFromFile(cacheFilePath); + const { cache } = fileCache; + + assert.strictEqual( + typeof cache.getKey(goodFile), + "object", + "the entry for the good file should have been in the cache", + ); + assert.strictEqual( + typeof cache.getKey(badFile), + "object", + "the entry for the bad file should have been in the cache", + ); + const cachedResult = await eslint.lintFiles([ + badFile, + goodFile, + ]); + + assert.deepStrictEqual( + result, + cachedResult, + "result should be the same with or without cache", + ); + }); + + it("should not contain in the cache a file that was deleted", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + }); + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + const toBeDeletedFile = fs.realpathSync( + getFixturePath("cache/src", "file-to-delete.js"), + ); + + await eslint.lintFiles([badFile, goodFile, toBeDeletedFile]); + const fileCache = fCache.createFromFile(cacheFilePath); + let { cache } = fileCache; + + assert.strictEqual( + typeof cache.getKey(toBeDeletedFile), + "object", + "the entry for the file to be deleted should have been in the cache", + ); + + // delete the file from the file system + fs.unlinkSync(toBeDeletedFile); + + /* + * file-entry-cache@2.0.0 will remove from the cache deleted files + * even when they were not part of the array of files to be analyzed + */ + await eslint.lintFiles([badFile, goodFile]); + + cache = JSON.parse(fs.readFileSync(cacheFilePath)); + + assert.strictEqual( + typeof cache[0][toBeDeletedFile], + "undefined", + "the entry for the file to be deleted should not have been in the cache", + ); + + // make sure that the previos assertion checks the right place + assert.notStrictEqual( + typeof cache[0][badFile], + "undefined", + "the entry for the bad file should have been in the cache", + ); + assert.notStrictEqual( + typeof cache[0][goodFile], + "undefined", + "the entry for the good file should have been in the cache", + ); + }); + + it("should contain files that were not visited in the cache provided they still exist", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + }); + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + const testFile2 = fs.realpathSync( + getFixturePath("cache/src", "test-file2.js"), + ); + + await eslint.lintFiles([badFile, goodFile, testFile2]); + + let fileCache = fCache.createFromFile(cacheFilePath); + let { cache } = fileCache; + + assert.strictEqual( + typeof cache.getKey(testFile2), + "object", + "the entry for the test-file2 should have been in the cache", + ); + + /* + * we pass a different set of files minus test-file2 + * previous version of file-entry-cache would remove the non visited + * entries. 2.0.0 version will keep them unless they don't exist + */ + await eslint.lintFiles([badFile, goodFile]); + + fileCache = fCache.createFromFile(cacheFilePath); + cache = fileCache.cache; + + assert.strictEqual( + typeof cache.getKey(testFile2), + "object", + "the entry for the test-file2 should have been in the cache", + ); + }); + + it("should not delete cache when executing on text", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + }); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should exist", + ); + + await eslint.lintText("var foo = 'bar';"); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should still exist", + ); + }); + + it("should not delete cache when executing on text with a provided filename", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + }); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should exist", + ); + + await eslint.lintText("var bar = foo;", { + filePath: "fixtures/passing.js", + }); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should still exist", + ); + }); + + it("should not delete cache when executing on files with --cache flag", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + fs.writeFileSync(cacheFilePath, ""); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + }); + const file = getFixturePath("cli-engine", "console.js"); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should exist", + ); + + await eslint.lintFiles([file]); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should still exist", + ); + }); + + it("should delete cache when executing on files without --cache flag", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + fs.writeFileSync(cacheFilePath, "[]"); // intenationally invalid to additionally make sure it isn't used + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + }); + const file = getFixturePath("cli-engine", "console.js"); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should exist", + ); + + await eslint.lintFiles([file]); + + assert( + !shell.test("-f", cacheFilePath), + "the cache for eslint should have been deleted", + ); + }); + + it("should use the specified cache file", async () => { + cacheFilePath = path.resolve(".cache/custom-cache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new LegacyESLint({ + useEslintrc: false, + + // specify a custom cache file + cacheLocation: cacheFilePath, + + // specifying cache true the cache will be created + cache: true, + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + cwd: path.join(fixtureDir, ".."), + }); + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + const result = await eslint.lintFiles([badFile, goodFile]); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created", + ); + + const fileCache = fCache.createFromFile(cacheFilePath); + const { cache } = fileCache; + + assert( + typeof cache.getKey(goodFile) === "object", + "the entry for the good file should have been in the cache", + ); + assert( + typeof cache.getKey(badFile) === "object", + "the entry for the bad file should have been in the cache", + ); + + const cachedResult = await eslint.lintFiles([ + badFile, + goodFile, + ]); + + assert.deepStrictEqual( + result, + cachedResult, + "result should be the same with or without cache", + ); + }); + + // https://github.com/eslint/eslint/issues/13507 + it("should not store `usedDeprecatedRules` in the cache file", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + const deprecatedRuleId = "space-in-parens"; + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + [deprecatedRuleId]: 2, + }, + }, + }); + + const filePath = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + + /* + * Run linting on the same file 3 times to cover multiple cases: + * Run 1: Lint result wasn't already cached. + * Run 2: Lint result was already cached. The cached lint result is used but the cache is reconciled before the run ends. + * Run 3: Lint result was already cached. The cached lint result was being used throughout the previous run, so possible + * mutations in the previous run that occured after the cache was reconciled may have side effects for this run. + */ + for (let i = 0; i < 3; i++) { + const [result] = await eslint.lintFiles([filePath]); + + assert( + result.usedDeprecatedRules && + result.usedDeprecatedRules.some( + rule => rule.ruleId === deprecatedRuleId, + ), + "the deprecated rule should have been in result.usedDeprecatedRules", + ); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created", + ); + + const fileCache = fCache.create(cacheFilePath); + const descriptor = fileCache.getFileDescriptor(filePath); + + assert( + typeof descriptor === "object", + "an entry for the file should have been in the cache file", + ); + assert( + typeof descriptor.meta.results === "object", + "lint result for the file should have been in its cache entry in the cache file", + ); + assert( + typeof descriptor.meta.results.usedDeprecatedRules === + "undefined", + "lint result in the cache file contains `usedDeprecatedRules`", + ); + } + }); + + // https://github.com/eslint/eslint/issues/13507 + it("should store `source` as `null` in the cache file if the lint result has `source` property", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + overrideConfig: { + rules: { + "no-unused-vars": 2, + }, + }, + }); + + const filePath = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + + /* + * Run linting on the same file 3 times to cover multiple cases: + * Run 1: Lint result wasn't already cached. + * Run 2: Lint result was already cached. The cached lint result is used but the cache is reconciled before the run ends. + * Run 3: Lint result was already cached. The cached lint result was being used throughout the previous run, so possible + * mutations in the previous run that occured after the cache was reconciled may have side effects for this run. + */ + for (let i = 0; i < 3; i++) { + const [result] = await eslint.lintFiles([filePath]); + + assert( + typeof result.source === "string", + "the result should have contained the `source` property", + ); + + assert( + shell.test("-f", cacheFilePath), + "the cache for eslint should have been created", + ); + + const fileCache = fCache.create(cacheFilePath); + const descriptor = fileCache.getFileDescriptor(filePath); + + assert( + typeof descriptor === "object", + "an entry for the file should have been in the cache file", + ); + assert( + typeof descriptor.meta.results === "object", + "lint result for the file should have been in its cache entry in the cache file", + ); + + // if the lint result contains `source`, it should be stored as `null` in the cache file + assert.strictEqual( + descriptor.meta.results.source, + null, + "lint result in the cache file contains non-null `source`", + ); + } + }); + + describe("cacheStrategy", () => { + it("should detect changes using a file's modification time when set to 'metadata'", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + cacheStrategy: "metadata", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + }); + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + + await eslint.lintFiles([badFile, goodFile]); + let fileCache = fCache.createFromFile(cacheFilePath); + const entries = fileCache.normalizeEntries([ + badFile, + goodFile, + ]); + + entries.forEach(entry => { + assert( + entry.changed === false, + `the entry for ${entry.key} should have been initially unchanged`, + ); + }); + + // this should result in a changed entry + shell.touch(goodFile); + fileCache = fCache.createFromFile(cacheFilePath); + assert( + fileCache.getFileDescriptor(badFile).changed === false, + `the entry for ${badFile} should have been unchanged`, + ); + assert( + fileCache.getFileDescriptor(goodFile).changed === true, + `the entry for ${goodFile} should have been changed`, + ); + }); + + it("should not detect changes using a file's modification time when set to 'content'", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + cacheStrategy: "content", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + }); + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + + await eslint.lintFiles([badFile, goodFile]); + let fileCache = fCache.createFromFile(cacheFilePath, true); + let entries = fileCache.normalizeEntries([ + badFile, + goodFile, + ]); + + entries.forEach(entry => { + assert( + entry.changed === false, + `the entry for ${entry.key} should have been initially unchanged`, + ); + }); + + // this should NOT result in a changed entry + shell.touch(goodFile); + fileCache = fCache.createFromFile(cacheFilePath, true); + entries = fileCache.normalizeEntries([badFile, goodFile]); + entries.forEach(entry => { + assert( + entry.changed === false, + `the entry for ${entry.key} should have remained unchanged`, + ); + }); + }); + + it("should detect changes using a file's contents when set to 'content'", async () => { + cacheFilePath = getFixturePath(".eslintcache"); + doDelete(cacheFilePath); + assert( + !shell.test("-f", cacheFilePath), + "the cache file already exists and wasn't successfully deleted", + ); + + eslint = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + + // specifying cache true the cache will be created + cache: true, + cacheLocation: cacheFilePath, + cacheStrategy: "content", + overrideConfig: { + rules: { + "no-console": 0, + "no-unused-vars": 2, + }, + }, + extensions: ["js"], + }); + const badFile = fs.realpathSync( + getFixturePath("cache/src", "fail-file.js"), + ); + const goodFile = fs.realpathSync( + getFixturePath("cache/src", "test-file.js"), + ); + const goodFileCopy = path.resolve( + `${path.dirname(goodFile)}`, + "test-file-copy.js", + ); + + shell.cp(goodFile, goodFileCopy); + + await eslint.lintFiles([badFile, goodFileCopy]); + let fileCache = fCache.createFromFile(cacheFilePath, true); + const entries = fileCache.normalizeEntries([ + badFile, + goodFileCopy, + ]); + + entries.forEach(entry => { + assert( + entry.changed === false, + `the entry for ${entry.key} should have been initially unchanged`, + ); + }); + + // this should result in a changed entry + shell.sed("-i", "abc", "xzy", goodFileCopy); + fileCache = fCache.createFromFile(cacheFilePath, true); + assert( + fileCache.getFileDescriptor(badFile).changed === false, + `the entry for ${badFile} should have been unchanged`, + ); + assert( + fileCache.getFileDescriptor(goodFileCopy).changed === + true, + `the entry for ${goodFileCopy} should have been changed`, + ); + }); + }); + }); + + describe("processors", () => { + it("should return two messages when executing with config file that specifies a processor", async () => { + eslint = eslintWithPlugins({ + overrideConfigFile: getFixturePath( + "configurations", + "processors.json", + ), + useEslintrc: false, + extensions: ["js", "txt"], + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "processors", + "test", + "test-processor.txt", + ), + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + }); + + it("should return two messages when executing with config file that specifies preloaded processor", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + plugins: ["test-processor"], + rules: { + "no-console": 2, + "no-unused-vars": 2, + }, + }, + extensions: ["js", "txt"], + cwd: path.join(fixtureDir, ".."), + plugins: { + "test-processor": { + processors: { + ".txt": { + preprocess(text) { + return [text]; + }, + postprocess(messages) { + return messages[0]; + }, + }, + }, + }, + }, + }); + const results = await eslint.lintFiles([ + fs.realpathSync( + getFixturePath( + "processors", + "test", + "test-processor.txt", + ), + ), + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + }); + + it("should run processors when calling lintFiles with config file that specifies a processor", async () => { + eslint = eslintWithPlugins({ + overrideConfigFile: getFixturePath( + "configurations", + "processors.json", + ), + useEslintrc: false, + extensions: ["js", "txt"], + cwd: path.join(fixtureDir, ".."), + }); + const results = await eslint.lintFiles([ + getFixturePath("processors", "test", "test-processor.txt"), + ]); + + assert.strictEqual( + results[0].messages[0].message, + "'b' is defined but never used.", + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "post-processed", + ); + }); + + it("should run processors when calling lintFiles with config file that specifies preloaded processor", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + plugins: ["test-processor"], + rules: { + "no-console": 2, + "no-unused-vars": 2, + }, + }, + extensions: ["js", "txt"], + cwd: path.join(fixtureDir, ".."), + plugins: { + "test-processor": { + processors: { + ".txt": { + preprocess(text) { + return [text.replace("a()", "b()")]; + }, + postprocess(messages) { + messages[0][0].ruleId = + "post-processed"; + return messages[0]; + }, + }, + }, + }, + }, + }); + const results = await eslint.lintFiles([ + getFixturePath("processors", "test", "test-processor.txt"), + ]); + + assert.strictEqual( + results[0].messages[0].message, + "'b' is defined but never used.", + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "post-processed", + ); + }); + + it("should run processors when calling lintText with config file that specifies a processor", async () => { + eslint = eslintWithPlugins({ + overrideConfigFile: getFixturePath( + "configurations", + "processors.json", + ), + useEslintrc: false, + extensions: ["js", "txt"], + ignore: false, + }); + const results = await eslint.lintText( + 'function a() {console.log("Test");}', + { + filePath: + "tests/fixtures/processors/test/test-processor.txt", + }, + ); + + assert.strictEqual( + results[0].messages[0].message, + "'b' is defined but never used.", + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "post-processed", + ); + }); + + it("should run processors when calling lintText with config file that specifies preloaded processor", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + plugins: ["test-processor"], + rules: { + "no-console": 2, + "no-unused-vars": 2, + }, + }, + extensions: ["js", "txt"], + ignore: false, + plugins: { + "test-processor": { + processors: { + ".txt": { + preprocess(text) { + return [text.replace("a()", "b()")]; + }, + postprocess(messages) { + messages[0][0].ruleId = + "post-processed"; + return messages[0]; + }, + }, + }, + }, + }, + }); + const results = await eslint.lintText( + 'function a() {console.log("Test");}', + { + filePath: + "tests/fixtures/processors/test/test-processor.txt", + }, + ); + + assert.strictEqual( + results[0].messages[0].message, + "'b' is defined but never used.", + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "post-processed", + ); + }); + + it("should run processors when calling lintText with processor resolves same extension but different content correctly", async () => { + let count = 0; + + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + plugins: ["test-processor"], + overrides: [ + { + files: ["**/*.txt/*.txt"], + rules: { + "no-console": 2, + "no-unused-vars": 2, + }, + }, + ], + }, + extensions: ["txt"], + ignore: false, + plugins: { + "test-processor": { + processors: { + ".txt": { + preprocess(text) { + count++; + return [ + { + // it will be run twice, and text will be as-is at the second time, then it will not run third time + text: text.replace( + "a()", + "b()", + ), + filename: ".txt", + }, + ]; + }, + postprocess(messages) { + messages[0][0].ruleId = + "post-processed"; + return messages[0]; + }, + }, + }, + }, + }, + }); + const results = await eslint.lintText( + 'function a() {console.log("Test");}', + { + filePath: + "tests/fixtures/processors/test/test-processor.txt", + }, + ); + + assert.strictEqual(count, 2); + assert.strictEqual( + results[0].messages[0].message, + "'b' is defined but never used.", + ); + assert.strictEqual( + results[0].messages[0].ruleId, + "post-processed", + ); + }); + + describe("autofixing with processors", () => { + const HTML_PROCESSOR = Object.freeze({ + preprocess(text) { + return [ + text + .replace(/^", + { filePath: "foo.html" }, + ); + + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual( + results[0].output, + "", + ); + }); + + it("should not run in autofix mode when using a processor that does not support autofixing", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + plugins: ["test-processor"], + rules: { + semi: 2, + }, + }, + extensions: ["js", "txt"], + ignore: false, + fix: true, + plugins: { + "test-processor": { + processors: { ".html": HTML_PROCESSOR }, + }, + }, + }); + const results = await eslint.lintText( + "", + { filePath: "foo.html" }, + ); + + assert.strictEqual(results[0].messages.length, 1); + assert(!Object.hasOwn(results[0], "output")); + }); + + it("should not run in autofix mode when `fix: true` is not provided, even if the processor supports autofixing", async () => { + eslint = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + plugins: ["test-processor"], + rules: { + semi: 2, + }, + }, + extensions: ["js", "txt"], + ignore: false, + plugins: { + "test-processor": { + processors: { + ".html": Object.assign( + { supportsAutofix: true }, + HTML_PROCESSOR, + ), + }, + }, + }, + }); + const results = await eslint.lintText( + "", + { filePath: "foo.html" }, + ); + + assert.strictEqual(results[0].messages.length, 1); + assert(!Object.hasOwn(results[0], "output")); + }); + }); + }); + + describe("Patterns which match no file should throw errors.", () => { + beforeEach(() => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + useEslintrc: false, + }); + }); + + it("one file", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["non-exist.js"]); + }, /No files matching 'non-exist\.js' were found\./u); + }); + + it("should throw if the directory exists and is empty", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["empty"]); + }, /No files matching 'empty' were found\./u); + }); + + it("one glob pattern", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["non-exist/**/*.js"]); + }, /No files matching 'non-exist\/\*\*\/\*\.js' were found\./u); + }); + + it("two files", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["aaa.js", "bbb.js"]); + }, /No files matching 'aaa\.js' were found\./u); + }); + + it("a mix of an existing file and a non-existing file", async () => { + await assert.rejects(async () => { + await eslint.lintFiles(["console.js", "non-exist.js"]); + }, /No files matching 'non-exist\.js' were found\./u); + }); + }); + + describe("overrides", () => { + beforeEach(() => { + eslint = new LegacyESLint({ + cwd: getFixturePath("cli-engine/overrides-with-dot"), + ignore: false, + }); + }); + + it("should recognize dotfiles", async () => { + const ret = await eslint.lintFiles([".test-target.js"]); + + assert.strictEqual(ret.length, 1); + assert.strictEqual(ret[0].messages.length, 1); + assert.strictEqual(ret[0].messages[0].ruleId, "no-unused-vars"); + }); + }); + + describe("a config file setting should have higher priority than a shareable config file's settings always; https://github.com/eslint/eslint/issues/11510", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: path.join(os.tmpdir(), "eslint/11510"), + files: { + "no-console-error-in-overrides.json": JSON.stringify({ + overrides: [ + { + files: ["*.js"], + rules: { "no-console": "error" }, + }, + ], + }), + ".eslintrc.json": JSON.stringify({ + extends: "./no-console-error-in-overrides.json", + rules: { "no-console": "off" }, + }), + "a.js": "console.log();", + }, + }); + + beforeEach(() => { + eslint = new LegacyESLint({ cwd: getPath() }); + return prepare(); + }); + + afterEach(cleanup); + + it("should not report 'no-console' error.", async () => { + const results = await eslint.lintFiles("a.js"); + + assert.strictEqual(results.length, 1); + assert.deepStrictEqual(results[0].messages, []); + }); + }); + + describe("configs of plugin rules should be validated even if 'plugins' key doesn't exist; https://github.com/eslint/eslint/issues/11559", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: path.join(os.tmpdir(), "eslint/11559"), + files: { + "node_modules/eslint-plugin-test/index.js": ` + exports.configs = { + recommended: { plugins: ["test"] } + }; + exports.rules = { + foo: { + meta: { schema: [{ type: "number" }] }, + create() { return {}; } + } + }; + `, + ".eslintrc.json": JSON.stringify({ + // Import via the recommended config. + extends: "plugin:test/recommended", + + // Has invalid option. + rules: { "test/foo": ["error", "invalid-option"] }, + }), + "a.js": "console.log();", + }, + }); + + beforeEach(() => { + eslint = new LegacyESLint({ cwd: getPath() }); + return prepare(); + }); + + afterEach(cleanup); + + it("should throw fatal error.", async () => { + await assert.rejects(async () => { + await eslint.lintFiles("a.js"); + }, /invalid-option/u); + }); + }); + + describe("'--fix-type' should not crash even if plugin rules exist; https://github.com/eslint/eslint/issues/11586", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: path.join(os.tmpdir(), "cli-engine/11586"), + files: { + "node_modules/eslint-plugin-test/index.js": ` + exports.rules = { + "no-example": { + meta: { type: "problem", fixable: "code" }, + create(context) { + return { + Identifier(node) { + if (node.name === "example") { + context.report({ + node, + message: "fix", + fix: fixer => fixer.replaceText(node, "fixed") + }) + } + } + }; + } + } + }; + `, + ".eslintrc.json": { + plugins: ["test"], + rules: { "test/no-example": "error" }, + }, + "a.js": "example;", + }, + }); + + beforeEach(() => { + eslint = new LegacyESLint({ + cwd: getPath(), + fix: true, + fixTypes: ["problem"], + }); + + return prepare(); + }); + + afterEach(cleanup); + + it("should not crash.", async () => { + const results = await eslint.lintFiles("a.js"); + + assert.strictEqual(results.length, 1); + assert.deepStrictEqual(results[0].messages, []); + assert.deepStrictEqual(results[0].output, "fixed;"); + }); + }); + + describe("multiple processors", () => { + const root = path.join( + os.tmpdir(), + "eslint/eslint/multiple-processors", + ); + const commonFiles = { + "node_modules/pattern-processor/index.js": fs.readFileSync( + require.resolve( + "../../fixtures/processors/pattern-processor", + ), + "utf8", + ), + "node_modules/eslint-plugin-markdown/index.js": ` + const { defineProcessor } = require("pattern-processor"); + const processor = defineProcessor(${/```(\w+)\n([\s\S]+?)\n```/gu}); + exports.processors = { + ".md": { ...processor, supportsAutofix: true }, + "non-fixable": processor + }; + `, + "node_modules/eslint-plugin-html/index.js": ` + const { defineProcessor } = require("pattern-processor"); + const processor = defineProcessor(${/ + + \`\`\` + `, + }; + + let cleanup; + + beforeEach(() => { + cleanup = () => {}; + }); + + afterEach(() => cleanup()); + + it("should lint only JavaScript blocks if '--ext' was not given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + }, + }, + }); + + cleanup = teardown.cleanup; + await teardown.prepare(); + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].line, 2); + }); + + it("should fix only JavaScript blocks if '--ext' was not given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ + cwd: teardown.getPath(), + fix: true, + }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual( + results[0].output, + unIndent` + \`\`\`js + console.log("hello");${/* ← fixed */ ""} + \`\`\` + \`\`\`html +
Hello
+ + + \`\`\` + `, + ); + }); + + it("should lint HTML blocks as well with multiple processors if '--ext' option was given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ + cwd: teardown.getPath(), + extensions: ["js", "html"], + }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block + assert.strictEqual(results[0].messages[1].line, 7); + }); + + it("should fix HTML blocks as well with multiple processors if '--ext' option was given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ + cwd: teardown.getPath(), + extensions: ["js", "html"], + fix: true, + }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 0); + assert.strictEqual( + results[0].output, + unIndent` + \`\`\`js + console.log("hello");${/* ← fixed */ ""} + \`\`\` + \`\`\`html +
Hello
+ + + \`\`\` + `, + ); + }); + + it("should use overridden processor; should report HTML blocks but not fix HTML blocks if the processor for '*.html' didn't support autofix.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + overrides: [ + { + files: "*.html", + processor: "html/non-fixable", // supportsAutofix: false + }, + ], + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ + cwd: teardown.getPath(), + extensions: ["js", "html"], + fix: true, + }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS Block in HTML Block + assert.strictEqual(results[0].messages[0].line, 7); + assert.strictEqual(results[0].messages[0].fix, void 0); + assert.strictEqual( + results[0].output, + unIndent` + \`\`\`js + console.log("hello");${/* ← fixed */ ""} + \`\`\` + \`\`\`html +
Hello
+ + + \`\`\` + `, + ); + }); + + it("should use the config '**/*.html/*.js' to lint JavaScript blocks in HTML.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + overrides: [ + { + files: "*.html", + + // this rules are not used because ESLint re-resolve configs if a code block had a different file extension. + rules: { + semi: "error", + "no-console": "off", + }, + }, + { + files: "**/*.html/*.js", + rules: { + semi: "off", + "no-console": "error", + }, + }, + ], + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ + cwd: teardown.getPath(), + extensions: ["js", "html"], + }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-console"); + assert.strictEqual(results[0].messages[1].line, 7); + }); + + it("should use the same config as one which has 'processor' property in order to lint blocks in HTML if the processor was legacy style.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + overrides: [ + { + files: "*.html", + processor: "html/legacy", // this processor returns strings rather than `{text, filename}` + rules: { + semi: "off", + "no-console": "error", + }, + }, + { + files: "**/*.html/*.js", + rules: { + semi: "error", + "no-console": "off", + }, + }, + ], + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ + cwd: teardown.getPath(), + extensions: ["js", "html"], + }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 3); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "no-console"); + assert.strictEqual(results[0].messages[1].line, 7); + assert.strictEqual(results[0].messages[2].ruleId, "no-console"); + assert.strictEqual(results[0].messages[2].line, 10); + }); + + it("should throw an error if invalid processor was specified.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + processor: "markdown/unknown", + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + + await assert.rejects(async () => { + await eslint.lintFiles(["test.md"]); + }, /ESLint configuration of processor in '\.eslintrc\.json' is invalid: 'markdown\/unknown' was not found\./u); + }); + + it("should lint HTML blocks as well with multiple processors if 'overrides[].files' is present.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ...commonFiles, + ".eslintrc.json": { + plugins: ["markdown", "html"], + rules: { semi: "error" }, + overrides: [ + { + files: "*.html", + processor: "html/.html", + }, + { + files: "*.md", + processor: "markdown/.md", + }, + ], + }, + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + const results = await eslint.lintFiles(["test.md"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 2); + assert.strictEqual(results[0].messages[0].ruleId, "semi"); // JS block + assert.strictEqual(results[0].messages[0].line, 2); + assert.strictEqual(results[0].messages[1].ruleId, "semi"); // JS block in HTML block + assert.strictEqual(results[0].messages[1].line, 7); + }); + }); + + describe("MODULE_NOT_FOUND error handling", () => { + const cwd = getFixturePath("module-not-found"); + + beforeEach(() => { + eslint = new LegacyESLint({ cwd }); + }); + + it("should throw an error with a message template when 'extends' property has a non-existence JavaScript config.", async () => { + try { + await eslint.lintText("test", { + filePath: "extends-js/test.js", + }); + } catch (err) { + assert.strictEqual( + err.messageTemplate, + "extend-config-missing", + ); + assert.deepStrictEqual(err.messageData, { + configName: "nonexistent-config", + importerName: getFixturePath( + "module-not-found", + "extends-js", + ".eslintrc.yml", + ), + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with a message template when 'extends' property has a non-existence plugin config.", async () => { + try { + await eslint.lintText("test", { + filePath: "extends-plugin/test.js", + }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, "plugin-missing"); + assert.deepStrictEqual(err.messageData, { + importerName: `extends-plugin${path.sep}.eslintrc.yml`, + pluginName: "eslint-plugin-nonexistent-plugin", + resolvePluginsRelativeTo: path.join( + cwd, + "extends-plugin", + ), // the directory of the config file. + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with a message template when 'plugins' property has a non-existence plugin.", async () => { + try { + await eslint.lintText("test", { + filePath: "plugins/test.js", + }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, "plugin-missing"); + assert.deepStrictEqual(err.messageData, { + importerName: `plugins${path.sep}.eslintrc.yml`, + pluginName: "eslint-plugin-nonexistent-plugin", + resolvePluginsRelativeTo: path.join(cwd, "plugins"), // the directory of the config file. + }); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when a JavaScript config threw a 'MODULE_NOT_FOUND' error.", async () => { + try { + await eslint.lintText("test", { + filePath: "throw-in-config-itself/test.js", + }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'extends' property has a JavaScript config that throws a 'MODULE_NOT_FOUND' error.", async () => { + try { + await eslint.lintText("test", { + filePath: "throw-in-extends-js/test.js", + }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'extends' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", async () => { + try { + await eslint.lintText("test", { + filePath: "throw-in-extends-plugin/test.js", + }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + + it("should throw an error with no message template when 'plugins' property has a plugin config that throws a 'MODULE_NOT_FOUND' error.", async () => { + try { + await eslint.lintText("test", { + filePath: "throw-in-plugins/test.js", + }); + } catch (err) { + assert.strictEqual(err.code, "MODULE_NOT_FOUND"); + assert.strictEqual(err.messageTemplate, void 0); + return; + } + assert.fail("Expected to throw an error"); + }); + }); + + describe("with '--rulesdir' option", () => { + const rootPath = getFixturePath("cli-engine/with-rulesdir"); + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: rootPath, + files: { + "internal-rules/test.js": ` + module.exports = { + create(context) { + return { + ExpressionStatement(node) { + context.report({ node, message: "ok" }); + }, + }; + }, + }; + `, + ".eslintrc.json": { + root: true, + rules: { test: "error" }, + }, + "test.js": "console.log('hello')", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should use the configured rules which are defined by '--rulesdir' option.", async () => { + eslint = new LegacyESLint({ + cwd: getPath(), + rulePaths: ["internal-rules"], + }); + const results = await eslint.lintFiles(["test.js"]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].messages.length, 1); + assert.strictEqual(results[0].messages[0].message, "ok"); + }); + }); + + describe("glob pattern '[ab].js'", () => { + const root = getFixturePath("cli-engine/unmatched-glob"); + + let cleanup; + + beforeEach(() => { + cleanup = () => {}; + }); + + afterEach(() => cleanup()); + + it("should match '[ab].js' if existed.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "a.js": "", + "b.js": "", + "ab.js": "", + "[ab].js": "", + ".eslintrc.yml": "root: true", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + const results = await eslint.lintFiles(["[ab].js"]); + const filenames = results.map(r => path.basename(r.filePath)); + + assert.deepStrictEqual(filenames, ["[ab].js"]); + }); + + it("should match 'a.js' and 'b.js' if '[ab].js' didn't existed.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "a.js": "", + "b.js": "", + "ab.js": "", + ".eslintrc.yml": "root: true", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + const results = await eslint.lintFiles(["[ab].js"]); + const filenames = results.map(r => path.basename(r.filePath)); + + assert.deepStrictEqual(filenames, ["a.js", "b.js"]); + }); + }); + + describe("with 'noInlineConfig' setting", () => { + const root = getFixturePath("cli-engine/noInlineConfig"); + + let cleanup; + + beforeEach(() => { + cleanup = () => {}; + }); + + afterEach(() => cleanup()); + + it("should warn directive comments if 'noInlineConfig' was given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "test.js": "/* globals foo */", + ".eslintrc.yml": "noInlineConfig: true", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml).", + ); + }); + + it("should show the config file what the 'noInlineConfig' came from.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-foo/index.js": + "module.exports = {noInlineConfig: true}", + "test.js": "/* globals foo */", + ".eslintrc.yml": "extends: foo", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "'/*globals*/' has no effect because you have 'noInlineConfig' setting in your config (.eslintrc.yml Âģ eslint-config-foo).", + ); + }); + }); + + describe("with 'reportUnusedDisableDirectives' setting", () => { + const root = getFixturePath( + "cli-engine/reportUnusedDisableDirectives", + ); + + let cleanup; + + beforeEach(() => { + cleanup = () => {}; + }); + + afterEach(() => cleanup()); + + it("should warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives' was given.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "test.js": "/* eslint-disable eqeqeq */", + ".eslintrc.yml": "reportUnusedDisableDirectives: true", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual( + messages[0].message, + "Unused eslint-disable directive (no problems were reported from 'eqeqeq').", + ); + }); + + describe("the runtime option overrides config files.", () => { + it("should not warn unused 'eslint-disable' comments if 'reportUnusedDisableDirectives=off' was given in runtime.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "test.js": "/* eslint-disable eqeqeq */", + ".eslintrc.yml": + "reportUnusedDisableDirectives: true", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new LegacyESLint({ + cwd: teardown.getPath(), + reportUnusedDisableDirectives: "off", + }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 0); + }); + + it("should warn unused 'eslint-disable' comments as error if 'reportUnusedDisableDirectives=error' was given in runtime.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "test.js": "/* eslint-disable eqeqeq */", + ".eslintrc.yml": + "reportUnusedDisableDirectives: true", + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + + eslint = new LegacyESLint({ + cwd: teardown.getPath(), + reportUnusedDisableDirectives: "error", + }); + + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + "Unused eslint-disable directive (no problems were reported from 'eqeqeq').", + ); + }); + }); + }); + + describe("with 'overrides[*].extends' setting on deep locations", () => { + const root = getFixturePath( + "cli-engine/deeply-overrides-i-extends", + ); + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify( + { + overrides: [{ files: ["*test*"], extends: "two" }], + }, + )}`, + "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify( + { + overrides: [{ files: ["*.js"], extends: "three" }], + }, + )}`, + "node_modules/eslint-config-three/index.js": `module.exports = ${JSON.stringify( + { + rules: { "no-console": "error" }, + }, + )}`, + "test.js": "console.log('hello')", + ".eslintrc.yml": "extends: one", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should not throw.", async () => { + eslint = new LegacyESLint({ cwd: getPath() }); + const results = await eslint.lintFiles(["test.js"]); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + }); + }); + + describe("don't ignore the entry directory.", () => { + const root = getFixturePath("cli-engine/dont-ignore-entry-dir"); + + let cleanup; + + beforeEach(() => { + cleanup = () => {}; + }); + + afterEach(async () => { + await cleanup(); + + const configFilePath = path.resolve(root, "../.eslintrc.json"); + + if (shell.test("-e", configFilePath)) { + shell.rm(configFilePath); + } + }); + + it('\'lintFiles(".")\' should not load config files from outside of ".".', async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "../.eslintrc.json": "BROKEN FILE", + ".eslintrc.json": JSON.stringify({ root: true }), + "index.js": 'console.log("hello")', + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + + // Don't throw "failed to load config file" error. + await eslint.lintFiles("."); + }); + + it("'lintFiles(\".\")' should not ignore '.' even if 'ignorePatterns' contains it.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + "../.eslintrc.json": { + ignorePatterns: ["/dont-ignore-entry-dir"], + }, + ".eslintrc.json": { root: true }, + "index.js": 'console.log("hello")', + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + + // Don't throw "file not found" error. + await eslint.lintFiles("."); + }); + + it("'lintFiles(\"subdir\")' should not ignore './subdir' even if 'ignorePatterns' contains it.", async () => { + const teardown = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": { ignorePatterns: ["/subdir"] }, + "subdir/.eslintrc.json": { root: true }, + "subdir/index.js": 'console.log("hello")', + }, + }); + + await teardown.prepare(); + cleanup = teardown.cleanup; + eslint = new LegacyESLint({ cwd: teardown.getPath() }); + + // Don't throw "file not found" error. + await eslint.lintFiles("subdir"); + }); + }); + + it("should throw if non-boolean value is given to 'options.warnIgnored' option", async () => { + eslint = new LegacyESLint(); + await assert.rejects( + () => eslint.lintFiles(777), + /'patterns' must be a non-empty string or an array of non-empty strings/u, + ); + await assert.rejects( + () => eslint.lintFiles([null]), + /'patterns' must be a non-empty string or an array of non-empty strings/u, + ); + }); + }); + + describe("calculateConfigForFile", () => { + it("should return the info from Config#getConfig when called", async () => { + const options = { + overrideConfigFile: getFixturePath( + "configurations", + "quotes-error.json", + ), + }; + const engine = new LegacyESLint(options); + const filePath = getFixturePath("single-quoted.js"); + const actualConfig = await engine.calculateConfigForFile(filePath); + const expectedConfig = new CascadingConfigArrayFactory({ + specificConfigPath: options.overrideConfigFile, + }) + .getConfigArrayForFile(filePath) + .extractConfig(filePath) + .toCompatibleObjectAsConfigFileContent(); + + assert.deepStrictEqual(actualConfig, expectedConfig); + }); + + it("should return the config for a file that doesn't exist", async () => { + const engine = new LegacyESLint(); + const filePath = getFixturePath("does_not_exist.js"); + const existingSiblingFilePath = getFixturePath("single-quoted.js"); + const actualConfig = await engine.calculateConfigForFile(filePath); + const expectedConfig = await engine.calculateConfigForFile( + existingSiblingFilePath, + ); + + assert.deepStrictEqual(actualConfig, expectedConfig); + }); + + it("should return the config for a virtual file that is a child of an existing file", async () => { + const engine = new LegacyESLint(); + const parentFileName = "single-quoted.js"; + const filePath = getFixturePath(parentFileName, "virtual.js"); // single-quoted.js/virtual.js + const parentFilePath = getFixturePath(parentFileName); + const actualConfig = await engine.calculateConfigForFile(filePath); + const expectedConfig = + await engine.calculateConfigForFile(parentFilePath); + + assert.deepStrictEqual(actualConfig, expectedConfig); + }); + + it("should return the config when run from within a subdir", async () => { + const options = { + cwd: getFixturePath( + "config-hierarchy", + "root-true", + "parent", + "root", + "subdir", + ), + }; + const engine = new LegacyESLint(options); + const filePath = getFixturePath( + "config-hierarchy", + "root-true", + "parent", + "root", + ".eslintrc", + ); + const actualConfig = + await engine.calculateConfigForFile("./.eslintrc"); + const expectedConfig = new CascadingConfigArrayFactory(options) + .getConfigArrayForFile(filePath) + .extractConfig(filePath) + .toCompatibleObjectAsConfigFileContent(); + + assert.deepStrictEqual(actualConfig, expectedConfig); + }); + + it("should throw an error if a directory path was given.", async () => { + const engine = new LegacyESLint(); + + try { + await engine.calculateConfigForFile("."); + } catch (error) { + assert.strictEqual( + error.messageTemplate, + "print-config-with-directory-path", + ); + return; + } + assert.fail("should throw an error"); + }); + + it("should throw if non-string value is given to 'filePath' parameter", async () => { + const eslint = new LegacyESLint(); + + await assert.rejects( + () => eslint.calculateConfigForFile(null), + /'filePath' must be a non-empty string/u, + ); + }); + + // https://github.com/eslint/eslint/issues/13793 + it("should throw with an invalid built-in rule config", async () => { + const options = { + baseConfig: { + rules: { + "no-alert": [ + "error", + { + thisDoesNotExist: true, + }, + ], + }, + }, + }; + const engine = new LegacyESLint(options); + const filePath = getFixturePath("single-quoted.js"); + + await assert.rejects( + () => engine.calculateConfigForFile(filePath), + /Configuration for rule "no-alert" is invalid:/u, + ); + }); + }); + + describe("isPathIgnored", () => { + it("should check if the given path is ignored", async () => { + const engine = new LegacyESLint({ + ignorePath: getFixturePath(".eslintignore2"), + cwd: getFixturePath(), + }); + + assert(await engine.isPathIgnored("undef.js")); + assert(!(await engine.isPathIgnored("passing.js"))); + }); + + it("should return false if ignoring is disabled", async () => { + const engine = new LegacyESLint({ + ignore: false, + ignorePath: getFixturePath(".eslintignore2"), + cwd: getFixturePath(), + }); + + assert(!(await engine.isPathIgnored("undef.js"))); + }); + + // https://github.com/eslint/eslint/issues/5547 + it("should return true for default ignores even if ignoring is disabled", async () => { + const engine = new LegacyESLint({ + ignore: false, + cwd: getFixturePath("cli-engine"), + }); + + assert(await engine.isPathIgnored("node_modules/foo.js")); + }); + + describe("about the default ignore patterns", () => { + it("should always apply defaultPatterns if ignore option is true", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "node_modules/package/file.js", + ), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "subdir/node_modules/package/file.js", + ), + ), + ); + }); + + it("should still apply defaultPatterns if ignore option is false", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ ignore: false, cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "node_modules/package/file.js", + ), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "subdir/node_modules/package/file.js", + ), + ), + ); + }); + + it("should allow subfolders of defaultPatterns to be unignored by ignorePattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + cwd, + overrideConfig: { + ignorePatterns: "!/node_modules/package", + }, + }); + + assert( + !(await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "node_modules", + "package", + "file.js", + ), + )), + ); + }); + + it("should allow subfolders of defaultPatterns to be unignored by ignorePath", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + cwd, + ignorePath: getFixturePath( + "ignored-paths", + ".eslintignoreWithUnignoredDefaults", + ), + }); + + assert( + !(await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "node_modules", + "package", + "file.js", + ), + )), + ); + }); + + it("should ignore dotfiles", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", ".foo"), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/.bar"), + ), + ); + }); + + it("should ignore directories beginning with a dot", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", ".foo/bar"), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/.bar/baz"), + ), + ); + }); + + it("should still ignore dotfiles when ignore option disabled", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ ignore: false, cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", ".foo"), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/.bar"), + ), + ); + }); + + it("should still ignore directories beginning with a dot when ignore option disabled", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ ignore: false, cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", ".foo/bar"), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/.bar/baz"), + ), + ); + }); + + it("should not ignore absolute paths containing '..'", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ cwd }); + + assert( + !(await engine.isPathIgnored( + `${getFixturePath("ignored-paths", "foo")}/../unignored.js`, + )), + ); + }); + + it("should ignore /node_modules/ relative to .eslintignore when loaded", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + ignorePath: getFixturePath( + "ignored-paths", + ".eslintignore", + ), + cwd, + }); + + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "node_modules", + "existing.js", + ), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "foo", + "node_modules", + "existing.js", + ), + ), + ); + }); + + it("should ignore /node_modules/ relative to cwd without an .eslintignore", async () => { + const cwd = getFixturePath("ignored-paths", "no-ignore-file"); + const engine = new LegacyESLint({ cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "no-ignore-file", + "node_modules", + "existing.js", + ), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "no-ignore-file", + "foo", + "node_modules", + "existing.js", + ), + ), + ); + }); + }); + + describe("with no .eslintignore file", () => { + it("should not travel to parent directories to find .eslintignore when it's missing and cwd is provided", async () => { + const cwd = getFixturePath("ignored-paths", "configurations"); + const engine = new LegacyESLint({ cwd }); + + // an .eslintignore in parent directories includes `*.js`, but don't load it. + assert(!(await engine.isPathIgnored("foo.js"))); + assert(await engine.isPathIgnored("node_modules/foo.js")); + }); + + it("should return false for files outside of the cwd (with no ignore file provided)", async () => { + // Default ignore patterns should not inadvertently ignore files in parent directories + const engine = new LegacyESLint({ + cwd: getFixturePath("ignored-paths", "no-ignore-file"), + }); + + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + )), + ); + }); + }); + + describe("with .eslintignore file or package.json file", () => { + it("should load .eslintignore from cwd when explicitly passed", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ cwd }); + + // `${cwd}/.eslintignore` includes `sampleignorepattern`. + assert(await engine.isPathIgnored("sampleignorepattern")); + }); + + it("should use package.json's eslintIgnore files if no specified .eslintignore file", async () => { + const cwd = getFixturePath( + "ignored-paths", + "package-json-ignore", + ); + const engine = new LegacyESLint({ cwd }); + + assert(await engine.isPathIgnored("hello.js")); + assert(await engine.isPathIgnored("world.js")); + }); + + it("should use correct message template if failed to parse package.json", () => { + const cwd = getFixturePath( + "ignored-paths", + "broken-package-json", + ); + + assert.throws(() => { + try { + // eslint-disable-next-line no-new -- Check for error + new LegacyESLint({ cwd }); + } catch (error) { + assert.strictEqual( + error.messageTemplate, + "failed-to-read-json", + ); + throw error; + } + }); + }); + + it("should not use package.json's eslintIgnore files if specified .eslintignore file", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ cwd }); + + /* + * package.json includes `hello.js` and `world.js`. + * .eslintignore includes `sampleignorepattern`. + */ + assert(!(await engine.isPathIgnored("hello.js"))); + assert(!(await engine.isPathIgnored("world.js"))); + assert(await engine.isPathIgnored("sampleignorepattern")); + }); + + it("should error if package.json's eslintIgnore is not an array of file paths", () => { + const cwd = getFixturePath( + "ignored-paths", + "bad-package-json-ignore", + ); + + assert.throws(() => { + // eslint-disable-next-line no-new -- Check for throwing + new LegacyESLint({ cwd }); + }, /Package\.json eslintIgnore property requires an array of paths/u); + }); + }); + + describe("with --ignore-pattern option", () => { + it("should accept a string for options.ignorePattern", async () => { + const cwd = getFixturePath("ignored-paths", "ignore-pattern"); + const engine = new LegacyESLint({ + overrideConfig: { + ignorePatterns: "ignore-me.txt", + }, + cwd, + }); + + assert(await engine.isPathIgnored("ignore-me.txt")); + }); + + it("should accept an array for options.ignorePattern", async () => { + const engine = new LegacyESLint({ + overrideConfig: { + ignorePatterns: ["a", "b"], + }, + useEslintrc: false, + }); + + assert(await engine.isPathIgnored("a")); + assert(await engine.isPathIgnored("b")); + assert(!(await engine.isPathIgnored("c"))); + }); + + it("should return true for files which match an ignorePattern even if they do not exist on the filesystem", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + overrideConfig: { + ignorePatterns: "not-a-file", + }, + cwd, + }); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "not-a-file"), + ), + ); + }); + + it("should return true for file matching an ignore pattern exactly", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + overrideConfig: { ignorePatterns: "undef.js" }, + cwd, + }); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + ), + ); + }); + + it("should return false for file matching an invalid ignore pattern with leading './'", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + overrideConfig: { ignorePatterns: "./undef.js" }, + cwd, + }); + + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + )), + ); + }); + + it("should return false for file in subfolder of cwd matching an ignore pattern with leading '/'", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + overrideConfig: { ignorePatterns: "/undef.js" }, + cwd, + }); + + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "subdir", "undef.js"), + )), + ); + }); + + it("should return true for file matching a child of an ignore pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + overrideConfig: { ignorePatterns: "ignore-pattern" }, + cwd, + }); + + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "ignore-pattern", + "ignore-me.txt", + ), + ), + ); + }); + + it("should return true for file matching a grandchild of an ignore pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + overrideConfig: { ignorePatterns: "ignore-pattern" }, + cwd, + }); + + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "ignore-pattern", + "subdir", + "ignore-me.txt", + ), + ), + ); + }); + + it("should return false for file not matching any ignore pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + overrideConfig: { ignorePatterns: "failing.js" }, + cwd, + }); + + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "unignored.js"), + )), + ); + }); + + it("two globstar '**' ignore pattern should ignore files in nested directories", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + overrideConfig: { ignorePatterns: "**/*.js" }, + cwd, + }); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo.js"), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/bar.js"), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/bar/baz.js"), + ), + ); + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo.j2"), + )), + ); + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/bar.j2"), + )), + ); + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo/bar/baz.j2"), + )), + ); + }); + }); + + describe("with --ignore-path option", () => { + it("initialization with ignorePath should work when cwd is a parent directory", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + "custom-name", + "ignore-file", + ); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored("custom-name/foo.js")); + }); + + it("initialization with ignorePath should work when the file is in the cwd", async () => { + const cwd = getFixturePath("ignored-paths", "custom-name"); + const ignorePath = getFixturePath( + "ignored-paths", + "custom-name", + "ignore-file", + ); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored("foo.js")); + }); + + it("initialization with ignorePath should work when cwd is a subdirectory", async () => { + const cwd = getFixturePath( + "ignored-paths", + "custom-name", + "subdirectory", + ); + const ignorePath = getFixturePath( + "ignored-paths", + "custom-name", + "ignore-file", + ); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored("../custom-name/foo.js")); + }); + + it("initialization with invalid file should throw error", () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + "not-a-directory", + ".foobaz", + ); + + assert.throws(() => { + // eslint-disable-next-line no-new -- Check for throwing + new LegacyESLint({ ignorePath, cwd }); + }, /Cannot read \.eslintignore file/u); + }); + + it("should return false for files outside of ignorePath's directory", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + "custom-name", + "ignore-file", + ); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + )), + ); + }); + + it("should resolve relative paths from CWD", async () => { + const cwd = getFixturePath("ignored-paths", "subdir"); + const ignorePath = getFixturePath( + "ignored-paths", + ".eslintignoreForDifferentCwd", + ); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "subdir/undef.js"), + ), + ); + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + )), + ); + }); + + it("should resolve relative paths from CWD when it's in a child directory", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + "subdir/.eslintignoreInChildDir", + ); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "subdir/undef.js"), + )), + ); + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "undef.js"), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "foo.js"), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "subdir/foo.js"), + ), + ); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "node_modules/bar.js"), + ), + ); + }); + + it("should resolve relative paths from CWD when it contains negated globs", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + "subdir/.eslintignoreInChildDir", + ); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored("subdir/blah.txt")); + assert(await engine.isPathIgnored("blah.txt")); + assert(await engine.isPathIgnored("subdir/bar.txt")); + assert(!(await engine.isPathIgnored("bar.txt"))); + assert(!(await engine.isPathIgnored("subdir/baz.txt"))); + assert(!(await engine.isPathIgnored("baz.txt"))); + }); + + it("should resolve default ignore patterns from the CWD even when the ignorePath is in a subdirectory", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + "subdir/.eslintignoreInChildDir", + ); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored("node_modules/blah.js")); + }); + + it("should resolve default ignore patterns from the CWD even when the ignorePath is in a parent directory", async () => { + const cwd = getFixturePath("ignored-paths", "subdir"); + const ignorePath = getFixturePath( + "ignored-paths", + ".eslintignoreForDifferentCwd", + ); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(await engine.isPathIgnored("node_modules/blah.js")); + }); + + it("should handle .eslintignore which contains CRLF correctly.", async () => { + const ignoreFileContent = fs.readFileSync( + getFixturePath("ignored-paths", "crlf/.eslintignore"), + "utf8", + ); + + assert( + ignoreFileContent.includes("\r"), + "crlf/.eslintignore should contains CR.", + ); + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + "crlf/.eslintignore", + ); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "crlf/hide1/a.js"), + ), + ); + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", "crlf/hide2/a.js"), + ), + ); + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "crlf/hide3/a.js"), + )), + ); + }); + + it("should not include comments in ignore rules", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + ".eslintignoreWithComments", + ); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert(!(await engine.isPathIgnored("# should be ignored"))); + assert(await engine.isPathIgnored("this_one_not")); + }); + + it("should ignore a non-negated pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + ".eslintignoreWithNegation", + ); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert( + await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "negation", + "ignore.js", + ), + ), + ); + }); + + it("should not ignore a negated pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + ".eslintignoreWithNegation", + ); + const engine = new LegacyESLint({ ignorePath, cwd }); + + assert( + !(await engine.isPathIgnored( + getFixturePath( + "ignored-paths", + "negation", + "unignore.js", + ), + )), + ); + }); + + // https://github.com/eslint/eslint/issues/15642 + it("should correctly handle patterns with escaped brackets", async () => { + const cwd = getFixturePath("ignored-paths"); + const ignorePath = getFixturePath( + "ignored-paths", + ".eslintignoreWithEscapedBrackets", + ); + const engine = new LegacyESLint({ ignorePath, cwd }); + + const subdir = "brackets"; + + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", subdir, "index.js"), + )), + `'${subdir}/index.js' should not be ignored`, + ); + + for (const filename of [ + "[index.js", + "index].js", + "[index].js", + ]) { + assert( + await engine.isPathIgnored( + getFixturePath("ignored-paths", subdir, filename), + ), + `'${subdir}/${filename}' should be ignored`, + ); + } + }); + }); + + describe("with --ignore-path option and --ignore-pattern option", () => { + it("should return false for ignored file when unignored with ignore pattern", async () => { + const cwd = getFixturePath("ignored-paths"); + const engine = new LegacyESLint({ + ignorePath: getFixturePath( + "ignored-paths", + ".eslintignore", + ), + overrideConfig: { + ignorePatterns: "!sampleignorepattern", + }, + cwd, + }); + + assert( + !(await engine.isPathIgnored( + getFixturePath("ignored-paths", "sampleignorepattern"), + )), + ); + }); + }); + + it("should throw if non-string value is given to 'filePath' parameter", async () => { + const eslint = new LegacyESLint(); + + await assert.rejects( + () => eslint.isPathIgnored(null), + /'filePath' must be a non-empty string/u, + ); + }); + }); + + describe("loadFormatter()", () => { + it("should return a formatter object when a bundled formatter is requested", async () => { + const engine = new LegacyESLint(); + const formatter = await engine.loadFormatter("json"); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when no argument is passed", async () => { + const engine = new LegacyESLint(); + const formatter = await engine.loadFormatter(); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a custom formatter is requested", async () => { + const engine = new LegacyESLint(); + const formatter = await engine.loadFormatter( + getFixturePath("formatters", "simple.js"), + ); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a custom formatter is requested, also if the path has backslashes", async () => { + const engine = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + }); + const formatter = await engine.loadFormatter( + ".\\fixtures\\formatters\\simple.js", + ); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a formatter prefixed with eslint-formatter is requested", async () => { + const engine = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + }); + const formatter = await engine.loadFormatter("bar"); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a formatter is requested, also when the eslint-formatter prefix is included in the format argument", async () => { + const engine = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + }); + const formatter = await engine.loadFormatter( + "eslint-formatter-bar", + ); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a formatter is requested within a scoped npm package", async () => { + const engine = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + }); + const formatter = await engine.loadFormatter("@somenamespace/foo"); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should return a formatter object when a formatter is requested within a scoped npm package, also when the eslint-formatter prefix is included in the format argument", async () => { + const engine = new LegacyESLint({ + cwd: getFixturePath("cli-engine"), + }); + const formatter = await engine.loadFormatter( + "@somenamespace/eslint-formatter-foo", + ); + + assert.strictEqual(typeof formatter, "object"); + assert.strictEqual(typeof formatter.format, "function"); + }); + + it("should throw if a custom formatter doesn't exist", async () => { + const engine = new LegacyESLint(); + const formatterPath = getFixturePath( + "formatters", + "doesntexist.js", + ); + const fullFormatterPath = path.resolve(formatterPath); + + await assert.rejects( + async () => { + await engine.loadFormatter(formatterPath); + }, + new RegExp( + escapeStringRegExp( + `There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`, + ), + "u", + ), + ); + }); + + it("should throw if a built-in formatter doesn't exist", async () => { + const engine = new LegacyESLint(); + const fullFormatterPath = path.resolve( + __dirname, + "../../../lib/cli-engine/formatters/special", + ); + + await assert.rejects( + async () => { + await engine.loadFormatter("special"); + }, + new RegExp( + escapeStringRegExp( + `There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`, + ), + "u", + ), + ); + }); + + it("should throw if the required formatter exists but has an error", async () => { + const engine = new LegacyESLint(); + const formatterPath = getFixturePath("formatters", "broken.js"); + + await assert.rejects( + async () => { + await engine.loadFormatter(formatterPath); + }, + new RegExp( + escapeStringRegExp( + `There was a problem loading formatter: ${formatterPath}\nError: Cannot find module 'this-module-does-not-exist'`, + ), + "u", + ), + ); + }); + + it("should throw if a non-string formatter name is passed", async () => { + const engine = new LegacyESLint(); + + await assert.rejects(async () => { + await engine.loadFormatter(5); + }, /'name' must be a string/u); + }); + + it("should pass cwd to the `cwd` property of the second argument.", async () => { + const cwd = getFixturePath(); + const engine = new LegacyESLint({ cwd }); + const formatterPath = getFixturePath("formatters", "cwd.js"); + const formatter = await engine.loadFormatter(formatterPath); + + assert.strictEqual(formatter.format([]), cwd); + }); + }); + + describe("getErrorResults()", () => { + it("should report 5 error messages when looking for errors only", async () => { + process.chdir(originalDir); + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2, + }, + env: { + node: true, + }, + }, + }); + const results = await engine.lintText("var foo = 'bar';"); + const errorResults = LegacyESLint.getErrorResults(results); + + assert.strictEqual(errorResults[0].messages.length, 5); + assert.strictEqual(errorResults[0].errorCount, 5); + assert.strictEqual(errorResults[0].fixableErrorCount, 3); + assert.strictEqual(errorResults[0].fixableWarningCount, 0); + assert.strictEqual(errorResults[0].messages[0].ruleId, "strict"); + assert.strictEqual(errorResults[0].messages[0].severity, 2); + assert.strictEqual(errorResults[0].messages[1].ruleId, "no-var"); + assert.strictEqual(errorResults[0].messages[1].severity, 2); + assert.strictEqual( + errorResults[0].messages[2].ruleId, + "no-unused-vars", + ); + assert.strictEqual(errorResults[0].messages[2].severity, 2); + assert.strictEqual(errorResults[0].messages[3].ruleId, "quotes"); + assert.strictEqual(errorResults[0].messages[3].severity, 2); + assert.strictEqual(errorResults[0].messages[4].ruleId, "eol-last"); + assert.strictEqual(errorResults[0].messages[4].severity, 2); + }); + + it("should not mutate passed report parameter", async () => { + process.chdir(originalDir); + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + quotes: [1, "double"], + "no-var": 2, + }, + }, + }); + const results = await engine.lintText("var foo = 'bar';"); + const reportResultsLength = results[0].messages.length; + + assert.strictEqual(results[0].messages.length, 2); + + LegacyESLint.getErrorResults(results); + + assert.strictEqual(results[0].messages.length, reportResultsLength); + }); + + it("should report a warningCount of 0 when looking for errors only", async () => { + process.chdir(originalDir); + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + quotes: 2, + "no-var": 2, + "eol-last": 2, + strict: [2, "global"], + "no-unused-vars": 2, + }, + env: { + node: true, + }, + }, + }); + const results = await engine.lintText("var foo = 'bar';"); + const errorResults = LegacyESLint.getErrorResults(results); + + assert.strictEqual(errorResults[0].warningCount, 0); + assert.strictEqual(errorResults[0].fixableWarningCount, 0); + }); + + it("should return 0 error or warning messages even when the file has warnings", async () => { + const engine = new LegacyESLint({ + ignorePath: path.join(fixtureDir, ".eslintignore"), + cwd: path.join(fixtureDir, ".."), + }); + const options = { + filePath: "fixtures/passing.js", + warnIgnored: true, + }; + const results = await engine.lintText("var bar = foo;", options); + const errorReport = LegacyESLint.getErrorResults(results); + + assert.strictEqual(errorReport.length, 0); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].errorCount, 0); + assert.strictEqual(results[0].warningCount, 1); + assert.strictEqual(results[0].fatalErrorCount, 0); + assert.strictEqual(results[0].fixableErrorCount, 0); + assert.strictEqual(results[0].fixableWarningCount, 0); + }); + + it("should return source code of file in the `source` property", async () => { + process.chdir(originalDir); + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { quotes: [2, "double"] }, + }, + }); + const results = await engine.lintText("var foo = 'bar';"); + const errorResults = LegacyESLint.getErrorResults(results); + + assert.strictEqual(errorResults[0].messages.length, 1); + assert.strictEqual(errorResults[0].source, "var foo = 'bar';"); + }); + + it("should contain `output` property after fixes", async () => { + process.chdir(originalDir); + const engine = new LegacyESLint({ + useEslintrc: false, + fix: true, + overrideConfig: { + rules: { + semi: 2, + "no-console": 2, + }, + }, + }); + const results = await engine.lintText("console.log('foo')"); + const errorResults = LegacyESLint.getErrorResults(results); + + assert.strictEqual(errorResults[0].messages.length, 1); + assert.strictEqual(errorResults[0].output, "console.log('foo');"); + }); + }); + + describe("getRulesMetaForResults()", () => { + it("should return empty object when there are no linting errors", async () => { + const engine = new LegacyESLint({ + useEslintrc: false, + }); + + const rulesMeta = engine.getRulesMetaForResults([]); + + assert.deepStrictEqual(rulesMeta, {}); + }); + + it("should return one rule meta when there is a linting error", async () => { + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + semi: 2, + }, + }, + }); + + const results = await engine.lintText("a"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(Object.keys(rulesMeta).length, 1); + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + }); + + it("should return one rule meta when there is a suppressed linting error", async () => { + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + semi: 2, + }, + }, + }); + + const results = await engine.lintText( + "a // eslint-disable-line semi", + ); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(Object.keys(rulesMeta).length, 1); + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + }); + + it("should return multiple rule meta when there are multiple linting errors", async () => { + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + rules: { + semi: 2, + quotes: [2, "double"], + }, + }, + }); + + const results = await engine.lintText("'a'"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); + }); + + it("should return multiple rule meta when there are multiple linting errors from a plugin", async () => { + const customPlugin = { + rules: { + "no-var": require("../../../lib/rules/no-var"), + }, + }; + + const engine = new LegacyESLint({ + useEslintrc: false, + plugins: { + "custom-plugin": customPlugin, + }, + overrideConfig: { + plugins: ["custom-plugin"], + rules: { + "custom-plugin/no-var": 2, + semi: 2, + quotes: [2, "double"], + }, + }, + }); + + const results = await engine.lintText("var foo = 0; var bar = '1'"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.strictEqual(rulesMeta.semi, coreRules.get("semi").meta); + assert.strictEqual(rulesMeta.quotes, coreRules.get("quotes").meta); + assert.strictEqual( + rulesMeta["custom-plugin/no-var"], + customPlugin.rules["no-var"].meta, + ); + }); + + it("should ignore messages not related to a rule", async () => { + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { + ignorePatterns: "ignored.js", + rules: { + "no-var": "warn", + }, + }, + reportUnusedDisableDirectives: "warn", + }); + + { + const results = await engine.lintText("syntax error"); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText( + "// eslint-disable-line no-var", + ); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + { + const results = await engine.lintText("", { + filePath: "ignored.js", + warnIgnored: true, + }); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, {}); + } + }); + + it("should return a non-empty value if some of the messages are related to a rule", async () => { + const engine = new LegacyESLint({ + useEslintrc: false, + overrideConfig: { rules: { "no-var": "warn" } }, + reportUnusedDisableDirectives: "warn", + }); + + const results = await engine.lintText( + "// eslint-disable-line no-var\nvar foo;", + ); + const rulesMeta = engine.getRulesMetaForResults(results); + + assert.deepStrictEqual(rulesMeta, { + "no-var": coreRules.get("no-var").meta, + }); + }); + }); + + describe("outputFixes()", () => { + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it("should call fs.writeFile() for each result with output", async () => { + const fakeFS = { + writeFile: sinon.spy(callLastArgument), + }; + const spy = fakeFS.writeFile; + const { LegacyESLint: localESLint } = proxyquire( + "../../../lib/eslint/legacy-eslint", + { + "node:fs": fakeFS, + }, + ); + + const results = [ + { + filePath: path.resolve("foo.js"), + output: "bar", + }, + { + filePath: path.resolve("bar.js"), + output: "baz", + }, + ]; + + await localESLint.outputFixes(results); + + assert.strictEqual(spy.callCount, 2); + assert( + spy.firstCall.calledWithExactly( + path.resolve("foo.js"), + "bar", + sinon.match.func, + ), + "First call was incorrect.", + ); + assert( + spy.secondCall.calledWithExactly( + path.resolve("bar.js"), + "baz", + sinon.match.func, + ), + "Second call was incorrect.", + ); + }); + + it("should call fs.writeFile() for each result with output and not at all for a result without output", async () => { + const fakeFS = { + writeFile: sinon.spy(callLastArgument), + }; + const spy = fakeFS.writeFile; + const { LegacyESLint: localESLint } = proxyquire( + "../../../lib/eslint/legacy-eslint", + { + "node:fs": fakeFS, + }, + ); + const results = [ + { + filePath: path.resolve("foo.js"), + output: "bar", + }, + { + filePath: path.resolve("abc.js"), + }, + { + filePath: path.resolve("bar.js"), + output: "baz", + }, + ]; + + await localESLint.outputFixes(results); + + assert.strictEqual(spy.callCount, 2); + assert( + spy.firstCall.calledWithExactly( + path.resolve("foo.js"), + "bar", + sinon.match.func, + ), + "First call was incorrect.", + ); + assert( + spy.secondCall.calledWithExactly( + path.resolve("bar.js"), + "baz", + sinon.match.func, + ), + "Second call was incorrect.", + ); + }); + + it("should throw if non object array is given to 'results' parameter", async () => { + await assert.rejects( + () => LegacyESLint.outputFixes(null), + /'results' must be an array/u, + ); + await assert.rejects( + () => LegacyESLint.outputFixes([null]), + /'results' must include only objects/u, + ); + }); + }); + + describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { + it("should report a violation for disabling rules", async () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + ].join("\n"); + const config = { + ignore: true, + useEslintrc: false, + allowInlineConfig: false, + overrideConfig: { + env: { browser: true }, + rules: { + "eol-last": 0, + "no-alert": 1, + "no-trailing-spaces": 0, + strict: 0, + quotes: 0, + }, + }, + }; + const eslintCLI = new LegacyESLint(config); + const results = await eslintCLI.lintText(code); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + }); + + it("should not report a violation by default", async () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + ].join("\n"); + const config = { + ignore: true, + useEslintrc: false, + allowInlineConfig: true, + overrideConfig: { + env: { browser: true }, + rules: { + "eol-last": 0, + "no-alert": 1, + "no-trailing-spaces": 0, + strict: 0, + quotes: 0, + }, + }, + }; + const eslintCLI = new LegacyESLint(config); + const results = await eslintCLI.lintText(code); + const messages = results[0].messages; + + assert.strictEqual(messages.length, 0); + }); + }); + + describe("when evaluating code when reportUnusedDisableDirectives is enabled", () => { + it("should report problems for unused eslint-disable directives", async () => { + const eslint = new LegacyESLint({ + useEslintrc: false, + reportUnusedDisableDirectives: "error", + }); + + assert.deepStrictEqual( + await eslint.lintText("/* eslint-disable */"), + [ + { + filePath: "", + messages: [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + suppressedMessages: [], + errorCount: 1, + warningCount: 0, + fatalErrorCount: 0, + fixableErrorCount: 1, + fixableWarningCount: 0, + source: "/* eslint-disable */", + usedDeprecatedRules: [], + }, + ], + ); + }); + }); + + describe("when retrieving version number", () => { + it("should return current version number", () => { + const eslintCLI = require("../../../lib/eslint").LegacyESLint; + const version = eslintCLI.version; + + assert.strictEqual(typeof version, "string"); + assert(parseInt(version[0], 10) >= 3); + }); + }); + + describe("mutability", () => { + describe("plugins", () => { + it("Loading plugin in one instance doesn't mutate to another instance", async () => { + const filePath = getFixturePath("single-quoted.js"); + const engine1 = eslintWithPlugins({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + overrideConfig: { + plugins: ["example"], + rules: { "example/example-rule": 1 }, + }, + }); + const engine2 = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + }); + const fileConfig1 = + await engine1.calculateConfigForFile(filePath); + const fileConfig2 = + await engine2.calculateConfigForFile(filePath); + + // plugin + assert.deepStrictEqual( + fileConfig1.plugins, + ["example"], + "Plugin is present for engine 1", + ); + assert.deepStrictEqual( + fileConfig2.plugins, + [], + "Plugin is not present for engine 2", + ); + }); + }); + + describe("rules", () => { + it("Loading rules in one instance doesn't mutate to another instance", async () => { + const filePath = getFixturePath("single-quoted.js"); + const engine1 = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + overrideConfig: { rules: { "example/example-rule": 1 } }, + }); + const engine2 = new LegacyESLint({ + cwd: path.join(fixtureDir, ".."), + useEslintrc: false, + }); + const fileConfig1 = + await engine1.calculateConfigForFile(filePath); + const fileConfig2 = + await engine2.calculateConfigForFile(filePath); + + // plugin + assert.deepStrictEqual( + fileConfig1.rules["example/example-rule"], + [1], + "example is present for engine 1", + ); + assert.strictEqual( + fileConfig2.rules["example/example-rule"], + void 0, + "example is not present for engine 2", + ); + }); + }); + }); + + describe("with ignorePatterns config", () => { + const root = getFixturePath("cli-engine/ignore-patterns"); + + describe("ignorePatterns can add an ignore pattern ('foo.js').", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": { + ignorePatterns: "foo.js", + }, + "foo.js": "", + "bar.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + assert.strictEqual( + await engine.isPathIgnored("subdir/foo.js"), + true, + ); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + assert.strictEqual( + await engine.isPathIgnored("subdir/bar.js"), + false, + ); + }); + + it("'lintFiles()' should not verify 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar.js"), + path.join(root, "subdir/bar.js"), + ]); + }); + }); + + describe("ignorePatterns can add ignore patterns ('foo.js', '/bar.js').", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": { + ignorePatterns: ["foo.js", "/bar.js"], + }, + "foo.js": "", + "bar.js": "", + "baz.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + "subdir/baz.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + assert.strictEqual( + await engine.isPathIgnored("subdir/foo.js"), + true, + ); + }); + + it("'isPathIgnored()' should return 'true' for '/bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), true); + assert.strictEqual( + await engine.isPathIgnored("subdir/bar.js"), + false, + ); + }); + + it("'lintFiles()' should not verify 'foo.js' and '/bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "baz.js"), + path.join(root, "subdir/bar.js"), + path.join(root, "subdir/baz.js"), + ]); + }); + }); + + describe("ignorePatterns can unignore '/node_modules/foo'.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": { + ignorePatterns: "!/node_modules/foo", + }, + "node_modules/foo/index.js": "", + "node_modules/foo/.dot.js": "", + "node_modules/bar/index.js": "", + "foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for 'node_modules/foo/index.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("node_modules/foo/index.js"), + false, + ); + }); + + it("'isPathIgnored()' should return 'true' for 'node_modules/foo/.dot.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("node_modules/foo/.dot.js"), + true, + ); + }); + + it("'isPathIgnored()' should return 'true' for 'node_modules/bar/index.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("node_modules/bar/index.js"), + true, + ); + }); + + it("'lintFiles()' should verify 'node_modules/foo/index.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "foo.js"), + path.join(root, "node_modules/foo/index.js"), + ]); + }); + }); + + describe("ignorePatterns can unignore '.eslintrc.js'.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.js": `module.exports = ${JSON.stringify({ + ignorePatterns: "!.eslintrc.js", + })}`, + "foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for '.eslintrc.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored(".eslintrc.js"), + false, + ); + }); + + it("'lintFiles()' should verify '.eslintrc.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, ".eslintrc.js"), + path.join(root, "foo.js"), + ]); + }); + }); + + describe(".eslintignore can re-ignore files that are unignored by ignorePatterns.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.js": `module.exports = ${JSON.stringify({ + ignorePatterns: "!.*", + })}`, + ".eslintignore": ".foo*", + ".foo.js": "", + ".bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for re-ignored '.foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored(".foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for unignored '.bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored(".bar.js"), + false, + ); + }); + + it("'lintFiles()' should not verify re-ignored '.foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, ".bar.js"), + path.join(root, ".eslintrc.js"), + ]); + }); + }); + + describe(".eslintignore can unignore files that are ignored by ignorePatterns.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.js": `module.exports = ${JSON.stringify({ + ignorePatterns: "*.js", + })}`, + ".eslintignore": "!foo.js", + "foo.js": "", + "bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), false); + }); + + it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), true); + }); + + it("'lintFiles()' should verify unignored 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [path.join(root, "foo.js")]); + }); + }); + + describe("ignorePatterns in the config file in a child directory affects to only in the directory.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + ignorePatterns: "foo.js", + }), + "subdir/.eslintrc.json": JSON.stringify({ + ignorePatterns: "bar.js", + }), + "foo.js": "", + "bar.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + "subdir/subsubdir/foo.js": "", + "subdir/subsubdir/bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + assert.strictEqual( + await engine.isPathIgnored("subdir/foo.js"), + true, + ); + assert.strictEqual( + await engine.isPathIgnored("subdir/subsubdir/foo.js"), + true, + ); + }); + + it("'isPathIgnored()' should return 'true' for 'bar.js' in 'subdir'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("subdir/bar.js"), + true, + ); + assert.strictEqual( + await engine.isPathIgnored("subdir/subsubdir/bar.js"), + true, + ); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js' in the outside of 'subdir'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + }); + + it("'lintFiles()' should verify 'bar.js' in the outside of 'subdir'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [path.join(root, "bar.js")]); + }); + }); + + describe("ignorePatterns in the config file in a child directory can unignore the ignored files in the parent directory's config.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + ignorePatterns: "foo.js", + }), + "subdir/.eslintrc.json": JSON.stringify({ + ignorePatterns: "!foo.js", + }), + "foo.js": "", + "subdir/foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("subdir/foo.js"), + false, + ); + }); + + it("'lintFiles()' should verify 'foo.js' in the child directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "subdir/foo.js"), + ]); + }); + }); + + describe(".eslintignore can unignore files that are ignored by ignorePatterns in the config file in the child directory.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({}), + "subdir/.eslintrc.json": JSON.stringify({ + ignorePatterns: "*.js", + }), + ".eslintignore": "!foo.js", + "foo.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for unignored 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), false); + assert.strictEqual( + await engine.isPathIgnored("subdir/foo.js"), + false, + ); + }); + + it("'isPathIgnored()' should return 'true' for ignored 'bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("subdir/bar.js"), + true, + ); + }); + + it("'lintFiles()' should verify unignored 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "foo.js"), + path.join(root, "subdir/foo.js"), + ]); + }); + }); + + describe("if the config in a child directory has 'root:true', ignorePatterns in the config file in the parent directory should not be used.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + ignorePatterns: "foo.js", + }), + "subdir/.eslintrc.json": JSON.stringify({ + root: true, + ignorePatterns: "bar.js", + }), + "foo.js": "", + "bar.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js' in the root directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + }); + + it("'isPathIgnored()' should return 'false' for 'foo.js' in the child directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("subdir/foo.js"), + false, + ); + }); + + it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("subdir/bar.js"), + true, + ); + }); + + it("'lintFiles()' should verify 'bar.js' in the root directory and 'foo.js' in the child directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar.js"), + path.join(root, "subdir/foo.js"), + ]); + }); + }); + + describe("even if the config in a child directory has 'root:true', .eslintignore should be used.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({}), + "subdir/.eslintrc.json": JSON.stringify({ + root: true, + ignorePatterns: "bar.js", + }), + ".eslintignore": "foo.js", + "foo.js": "", + "bar.js": "", + "subdir/foo.js": "", + "subdir/bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + assert.strictEqual( + await engine.isPathIgnored("subdir/foo.js"), + true, + ); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js' in the root directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + }); + + it("'isPathIgnored()' should return 'true' for 'bar.js' in the child directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("subdir/bar.js"), + true, + ); + }); + + it("'lintFiles()' should verify 'bar.js' in the root directory.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [path.join(root, "bar.js")]); + }); + }); + + describe("ignorePatterns in the shareable config should be used.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify( + { + ignorePatterns: "foo.js", + }, + )}`, + ".eslintrc.json": JSON.stringify({ + extends: "one", + }), + "foo.js": "", + "bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + }); + + it("'lintFiles()' should verify 'bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [path.join(root, "bar.js")]); + }); + }); + + describe("ignorePatterns in the shareable config should be relative to the entry config file.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify( + { + ignorePatterns: "/foo.js", + }, + )}`, + ".eslintrc.json": JSON.stringify({ + extends: "one", + }), + "foo.js": "", + "subdir/foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'subdir/foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual( + await engine.isPathIgnored("subdir/foo.js"), + false, + ); + }); + + it("'lintFiles()' should verify 'subdir/foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "subdir/foo.js"), + ]); + }); + }); + + describe("ignorePatterns in a config file can unignore the files which are ignored by ignorePatterns in the shareable config.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify( + { + ignorePatterns: "*.js", + }, + )}`, + ".eslintrc.json": JSON.stringify({ + extends: "one", + ignorePatterns: "!bar.js", + }), + "foo.js": "", + "bar.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'true' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), true); + }); + + it("'isPathIgnored()' should return 'false' for 'bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + assert.strictEqual(await engine.isPathIgnored("bar.js"), false); + }); + + it("'lintFiles()' should verify 'bar.js'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [path.join(root, "bar.js")]); + }); + }); + + describe("ignorePatterns in a config file should not be used if --no-ignore option was given.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + ignorePatterns: "*.js", + }), + "foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'isPathIgnored()' should return 'false' for 'foo.js'.", async () => { + const engine = new LegacyESLint({ + cwd: getPath(), + ignore: false, + }); + + assert.strictEqual(await engine.isPathIgnored("foo.js"), false); + }); + + it("'lintFiles()' should verify 'foo.js'.", async () => { + const engine = new LegacyESLint({ + cwd: getPath(), + ignore: false, + }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [path.join(root, "foo.js")]); + }); + }); + + describe("ignorePatterns in overrides section is not allowed.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.js": `module.exports = ${JSON.stringify({ + overrides: [ + { + files: "*.js", + ignorePatterns: "foo.js", + }, + ], + })}`, + "foo.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("should throw a configuration error.", async () => { + await assert.rejects(async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + await engine.lintFiles("*.js"); + }, /Unexpected top-level property "overrides\[0\]\.ignorePatterns"/u); + }); + }); + }); + + describe("'overrides[].files' adds lint targets", () => { + const root = getFixturePath("cli-engine/additional-lint-targets"); + + describe("if { files: 'foo/*.txt', excludedFiles: '**/ignore.txt' } is present,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + overrides: [ + { + files: "foo/*.txt", + excludedFiles: "**/ignore.txt", + }, + ], + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "foo/ignore.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "bar/ignore.txt": "", + "test.js": "", + "test.txt": "", + "ignore.txt": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with a directory path should contain 'foo/test.txt'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles(".")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/test.js"), + path.join(root, "foo/test.txt"), + path.join(root, "test.js"), + ]); + }); + + it("'lintFiles()' with a glob pattern '*.js' should not contain 'foo/test.txt'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/test.js"), + path.join(root, "test.js"), + ]); + }); + }); + + describe("if { files: 'foo/**/*.txt' } is present,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + overrides: [ + { + files: "foo/**/*.txt", + }, + ], + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "test.js": "", + "test.txt": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles(".")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/nested/test.txt"), + path.join(root, "foo/test.js"), + path.join(root, "foo/test.txt"), + path.join(root, "test.js"), + ]); + }); + }); + + describe("if { files: 'foo/**/*' } is present,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + ".eslintrc.json": JSON.stringify({ + overrides: [ + { + files: "foo/**/*", + }, + ], + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "test.js": "", + "test.txt": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with a directory path should NOT contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles(".")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/test.js"), + path.join(root, "test.js"), + ]); + }); + }); + + describe("if { files: 'foo/**/*.txt' } is present in a shareable config,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-config-foo/index.js": `module.exports = ${JSON.stringify( + { + overrides: [ + { + files: "foo/**/*.txt", + }, + ], + }, + )}`, + ".eslintrc.json": JSON.stringify({ + extends: "foo", + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "test.js": "", + "test.txt": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles(".")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/nested/test.txt"), + path.join(root, "foo/test.js"), + path.join(root, "foo/test.txt"), + path.join(root, "test.js"), + ]); + }); + }); + + describe("if { files: 'foo/**/*.txt' } is present in a plugin config,", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/eslint-plugin-foo/index.js": `exports.configs = ${JSON.stringify( + { + bar: { + overrides: [ + { + files: "foo/**/*.txt", + }, + ], + }, + }, + )}`, + ".eslintrc.json": JSON.stringify({ + extends: "plugin:foo/bar", + }), + "foo/nested/test.txt": "", + "foo/test.js": "", + "foo/test.txt": "", + "bar/test.js": "", + "bar/test.txt": "", + "test.js": "", + "test.txt": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with a directory path should contain 'foo/test.txt' and 'foo/nested/test.txt'.", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const filePaths = (await engine.lintFiles(".")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(filePaths, [ + path.join(root, "bar/test.js"), + path.join(root, "foo/nested/test.txt"), + path.join(root, "foo/test.js"), + path.join(root, "foo/test.txt"), + path.join(root, "test.js"), + ]); + }); + }); + }); + + describe("'ignorePatterns', 'overrides[].files', and 'overrides[].excludedFiles' of the configuration that the '--config' option provided should be resolved from CWD.", () => { + const root = getFixturePath("cli-engine/config-and-overrides-files"); + + describe("if { files: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/myconf/.eslintrc.json": { + overrides: [ + { + files: "foo/*.js", + rules: { + eqeqeq: "error", + }, + }, + ], + }, + "node_modules/myconf/foo/test.js": "a == b", + "foo/test.js": "a == b", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with 'foo/test.js' should use the override entry.", async () => { + const engine = new LegacyESLint({ + overrideConfigFile: "node_modules/myconf/.eslintrc.json", + cwd: getPath(), + ignore: false, + useEslintrc: false, + }); + const results = await engine.lintFiles("foo/test.js"); + + // Expected to be an 'eqeqeq' error because the file matches to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + errorCount: 1, + filePath: path.join(getPath(), "foo/test.js"), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [ + { + column: 3, + endColumn: 5, + endLine: 1, + line: 1, + message: "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + severity: 2, + suggestions: [ + { + data: { + actualOperator: "==", + expectedOperator: "===", + }, + desc: "Use '===' instead of '=='.", + fix: { + range: [2, 4], + text: "===", + }, + messageId: "replaceOperator", + }, + ], + }, + ], + suppressedMessages: [], + source: "a == b", + usedDeprecatedRules: [], + warningCount: 0, + fatalErrorCount: 0, + }, + ]); + }); + + it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should NOT use the override entry.", async () => { + const engine = new LegacyESLint({ + overrideConfigFile: "node_modules/myconf/.eslintrc.json", + cwd: root, + ignore: false, + useEslintrc: false, + }); + const results = await engine.lintFiles( + "node_modules/myconf/foo/test.js", + ); + + // Expected to be no errors because the file doesn't match to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + errorCount: 0, + filePath: path.join( + getPath(), + "node_modules/myconf/foo/test.js", + ), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [], + suppressedMessages: [], + usedDeprecatedRules: [], + warningCount: 0, + fatalErrorCount: 0, + }, + ]); + }); + }); + + describe("if { files: '*', excludedFiles: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/myconf/.eslintrc.json": JSON.stringify({ + overrides: [ + { + files: "*", + excludedFiles: "foo/*.js", + rules: { + eqeqeq: "error", + }, + }, + ], + }), + "node_modules/myconf/foo/test.js": "a == b", + "foo/test.js": "a == b", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with 'foo/test.js' should NOT use the override entry.", async () => { + const engine = new LegacyESLint({ + overrideConfigFile: "node_modules/myconf/.eslintrc.json", + cwd: root, + ignore: false, + useEslintrc: false, + }); + const results = await engine.lintFiles("foo/test.js"); + + // Expected to be no errors because the file matches to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + errorCount: 0, + filePath: path.join(getPath(), "foo/test.js"), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [], + suppressedMessages: [], + usedDeprecatedRules: [], + warningCount: 0, + fatalErrorCount: 0, + }, + ]); + }); + + it("'lintFiles()' with 'node_modules/myconf/foo/test.js' should use the override entry.", async () => { + const engine = new LegacyESLint({ + overrideConfigFile: "node_modules/myconf/.eslintrc.json", + cwd: root, + ignore: false, + useEslintrc: false, + }); + const results = await engine.lintFiles( + "node_modules/myconf/foo/test.js", + ); + + // Expected to be an 'eqeqeq' error because the file doesn't match to `$CWD/foo/*.js`. + assert.deepStrictEqual(results, [ + { + errorCount: 1, + filePath: path.join( + getPath(), + "node_modules/myconf/foo/test.js", + ), + fixableErrorCount: 0, + fixableWarningCount: 0, + messages: [ + { + column: 3, + endColumn: 5, + endLine: 1, + line: 1, + message: "Expected '===' and instead saw '=='.", + messageId: "unexpected", + nodeType: "BinaryExpression", + ruleId: "eqeqeq", + severity: 2, + suggestions: [ + { + data: { + actualOperator: "==", + expectedOperator: "===", + }, + desc: "Use '===' instead of '=='.", + fix: { + range: [2, 4], + text: "===", + }, + messageId: "replaceOperator", + }, + ], + }, + ], + suppressedMessages: [], + source: "a == b", + usedDeprecatedRules: [], + warningCount: 0, + fatalErrorCount: 0, + }, + ]); + }); + }); + + describe("if { ignorePatterns: 'foo/*.txt', ... } is present by '--config node_modules/myconf/.eslintrc.json',", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: root, + files: { + "node_modules/myconf/.eslintrc.json": JSON.stringify({ + ignorePatterns: ["!/node_modules/myconf", "foo/*.js"], + rules: { + eqeqeq: "error", + }, + }), + "node_modules/myconf/foo/test.js": "a == b", + "foo/test.js": "a == b", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' with '**/*.js' should iterate 'node_modules/myconf/foo/test.js' but not 'foo/test.js'.", async () => { + const engine = new LegacyESLint({ + overrideConfigFile: "node_modules/myconf/.eslintrc.json", + cwd: getPath(), + useEslintrc: false, + }); + const files = (await engine.lintFiles("**/*.js")) + .map(r => r.filePath) + .sort(); + + assert.deepStrictEqual(files, [ + path.join(root, "node_modules/myconf/foo/test.js"), + ]); + }); + }); + }); + + describe("plugin conflicts", () => { + let uid = 0; + const root = getFixturePath("cli-engine/plugin-conflicts-"); + + /** + * Verify thrown errors. + * @param {() => Promise} f The function to run and throw. + * @param {Record} props The properties to verify. + * @returns {Promise} void + */ + async function assertThrows(f, props) { + try { + await f(); + } catch (error) { + for (const [key, value] of Object.entries(props)) { + assert.deepStrictEqual(error[key], value, key); + } + return; + } + + assert.fail("Function should throw an error, but not."); + } + + describe("between a config file and linear extendees.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": + "", + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify( + { + extends: ["two"], + plugins: ["foo"], + }, + )}`, + "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": + "", + "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify( + { + plugins: ["foo"], + }, + )}`, + ".eslintrc.json": JSON.stringify({ + extends: ["one"], + plugins: ["foo"], + }), + "test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + await engine.lintFiles("test.js"); + }); + }); + + describe("between a config file and same-depth extendees.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "node_modules/eslint-config-one/node_modules/eslint-plugin-foo/index.js": + "", + "node_modules/eslint-config-one/index.js": `module.exports = ${JSON.stringify( + { + plugins: ["foo"], + }, + )}`, + "node_modules/eslint-config-two/node_modules/eslint-plugin-foo/index.js": + "", + "node_modules/eslint-config-two/index.js": `module.exports = ${JSON.stringify( + { + plugins: ["foo"], + }, + )}`, + ".eslintrc.json": JSON.stringify({ + extends: ["one", "two"], + plugins: ["foo"], + }), + "test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file.)", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + await engine.lintFiles("test.js"); + }); + }); + + describe("between two config files in different directories, with single node_modules.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + await engine.lintFiles("subdir/test.js"); + }); + }); + + describe("between two config files in different directories, with multiple node_modules.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/node_modules/eslint-plugin-foo/index.js": "", + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + + await assertThrows(() => engine.lintFiles("subdir/test.js"), { + message: `Plugin "foo" was conflicted between "subdir${path.sep}.eslintrc.json" and ".eslintrc.json".`, + messageTemplate: "plugin-conflict", + messageData: { + pluginId: "foo", + plugins: [ + { + filePath: path.join( + getPath(), + "subdir/node_modules/eslint-plugin-foo/index.js", + ), + importerName: `subdir${path.sep}.eslintrc.json`, + }, + { + filePath: path.join( + getPath(), + "node_modules/eslint-plugin-foo/index.js", + ), + importerName: ".eslintrc.json", + }, + ], + }, + }); + }); + }); + + describe("between '--config' option and a regular config file, with single node_modules.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "node_modules/mine/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files, but node_modules directory is unique.)", async () => { + const engine = new LegacyESLint({ + cwd: getPath(), + overrideConfigFile: "node_modules/mine/.eslintrc.json", + }); + + await engine.lintFiles("test.js"); + }); + }); + + describe("between '--config' option and a regular config file, with multiple node_modules.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "node_modules/mine/node_modules/eslint-plugin-foo/index.js": + "", + "node_modules/mine/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from the base directory of the entry config file, but there are two entry config files.)", async () => { + const engine = new LegacyESLint({ + cwd: getPath(), + overrideConfigFile: "node_modules/mine/.eslintrc.json", + }); + + await assertThrows(() => engine.lintFiles("test.js"), { + message: + 'Plugin "foo" was conflicted between "--config" and ".eslintrc.json".', + messageTemplate: "plugin-conflict", + messageData: { + pluginId: "foo", + plugins: [ + { + filePath: path.join( + getPath(), + "node_modules/mine/node_modules/eslint-plugin-foo/index.js", + ), + importerName: "--config", + }, + { + filePath: path.join( + getPath(), + "node_modules/eslint-plugin-foo/index.js", + ), + importerName: ".eslintrc.json", + }, + ], + }, + }); + }); + }); + + describe("between '--plugin' option and a regular config file, with single node_modules.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file, but node_modules directory is unique.)", async () => { + const engine = new LegacyESLint({ + cwd: getPath(), + overrideConfig: { plugins: ["foo"] }, + }); + + await engine.lintFiles("subdir/test.js"); + }); + }); + + describe("between '--plugin' option and a regular config file, with multiple node_modules.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + "subdir/node_modules/eslint-plugin-foo/index.js": "", + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should throw plugin-conflict error. (Load the plugin from both CWD and the base directory of the entry config file.)", async () => { + const engine = new LegacyESLint({ + cwd: getPath(), + overrideConfig: { plugins: ["foo"] }, + }); + + await assertThrows(() => engine.lintFiles("subdir/test.js"), { + message: `Plugin "foo" was conflicted between "CLIOptions" and "subdir${path.sep}.eslintrc.json".`, + messageTemplate: "plugin-conflict", + messageData: { + pluginId: "foo", + plugins: [ + { + filePath: path.join( + getPath(), + "node_modules/eslint-plugin-foo/index.js", + ), + importerName: "CLIOptions", + }, + { + filePath: path.join( + getPath(), + "subdir/node_modules/eslint-plugin-foo/index.js", + ), + importerName: `subdir${path.sep}.eslintrc.json`, + }, + ], + }, + }); + }); + }); + + describe("'--resolve-plugins-relative-to' option overrides the location that ESLint load plugins from.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "node_modules/eslint-plugin-foo/index.js": "", + ".eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/node_modules/eslint-plugin-foo/index.js": "", + "subdir/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "subdir/test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from '--resolve-plugins-relative-to'.)", async () => { + const engine = new LegacyESLint({ + cwd: getPath(), + resolvePluginsRelativeTo: getPath(), + }); + + await engine.lintFiles("subdir/test.js"); + }); + }); + + describe("between two config files with different target files.", () => { + const { prepare, cleanup, getPath } = createCustomTeardown({ + cwd: `${root}${++uid}`, + files: { + "one/node_modules/eslint-plugin-foo/index.js": "", + "one/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "one/test.js": "", + "two/node_modules/eslint-plugin-foo/index.js": "", + "two/.eslintrc.json": JSON.stringify({ + plugins: ["foo"], + }), + "two/test.js": "", + }, + }); + + beforeEach(prepare); + afterEach(cleanup); + + it("'lintFiles()' should NOT throw plugin-conflict error. (Load the plugin from the base directory of the entry config file for each target file. Not related to each other.)", async () => { + const engine = new LegacyESLint({ cwd: getPath() }); + const results = await engine.lintFiles("*/test.js"); + + assert.strictEqual(results.length, 2); + }); + }); + }); + + describe("loading rules", () => { + it("should not load unused core rules", done => { + let calledDone = false; + + const cwd = getFixturePath("lazy-loading-rules"); + const pattern = "foo.js"; + const usedRules = ["semi"]; + + const forkedProcess = childProcess.fork( + path.join(__dirname, "../../_utils/test-lazy-loading-rules.js"), + [cwd, pattern, String(usedRules)], + ); + + // this is an error message + forkedProcess.on("message", ({ message, stack }) => { + if (calledDone) { + return; + } + calledDone = true; + + const error = new Error(message); + + error.stack = stack; + done(error); + }); + + forkedProcess.on("exit", exitCode => { + if (calledDone) { + return; + } + calledDone = true; + + if (exitCode === 0) { + done(); + } else { + done( + new Error( + "Forked process exited with a non-zero exit code", + ), + ); + } + }); + }); + }); + + // only works on a Windows machine + if (os.platform() === "win32") { + // https://github.com/eslint/eslint/issues/17042 + describe("with cwd that is using forward slash on Windows", () => { + const cwd = getFixturePath("example-app3"); + const cwdForwardSlash = cwd.replace(/\\/gu, "/"); + + it("should correctly handle ignore patterns", async () => { + const engine = new LegacyESLint({ cwd: cwdForwardSlash }); + const results = await engine.lintFiles(["./src"]); + + // src/dist/2.js should be ignored + assert.strictEqual(results.length, 1); + assert.strictEqual( + results[0].filePath, + path.join(cwd, "src\\1.js"), + ); + }); + + it("should pass cwd with backslashes to rules", async () => { + const engine = new LegacyESLint({ + cwd: cwdForwardSlash, + useEslintrc: false, + overrideConfig: { + plugins: ["test"], + rules: { + "test/report-cwd": "error", + }, + }, + }); + const results = await engine.lintText(""); + + assert.strictEqual( + results[0].messages[0].ruleId, + "test/report-cwd", + ); + assert.strictEqual(results[0].messages[0].message, cwd); + }); + + it("should pass cwd with backslashes to formatters", async () => { + const engine = new LegacyESLint({ + cwd: cwdForwardSlash, + }); + const results = await engine.lintText(""); + const formatter = await engine.loadFormatter("cwd"); + + assert.strictEqual(formatter.format(results), cwd); + }); + }); + } +}); diff --git a/tests/lib/languages/js/source-code/source-code.js b/tests/lib/languages/js/source-code/source-code.js new file mode 100644 index 000000000000..fda822f666bf --- /dev/null +++ b/tests/lib/languages/js/source-code/source-code.js @@ -0,0 +1,4521 @@ +/** + * @fileoverview Abstraction of JavaScript source code. + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const fs = require("node:fs"), + path = require("node:path"), + assert = require("chai").assert, + espree = require("espree"), + eslintScope = require("eslint-scope"), + sinon = require("sinon"), + { Linter } = require("../../../../../lib/linter"), + SourceCode = require("../../../../../lib/languages/js/source-code/source-code"), + astUtils = require("../../../../../lib/shared/ast-utils"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const DEFAULT_CONFIG = { + ecmaVersion: 6, + comment: true, + tokens: true, + range: true, + loc: true, +}; +const linter = new Linter({ configType: "eslintrc" }); +const flatLinter = new Linter({ configType: "flat" }); +const AST = espree.parse("let foo = bar;", DEFAULT_CONFIG), + TEST_CODE = "var answer = 6 * 7;", + SHEBANG_TEST_CODE = `#!/usr/bin/env node\n${TEST_CODE}`; +const filename = "foo.js"; + +/** + * Get variables in the current scope + * @param {Object} scope current scope + * @param {string} name name of the variable to look for + * @returns {ASTNode|null} The variable object + * @private + */ +function getVariable(scope, name) { + return scope.variables.find(v => v.name === name) || null; +} + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("SourceCode", () => { + describe("new SourceCode()", () => { + it("should create a new instance when called with valid data", () => { + const ast = { comments: [], tokens: [], loc: {}, range: [] }; + const sourceCode = new SourceCode("foo;", ast); + + assert.isObject(sourceCode); + assert.strictEqual(sourceCode.text, "foo;"); + assert.strictEqual(sourceCode.ast, ast); + }); + + it("should create a new instance when called with valid optional data", () => { + const parserServices = {}; + const scopeManager = {}; + const visitorKeys = {}; + const ast = { comments: [], tokens: [], loc: {}, range: [] }; + const sourceCode = new SourceCode({ + text: "foo;", + ast, + parserServices, + scopeManager, + visitorKeys, + }); + + assert.isObject(sourceCode); + assert.strictEqual(sourceCode.text, "foo;"); + assert.strictEqual(sourceCode.ast, ast); + assert.strictEqual(sourceCode.parserServices, parserServices); + assert.strictEqual(sourceCode.scopeManager, scopeManager); + assert.strictEqual(sourceCode.visitorKeys, visitorKeys); + }); + + it("should split text into lines when called with valid data", () => { + const ast = { comments: [], tokens: [], loc: {}, range: [] }; + const sourceCode = new SourceCode("foo;\nbar;", ast); + + assert.isObject(sourceCode); + assert.strictEqual(sourceCode.lines.length, 2); + assert.strictEqual(sourceCode.lines[0], "foo;"); + assert.strictEqual(sourceCode.lines[1], "bar;"); + }); + + it("should throw an error when called with a false AST", () => { + assert.throws( + () => new SourceCode("foo;", false), + /Unexpected empty AST\. \(false\)/u, + ); + }); + + it("should throw an error when called with a null AST", () => { + assert.throws( + () => new SourceCode("foo;", null), + /Unexpected empty AST\. \(null\)/u, + ); + }); + + it("should throw an error when called with a undefined AST", () => { + assert.throws( + () => new SourceCode("foo;", void 0), + /Unexpected empty AST\. \(undefined\)/u, + ); + }); + + it("should throw an error when called with an AST that's missing tokens", () => { + assert.throws( + () => + new SourceCode("foo;", { + comments: [], + loc: {}, + range: [], + }), + /missing the tokens array/u, + ); + }); + + it("should throw an error when called with an AST that's missing comments", () => { + assert.throws( + () => + new SourceCode("foo;", { tokens: [], loc: {}, range: [] }), + /missing the comments array/u, + ); + }); + + it("should throw an error when called with an AST that's missing location", () => { + assert.throws( + () => + new SourceCode("foo;", { + comments: [], + tokens: [], + range: [], + }), + /missing location information/u, + ); + }); + + it("should throw an error when called with an AST that's missing range", () => { + assert.throws( + () => + new SourceCode("foo;", { + comments: [], + tokens: [], + loc: {}, + }), + /missing range information/u, + ); + }); + + it("should store all tokens and comments sorted by range", () => { + const comments = [{ range: [0, 2] }, { range: [10, 12] }]; + const tokens = [ + { range: [3, 8] }, + { range: [8, 10] }, + { range: [12, 20] }, + ]; + const sourceCode = new SourceCode("", { + comments, + tokens, + loc: {}, + range: [], + }); + + const actual = sourceCode.tokensAndComments; + const expected = [ + comments[0], + tokens[0], + tokens[1], + comments[1], + tokens[2], + ]; + + assert.deepStrictEqual(actual, expected); + }); + + describe("if a text has BOM,", () => { + let sourceCode; + + beforeEach(() => { + const ast = { comments: [], tokens: [], loc: {}, range: [] }; + + sourceCode = new SourceCode("\uFEFFconsole.log('hello');", ast); + }); + + it("should has true at `hasBOM` property.", () => { + assert.strictEqual(sourceCode.hasBOM, true); + }); + + it("should not has BOM in `text` property.", () => { + assert.strictEqual(sourceCode.text, "console.log('hello');"); + }); + }); + + describe("if a text doesn't have BOM,", () => { + let sourceCode; + + beforeEach(() => { + const ast = { comments: [], tokens: [], loc: {}, range: [] }; + + sourceCode = new SourceCode("console.log('hello');", ast); + }); + + it("should has false at `hasBOM` property.", () => { + assert.strictEqual(sourceCode.hasBOM, false); + }); + + it("should not has BOM in `text` property.", () => { + assert.strictEqual(sourceCode.text, "console.log('hello');"); + }); + }); + + describe("when a text has a shebang", () => { + let sourceCode; + + beforeEach(() => { + const ast = { + comments: [ + { + type: "Line", + value: "/usr/bin/env node", + range: [0, 19], + }, + ], + tokens: [], + loc: {}, + range: [], + }; + + sourceCode = new SourceCode(SHEBANG_TEST_CODE, ast); + }); + + it('should change the type of the first comment to "Shebang"', () => { + const firstToken = sourceCode.getAllComments()[0]; + + assert.strictEqual(firstToken.type, "Shebang"); + }); + }); + + describe("when a text does not have a shebang", () => { + it("should not change the type of the first comment", () => { + const ast = { + comments: [ + { type: "Line", value: "comment", range: [0, 9] }, + ], + tokens: [], + loc: {}, + range: [], + }; + const sourceCode = new SourceCode( + "//comment\nconsole.log('hello');", + ast, + ); + const firstToken = sourceCode.getAllComments()[0]; + + assert.strictEqual(firstToken.type, "Line"); + }); + }); + + describe("when it read a UTF-8 file (has BOM), SourceCode", () => { + const UTF8_FILE = path.resolve( + __dirname, + "../../../../fixtures/utf8-bom.js", + ); + const text = fs + .readFileSync(UTF8_FILE, "utf8") + .replace(/\r\n/gu, "\n"); // <-- For autocrlf of "git for Windows" + let sourceCode; + + beforeEach(() => { + const ast = { comments: [], tokens: [], loc: {}, range: [] }; + + sourceCode = new SourceCode(text, ast); + }); + + it("to be clear, check the file has UTF-8 BOM.", () => { + const buffer = fs.readFileSync(UTF8_FILE); + + assert.strictEqual(buffer[0], 0xef); + assert.strictEqual(buffer[1], 0xbb); + assert.strictEqual(buffer[2], 0xbf); + }); + + it("should has true at `hasBOM` property.", () => { + assert.strictEqual(sourceCode.hasBOM, true); + }); + + it("should not has BOM in `text` property.", () => { + assert.strictEqual( + sourceCode.text, + '"use strict";\n\nconsole.log("This file has [0xEF, 0xBB, 0xBF] as BOM.");\n', + ); + }); + }); + }); + + describe("getJSDocComment()", () => { + afterEach(() => { + sinon.verifyAndRestore(); + }); + + it("should not take a JSDoc comment from a FunctionDeclaration parent node when the node is a FunctionExpression", () => { + const code = [ + "/** Desc*/", + "function Foo(){var t = function(){}}", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc, null); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionExpression: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should not take a JSDoc comment from a VariableDeclaration parent node when the node is a FunctionExpression inside a NewExpression", () => { + const code = ["/** Desc*/", "var x = new Foo(function(){});"].join( + "\n", + ); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc, null); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionExpression: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should not take a JSDoc comment from a FunctionExpression parent node when the node is a FunctionExpression", () => { + const code = [ + "/** Desc*/", + "var f = function(){var t = function(arg){}}", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + if (node.params.length === 1) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc, null); + } + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionExpression: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue( + spy.calledTwice, + "Event handler should be called twice.", + ); + }); + + it("should get JSDoc comment for FunctionExpression in a CallExpression", () => { + const code = [ + "call(", + " /** Documentation. */", + " function(argName) {", + " return 'the return';", + " }", + ");", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc.type, "Block"); + assert.strictEqual(jsdoc.value, "* Documentation. "); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionExpression: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should get JSDoc comment for node when the node is a FunctionDeclaration", () => { + const code = ["/** Desc*/", "function Foo(){}"].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc.type, "Block"); + assert.strictEqual(jsdoc.value, "* Desc"); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionDeclaration: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should get JSDoc comment for node when the node is a FunctionDeclaration but its parent is an export", () => { + const code = ["/** Desc*/", "export function Foo(){}"].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc.type, "Block"); + assert.strictEqual(jsdoc.value, "* Desc"); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionDeclaration: spy, + }; + }, + }); + linter.verify(code, { + rules: { checker: "error" }, + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should get JSDoc comment for node when the node is a FunctionDeclaration but not the first statement", () => { + const code = [ + "'use strict';", + "/** Desc*/", + "function Foo(){}", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc.type, "Block"); + assert.strictEqual(jsdoc.value, "* Desc"); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionDeclaration: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should not get JSDoc comment for node when the node is a FunctionDeclaration inside of an IIFE without a JSDoc comment", () => { + const code = [ + "/** Desc*/", + "(function(){", + "function Foo(){}", + "}())", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.isNull(jsdoc); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionDeclaration: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should get JSDoc comment for node when the node is a FunctionDeclaration and there are multiple comments", () => { + const code = [ + "/* Code is good */", + "/** Desc*/", + "function Foo(){}", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc.type, "Block"); + assert.strictEqual(jsdoc.value, "* Desc"); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionDeclaration: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should get JSDoc comment for node when the node is a FunctionDeclaration inside of an IIFE", () => { + const code = [ + "/** Code is good */", + "(function() {", + "/** Desc*/", + "function Foo(){}", + "}())", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc.type, "Block"); + assert.strictEqual(jsdoc.value, "* Desc"); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionDeclaration: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should get JSDoc comment for node when the node is a FunctionExpression inside of an object literal", () => { + const code = [ + "/** Code is good */", + "var o = {", + "/** Desc*/", + "foo: function(){}", + "};", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc.type, "Block"); + assert.strictEqual(jsdoc.value, "* Desc"); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionExpression: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should get JSDoc comment for node when the node is a ArrowFunctionExpression inside of an object literal", () => { + const code = [ + "/** Code is good */", + "var o = {", + "/** Desc*/", + "foo: () => {}", + "};", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc.type, "Block"); + assert.strictEqual(jsdoc.value, "* Desc"); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + ArrowFunctionExpression: spy, + }; + }, + }); + linter.verify(code, { + rules: { checker: "error" }, + parserOptions: { ecmaVersion: 6 }, + }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should get JSDoc comment for node when the node is a FunctionExpression in an assignment", () => { + const code = [ + "/** Code is good */", + "/** Desc*/", + "Foo.bar = function(){}", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc.type, "Block"); + assert.strictEqual(jsdoc.value, "* Desc"); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionExpression: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should get JSDoc comment for node when the node is a FunctionExpression in an assignment inside an IIFE", () => { + const code = [ + "/** Code is good */", + "(function iife() {", + "/** Desc*/", + "Foo.bar = function(){}", + "}());", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + if (!node.id) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc.type, "Block"); + assert.strictEqual(jsdoc.value, "* Desc"); + } + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionExpression: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue(spy.calledTwice, "Event handler should be called."); + }); + + it("should not get JSDoc comment for node when the node is a FunctionExpression in an assignment inside an IIFE without a JSDoc comment", () => { + const code = [ + "/** Code is good */", + "(function iife() {", + "//* whatever", + "Foo.bar = function(){}", + "}());", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + if (!node.id) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.isNull(jsdoc); + } + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionExpression: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue(spy.calledTwice, "Event handler should be called."); + }); + + it("should not get JSDoc comment for node when the node is a FunctionExpression inside of a CallExpression", () => { + const code = [ + "/** Code is good */", + "module.exports = (function() {", + "}());", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + if (!node.id) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.isNull(jsdoc); + } + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionExpression: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should not get JSDoc comment for node when the node is a FunctionExpression in an assignment inside an IIFE without a JSDoc comment", () => { + const code = [ + "/**", + " * Merges two objects together.", + " * @param {Object} target of the cloning operation", + " * @param {Object} [source] object", + " * @returns {void}", + " */", + "exports.mixin = function(target, source) {", + " Object.keys(source).forEach(function forEach(key) {", + " target[key] = source[key];", + " });", + "};", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + if (node.id) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.isNull(jsdoc); + } + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionExpression: spy, + }; + }, + }); + linter.verify(code, { rules: { checker: "error" } }); + assert.isTrue(spy.calledTwice, "Event handler should be called."); + }); + + it("should get JSDoc comment for node when the node is a ClassExpression", () => { + const code = [ + "/** Merges two objects together.*/", + "var A = class {", + "};", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc.type, "Block"); + assert.strictEqual( + jsdoc.value, + "* Merges two objects together.", + ); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + ClassExpression: spy, + }; + }, + }); + linter.verify(code, { + rules: { checker: "error" }, + parserOptions: { ecmaVersion: 6 }, + }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should get JSDoc comment for node when the node is a ClassDeclaration", () => { + const code = [ + "/** Merges two objects together.*/", + "class A {", + "};", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc.type, "Block"); + assert.strictEqual( + jsdoc.value, + "* Merges two objects together.", + ); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + ClassDeclaration: spy, + }; + }, + }); + linter.verify(code, { + rules: { checker: "error" }, + parserOptions: { ecmaVersion: 6 }, + }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should not get JSDoc comment for class method even if the class has jsdoc present", () => { + const code = [ + "/** Merges two objects together.*/", + "var A = class {", + " constructor(xs) {}", + "};", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.isNull(jsdoc); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionExpression: spy, + }; + }, + }); + linter.verify(code, { + rules: { checker: "error" }, + parserOptions: { ecmaVersion: 6 }, + }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should get JSDoc comment for function expression even if function has blank lines on top", () => { + const code = [ + "/** Merges two objects together.*/", + "var A = ", + " ", + " ", + " ", + " function() {", + "};", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.strictEqual(jsdoc.type, "Block"); + assert.strictEqual( + jsdoc.value, + "* Merges two objects together.", + ); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionExpression: spy, + }; + }, + }); + linter.verify(code, { + rules: { checker: "error" }, + parserOptions: { ecmaVersion: 6 }, + }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + + it("should not get JSDoc comment for function declaration when the function has blank lines on top", () => { + const code = [ + "/** Merges two objects together.*/", + " ", + " ", + " ", + "function test() {", + "};", + ].join("\n"); + + /** + * Check jsdoc presence + * @param {ASTNode} node not to check + * @returns {void} + * @private + */ + function assertJSDoc(node) { + const sourceCode = linter.getSourceCode(); + const jsdoc = sourceCode.getJSDocComment(node); + + assert.isNull(jsdoc); + } + + const spy = sinon.spy(assertJSDoc); + + linter.defineRule("checker", { + create() { + return { + FunctionDeclaration: spy, + }; + }, + }); + linter.verify(code, { + rules: { checker: "error" }, + parserOptions: { ecmaVersion: 6 }, + }); + assert.isTrue(spy.calledOnce, "Event handler should be called."); + }); + }); + + describe("getLines()", () => { + it("should get proper lines when using \\n as a line break", () => { + const code = "a;\nb;", + ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + const lines = sourceCode.getLines(); + + assert.strictEqual(lines[0], "a;"); + assert.strictEqual(lines[1], "b;"); + }); + + it("should get proper lines when using \\r\\n as a line break", () => { + const code = "a;\r\nb;", + ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + const lines = sourceCode.getLines(); + + assert.strictEqual(lines[0], "a;"); + assert.strictEqual(lines[1], "b;"); + }); + + it("should get proper lines when using \\r as a line break", () => { + const code = "a;\rb;", + ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + const lines = sourceCode.getLines(); + + assert.strictEqual(lines[0], "a;"); + assert.strictEqual(lines[1], "b;"); + }); + + it("should get proper lines when using \\u2028 as a line break", () => { + const code = "a;\u2028b;", + ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + const lines = sourceCode.getLines(); + + assert.strictEqual(lines[0], "a;"); + assert.strictEqual(lines[1], "b;"); + }); + + it("should get proper lines when using \\u2029 as a line break", () => { + const code = "a;\u2029b;", + ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + const lines = sourceCode.getLines(); + + assert.strictEqual(lines[0], "a;"); + assert.strictEqual(lines[1], "b;"); + }); + }); + + describe("getText()", () => { + let sourceCode, ast; + + describe("when text begins with a shebang", () => { + it("should retrieve unaltered shebang text", () => { + // Shebangs are normalized to line comments before parsing. + ast = espree.parse( + SHEBANG_TEST_CODE.replace( + astUtils.shebangPattern, + (match, captured) => `//${captured}`, + ), + DEFAULT_CONFIG, + ); + sourceCode = new SourceCode(SHEBANG_TEST_CODE, ast); + + const shebangToken = sourceCode.getAllComments()[0]; + const shebangText = sourceCode.getText(shebangToken); + + assert.strictEqual(shebangToken.type, "Shebang"); + assert.strictEqual(shebangText, "#!/usr/bin/env node"); + }); + }); + + beforeEach(() => { + ast = espree.parse(TEST_CODE, DEFAULT_CONFIG); + sourceCode = new SourceCode(TEST_CODE, ast); + }); + + it("should retrieve all text when used without parameters", () => { + const text = sourceCode.getText(); + + assert.strictEqual(text, TEST_CODE); + }); + + it("should retrieve all text for root node", () => { + const text = sourceCode.getText(ast); + + assert.strictEqual(text, TEST_CODE); + }); + + it("should clamp to valid range when retrieving characters before start of source", () => { + const text = sourceCode.getText(ast, 2, 0); + + assert.strictEqual(text, TEST_CODE); + }); + + it("should retrieve all text for binary expression", () => { + const node = ast.body[0].declarations[0].init; + const text = sourceCode.getText(node); + + assert.strictEqual(text, "6 * 7"); + }); + + it("should retrieve all text plus two characters before for binary expression", () => { + const node = ast.body[0].declarations[0].init; + const text = sourceCode.getText(node, 2); + + assert.strictEqual(text, "= 6 * 7"); + }); + + it("should retrieve all text plus one character after for binary expression", () => { + const node = ast.body[0].declarations[0].init; + const text = sourceCode.getText(node, 0, 1); + + assert.strictEqual(text, "6 * 7;"); + }); + + it("should retrieve all text plus two characters before and one character after for binary expression", () => { + const node = ast.body[0].declarations[0].init; + const text = sourceCode.getText(node, 2, 1); + + assert.strictEqual(text, "= 6 * 7;"); + }); + }); + + describe("getNodeByRangeIndex()", () => { + let sourceCode; + + beforeEach(() => { + const ast = espree.parse(TEST_CODE, DEFAULT_CONFIG); + + sourceCode = new SourceCode(TEST_CODE, ast); + }); + + it("should retrieve a node starting at the given index", () => { + const node = sourceCode.getNodeByRangeIndex(4); + + assert.strictEqual(node.type, "Identifier"); + }); + + it("should retrieve a node containing the given index", () => { + const node = sourceCode.getNodeByRangeIndex(6); + + assert.strictEqual(node.type, "Identifier"); + }); + + it("should retrieve a node that is exactly the given index", () => { + const node = sourceCode.getNodeByRangeIndex(13); + + assert.strictEqual(node.type, "Literal"); + assert.strictEqual(node.value, 6); + }); + + it("should retrieve a node ending with the given index", () => { + const node = sourceCode.getNodeByRangeIndex(9); + + assert.strictEqual(node.type, "Identifier"); + }); + + it("should retrieve the deepest node containing the given index", () => { + let node = sourceCode.getNodeByRangeIndex(14); + + assert.strictEqual(node.type, "BinaryExpression"); + node = sourceCode.getNodeByRangeIndex(3); + assert.strictEqual(node.type, "VariableDeclaration"); + }); + + it("should return null if the index is outside the range of any node", () => { + let node = sourceCode.getNodeByRangeIndex(-1); + + assert.isNull(node); + node = sourceCode.getNodeByRangeIndex(-99); + assert.isNull(node); + }); + }); + + describe("isSpaceBetween()", () => { + describe("should return true when there is at least one whitespace character between two tokens", () => { + [ + ["let foo", true], + ["let foo", true], + ["let /**/ foo", true], + ["let/**/foo", false], + ["let/*\n*/foo", false], + ].forEach(([code, expected]) => { + describe("when the first given is located before the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetween( + sourceCode.ast.tokens[0], + sourceCode.ast.tokens.at(-1), + ), + expected, + ); + }); + }); + + describe("when the first given is located after the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetween( + sourceCode.ast.tokens.at(-1), + sourceCode.ast.tokens[0], + ), + expected, + ); + }); + }); + }); + + [ + ["a+b", false], + ["a +b", true], + ["a/**/+b", false], + ["a/* */+b", false], + ["a/**/ +b", true], + ["a/**/ /**/+b", true], + ["a/* */ /* */+b", true], + ["a/**/\n/**/+b", true], + ["a/* */\n/* */+b", true], + ["a/**/+b/**/+c", false], + ["a/* */+b/* */+c", false], + ["a/**/+b /**/+c", true], + ["a/* */+b /* */+c", true], + ["a/**/ +b/**/+c", true], + ["a/* */ +b/* */+c", true], + ["a/**/+b\t/**/+c", true], + ["a/* */+b\t/* */+c", true], + ["a/**/\t+b/**/+c", true], + ["a/* */\t+b/* */+c", true], + ["a/**/+b\n/**/+c", true], + ["a/* */+b\n/* */+c", true], + ["a/**/\n+b/**/+c", true], + ["a/* */\n+b/* */+c", true], + ["a/* */+' /**/ '/* */+c", false], + ["a/* */+ ' /**/ '/* */+c", true], + ["a/* */+' /**/ ' /* */+c", true], + ["a/* */+ ' /**/ ' /* */+c", true], + ["a/* */+` /*\n*/ `/* */+c", false], + ["a/* */+ ` /*\n*/ `/* */+c", true], + ["a/* */+` /*\n*/ ` /* */+c", true], + ["a/* */+ ` /*\n*/ ` /* */+c", true], + ].forEach(([code, expected]) => { + describe("when the first given is located before the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetween( + sourceCode.ast.tokens[0], + sourceCode.ast.tokens.at(-2), + ), + expected, + ); + }); + }); + + describe("when the first given is located after the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetween( + sourceCode.ast.tokens.at(-2), + sourceCode.ast.tokens[0], + ), + expected, + ); + }); + }); + }); + }); + + describe("should return true when there is at least one whitespace character between a token and a node", () => { + [ + [";let foo = bar", false], + [";/**/let foo = bar", false], + [";/* */let foo = bar", false], + ["; let foo = bar", true], + ["; let foo = bar", true], + ["; /**/let foo = bar", true], + ["; /* */let foo = bar", true], + [";/**/ let foo = bar", true], + [";/* */ let foo = bar", true], + ["; /**/ let foo = bar", true], + ["; /* */ let foo = bar", true], + [";\tlet foo = bar", true], + [";\tlet foo = bar", true], + [";\t/**/let foo = bar", true], + [";\t/* */let foo = bar", true], + [";/**/\tlet foo = bar", true], + [";/* */\tlet foo = bar", true], + [";\t/**/\tlet foo = bar", true], + [";\t/* */\tlet foo = bar", true], + [";\nlet foo = bar", true], + [";\nlet foo = bar", true], + [";\n/**/let foo = bar", true], + [";\n/* */let foo = bar", true], + [";/**/\nlet foo = bar", true], + [";/* */\nlet foo = bar", true], + [";\n/**/\nlet foo = bar", true], + [";\n/* */\nlet foo = bar", true], + ].forEach(([code, expected]) => { + describe("when the first given is located before the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetween( + sourceCode.ast.tokens[0], + sourceCode.ast.body.at(-1), + ), + expected, + ); + }); + }); + + describe("when the first given is located after the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetween( + sourceCode.ast.body.at(-1), + sourceCode.ast.tokens[0], + ), + expected, + ); + }); + }); + }); + }); + + describe("should return true when there is at least one whitespace character between a node and a token", () => { + [ + ["let foo = bar;;", false], + ["let foo = bar;;;", false], + ["let foo = 1; let bar = 2;;", true], + ["let foo = bar;/**/;", false], + ["let foo = bar;/* */;", false], + ["let foo = bar;;;", false], + ["let foo = bar; ;", true], + ["let foo = bar; /**/;", true], + ["let foo = bar; /* */;", true], + ["let foo = bar;/**/ ;", true], + ["let foo = bar;/* */ ;", true], + ["let foo = bar; /**/ ;", true], + ["let foo = bar; /* */ ;", true], + ["let foo = bar;\t;", true], + ["let foo = bar;\t/**/;", true], + ["let foo = bar;\t/* */;", true], + ["let foo = bar;/**/\t;", true], + ["let foo = bar;/* */\t;", true], + ["let foo = bar;\t/**/\t;", true], + ["let foo = bar;\t/* */\t;", true], + ["let foo = bar;\n;", true], + ["let foo = bar;\n/**/;", true], + ["let foo = bar;\n/* */;", true], + ["let foo = bar;/**/\n;", true], + ["let foo = bar;/* */\n;", true], + ["let foo = bar;\n/**/\n;", true], + ["let foo = bar;\n/* */\n;", true], + ].forEach(([code, expected]) => { + describe("when the first given is located before the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetween( + sourceCode.ast.body[0], + sourceCode.ast.tokens.at(-1), + ), + expected, + ); + }); + }); + + describe("when the first given is located after the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetween( + sourceCode.ast.tokens.at(-1), + sourceCode.ast.body[0], + ), + expected, + ); + }); + }); + }); + }); + + describe("should return true when there is at least one whitespace character between two nodes", () => { + [ + ["let foo = bar;let baz = qux;", false], + ["let foo = bar;/**/let baz = qux;", false], + ["let foo = bar;/* */let baz = qux;", false], + ["let foo = bar; let baz = qux;", true], + ["let foo = bar; /**/let baz = qux;", true], + ["let foo = bar; /* */let baz = qux;", true], + ["let foo = bar;/**/ let baz = qux;", true], + ["let foo = bar;/* */ let baz = qux;", true], + ["let foo = bar; /**/ let baz = qux;", true], + ["let foo = bar; /* */ let baz = qux;", true], + ["let foo = bar;\tlet baz = qux;", true], + ["let foo = bar;\t/**/let baz = qux;", true], + ["let foo = bar;\t/* */let baz = qux;", true], + ["let foo = bar;/**/\tlet baz = qux;", true], + ["let foo = bar;/* */\tlet baz = qux;", true], + ["let foo = bar;\t/**/\tlet baz = qux;", true], + ["let foo = bar;\t/* */\tlet baz = qux;", true], + ["let foo = bar;\nlet baz = qux;", true], + ["let foo = bar;\n/**/let baz = qux;", true], + ["let foo = bar;\n/* */let baz = qux;", true], + ["let foo = bar;/**/\nlet baz = qux;", true], + ["let foo = bar;/* */\nlet baz = qux;", true], + ["let foo = bar;\n/**/\nlet baz = qux;", true], + ["let foo = bar;\n/* */\nlet baz = qux;", true], + ["let foo = 1;let foo2 = 2; let foo3 = 3;", true], + ].forEach(([code, expected]) => { + describe("when the first given is located before the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetween( + sourceCode.ast.body[0], + sourceCode.ast.body.at(-1), + ), + expected, + ); + }); + }); + + describe("when the first given is located after the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetween( + sourceCode.ast.body.at(-1), + sourceCode.ast.body[0], + ), + expected, + ); + }); + }); + }); + + it("JSXText tokens that contain only whitespaces should NOT be handled as space", () => { + const code = "let jsx =
\n {content}\n
"; + const ast = espree.parse(code, { + ...DEFAULT_CONFIG, + ecmaFeatures: { jsx: true }, + }); + const sourceCode = new SourceCode(code, ast); + const jsx = ast.body[0].declarations[0].init; + const interpolation = jsx.children[1]; + + assert.strictEqual( + sourceCode.isSpaceBetween( + jsx.openingElement, + interpolation, + ), + false, + ); + assert.strictEqual( + sourceCode.isSpaceBetween( + interpolation, + jsx.closingElement, + ), + false, + ); + + // Reversed order + assert.strictEqual( + sourceCode.isSpaceBetween( + interpolation, + jsx.openingElement, + ), + false, + ); + assert.strictEqual( + sourceCode.isSpaceBetween( + jsx.closingElement, + interpolation, + ), + false, + ); + }); + + it("JSXText tokens that contain both letters and whitespaces should NOT be handled as space", () => { + const code = "let jsx =
\n Hello\n
"; + const ast = espree.parse(code, { + ...DEFAULT_CONFIG, + ecmaFeatures: { jsx: true }, + }); + const sourceCode = new SourceCode(code, ast); + const jsx = ast.body[0].declarations[0].init; + + assert.strictEqual( + sourceCode.isSpaceBetween( + jsx.openingElement, + jsx.closingElement, + ), + false, + ); + + // Reversed order + assert.strictEqual( + sourceCode.isSpaceBetween( + jsx.closingElement, + jsx.openingElement, + ), + false, + ); + }); + + it("JSXText tokens that contain only letters should NOT be handled as space", () => { + const code = "let jsx =
Hello
"; + const ast = espree.parse(code, { + ...DEFAULT_CONFIG, + ecmaFeatures: { jsx: true }, + }); + const sourceCode = new SourceCode(code, ast); + const jsx = ast.body[0].declarations[0].init; + + assert.strictEqual( + sourceCode.isSpaceBetween( + jsx.openingElement, + jsx.closingElement, + ), + false, + ); + + // Reversed order + assert.strictEqual( + sourceCode.isSpaceBetween( + jsx.closingElement, + jsx.openingElement, + ), + false, + ); + }); + }); + + describe("should return false either of the arguments' location is inside the other one", () => { + [["let foo = bar;", false]].forEach(([code, expected]) => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetween( + sourceCode.ast.tokens[0], + sourceCode.ast.body[0], + ), + expected, + ); + + assert.strictEqual( + sourceCode.isSpaceBetween( + sourceCode.ast.tokens.at(-1), + sourceCode.ast.body[0], + ), + expected, + ); + + assert.strictEqual( + sourceCode.isSpaceBetween( + sourceCode.ast.body[0], + sourceCode.ast.tokens[0], + ), + expected, + ); + + assert.strictEqual( + sourceCode.isSpaceBetween( + sourceCode.ast.body[0], + sourceCode.ast.tokens.at(-1), + ), + expected, + ); + }); + }); + }); + }); + + describe("isSpaceBetweenTokens()", () => { + describe("should return true when there is at least one whitespace character between two tokens", () => { + [ + ["let foo", true], + ["let foo", true], + ["let /**/ foo", true], + ["let/**/foo", false], + ["let/*\n*/foo", false], + ].forEach(([code, expected]) => { + describe("when the first given is located before the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + sourceCode.ast.tokens[0], + sourceCode.ast.tokens.at(-1), + ), + expected, + ); + }); + }); + + describe("when the first given is located after the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + sourceCode.ast.tokens.at(-1), + sourceCode.ast.tokens[0], + ), + expected, + ); + }); + }); + }); + + [ + ["a+b", false], + ["a +b", true], + ["a/**/+b", false], + ["a/* */+b", false], + ["a/**/ +b", true], + ["a/**/ /**/+b", true], + ["a/* */ /* */+b", true], + ["a/**/\n/**/+b", true], + ["a/* */\n/* */+b", true], + ["a/**/+b/**/+c", false], + ["a/* */+b/* */+c", false], + ["a/**/+b /**/+c", true], + ["a/* */+b /* */+c", true], + ["a/**/ +b/**/+c", true], + ["a/* */ +b/* */+c", true], + ["a/**/+b\t/**/+c", true], + ["a/* */+b\t/* */+c", true], + ["a/**/\t+b/**/+c", true], + ["a/* */\t+b/* */+c", true], + ["a/**/+b\n/**/+c", true], + ["a/* */+b\n/* */+c", true], + ["a/**/\n+b/**/+c", true], + ["a/* */\n+b/* */+c", true], + ["a/* */+' /**/ '/* */+c", false], + ["a/* */+ ' /**/ '/* */+c", true], + ["a/* */+' /**/ ' /* */+c", true], + ["a/* */+ ' /**/ ' /* */+c", true], + ["a/* */+` /*\n*/ `/* */+c", false], + ["a/* */+ ` /*\n*/ `/* */+c", true], + ["a/* */+` /*\n*/ ` /* */+c", true], + ["a/* */+ ` /*\n*/ ` /* */+c", true], + ].forEach(([code, expected]) => { + describe("when the first given is located before the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + sourceCode.ast.tokens[0], + sourceCode.ast.tokens.at(-2), + ), + expected, + ); + }); + }); + + describe("when the first given is located after the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + sourceCode.ast.tokens.at(-2), + sourceCode.ast.tokens[0], + ), + expected, + ); + }); + }); + }); + }); + + describe("should return true when there is at least one whitespace character between a token and a node", () => { + [ + [";let foo = bar", false], + [";/**/let foo = bar", false], + [";/* */let foo = bar", false], + ["; let foo = bar", true], + ["; let foo = bar", true], + ["; /**/let foo = bar", true], + ["; /* */let foo = bar", true], + [";/**/ let foo = bar", true], + [";/* */ let foo = bar", true], + ["; /**/ let foo = bar", true], + ["; /* */ let foo = bar", true], + [";\tlet foo = bar", true], + [";\tlet foo = bar", true], + [";\t/**/let foo = bar", true], + [";\t/* */let foo = bar", true], + [";/**/\tlet foo = bar", true], + [";/* */\tlet foo = bar", true], + [";\t/**/\tlet foo = bar", true], + [";\t/* */\tlet foo = bar", true], + [";\nlet foo = bar", true], + [";\nlet foo = bar", true], + [";\n/**/let foo = bar", true], + [";\n/* */let foo = bar", true], + [";/**/\nlet foo = bar", true], + [";/* */\nlet foo = bar", true], + [";\n/**/\nlet foo = bar", true], + [";\n/* */\nlet foo = bar", true], + ].forEach(([code, expected]) => { + describe("when the first given is located before the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + sourceCode.ast.tokens[0], + sourceCode.ast.body.at(-1), + ), + expected, + ); + }); + }); + + describe("when the first given is located after the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + sourceCode.ast.body.at(-1), + sourceCode.ast.tokens[0], + ), + expected, + ); + }); + }); + }); + }); + + describe("should return true when there is at least one whitespace character between a node and a token", () => { + [ + ["let foo = bar;;", false], + ["let foo = bar;;;", false], + ["let foo = 1; let bar = 2;;", true], + ["let foo = bar;/**/;", false], + ["let foo = bar;/* */;", false], + ["let foo = bar;;;", false], + ["let foo = bar; ;", true], + ["let foo = bar; /**/;", true], + ["let foo = bar; /* */;", true], + ["let foo = bar;/**/ ;", true], + ["let foo = bar;/* */ ;", true], + ["let foo = bar; /**/ ;", true], + ["let foo = bar; /* */ ;", true], + ["let foo = bar;\t;", true], + ["let foo = bar;\t/**/;", true], + ["let foo = bar;\t/* */;", true], + ["let foo = bar;/**/\t;", true], + ["let foo = bar;/* */\t;", true], + ["let foo = bar;\t/**/\t;", true], + ["let foo = bar;\t/* */\t;", true], + ["let foo = bar;\n;", true], + ["let foo = bar;\n/**/;", true], + ["let foo = bar;\n/* */;", true], + ["let foo = bar;/**/\n;", true], + ["let foo = bar;/* */\n;", true], + ["let foo = bar;\n/**/\n;", true], + ["let foo = bar;\n/* */\n;", true], + ].forEach(([code, expected]) => { + describe("when the first given is located before the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + sourceCode.ast.body[0], + sourceCode.ast.tokens.at(-1), + ), + expected, + ); + }); + }); + + describe("when the first given is located after the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + sourceCode.ast.tokens.at(-1), + sourceCode.ast.body[0], + ), + expected, + ); + }); + }); + }); + }); + + describe("should return true when there is at least one whitespace character between two nodes", () => { + [ + ["let foo = bar;let baz = qux;", false], + ["let foo = bar;/**/let baz = qux;", false], + ["let foo = bar;/* */let baz = qux;", false], + ["let foo = bar; let baz = qux;", true], + ["let foo = bar; /**/let baz = qux;", true], + ["let foo = bar; /* */let baz = qux;", true], + ["let foo = bar;/**/ let baz = qux;", true], + ["let foo = bar;/* */ let baz = qux;", true], + ["let foo = bar; /**/ let baz = qux;", true], + ["let foo = bar; /* */ let baz = qux;", true], + ["let foo = bar;\tlet baz = qux;", true], + ["let foo = bar;\t/**/let baz = qux;", true], + ["let foo = bar;\t/* */let baz = qux;", true], + ["let foo = bar;/**/\tlet baz = qux;", true], + ["let foo = bar;/* */\tlet baz = qux;", true], + ["let foo = bar;\t/**/\tlet baz = qux;", true], + ["let foo = bar;\t/* */\tlet baz = qux;", true], + ["let foo = bar;\nlet baz = qux;", true], + ["let foo = bar;\n/**/let baz = qux;", true], + ["let foo = bar;\n/* */let baz = qux;", true], + ["let foo = bar;/**/\nlet baz = qux;", true], + ["let foo = bar;/* */\nlet baz = qux;", true], + ["let foo = bar;\n/**/\nlet baz = qux;", true], + ["let foo = bar;\n/* */\nlet baz = qux;", true], + ["let foo = 1;let foo2 = 2; let foo3 = 3;", true], + ].forEach(([code, expected]) => { + describe("when the first given is located before the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + sourceCode.ast.body[0], + sourceCode.ast.body.at(-1), + ), + expected, + ); + }); + }); + + describe("when the first given is located after the second", () => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + sourceCode.ast.body.at(-1), + sourceCode.ast.body[0], + ), + expected, + ); + }); + }); + }); + + it("JSXText tokens that contain only whitespaces should be handled as space", () => { + const code = "let jsx =
\n {content}\n
"; + const ast = espree.parse(code, { + ...DEFAULT_CONFIG, + ecmaFeatures: { jsx: true }, + }); + const sourceCode = new SourceCode(code, ast); + const jsx = ast.body[0].declarations[0].init; + const interpolation = jsx.children[1]; + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + jsx.openingElement, + interpolation, + ), + true, + ); + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + interpolation, + jsx.closingElement, + ), + true, + ); + + // Reversed order + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + interpolation, + jsx.openingElement, + ), + true, + ); + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + jsx.closingElement, + interpolation, + ), + true, + ); + }); + + it("JSXText tokens that contain both letters and whitespaces should be handled as space", () => { + const code = "let jsx =
\n Hello\n
"; + const ast = espree.parse(code, { + ...DEFAULT_CONFIG, + ecmaFeatures: { jsx: true }, + }); + const sourceCode = new SourceCode(code, ast); + const jsx = ast.body[0].declarations[0].init; + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + jsx.openingElement, + jsx.closingElement, + ), + true, + ); + + // Reversed order + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + jsx.closingElement, + jsx.openingElement, + ), + true, + ); + }); + + it("JSXText tokens that contain only letters should NOT be handled as space", () => { + const code = "let jsx =
Hello
"; + const ast = espree.parse(code, { + ...DEFAULT_CONFIG, + ecmaFeatures: { jsx: true }, + }); + const sourceCode = new SourceCode(code, ast); + const jsx = ast.body[0].declarations[0].init; + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + jsx.openingElement, + jsx.closingElement, + ), + false, + ); + + // Reversed order + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + jsx.closingElement, + jsx.openingElement, + ), + false, + ); + }); + }); + + describe("should return false either of the arguments' location is inside the other one", () => { + [["let foo = bar;", false]].forEach(([code, expected]) => { + it(code, () => { + const ast = espree.parse(code, DEFAULT_CONFIG), + sourceCode = new SourceCode(code, ast); + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + sourceCode.ast.tokens[0], + sourceCode.ast.body[0], + ), + expected, + ); + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + sourceCode.ast.tokens.at(-1), + sourceCode.ast.body[0], + ), + expected, + ); + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + sourceCode.ast.body[0], + sourceCode.ast.tokens[0], + ), + expected, + ); + + assert.strictEqual( + sourceCode.isSpaceBetweenTokens( + sourceCode.ast.body[0], + sourceCode.ast.tokens.at(-1), + ), + expected, + ); + }); + }); + }); + }); + + // need to check that linter.verify() works with SourceCode + + describe("linter.verify()", () => { + const CONFIG = { + parserOptions: { ecmaVersion: 6 }, + }; + + it("should work when passed a SourceCode object without a config", () => { + const ast = espree.parse(TEST_CODE, DEFAULT_CONFIG); + + const sourceCode = new SourceCode(TEST_CODE, ast), + messages = linter.verify(sourceCode); + + assert.strictEqual(messages.length, 0); + }); + + it("should work when passed a SourceCode object containing ES6 syntax and config", () => { + const sourceCode = new SourceCode("let foo = bar;", AST), + messages = linter.verify(sourceCode, CONFIG); + + assert.strictEqual(messages.length, 0); + }); + + it("should report an error when using let and ecmaVersion is 6", () => { + const sourceCode = new SourceCode("let foo = bar;", AST), + messages = linter.verify(sourceCode, { + parserOptions: { ecmaVersion: 6 }, + rules: { "no-unused-vars": 2 }, + }); + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "'foo' is assigned a value but never used.", + ); + }); + }); + + describe("getLocFromIndex()", () => { + const CODE = + "foo\n" + + "bar\r\n" + + "baz\r" + + "qux\u2028" + + "foo\u2029" + + "\n" + + "qux\n"; + + let sourceCode; + + beforeEach(() => { + sourceCode = new SourceCode( + CODE, + espree.parse(CODE, DEFAULT_CONFIG), + ); + }); + + it("should return the location of a range index", () => { + assert.deepStrictEqual(sourceCode.getLocFromIndex(0), { + line: 1, + column: 0, + }); + assert.deepStrictEqual(sourceCode.getLocFromIndex(3), { + line: 1, + column: 3, + }); + assert.deepStrictEqual(sourceCode.getLocFromIndex(4), { + line: 2, + column: 0, + }); + assert.deepStrictEqual(sourceCode.getLocFromIndex(5), { + line: 2, + column: 1, + }); + assert.deepStrictEqual(sourceCode.getLocFromIndex(15), { + line: 4, + column: 2, + }); + assert.deepStrictEqual(sourceCode.getLocFromIndex(21), { + line: 6, + column: 0, + }); + }); + + it("should throw if given a bad input", () => { + assert.throws( + () => sourceCode.getLocFromIndex({ line: 1, column: 1 }), + /Expected `index` to be a number\./u, + ); + }); + + it("should not throw if given sourceCode.text.length", () => { + assert.deepStrictEqual(sourceCode.getLocFromIndex(CODE.length), { + line: 8, + column: 0, + }); + }); + + it("should throw if given an out-of-range input", () => { + assert.throws( + () => sourceCode.getLocFromIndex(CODE.length + 1), + /Index out of range \(requested index 27, but source text has length 26\)\./u, + ); + }); + + it("is symmetric with getIndexFromLoc()", () => { + for (let index = 0; index <= CODE.length; index++) { + assert.strictEqual( + index, + sourceCode.getIndexFromLoc( + sourceCode.getLocFromIndex(index), + ), + ); + } + }); + }); + + describe("getIndexFromLoc()", () => { + const CODE = + "foo\n" + + "bar\r\n" + + "baz\r" + + "qux\u2028" + + "foo\u2029" + + "\n" + + "qux\n"; + + let sourceCode; + + beforeEach(() => { + sourceCode = new SourceCode( + CODE, + espree.parse(CODE, DEFAULT_CONFIG), + ); + }); + it("should return the range index of a location", () => { + assert.strictEqual( + sourceCode.getIndexFromLoc({ line: 2, column: 1 }), + 5, + ); + assert.strictEqual( + sourceCode.getIndexFromLoc({ line: 1, column: 3 }), + 3, + ); + assert.strictEqual( + sourceCode.getIndexFromLoc({ line: 2, column: 0 }), + 4, + ); + assert.strictEqual( + sourceCode.getIndexFromLoc({ line: 7, column: 0 }), + 22, + ); + assert.strictEqual( + sourceCode.getIndexFromLoc({ line: 7, column: 3 }), + 25, + ); + }); + + it("should throw a useful error if given a malformed location", () => { + assert.throws( + () => sourceCode.getIndexFromLoc(5), + /Expected `loc` to be an object with numeric `line` and `column` properties\./u, + ); + + assert.throws( + () => sourceCode.getIndexFromLoc(null), + /Expected `loc` to be an object with numeric `line` and `column` properties\./u, + ); + + assert.throws( + () => + sourceCode.getIndexFromLoc({ + line: "three", + column: "four", + }), + /Expected `loc` to be an object with numeric `line` and `column` properties\./u, + ); + }); + + it("should throw a useful error if `line` is out of range", () => { + assert.throws( + () => sourceCode.getIndexFromLoc({ line: 9, column: 0 }), + /Line number out of range \(line 9 requested, but only 8 lines present\)\./u, + ); + + assert.throws( + () => sourceCode.getIndexFromLoc({ line: 50, column: 3 }), + /Line number out of range \(line 50 requested, but only 8 lines present\)\./u, + ); + + assert.throws( + () => sourceCode.getIndexFromLoc({ line: 0, column: 0 }), + /Line number out of range \(line 0 requested\)\. Line numbers should be 1-based\./u, + ); + }); + + it("should throw a useful error if `column` is out of range", () => { + assert.throws( + () => sourceCode.getIndexFromLoc({ line: 1, column: -1 }), + "Invalid column number (column -1 requested).", + ); + + assert.throws( + () => sourceCode.getIndexFromLoc({ line: 1, column: -5 }), + "Invalid column number (column -5 requested).", + ); + + assert.throws( + () => sourceCode.getIndexFromLoc({ line: 3, column: -1 }), + "Invalid column number (column -1 requested).", + ); + + assert.throws( + () => sourceCode.getIndexFromLoc({ line: 3, column: 4 }), + /Column number out of range \(column 4 requested, but the length of line 3 is 4\)\./u, + ); + + assert.throws( + () => sourceCode.getIndexFromLoc({ line: 3, column: 50 }), + /Column number out of range \(column 50 requested, but the length of line 3 is 4\)\./u, + ); + + assert.throws( + () => sourceCode.getIndexFromLoc({ line: 8, column: 1 }), + /Column number out of range \(column 1 requested, but the length of line 8 is 0\)\./u, + ); + }); + + it("should not throw if the location one spot past the last character is given", () => { + assert.strictEqual( + sourceCode.getIndexFromLoc({ line: 8, column: 0 }), + CODE.length, + ); + }); + }); + + describe("getScope()", () => { + it("should throw an error when argument is missing", () => { + linter.defineRule("get-scope", { + create: context => ({ + Program() { + context.sourceCode.getScope(); + }, + }), + }); + + assert.throws(() => { + linter.verify("foo", { + rules: { "get-scope": 2 }, + }); + }, /Missing required argument: node/u); + }); + + /** + * Get the scope on the node `astSelector` specified. + * @param {string} code The source code to verify. + * @param {string} astSelector The AST selector to get scope. + * @param {number} [ecmaVersion=5] The ECMAScript version. + * @returns {{node: ASTNode, scope: escope.Scope}} Gotten scope. + */ + function getScope(code, astSelector, ecmaVersion = 5) { + let node, scope; + + linter.defineRule("get-scope", { + create: context => ({ + [astSelector](node0) { + node = node0; + scope = context.sourceCode.getScope(node); + }, + }), + }); + linter.verify(code, { + parserOptions: { ecmaVersion }, + rules: { "get-scope": 2 }, + }); + + return { node, scope }; + } + + it("should return 'function' scope on FunctionDeclaration (ES5)", () => { + const { node, scope } = getScope( + "function f() {}", + "FunctionDeclaration", + ); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node); + }); + + it("should return 'function' scope on FunctionExpression (ES5)", () => { + const { node, scope } = getScope( + "!function f() {}", + "FunctionExpression", + ); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node); + }); + + it("should return 'function' scope on the body of FunctionDeclaration (ES5)", () => { + const { node, scope } = getScope( + "function f() {}", + "BlockStatement", + ); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent); + }); + + it("should return 'function' scope on the body of FunctionDeclaration (ES2015)", () => { + const { node, scope } = getScope( + "function f() {}", + "BlockStatement", + 2015, + ); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent); + }); + + it("should return 'function' scope on BlockStatement in functions (ES5)", () => { + const { node, scope } = getScope( + "function f() { { var b; } }", + "BlockStatement > BlockStatement", + ); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["arguments", "b"], + ); + }); + + it("should return 'block' scope on BlockStatement in functions (ES2015)", () => { + const { node, scope } = getScope( + "function f() { { let a; var b; } }", + "BlockStatement > BlockStatement", + 2015, + ); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "function"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["a"], + ); + assert.deepStrictEqual( + scope.variableScope.variables.map(v => v.name), + ["arguments", "b"], + ); + }); + + it("should return 'block' scope on nested BlockStatement in functions (ES2015)", () => { + const { node, scope } = getScope( + "function f() { { let a; { let b; var c; } } }", + "BlockStatement > BlockStatement > BlockStatement", + 2015, + ); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "block"); + assert.strictEqual(scope.upper.upper.type, "function"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["b"], + ); + assert.deepStrictEqual( + scope.upper.variables.map(v => v.name), + ["a"], + ); + assert.deepStrictEqual( + scope.variableScope.variables.map(v => v.name), + ["arguments", "c"], + ); + }); + + it("should return 'function' scope on SwitchStatement in functions (ES5)", () => { + const { node, scope } = getScope( + "function f() { switch (a) { case 0: var b; } }", + "SwitchStatement", + ); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["arguments", "b"], + ); + }); + + it("should return 'switch' scope on SwitchStatement in functions (ES2015)", () => { + const { node, scope } = getScope( + "function f() { switch (a) { case 0: let b; } }", + "SwitchStatement", + 2015, + ); + + assert.strictEqual(scope.type, "switch"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["b"], + ); + }); + + it("should return 'function' scope on SwitchCase in functions (ES5)", () => { + const { node, scope } = getScope( + "function f() { switch (a) { case 0: var b; } }", + "SwitchCase", + ); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent.parent); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["arguments", "b"], + ); + }); + + it("should return 'switch' scope on SwitchCase in functions (ES2015)", () => { + const { node, scope } = getScope( + "function f() { switch (a) { case 0: let b; } }", + "SwitchCase", + 2015, + ); + + assert.strictEqual(scope.type, "switch"); + assert.strictEqual(scope.block, node.parent); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["b"], + ); + }); + + it("should return 'catch' scope on CatchClause in functions (ES5)", () => { + const { node, scope } = getScope( + "function f() { try {} catch (e) { var a; } }", + "CatchClause", + ); + + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["e"], + ); + }); + + it("should return 'catch' scope on CatchClause in functions (ES2015)", () => { + const { node, scope } = getScope( + "function f() { try {} catch (e) { let a; } }", + "CatchClause", + 2015, + ); + + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["e"], + ); + }); + + it("should return 'catch' scope on the block of CatchClause in functions (ES5)", () => { + const { node, scope } = getScope( + "function f() { try {} catch (e) { var a; } }", + "CatchClause > BlockStatement", + ); + + assert.strictEqual(scope.type, "catch"); + assert.strictEqual(scope.block, node.parent); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["e"], + ); + }); + + it("should return 'block' scope on the block of CatchClause in functions (ES2015)", () => { + const { node, scope } = getScope( + "function f() { try {} catch (e) { let a; } }", + "CatchClause > BlockStatement", + 2015, + ); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["a"], + ); + }); + + it("should return 'function' scope on ForStatement in functions (ES5)", () => { + const { node, scope } = getScope( + "function f() { for (var i = 0; i < 10; ++i) {} }", + "ForStatement", + ); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["arguments", "i"], + ); + }); + + it("should return 'for' scope on ForStatement in functions (ES2015)", () => { + const { node, scope } = getScope( + "function f() { for (let i = 0; i < 10; ++i) {} }", + "ForStatement", + 2015, + ); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["i"], + ); + }); + + it("should return 'function' scope on the block body of ForStatement in functions (ES5)", () => { + const { node, scope } = getScope( + "function f() { for (var i = 0; i < 10; ++i) {} }", + "ForStatement > BlockStatement", + ); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent.parent); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["arguments", "i"], + ); + }); + + it("should return 'block' scope on the block body of ForStatement in functions (ES2015)", () => { + const { node, scope } = getScope( + "function f() { for (let i = 0; i < 10; ++i) {} }", + "ForStatement > BlockStatement", + 2015, + ); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + [], + ); + assert.deepStrictEqual( + scope.upper.variables.map(v => v.name), + ["i"], + ); + }); + + it("should return 'function' scope on ForInStatement in functions (ES5)", () => { + const { node, scope } = getScope( + "function f() { for (var key in obj) {} }", + "ForInStatement", + ); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["arguments", "key"], + ); + }); + + it("should return 'for' scope on ForInStatement in functions (ES2015)", () => { + const { node, scope } = getScope( + "function f() { for (let key in obj) {} }", + "ForInStatement", + 2015, + ); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["key"], + ); + }); + + it("should return 'function' scope on the block body of ForInStatement in functions (ES5)", () => { + const { node, scope } = getScope( + "function f() { for (var key in obj) {} }", + "ForInStatement > BlockStatement", + ); + + assert.strictEqual(scope.type, "function"); + assert.strictEqual(scope.block, node.parent.parent.parent); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["arguments", "key"], + ); + }); + + it("should return 'block' scope on the block body of ForInStatement in functions (ES2015)", () => { + const { node, scope } = getScope( + "function f() { for (let key in obj) {} }", + "ForInStatement > BlockStatement", + 2015, + ); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + [], + ); + assert.deepStrictEqual( + scope.upper.variables.map(v => v.name), + ["key"], + ); + }); + + it("should return 'for' scope on ForOfStatement in functions (ES2015)", () => { + const { node, scope } = getScope( + "function f() { for (let x of xs) {} }", + "ForOfStatement", + 2015, + ); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + ["x"], + ); + }); + + it("should return 'block' scope on the block body of ForOfStatement in functions (ES2015)", () => { + const { node, scope } = getScope( + "function f() { for (let x of xs) {} }", + "ForOfStatement > BlockStatement", + 2015, + ); + + assert.strictEqual(scope.type, "block"); + assert.strictEqual(scope.upper.type, "for"); + assert.strictEqual(scope.block, node); + assert.deepStrictEqual( + scope.variables.map(v => v.name), + [], + ); + assert.deepStrictEqual( + scope.upper.variables.map(v => v.name), + ["x"], + ); + }); + + it("should shadow the same name variable by the iteration variable.", () => { + const { node, scope } = getScope( + "let x; for (let x of x) {}", + "ForOfStatement", + 2015, + ); + + assert.strictEqual(scope.type, "for"); + assert.strictEqual(scope.upper.type, "global"); + assert.strictEqual(scope.block, node); + assert.strictEqual(scope.upper.variables[0].references.length, 0); + assert.strictEqual( + scope.references[0].identifier, + node.left.declarations[0].id, + ); + assert.strictEqual(scope.references[1].identifier, node.right); + assert.strictEqual( + scope.references[1].resolved, + scope.variables[0], + ); + }); + }); + + describe("getAncestors()", () => { + const code = TEST_CODE; + + it("should retrieve all ancestors when used", () => { + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const sourceCode = context.sourceCode; + const ancestors = + sourceCode.getAncestors(node); + + assert.strictEqual(ancestors.length, 3); + }); + return { BinaryExpression: spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + flatLinter.verify(code, config, filename); + assert(spy && spy.calledOnce, "Spy was not called."); + }); + + it("should retrieve empty ancestors for root node", () => { + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const sourceCode = context.sourceCode; + const ancestors = + sourceCode.getAncestors(node); + + assert.strictEqual(ancestors.length, 0); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + flatLinter.verify(code, config); + assert(spy && spy.calledOnce, "Spy was not called."); + }); + + it("should throw an error when the argument is missing", () => { + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(() => { + const sourceCode = context.sourceCode; + + assert.throws(() => { + sourceCode.getAncestors(); + }, /Missing required argument: node/u); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + flatLinter.verify(code, config); + assert(spy && spy.calledOnce, "Spy was not called."); + }); + }); + + describe("getDeclaredVariables(node)", () => { + /** + * Assert `sourceCode.getDeclaredVariables(node)` is valid. + * @param {string} code A code to check. + * @param {string} type A type string of ASTNode. This method checks variables on the node of the type. + * @param {Array>} expectedNamesList An array of expected variable names. The expected variable names is an array of string. + * @returns {void} + */ + function verify(code, type, expectedNamesList) { + linter.defineRules({ + test: { + create(context) { + const sourceCode = context.sourceCode; + + /** + * Assert `sourceCode.getDeclaredVariables(node)` is empty. + * @param {ASTNode} node A node to check. + * @returns {void} + */ + function checkEmpty(node) { + assert.strictEqual( + 0, + sourceCode.getDeclaredVariables(node).length, + ); + } + const rule = { + Program: checkEmpty, + EmptyStatement: checkEmpty, + BlockStatement: checkEmpty, + ExpressionStatement: checkEmpty, + LabeledStatement: checkEmpty, + BreakStatement: checkEmpty, + ContinueStatement: checkEmpty, + WithStatement: checkEmpty, + SwitchStatement: checkEmpty, + ReturnStatement: checkEmpty, + ThrowStatement: checkEmpty, + TryStatement: checkEmpty, + WhileStatement: checkEmpty, + DoWhileStatement: checkEmpty, + ForStatement: checkEmpty, + ForInStatement: checkEmpty, + DebuggerStatement: checkEmpty, + ThisExpression: checkEmpty, + ArrayExpression: checkEmpty, + ObjectExpression: checkEmpty, + Property: checkEmpty, + SequenceExpression: checkEmpty, + UnaryExpression: checkEmpty, + BinaryExpression: checkEmpty, + AssignmentExpression: checkEmpty, + UpdateExpression: checkEmpty, + LogicalExpression: checkEmpty, + ConditionalExpression: checkEmpty, + CallExpression: checkEmpty, + NewExpression: checkEmpty, + MemberExpression: checkEmpty, + SwitchCase: checkEmpty, + Identifier: checkEmpty, + Literal: checkEmpty, + ForOfStatement: checkEmpty, + ArrowFunctionExpression: checkEmpty, + YieldExpression: checkEmpty, + TemplateLiteral: checkEmpty, + TaggedTemplateExpression: checkEmpty, + TemplateElement: checkEmpty, + ObjectPattern: checkEmpty, + ArrayPattern: checkEmpty, + RestElement: checkEmpty, + AssignmentPattern: checkEmpty, + ClassBody: checkEmpty, + MethodDefinition: checkEmpty, + MetaProperty: checkEmpty, + }; + + rule[type] = function (node) { + const expectedNames = expectedNamesList.shift(); + const variables = + sourceCode.getDeclaredVariables(node); + + assert(Array.isArray(expectedNames)); + assert(Array.isArray(variables)); + assert.strictEqual( + expectedNames.length, + variables.length, + ); + for (let i = variables.length - 1; i >= 0; i--) { + assert.strictEqual( + expectedNames[i], + variables[i].name, + ); + } + }; + return rule; + }, + }, + }); + linter.verify(code, { + rules: { test: 2 }, + parserOptions: { + ecmaVersion: 6, + sourceType: "module", + }, + }); + + // Check all expected names are asserted. + assert.strictEqual(0, expectedNamesList.length); + } + + it("VariableDeclaration", () => { + const code = + "\n var {a, x: [b], y: {c = 0}} = foo;\n let {d, x: [e], y: {f = 0}} = foo;\n const {g, x: [h], y: {i = 0}} = foo, {j, k = function(z) { let l; }} = bar;\n "; + const namesList = [ + ["a", "b", "c"], + ["d", "e", "f"], + ["g", "h", "i", "j", "k"], + ["l"], + ]; + + verify(code, "VariableDeclaration", namesList); + }); + + it("VariableDeclaration (on for-in/of loop)", () => { + // TDZ scope is created here, so tests to exclude those. + const code = + "\n for (var {a, x: [b], y: {c = 0}} in foo) {\n let g;\n }\n for (let {d, x: [e], y: {f = 0}} of foo) {\n let h;\n }\n "; + const namesList = [["a", "b", "c"], ["g"], ["d", "e", "f"], ["h"]]; + + verify(code, "VariableDeclaration", namesList); + }); + + it("VariableDeclarator", () => { + // TDZ scope is created here, so tests to exclude those. + const code = + "\n var {a, x: [b], y: {c = 0}} = foo;\n let {d, x: [e], y: {f = 0}} = foo;\n const {g, x: [h], y: {i = 0}} = foo, {j, k = function(z) { let l; }} = bar;\n "; + const namesList = [ + ["a", "b", "c"], + ["d", "e", "f"], + ["g", "h", "i"], + ["j", "k"], + ["l"], + ]; + + verify(code, "VariableDeclarator", namesList); + }); + + it("FunctionDeclaration", () => { + const code = + "\n function foo({a, x: [b], y: {c = 0}}, [d, e]) {\n let z;\n }\n function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) {\n let z;\n }\n "; + const namesList = [ + ["foo", "a", "b", "c", "d", "e"], + ["bar", "f", "g", "h", "i", "j"], + ]; + + verify(code, "FunctionDeclaration", namesList); + }); + + it("FunctionExpression", () => { + const code = + "\n (function foo({a, x: [b], y: {c = 0}}, [d, e]) {\n let z;\n });\n (function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) {\n let z;\n });\n "; + const namesList = [ + ["foo", "a", "b", "c", "d", "e"], + ["bar", "f", "g", "h", "i", "j"], + ["q"], + ]; + + verify(code, "FunctionExpression", namesList); + }); + + it("ArrowFunctionExpression", () => { + const code = + "\n (({a, x: [b], y: {c = 0}}, [d, e]) => {\n let z;\n });\n (({f, x: [g], y: {h = 0}}, [i, j]) => {\n let z;\n });\n "; + const namesList = [ + ["a", "b", "c", "d", "e"], + ["f", "g", "h", "i", "j"], + ]; + + verify(code, "ArrowFunctionExpression", namesList); + }); + + it("ClassDeclaration", () => { + const code = + "\n class A { foo(x) { let y; } }\n class B { foo(x) { let y; } }\n "; + const namesList = [ + ["A", "A"], // outer scope's and inner scope's. + ["B", "B"], + ]; + + verify(code, "ClassDeclaration", namesList); + }); + + it("ClassExpression", () => { + const code = + "\n (class A { foo(x) { let y; } });\n (class B { foo(x) { let y; } });\n "; + const namesList = [["A"], ["B"]]; + + verify(code, "ClassExpression", namesList); + }); + + it("CatchClause", () => { + const code = + "\n try {} catch ({a, b}) {\n let x;\n try {} catch ({c, d}) {\n let y;\n }\n }\n "; + const namesList = [ + ["a", "b"], + ["c", "d"], + ]; + + verify(code, "CatchClause", namesList); + }); + + it("ImportDeclaration", () => { + const code = + '\n import "aaa";\n import * as a from "bbb";\n import b, {c, x as d} from "ccc";\n '; + const namesList = [[], ["a"], ["b", "c", "d"]]; + + verify(code, "ImportDeclaration", namesList); + }); + + it("ImportSpecifier", () => { + const code = + '\n import "aaa";\n import * as a from "bbb";\n import b, {c, x as d} from "ccc";\n '; + const namesList = [["c"], ["d"]]; + + verify(code, "ImportSpecifier", namesList); + }); + + it("ImportDefaultSpecifier", () => { + const code = + '\n import "aaa";\n import * as a from "bbb";\n import b, {c, x as d} from "ccc";\n '; + const namesList = [["b"]]; + + verify(code, "ImportDefaultSpecifier", namesList); + }); + + it("ImportNamespaceSpecifier", () => { + const code = + '\n import "aaa";\n import * as a from "bbb";\n import b, {c, x as d} from "ccc";\n '; + const namesList = [["a"]]; + + verify(code, "ImportNamespaceSpecifier", namesList); + }); + }); + + describe("markVariableAsUsed()", () => { + it("should mark variables in current scope as used", () => { + const code = "var a = 1, b = 2;"; + let spy; + + linter.defineRule("checker", { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(node => { + assert.isTrue(sourceCode.markVariableAsUsed("a")); + + const scope = sourceCode.getScope(node); + + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); + + return { "Program:exit": spy }; + }, + }); + + linter.verify(code, { rules: { checker: "error" } }); + assert(spy && spy.calledOnce); + }); + + it("should mark variables in function args as used", () => { + const code = "function abc(a, b) { return 1; }"; + let spy; + + linter.defineRule("checker", { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(node => { + assert.isTrue(sourceCode.markVariableAsUsed("a", node)); + + const scope = sourceCode.getScope(node); + + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); + + return { ReturnStatement: spy }; + }, + }); + + linter.verify(code, { rules: { checker: "error" } }); + assert(spy && spy.calledOnce); + }); + + it("should mark variables in higher scopes as used", () => { + const code = "var a, b; function abc() { return 1; }"; + let returnSpy, exitSpy; + + linter.defineRule("checker", { + create(context) { + const sourceCode = context.sourceCode; + + returnSpy = sinon.spy(node => { + assert.isTrue(sourceCode.markVariableAsUsed("a", node)); + }); + exitSpy = sinon.spy(node => { + const scope = sourceCode.getScope(node); + + assert.isTrue(getVariable(scope, "a").eslintUsed); + assert.notOk(getVariable(scope, "b").eslintUsed); + }); + + return { + ReturnStatement: returnSpy, + "Program:exit": exitSpy, + }; + }, + }); + + linter.verify(code, { rules: { checker: "error" } }); + assert(returnSpy && returnSpy.calledOnce); + assert(exitSpy && exitSpy.calledOnce); + }); + + it("should mark variables in Node.js environment as used", () => { + const code = "var a = 1, b = 2;"; + let spy; + + linter.defineRule("checker", { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(node => { + const globalScope = sourceCode.getScope(node), + childScope = globalScope.childScopes[0]; + + assert.isTrue(sourceCode.markVariableAsUsed("a")); + + assert.isTrue(getVariable(childScope, "a").eslintUsed); + assert.isUndefined( + getVariable(childScope, "b").eslintUsed, + ); + }); + + return { "Program:exit": spy }; + }, + }); + + linter.verify(code, { + rules: { checker: "error" }, + env: { node: true }, + }); + assert(spy && spy.calledOnce); + }); + + it("should mark variables in modules as used", () => { + const code = "var a = 1, b = 2;"; + let spy; + + linter.defineRule("checker", { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(node => { + const globalScope = sourceCode.getScope(node), + childScope = globalScope.childScopes[0]; + + assert.isTrue(sourceCode.markVariableAsUsed("a")); + + assert.isTrue(getVariable(childScope, "a").eslintUsed); + assert.isUndefined( + getVariable(childScope, "b").eslintUsed, + ); + }); + + return { "Program:exit": spy }; + }, + }); + + linter.verify( + code, + { + rules: { checker: "error" }, + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + filename, + ); + assert(spy && spy.calledOnce); + }); + + it("should return false if the given variable is not found", () => { + const code = "var a = 1, b = 2;"; + let spy; + + linter.defineRule("checker", { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(() => { + assert.isFalse(sourceCode.markVariableAsUsed("c")); + }); + + return { "Program:exit": spy }; + }, + }); + + linter.verify(code, { rules: { checker: "error" } }); + assert(spy && spy.calledOnce); + }); + }); + + describe("getInlineConfigNodes()", () => { + it("should return inline config comments", () => { + const code = + "/*eslint foo: 1*/ foo; /* non-config comment*/ /* eslint-disable bar */ bar; /* eslint-enable bar */"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const sourceCode = new SourceCode(code, ast); + const configComments = sourceCode.getInlineConfigNodes(); + + // not sure why but without the JSON parse/stringify Chai won't see these as equal + assert.deepStrictEqual(JSON.parse(JSON.stringify(configComments)), [ + { + type: "Block", + value: "eslint foo: 1", + start: 0, + end: 17, + range: [0, 17], + loc: { + start: { + line: 1, + column: 0, + }, + end: { + line: 1, + column: 17, + }, + }, + }, + { + type: "Block", + value: " eslint-disable bar ", + start: 47, + end: 71, + range: [47, 71], + loc: { + start: { + line: 1, + column: 47, + }, + end: { + line: 1, + column: 71, + }, + }, + }, + { + type: "Block", + value: " eslint-enable bar ", + start: 77, + end: 100, + range: [77, 100], + loc: { + start: { + line: 1, + column: 77, + }, + end: { + line: 1, + column: 100, + }, + }, + }, + ]); + }); + }); + + describe("applyLanguageOptions()", () => { + it("should add ES6 globals", () => { + const code = "foo"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + ecmaVersion: 6, + }); + const sourceCode = new SourceCode({ + text: code, + ast, + scopeManager, + }); + + sourceCode.applyLanguageOptions({ + ecmaVersion: 2015, + }); + + sourceCode.finalize(); + + const globalScope = sourceCode.scopeManager.scopes[0]; + const variable = globalScope.set.get("Promise"); + + assert.isDefined(variable); + }); + + it("should add custom globals", () => { + const code = "foo"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + ecmaVersion: 6, + }); + const sourceCode = new SourceCode({ + text: code, + ast, + scopeManager, + }); + + sourceCode.applyLanguageOptions({ + ecmaVersion: 2015, + globals: { + FOO: true, + }, + }); + + sourceCode.finalize(); + + const globalScope = sourceCode.scopeManager.scopes[0]; + const variable = globalScope.set.get("FOO"); + + assert.isDefined(variable); + assert.isTrue(variable.writeable); + }); + + it("should add commonjs globals", () => { + const code = "foo"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + nodejsScope: true, + ecmaVersion: 6, + }); + const sourceCode = new SourceCode({ + text: code, + ast, + scopeManager, + }); + + sourceCode.applyLanguageOptions({ + ecmaVersion: 2015, + sourceType: "commonjs", + }); + + sourceCode.finalize(); + + const globalScope = sourceCode.scopeManager.scopes[0]; + const variable = globalScope.set.get("require"); + + assert.isDefined(variable); + }); + }); + + describe("applyInlineConfig()", () => { + it("should add inline globals", () => { + const code = "/*global bar: true */ foo;"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + ecmaVersion: 6, + }); + const sourceCode = new SourceCode({ + text: code, + ast, + scopeManager, + }); + + sourceCode.applyInlineConfig(); + sourceCode.finalize(); + + const globalScope = sourceCode.scopeManager.scopes[0]; + const variable = globalScope.set.get("bar"); + + assert.isDefined(variable); + assert.isTrue(variable.writeable); + }); + + describe("exported variables", () => { + /** + * GlobalScope + * @param {string} code the code to check + * @returns {Scope} globalScope + */ + function loadGlobalScope(code) { + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + ecmaVersion: 6, + }); + const sourceCode = new SourceCode({ + text: code, + ast, + scopeManager, + }); + + sourceCode.applyInlineConfig(); + sourceCode.finalize(); + + const globalScope = sourceCode.scopeManager.scopes[0].set; + + return globalScope; + } + + it("should mark exported variable", () => { + const code = "/*exported foo */ var foo;"; + const globalScope = loadGlobalScope(code); + const variable = globalScope.get("foo"); + + assert.isDefined(variable); + assert.isTrue(variable.eslintUsed); + assert.isTrue(variable.eslintExported); + }); + + it("should not mark exported variable with `key: value` pair", () => { + const code = "/*exported foo: true */ var foo;"; + const globalScope = loadGlobalScope(code); + const variable = globalScope.get("foo"); + + assert.isDefined(variable); + assert.notOk(variable.eslintUsed); + assert.notOk(variable.eslintExported); + }); + + it("should mark exported variables with comma", () => { + const code = "/*exported foo, bar */ var foo, bar;"; + const globalScope = loadGlobalScope(code); + + ["foo", "bar"].forEach(name => { + const variable = globalScope.get(name); + + assert.isDefined(variable); + assert.isTrue(variable.eslintUsed); + assert.isTrue(variable.eslintExported); + }); + }); + + it("should not mark exported variables without comma", () => { + const code = "/*exported foo bar */ var foo, bar;"; + const globalScope = loadGlobalScope(code); + + ["foo", "bar"].forEach(name => { + const variable = globalScope.get(name); + + assert.isDefined(variable); + assert.notOk(variable.eslintUsed); + assert.notOk(variable.eslintExported); + }); + }); + }); + + it("should extract rule configuration", () => { + const code = "/*eslint some-rule: 2 */ var foo;"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const sourceCode = new SourceCode(code, ast); + const result = sourceCode.applyInlineConfig(); + + assert.strictEqual(result.configs.length, 1); + assert.strictEqual(result.configs[0].config.rules["some-rule"], 2); + }); + + it("should extract multiple rule configurations", () => { + const code = + '/*eslint some-rule: 2, other-rule: ["error", { skip: true }] */ var foo;'; + const ast = espree.parse(code, DEFAULT_CONFIG); + const sourceCode = new SourceCode(code, ast); + const result = sourceCode.applyInlineConfig(); + + assert.strictEqual(result.configs.length, 1); + assert.strictEqual(result.configs[0].config.rules["some-rule"], 2); + assert.deepStrictEqual( + result.configs[0].config.rules["other-rule"], + ["error", { skip: true }], + ); + }); + + it("should extract multiple comments into multiple configurations", () => { + const code = + '/*eslint some-rule: 2*/ /*eslint other-rule: ["error", { skip: true }] */ var foo;'; + const ast = espree.parse(code, DEFAULT_CONFIG); + const sourceCode = new SourceCode(code, ast); + const result = sourceCode.applyInlineConfig(); + + assert.strictEqual(result.configs.length, 2); + assert.strictEqual(result.configs[0].config.rules["some-rule"], 2); + assert.deepStrictEqual( + result.configs[1].config.rules["other-rule"], + ["error", { skip: true }], + ); + }); + + it("should report problem with rule configuration parsing", () => { + const code = "/*eslint some-rule::, */ var foo;"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const sourceCode = new SourceCode(code, ast); + const result = sourceCode.applyInlineConfig(); + const problem = result.problems[0]; + + // Node.js 19 changes the JSON parsing error format, so we need to check each field separately to use a regex + assert.strictEqual(problem.loc.start.column, 0); + assert.strictEqual(problem.loc.start.line, 1); + assert.strictEqual(problem.loc.end.column, 24); + assert.strictEqual(problem.loc.end.line, 1); + assert.match( + problem.message, + /Failed to parse JSON from '"some-rule"::,': Unexpected token '?:'?/u, + ); + assert.isNull(problem.ruleId); + }); + }); + + describe("finalize()", () => { + it("should remove ECMAScript globals from global scope's `implicit`", () => { + const code = "Array = 1; Foo = 1; Promise = 1; Array; Foo; Promise"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + ecmaVersion: 6, + }); + const sourceCode = new SourceCode({ + text: code, + ast, + scopeManager, + }); + + sourceCode.applyLanguageOptions({ + ecmaVersion: 2015, + }); + + sourceCode.finalize(); + + const globalScope = sourceCode.scopeManager.scopes[0]; + const { implicit } = globalScope; + + assert.deepStrictEqual( + [...implicit.set].map(([name]) => name), + ["Foo"], + ); + assert.deepStrictEqual( + implicit.variables.map(({ name }) => name), + ["Foo"], + ); + assert.deepStrictEqual( + implicit.left.map(reference => reference.identifier.name), + ["Foo", "Foo"], + ); + }); + + it("should remove custom globals from global scope's `implicit`", () => { + const code = "Bar = 1; Foo = 1; Baz = 1; Bar; Foo; Baz"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + ecmaVersion: 6, + }); + const sourceCode = new SourceCode({ + text: code, + ast, + scopeManager, + }); + + sourceCode.applyLanguageOptions({ + ecmaVersion: 2015, + globals: { + Bar: "writable", + Baz: "readonly", + }, + }); + + sourceCode.finalize(); + + const globalScope = sourceCode.scopeManager.scopes[0]; + const { implicit } = globalScope; + + assert.deepStrictEqual( + [...implicit.set].map(([name]) => name), + ["Foo"], + ); + assert.deepStrictEqual( + implicit.variables.map(({ name }) => name), + ["Foo"], + ); + assert.deepStrictEqual( + implicit.left.map(reference => reference.identifier.name), + ["Foo", "Foo"], + ); + }); + + it("should remove commonjs globals from global scope's `implicit`", () => { + const code = + "exports = {}; Foo = 1; require = () => {}; exports; Foo; require"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + nodejsScope: true, + ecmaVersion: 6, + }); + const sourceCode = new SourceCode({ + text: code, + ast, + scopeManager, + }); + + sourceCode.applyLanguageOptions({ + ecmaVersion: 2015, + sourceType: "commonjs", + }); + + sourceCode.finalize(); + + const globalScope = sourceCode.scopeManager.scopes[0]; + const { implicit } = globalScope; + + assert.deepStrictEqual( + [...implicit.set].map(([name]) => name), + ["Foo"], + ); + assert.deepStrictEqual( + implicit.variables.map(({ name }) => name), + ["Foo"], + ); + assert.deepStrictEqual( + implicit.left.map(reference => reference.identifier.name), + ["Foo", "Foo"], + ); + }); + + it("should remove inline globals from global scope's `implicit`", () => { + const code = + "/* globals Bar: writable, Baz: readonly */ Bar = 1; Foo = 1; Baz = 1; Bar; Foo; Baz"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + ecmaVersion: 6, + }); + const sourceCode = new SourceCode({ + text: code, + ast, + scopeManager, + }); + + sourceCode.applyInlineConfig(); + sourceCode.finalize(); + + const globalScope = sourceCode.scopeManager.scopes[0]; + const { implicit } = globalScope; + + assert.deepStrictEqual( + [...implicit.set].map(([name]) => name), + ["Foo"], + ); + assert.deepStrictEqual( + implicit.variables.map(({ name }) => name), + ["Foo"], + ); + assert.deepStrictEqual( + implicit.left.map(reference => reference.identifier.name), + ["Foo", "Foo"], + ); + }); + + it("should not crash if global scope doesn't have `implicit` property", () => { + const code = "Array = 1; Foo = 1; Promise = 1; Array; Foo; Promise"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + ecmaVersion: 6, + }); + + const globalScope = scopeManager.scopes[0]; + delete globalScope.implicit; + + const sourceCode = new SourceCode({ + text: code, + ast, + scopeManager, + }); + + sourceCode.applyLanguageOptions({ + ecmaVersion: 2015, + }); + + // should not throw + sourceCode.finalize(); + }); + + it("should not crash if global scope doesn't have `implicit.left` property", () => { + const code = "Array = 1; Foo = 1; Promise = 1; Array; Foo; Promise"; + const ast = espree.parse(code, DEFAULT_CONFIG); + const scopeManager = eslintScope.analyze(ast, { + ignoreEval: true, + ecmaVersion: 6, + }); + + const globalScope = scopeManager.scopes[0]; + delete globalScope.implicit.left; + + const sourceCode = new SourceCode({ + text: code, + ast, + scopeManager, + }); + + sourceCode.applyLanguageOptions({ + ecmaVersion: 2015, + }); + + // should not throw + sourceCode.finalize(); + + const { implicit } = globalScope; + + assert.deepStrictEqual( + [...implicit.set].map(([name]) => name), + ["Foo"], + ); + assert.deepStrictEqual( + implicit.variables.map(({ name }) => name), + ["Foo"], + ); + }); + }); + + describe("isGlobalReference(node)", () => { + it("should throw an error when argument is missing", () => { + linter.defineRule("is-global-reference", { + create: context => ({ + Program() { + context.sourceCode.isGlobalReference(); + }, + }), + }); + + assert.throws(() => { + linter.verify("foo", { + rules: { "is-global-reference": 2 }, + }); + }, /Missing required argument: node/u); + }); + + it("should correctly identify global references", () => { + const code = + "undefined; globalThis; NaN; Object; Boolean; String; Math; Date; Array; Map; Set; var foo; foo;"; + let identifierSpy; + + const config = { + languageOptions: { + sourceType: "script", + }, + plugins: { + test: { + rules: { + checker: { + create(context) { + const sourceCode = context.sourceCode; + const globals = new Set([ + "undefined", + "globalThis", + "NaN", + "Object", + "Boolean", + "String", + "Math", + "Date", + "Array", + "Map", + "Set", + ]); + + identifierSpy = sinon.spy(node => { + if (globals.has(node.name)) { + assert.isTrue( + sourceCode.isGlobalReference( + node, + ), + `Expected ${node.name} to be identified as a global reference`, + ); + } else if (node.name === "foo") { + // The second "foo" reference (not the declaration) should not be a global reference + if ( + node.parent.type !== + "VariableDeclarator" + ) { + assert.isFalse( + sourceCode.isGlobalReference( + node, + ), + "Expected local variable to not be identified as a global reference", + ); + } + } + }); + + return { Identifier: identifierSpy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + flatLinter.verify(code, config); + assert(identifierSpy.called, "Identifier spy was not called."); + assert( + identifierSpy.callCount > 10, + "Identifier spy was not called enough times.", + ); + }); + + it("should handle function parameters and shadowed globals", () => { + const code = "function test(param, NaN) { param; NaN; }"; + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(functionDecl => { + const blockStatement = + functionDecl.body; + + // Function parameter references + const paramRef = + blockStatement.body[0].expression; + const NaNRef = + blockStatement.body[1].expression; + + assert.strictEqual( + paramRef.name, + "param", + ); + assert.strictEqual(NaNRef.name, "NaN"); + + assert.isFalse( + sourceCode.isGlobalReference( + paramRef, + ), + ); + assert.isFalse( + sourceCode.isGlobalReference( + NaNRef, + ), + ); + }); + + return { FunctionDeclaration: spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + flatLinter.verify(code, config); + assert(spy && spy.calledOnce, "Spy was not called."); + }); + + it("should identify global references in modules", () => { + const code = "Math;"; + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(() => { + const program = sourceCode.ast; + const mathRef = + program.body[0].expression; + + assert.strictEqual( + mathRef.name, + "Math", + ); + assert.isTrue( + sourceCode.isGlobalReference( + mathRef, + ), + ); + }); + + return { "Program:exit": spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + languageOptions: { + ecmaVersion: 2015, + sourceType: "module", + }, + }; + + flatLinter.verify(code, config); + assert(spy && spy.calledOnce, "Spy was not called."); + }); + + it("should identify variables in higher scopes as non-global", () => { + const code = "var outer; function foo() { outer; }"; + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(functionDecl => { + const blockStatement = + functionDecl.body; + const outerRef = + blockStatement.body[0].expression; + + assert.strictEqual( + outerRef.name, + "outer", + ); + assert.isFalse( + sourceCode.isGlobalReference( + outerRef, + ), + ); + }); + + return { FunctionDeclaration: spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + flatLinter.verify(code, config); + assert(spy && spy.calledOnce, "Spy was not called."); + }); + + it("should distinguish between object property access and global references", () => { + const code = "String; String.length; Math; obj.Math;"; + let identifierSpy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + const sourceCode = context.sourceCode; + + identifierSpy = sinon.spy(node => { + if (node.name === "String") { + assert.isTrue( + sourceCode.isGlobalReference( + node, + ), + "Expected 'String' to be identified as a global reference", + ); + } else if ( + node.name === "length" && + node.parent.type === + "MemberExpression" + ) { + assert.isFalse( + sourceCode.isGlobalReference( + node, + ), + "Expected property 'length' to not be identified as a global reference", + ); + } else if ( + node.name === "Math" && + node.parent.type !== + "MemberExpression" + ) { + assert.isTrue( + sourceCode.isGlobalReference( + node, + ), + "Expected 'Math' to be identified as a global reference", + ); + } else if ( + node.name === "Math" && + node.parent.object.name === "obj" + ) { + assert.isFalse( + sourceCode.isGlobalReference( + node, + ), + "Expected 'obj.Math' property to not be identified as a global reference", + ); + } + }); + + return { Identifier: identifierSpy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + flatLinter.verify(code, config); + assert(identifierSpy.called, "Identifier spy was not called."); + }); + + it("should handle destructuring assignments properly", () => { + const code = + "const { Math } = obj; Math; const [Array] = list; Array;"; + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(() => { + // Get the second Math identifier (outside destructuring) + const mathRef = + sourceCode.ast.body[1].expression; + // Get the second Array identifier (outside destructuring) + const arrayRef = + sourceCode.ast.body[3].expression; + + assert.strictEqual( + mathRef.name, + "Math", + ); + assert.strictEqual( + arrayRef.name, + "Array", + ); + + assert.isFalse( + sourceCode.isGlobalReference( + mathRef, + ), + "Destructured 'Math' should not be identified as a global reference", + ); + assert.isFalse( + sourceCode.isGlobalReference( + arrayRef, + ), + "Destructured 'Array' should not be identified as a global reference", + ); + }); + + return { "Program:exit": spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + languageOptions: { ecmaVersion: 2015 }, + }; + + flatLinter.verify(code, config); + assert(spy && spy.calledOnce, "Spy was not called."); + }); + + it("should handle imported names that shadow globals", () => { + const code = + "import { Object, String } from './module'; Object; String;"; + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(() => { + const objectRef = + sourceCode.ast.body[1].expression; + const stringRef = + sourceCode.ast.body[2].expression; + + assert.strictEqual( + objectRef.name, + "Object", + ); + assert.strictEqual( + stringRef.name, + "String", + ); + + assert.isFalse( + sourceCode.isGlobalReference( + objectRef, + ), + "Imported 'Object' should not be identified as a global reference", + ); + assert.isFalse( + sourceCode.isGlobalReference( + stringRef, + ), + "Imported 'String' should not be identified as a global reference", + ); + }); + + return { "Program:exit": spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + flatLinter.verify(code, config); + assert(spy && spy.calledOnce, "Spy was not called."); + }); + + it("should handle temporal dead zone (TDZ) for let variables", () => { + const code = "{ console.log(x); let x = 5; }"; + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(node => { + if ( + node.name === "x" && + node.parent.type !== + "VariableDeclarator" + ) { + assert.isFalse( + sourceCode.isGlobalReference( + node, + ), + "Reference in TDZ should not be identified as a global reference", + ); + } + }); + + return { Identifier: spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + languageOptions: { ecmaVersion: 2015 }, + }; + + flatLinter.verify(code, config); + assert(spy && spy.called, "Spy was not called."); + }); + + it("should handle variables shadowed in catch blocks", () => { + const code = "try {} catch (Error) { Error; }"; + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(node => { + if ( + node.parent.type === "CatchClause" + ) { + // Skip the catch parameter declaration + return; + } + + if (node.name === "Error") { + assert.isFalse( + sourceCode.isGlobalReference( + node, + ), + "Error in catch block should not be identified as a global reference", + ); + } + }); + + return { Identifier: spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + flatLinter.verify(code, config); + assert(spy && spy.called, "Spy was not called."); + }); + + it("should handle class declarations and methods", () => { + const code = + "class MyClass { method() { Math.random(); this.Math = 5; } }"; + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(node => { + if ( + node.name === "Math" && + node.parent.type === + "MemberExpression" && + node.parent.object.type !== + "ThisExpression" + ) { + assert.isTrue( + sourceCode.isGlobalReference( + node, + ), + "Math in method should be identified as a global reference", + ); + } else if ( + node.name === "Math" && + node.parent.object && + node.parent.object.type === + "ThisExpression" + ) { + assert.isFalse( + sourceCode.isGlobalReference( + node, + ), + "this.Math should not be identified as a global reference", + ); + } + }); + + return { Identifier: spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + languageOptions: { ecmaVersion: 2015 }, + }; + + flatLinter.verify(code, config); + assert(spy && spy.called, "Spy was not called."); + }); + + it("should respect /*globals*/ directive comments", () => { + const code = + "/*globals customGlobal:writable, String:off */ customGlobal; String;"; + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + const sourceCode = context.sourceCode; + + spy = sinon.spy(node => { + if (node.name === "customGlobal") { + assert.isTrue( + sourceCode.isGlobalReference( + node, + ), + "Variable declared in globals directive should be identified as a global reference", + ); + } else if (node.name === "String") { + assert.isFalse( + sourceCode.isGlobalReference( + node, + ), + "Global turned off in directive should not be identified as a global reference", + ); + } + }); + + return { Identifier: spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + flatLinter.verify(code, config); + assert(spy && spy.called, "Spy was not called."); + }); + + it("should cache the result of isGlobalReference for the same node", () => { + const code = "Math; Math;"; + let firstNode, secondNode, sourceCodeInstance; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + const sourceCode = context.sourceCode; + sourceCodeInstance = sourceCode; + return { + Identifier(node) { + if (!firstNode) { + firstNode = node; + } else if (!secondNode) { + secondNode = node; + } + }, + }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + flatLinter.verify(code, config); + + // Spy on the internal cache + const cache = + sourceCodeInstance[ + Object.getOwnPropertySymbols(sourceCodeInstance).find( + sym => + sourceCodeInstance[sym] instanceof Map && + sourceCodeInstance[sym].has("isGlobalReference"), + ) + ].get("isGlobalReference"); + + // Clear cache for firstNode and count calls + cache.delete(firstNode); + let computeCount = 0; + const original = + sourceCodeInstance.scopeManager.scopes[0].set.get("Math") + .references.some; + sourceCodeInstance.scopeManager.scopes[0].set.get( + "Math", + ).references.some = function (...args) { + computeCount++; + return original.apply(this, args); + }; + + // Call twice, should only compute once + sourceCodeInstance.isGlobalReference(firstNode); + sourceCodeInstance.isGlobalReference(firstNode); + + assert.strictEqual( + computeCount, + 1, + "isGlobalReference should compute only once per node", + ); + + // Second node should compute + sourceCodeInstance.isGlobalReference(secondNode); + sourceCodeInstance.isGlobalReference(secondNode); + assert.strictEqual( + computeCount, + 2, + "isGlobalReference should compute only once per node", + ); + + // Restore + sourceCodeInstance.scopeManager.scopes[0].set.get( + "Math", + ).references.some = original; + }); + }); +}); diff --git a/tests/lib/languages/js/source-code/token-store.js b/tests/lib/languages/js/source-code/token-store.js new file mode 100644 index 000000000000..57cfeb22f654 --- /dev/null +++ b/tests/lib/languages/js/source-code/token-store.js @@ -0,0 +1,1840 @@ +/** + * @fileoverview Tests for TokenStore class. + * @author Brandon Mills + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("chai").assert, + espree = require("espree"), + TokenStore = require("../../../../../lib/languages/js/source-code/token-store"); + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +const SOURCE_CODE = + "/*A*/var answer/*B*/=/*C*/a/*D*/* b/*E*///F\n call();\n/*Z*/", + AST = espree.parse(SOURCE_CODE, { + loc: true, + range: true, + tokens: true, + comment: true, + }), + TOKENS = AST.tokens, + COMMENTS = AST.comments, + Program = AST, + VariableDeclaration = Program.body[0], + VariableDeclarator = VariableDeclaration.declarations[0], + BinaryExpression = VariableDeclarator.init, + CallExpression = Program.body[1].expression; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * Checks the values of tokens against an array of expected values. + * @param {Token[]} tokens Tokens returned from the API. + * @param {string[]} expected Expected token values + * @returns {void} + */ +function check(tokens, expected) { + const length = tokens.length; + + assert.strictEqual(length, expected.length); + for (let i = 0; i < length; i++) { + assert.strictEqual(tokens[i].value, expected[i]); + } +} + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("TokenStore", () => { + const store = new TokenStore(TOKENS, COMMENTS); + + describe("when calling getTokens", () => { + it("should retrieve all tokens for root node", () => { + check(store.getTokens(Program), [ + "var", + "answer", + "=", + "a", + "*", + "b", + "call", + "(", + ")", + ";", + ]); + }); + + it("should retrieve all tokens for binary expression", () => { + check(store.getTokens(BinaryExpression), ["a", "*", "b"]); + }); + + it("should retrieve all tokens plus one before for binary expression", () => { + check(store.getTokens(BinaryExpression, 1), ["=", "a", "*", "b"]); + }); + + it("should retrieve all tokens plus one after for binary expression", () => { + check(store.getTokens(BinaryExpression, 0, 1), [ + "a", + "*", + "b", + "call", + ]); + }); + + it("should retrieve all tokens plus two before and one after for binary expression", () => { + check(store.getTokens(BinaryExpression, 2, 1), [ + "answer", + "=", + "a", + "*", + "b", + "call", + ]); + }); + + it("should retrieve all matched tokens for root node with filter", () => { + check( + store.getTokens(Program, t => t.type === "Identifier"), + ["answer", "a", "b", "call"], + ); + check( + store.getTokens(Program, { + filter: t => t.type === "Identifier", + }), + ["answer", "a", "b", "call"], + ); + }); + + it("should retrieve all tokens and comments in the node for root node with includeComments option", () => { + check(store.getTokens(Program, { includeComments: true }), [ + "var", + "answer", + "B", + "=", + "C", + "a", + "D", + "*", + "b", + "E", + "F", + "call", + "(", + ")", + ";", + ]); + }); + + it("should retrieve matched tokens and comments in the node for root node with includeComments and filter options", () => { + check( + store.getTokens(Program, { + includeComments: true, + filter: t => t.type.startsWith("Block"), + }), + ["B", "C", "D", "E"], + ); + }); + + it("should retrieve all tokens and comments in the node for binary expression with includeComments option", () => { + check( + store.getTokens(BinaryExpression, { includeComments: true }), + ["a", "D", "*", "b"], + ); + }); + }); + + describe("when calling getTokensBefore", () => { + it("should retrieve zero tokens before a node", () => { + check(store.getTokensBefore(BinaryExpression, 0), []); + }); + + it("should retrieve one token before a node", () => { + check(store.getTokensBefore(BinaryExpression, 1), ["="]); + }); + + it("should retrieve more than one token before a node", () => { + check(store.getTokensBefore(BinaryExpression, 2), ["answer", "="]); + }); + + it("should retrieve all tokens before a node", () => { + check(store.getTokensBefore(BinaryExpression, 9e9), [ + "var", + "answer", + "=", + ]); + }); + + it("should retrieve more than one token before a node with count option", () => { + check(store.getTokensBefore(BinaryExpression, { count: 2 }), [ + "answer", + "=", + ]); + }); + + it("should retrieve matched tokens before a node with count and filter options", () => { + check( + store.getTokensBefore(BinaryExpression, { + count: 1, + filter: t => t.value !== "=", + }), + ["answer"], + ); + }); + + it("should retrieve all matched tokens before a node with filter option", () => { + check( + store.getTokensBefore(BinaryExpression, { + filter: t => t.value !== "answer", + }), + ["var", "="], + ); + }); + + it("should retrieve no tokens before the root node", () => { + check(store.getTokensBefore(Program, { count: 1 }), []); + }); + + it("should retrieve tokens and comments before a node with count and includeComments option", () => { + check( + store.getTokensBefore(BinaryExpression, { + count: 3, + includeComments: true, + }), + ["B", "=", "C"], + ); + }); + + it("should retrieve all tokens and comments before a node with includeComments option only", () => { + check( + store.getTokensBefore(BinaryExpression, { + includeComments: true, + }), + ["A", "var", "answer", "B", "=", "C"], + ); + }); + + it("should retrieve all tokens and comments before a node with includeComments and filter options", () => { + check( + store.getTokensBefore(BinaryExpression, { + includeComments: true, + filter: t => t.type.startsWith("Block"), + }), + ["A", "B", "C"], + ); + }); + }); + + describe("when calling getTokenBefore", () => { + it("should retrieve one token before a node", () => { + assert.strictEqual( + store.getTokenBefore(BinaryExpression).value, + "=", + ); + }); + + it("should skip a given number of tokens", () => { + assert.strictEqual( + store.getTokenBefore(BinaryExpression, 1).value, + "answer", + ); + assert.strictEqual( + store.getTokenBefore(BinaryExpression, 2).value, + "var", + ); + }); + + it("should skip a given number of tokens with skip option", () => { + assert.strictEqual( + store.getTokenBefore(BinaryExpression, { skip: 1 }).value, + "answer", + ); + assert.strictEqual( + store.getTokenBefore(BinaryExpression, { skip: 2 }).value, + "var", + ); + }); + + it("should retrieve matched token with filter option", () => { + assert.strictEqual( + store.getTokenBefore(BinaryExpression, t => t.value !== "=") + .value, + "answer", + ); + }); + + it("should retrieve matched token with skip and filter options", () => { + assert.strictEqual( + store.getTokenBefore(BinaryExpression, { + skip: 1, + filter: t => t.value !== "=", + }).value, + "var", + ); + }); + + it("should retrieve one token or comment before a node with includeComments option", () => { + assert.strictEqual( + store.getTokenBefore(BinaryExpression, { + includeComments: true, + }).value, + "C", + ); + }); + + it("should retrieve one token or comment before a node with includeComments and skip options", () => { + assert.strictEqual( + store.getTokenBefore(BinaryExpression, { + includeComments: true, + skip: 1, + }).value, + "=", + ); + }); + + it("should retrieve one token or comment before a node with includeComments and skip and filter options", () => { + assert.strictEqual( + store.getTokenBefore(BinaryExpression, { + includeComments: true, + skip: 1, + filter: t => t.type.startsWith("Block"), + }).value, + "B", + ); + }); + + it("should retrieve the previous node if the comment at the end of source code is specified.", () => { + const code = "a + b /*comment*/"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getTokenBefore(ast.comments[0]); + + assert.strictEqual(token.value, "b"); + }); + + it("should retrieve the previous comment if the first token is specified.", () => { + const code = "/*comment*/ a + b"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getTokenBefore(ast.tokens[0], { + includeComments: true, + }); + + assert.strictEqual(token.value, "comment"); + }); + + it("should retrieve null if the first comment is specified.", () => { + const code = "/*comment*/ a + b"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getTokenBefore(ast.comments[0], { + includeComments: true, + }); + + assert.strictEqual(token, null); + }); + }); + + describe("when calling getTokensAfter", () => { + it("should retrieve zero tokens after a node", () => { + check(store.getTokensAfter(VariableDeclarator.id, 0), []); + }); + + it("should retrieve one token after a node", () => { + check(store.getTokensAfter(VariableDeclarator.id, 1), ["="]); + }); + + it("should retrieve more than one token after a node", () => { + check(store.getTokensAfter(VariableDeclarator.id, 2), ["=", "a"]); + }); + + it("should retrieve all tokens after a node", () => { + check(store.getTokensAfter(VariableDeclarator.id, 9e9), [ + "=", + "a", + "*", + "b", + "call", + "(", + ")", + ";", + ]); + }); + + it("should retrieve more than one token after a node with count option", () => { + check(store.getTokensAfter(VariableDeclarator.id, { count: 2 }), [ + "=", + "a", + ]); + }); + + it("should retrieve all matched tokens after a node with filter option", () => { + check( + store.getTokensAfter(VariableDeclarator.id, { + filter: t => t.type === "Identifier", + }), + ["a", "b", "call"], + ); + }); + + it("should retrieve matched tokens after a node with count and filter options", () => { + check( + store.getTokensAfter(VariableDeclarator.id, { + count: 2, + filter: t => t.type === "Identifier", + }), + ["a", "b"], + ); + }); + + it("should retrieve all tokens and comments after a node with includeComments option", () => { + check( + store.getTokensAfter(VariableDeclarator.id, { + includeComments: true, + }), + [ + "B", + "=", + "C", + "a", + "D", + "*", + "b", + "E", + "F", + "call", + "(", + ")", + ";", + "Z", + ], + ); + }); + + it("should retrieve several tokens and comments after a node with includeComments and count options", () => { + check( + store.getTokensAfter(VariableDeclarator.id, { + includeComments: true, + count: 3, + }), + ["B", "=", "C"], + ); + }); + + it("should retrieve matched tokens and comments after a node with includeComments and count and filter options", () => { + check( + store.getTokensAfter(VariableDeclarator.id, { + includeComments: true, + count: 3, + filter: t => t.type.startsWith("Block"), + }), + ["B", "C", "D"], + ); + }); + }); + + describe("when calling getTokenAfter", () => { + it("should retrieve one token after a node", () => { + assert.strictEqual( + store.getTokenAfter(VariableDeclarator.id).value, + "=", + ); + }); + + it("should skip a given number of tokens", () => { + assert.strictEqual( + store.getTokenAfter(VariableDeclarator.id, 1).value, + "a", + ); + assert.strictEqual( + store.getTokenAfter(VariableDeclarator.id, 2).value, + "*", + ); + }); + + it("should skip a given number of tokens with skip option", () => { + assert.strictEqual( + store.getTokenAfter(VariableDeclarator.id, { skip: 1 }).value, + "a", + ); + assert.strictEqual( + store.getTokenAfter(VariableDeclarator.id, { skip: 2 }).value, + "*", + ); + }); + + it("should retrieve matched token with filter option", () => { + assert.strictEqual( + store.getTokenAfter( + VariableDeclarator.id, + t => t.type === "Identifier", + ).value, + "a", + ); + assert.strictEqual( + store.getTokenAfter(VariableDeclarator.id, { + filter: t => t.type === "Identifier", + }).value, + "a", + ); + }); + + it("should retrieve matched token with filter and skip options", () => { + assert.strictEqual( + store.getTokenAfter(VariableDeclarator.id, { + skip: 1, + filter: t => t.type === "Identifier", + }).value, + "b", + ); + }); + + it("should retrieve one token or comment after a node with includeComments option", () => { + assert.strictEqual( + store.getTokenAfter(VariableDeclarator.id, { + includeComments: true, + }).value, + "B", + ); + }); + + it("should retrieve one token or comment after a node with includeComments and skip options", () => { + assert.strictEqual( + store.getTokenAfter(VariableDeclarator.id, { + includeComments: true, + skip: 2, + }).value, + "C", + ); + }); + + it("should retrieve one token or comment after a node with includeComments and skip and filter options", () => { + assert.strictEqual( + store.getTokenAfter(VariableDeclarator.id, { + includeComments: true, + skip: 2, + filter: t => t.type.startsWith("Block"), + }).value, + "D", + ); + }); + + it("should retrieve the next node if the comment at the first of source code is specified.", () => { + const code = "/*comment*/ a + b"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getTokenAfter(ast.comments[0]); + + assert.strictEqual(token.value, "a"); + }); + + it("should retrieve the next comment if the last token is specified.", () => { + const code = "a + b /*comment*/"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getTokenAfter(ast.tokens[2], { + includeComments: true, + }); + + assert.strictEqual(token.value, "comment"); + }); + + it("should retrieve null if the last comment is specified.", () => { + const code = "a + b /*comment*/"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getTokenAfter(ast.comments[0], { + includeComments: true, + }); + + assert.strictEqual(token, null); + }); + }); + + describe("when calling getFirstTokens", () => { + it("should retrieve zero tokens from a node's token stream", () => { + check(store.getFirstTokens(BinaryExpression, 0), []); + }); + + it("should retrieve one token from a node's token stream", () => { + check(store.getFirstTokens(BinaryExpression, 1), ["a"]); + }); + + it("should retrieve more than one token from a node's token stream", () => { + check(store.getFirstTokens(BinaryExpression, 2), ["a", "*"]); + }); + + it("should retrieve all tokens from a node's token stream", () => { + check(store.getFirstTokens(BinaryExpression, 9e9), ["a", "*", "b"]); + }); + + it("should retrieve more than one token from a node's token stream with count option", () => { + check(store.getFirstTokens(BinaryExpression, { count: 2 }), [ + "a", + "*", + ]); + }); + + it("should retrieve matched tokens from a node's token stream with filter option", () => { + check( + store.getFirstTokens( + BinaryExpression, + t => t.type === "Identifier", + ), + ["a", "b"], + ); + check( + store.getFirstTokens(BinaryExpression, { + filter: t => t.type === "Identifier", + }), + ["a", "b"], + ); + }); + + it("should retrieve matched tokens from a node's token stream with filter and count options", () => { + check( + store.getFirstTokens(BinaryExpression, { + count: 1, + filter: t => t.type === "Identifier", + }), + ["a"], + ); + }); + + it("should retrieve all tokens and comments from a node's token stream with includeComments option", () => { + check( + store.getFirstTokens(BinaryExpression, { + includeComments: true, + }), + ["a", "D", "*", "b"], + ); + }); + + it("should retrieve several tokens and comments from a node's token stream with includeComments and count options", () => { + check( + store.getFirstTokens(BinaryExpression, { + includeComments: true, + count: 3, + }), + ["a", "D", "*"], + ); + }); + + it("should retrieve several tokens and comments from a node's token stream with includeComments and count and filter options", () => { + check( + store.getFirstTokens(BinaryExpression, { + includeComments: true, + count: 3, + filter: t => t.value !== "a", + }), + ["D", "*", "b"], + ); + }); + }); + + describe("when calling getFirstToken", () => { + it("should retrieve the first token of a node's token stream", () => { + assert.strictEqual( + store.getFirstToken(BinaryExpression).value, + "a", + ); + }); + + it("should skip a given number of tokens", () => { + assert.strictEqual( + store.getFirstToken(BinaryExpression, 1).value, + "*", + ); + assert.strictEqual( + store.getFirstToken(BinaryExpression, 2).value, + "b", + ); + }); + + it("should skip a given number of tokens with skip option", () => { + assert.strictEqual( + store.getFirstToken(BinaryExpression, { skip: 1 }).value, + "*", + ); + assert.strictEqual( + store.getFirstToken(BinaryExpression, { skip: 2 }).value, + "b", + ); + }); + + it("should retrieve matched token with filter option", () => { + assert.strictEqual( + store.getFirstToken( + BinaryExpression, + t => t.type === "Identifier", + ).value, + "a", + ); + assert.strictEqual( + store.getFirstToken(BinaryExpression, { + filter: t => t.type === "Identifier", + }).value, + "a", + ); + }); + + it("should retrieve matched token with filter and skip options", () => { + assert.strictEqual( + store.getFirstToken(BinaryExpression, { + skip: 1, + filter: t => t.type === "Identifier", + }).value, + "b", + ); + }); + + it("should retrieve the first token or comment of a node's token stream with includeComments option", () => { + assert.strictEqual( + store.getFirstToken(BinaryExpression, { includeComments: true }) + .value, + "a", + ); + }); + + it("should retrieve the first matched token or comment of a node's token stream with includeComments and skip options", () => { + assert.strictEqual( + store.getFirstToken(BinaryExpression, { + includeComments: true, + skip: 1, + }).value, + "D", + ); + }); + + it("should retrieve the first matched token or comment of a node's token stream with includeComments and skip and filter options", () => { + assert.strictEqual( + store.getFirstToken(BinaryExpression, { + includeComments: true, + skip: 1, + filter: t => t.value !== "a", + }).value, + "*", + ); + }); + + it("should retrieve the first comment if the comment is at the last of nodes", () => { + const code = "a + b\n/*comment*/ c + d"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + + /* + * A node must not start with a token: it can start with a comment or be empty. + * This test case is needed for completeness. + */ + const token = tokenStore.getFirstToken( + { range: [ast.comments[0].range[0], ast.tokens[5].range[1]] }, + { includeComments: true }, + ); + + assert.strictEqual(token.value, "comment"); + }); + + it("should retrieve the first token (without includeComments option) if the comment is at the last of nodes", () => { + const code = "a + b\n/*comment*/ c + d"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + + /* + * A node must not start with a token: it can start with a comment or be empty. + * This test case is needed for completeness. + */ + const token = tokenStore.getFirstToken({ + range: [ast.comments[0].range[0], ast.tokens[5].range[1]], + }); + + assert.strictEqual(token.value, "c"); + }); + + it("should retrieve the first token if the root node contains a trailing comment", () => { + const parser = require("../../../../fixtures/parsers/all-comments-parser"); + const code = "foo // comment"; + const ast = parser.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getFirstToken(ast); + + assert.strictEqual(token, ast.tokens[0]); + }); + + it("should return null if the source contains only comments", () => { + const code = "// comment"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getFirstToken(ast, { + filter() { + assert.fail("Unexpected call to filter callback"); + }, + }); + + assert.strictEqual(token, null); + }); + + it("should return null if the source is empty", () => { + const code = ""; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getFirstToken(ast); + + assert.strictEqual(token, null); + }); + }); + + describe("when calling getLastTokens", () => { + it("should retrieve zero tokens from the end of a node's token stream", () => { + check(store.getLastTokens(BinaryExpression, 0), []); + }); + + it("should retrieve one token from the end of a node's token stream", () => { + check(store.getLastTokens(BinaryExpression, 1), ["b"]); + }); + + it("should retrieve more than one token from the end of a node's token stream", () => { + check(store.getLastTokens(BinaryExpression, 2), ["*", "b"]); + }); + + it("should retrieve all tokens from the end of a node's token stream", () => { + check(store.getLastTokens(BinaryExpression, 9e9), ["a", "*", "b"]); + }); + + it("should retrieve more than one token from the end of a node's token stream with count option", () => { + check(store.getLastTokens(BinaryExpression, { count: 2 }), [ + "*", + "b", + ]); + }); + + it("should retrieve matched tokens from the end of a node's token stream with filter option", () => { + check( + store.getLastTokens( + BinaryExpression, + t => t.type === "Identifier", + ), + ["a", "b"], + ); + check( + store.getLastTokens(BinaryExpression, { + filter: t => t.type === "Identifier", + }), + ["a", "b"], + ); + }); + + it("should retrieve matched tokens from the end of a node's token stream with filter and count options", () => { + check( + store.getLastTokens(BinaryExpression, { + count: 1, + filter: t => t.type === "Identifier", + }), + ["b"], + ); + }); + + it("should retrieve all tokens from the end of a node's token stream with includeComments option", () => { + check( + store.getLastTokens(BinaryExpression, { + includeComments: true, + }), + ["a", "D", "*", "b"], + ); + }); + + it("should retrieve matched tokens from the end of a node's token stream with includeComments and count options", () => { + check( + store.getLastTokens(BinaryExpression, { + includeComments: true, + count: 3, + }), + ["D", "*", "b"], + ); + }); + + it("should retrieve matched tokens from the end of a node's token stream with includeComments and count and filter options", () => { + check( + store.getLastTokens(BinaryExpression, { + includeComments: true, + count: 3, + filter: t => t.type !== "Punctuator", + }), + ["a", "D", "b"], + ); + }); + }); + + describe("when calling getLastToken", () => { + it("should retrieve the last token of a node's token stream", () => { + assert.strictEqual(store.getLastToken(BinaryExpression).value, "b"); + assert.strictEqual( + store.getLastToken(VariableDeclaration).value, + "b", + ); + }); + + it("should skip a given number of tokens", () => { + assert.strictEqual( + store.getLastToken(BinaryExpression, 1).value, + "*", + ); + assert.strictEqual( + store.getLastToken(BinaryExpression, 2).value, + "a", + ); + }); + + it("should skip a given number of tokens with skip option", () => { + assert.strictEqual( + store.getLastToken(BinaryExpression, { skip: 1 }).value, + "*", + ); + assert.strictEqual( + store.getLastToken(BinaryExpression, { skip: 2 }).value, + "a", + ); + }); + + it("should retrieve the last matched token of a node's token stream with filter option", () => { + assert.strictEqual( + store.getLastToken(BinaryExpression, t => t.value !== "b") + .value, + "*", + ); + assert.strictEqual( + store.getLastToken(BinaryExpression, { + filter: t => t.value !== "b", + }).value, + "*", + ); + }); + + it("should retrieve the last matched token of a node's token stream with filter and skip options", () => { + assert.strictEqual( + store.getLastToken(BinaryExpression, { + skip: 1, + filter: t => t.type === "Identifier", + }).value, + "a", + ); + }); + + it("should retrieve the last token of a node's token stream with includeComments option", () => { + assert.strictEqual( + store.getLastToken(BinaryExpression, { includeComments: true }) + .value, + "b", + ); + }); + + it("should retrieve the last token of a node's token stream with includeComments and skip options", () => { + assert.strictEqual( + store.getLastToken(BinaryExpression, { + includeComments: true, + skip: 2, + }).value, + "D", + ); + }); + + it("should retrieve the last token of a node's token stream with includeComments and skip and filter options", () => { + assert.strictEqual( + store.getLastToken(BinaryExpression, { + includeComments: true, + skip: 1, + filter: t => t.type !== "Identifier", + }).value, + "D", + ); + }); + + it("should retrieve the last comment if the comment is at the last of nodes", () => { + const code = "a + b /*comment*/\nc + d"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + + /* + * A node must not end with a token: it can end with a comment or be empty. + * This test case is needed for completeness. + */ + const token = tokenStore.getLastToken( + { range: [ast.tokens[0].range[0], ast.comments[0].range[1]] }, + { includeComments: true }, + ); + + assert.strictEqual(token.value, "comment"); + }); + + it("should retrieve the last token (without includeComments option) if the comment is at the last of nodes", () => { + const code = "a + b /*comment*/\nc + d"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + + /* + * A node must not end with a token: it can end with a comment or be empty. + * This test case is needed for completeness. + */ + const token = tokenStore.getLastToken({ + range: [ast.tokens[0].range[0], ast.comments[0].range[1]], + }); + + assert.strictEqual(token.value, "b"); + }); + + it("should retrieve the last token if the root node contains a trailing comment", () => { + const parser = require("../../../../fixtures/parsers/all-comments-parser"); + const code = "foo // comment"; + const ast = parser.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getLastToken(ast); + + assert.strictEqual(token, ast.tokens[0]); + }); + + it("should return null if the source contains only comments", () => { + const code = "// comment"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getLastToken(ast, { + filter() { + assert.fail("Unexpected call to filter callback"); + }, + }); + + assert.strictEqual(token, null); + }); + + it("should return null if the source is empty", () => { + const code = ""; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const token = tokenStore.getLastToken(ast); + + assert.strictEqual(token, null); + }); + }); + + describe("when calling getFirstTokensBetween", () => { + it("should retrieve zero tokens between adjacent nodes", () => { + check( + store.getFirstTokensBetween(BinaryExpression, CallExpression), + [], + ); + }); + + it("should retrieve multiple tokens between non-adjacent nodes with count option", () => { + check( + store.getFirstTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + 2, + ), + ["=", "a"], + ); + check( + store.getFirstTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + { count: 2 }, + ), + ["=", "a"], + ); + }); + + it("should retrieve matched tokens between non-adjacent nodes with filter option", () => { + check( + store.getFirstTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + { filter: t => t.type !== "Punctuator" }, + ), + ["a"], + ); + }); + + it("should retrieve all tokens between non-adjacent nodes with empty object option", () => { + check( + store.getFirstTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + {}, + ), + ["=", "a", "*"], + ); + }); + + it("should retrieve multiple tokens between non-adjacent nodes with includeComments option", () => { + check( + store.getFirstTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + { includeComments: true }, + ), + ["B", "=", "C", "a", "D", "*"], + ); + }); + + it("should retrieve multiple tokens between non-adjacent nodes with includeComments and count options", () => { + check( + store.getFirstTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + { includeComments: true, count: 3 }, + ), + ["B", "=", "C"], + ); + }); + + it("should retrieve multiple tokens and comments between non-adjacent nodes with includeComments and filter options", () => { + check( + store.getFirstTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + { + includeComments: true, + filter: t => t.type !== "Punctuator", + }, + ), + ["B", "C", "a", "D"], + ); + }); + }); + + describe("when calling getFirstTokenBetween", () => { + it("should return null between adjacent nodes", () => { + assert.strictEqual( + store.getFirstTokenBetween(BinaryExpression, CallExpression), + null, + ); + }); + + it("should retrieve one token between non-adjacent nodes with count option", () => { + assert.strictEqual( + store.getFirstTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + ).value, + "=", + ); + }); + + it("should retrieve one token between non-adjacent nodes with skip option", () => { + assert.strictEqual( + store.getFirstTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + 1, + ).value, + "a", + ); + assert.strictEqual( + store.getFirstTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + { skip: 2 }, + ).value, + "*", + ); + }); + + it("should return null if it's skipped beyond the right token", () => { + assert.strictEqual( + store.getFirstTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + { skip: 3 }, + ), + null, + ); + assert.strictEqual( + store.getFirstTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + { skip: 4 }, + ), + null, + ); + }); + + it("should retrieve the first matched token between non-adjacent nodes with filter option", () => { + assert.strictEqual( + store.getFirstTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + { filter: t => t.type !== "Identifier" }, + ).value, + "=", + ); + }); + + it("should retrieve first token or comment between non-adjacent nodes with includeComments option", () => { + assert.strictEqual( + store.getFirstTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + { includeComments: true }, + ).value, + "B", + ); + }); + + it("should retrieve first token or comment between non-adjacent nodes with includeComments and skip options", () => { + assert.strictEqual( + store.getFirstTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + { includeComments: true, skip: 1 }, + ).value, + "=", + ); + }); + + it("should retrieve first token or comment between non-adjacent nodes with includeComments and skip and filter options", () => { + assert.strictEqual( + store.getFirstTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + { + includeComments: true, + skip: 1, + filter: t => t.type !== "Punctuator", + }, + ).value, + "C", + ); + }); + }); + + describe("when calling getLastTokensBetween", () => { + it("should retrieve zero tokens between adjacent nodes", () => { + check( + store.getLastTokensBetween(BinaryExpression, CallExpression), + [], + ); + }); + + it("should retrieve multiple tokens between non-adjacent nodes with count option", () => { + check( + store.getLastTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + 2, + ), + ["a", "*"], + ); + check( + store.getLastTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + { count: 2 }, + ), + ["a", "*"], + ); + }); + + it("should retrieve matched tokens between non-adjacent nodes with filter option", () => { + check( + store.getLastTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + { filter: t => t.type !== "Punctuator" }, + ), + ["a"], + ); + }); + + it("should retrieve all tokens between non-adjacent nodes with empty object option", () => { + check( + store.getLastTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + {}, + ), + ["=", "a", "*"], + ); + }); + + it("should retrieve all tokens and comments between non-adjacent nodes with includeComments option", () => { + check( + store.getLastTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + { includeComments: true }, + ), + ["B", "=", "C", "a", "D", "*"], + ); + }); + + it("should retrieve multiple tokens between non-adjacent nodes with includeComments and count options", () => { + check( + store.getLastTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + { includeComments: true, count: 3 }, + ), + ["a", "D", "*"], + ); + }); + + it("should retrieve multiple tokens and comments between non-adjacent nodes with includeComments and filter options", () => { + check( + store.getLastTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + { + includeComments: true, + filter: t => t.type !== "Punctuator", + }, + ), + ["B", "C", "a", "D"], + ); + }); + }); + + describe("when calling getLastTokenBetween", () => { + it("should return null between adjacent nodes", () => { + assert.strictEqual( + store.getLastTokenBetween(BinaryExpression, CallExpression), + null, + ); + }); + + it("should retrieve one token between non-adjacent nodes with count option", () => { + assert.strictEqual( + store.getLastTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + ).value, + "*", + ); + }); + + it("should retrieve one token between non-adjacent nodes with skip option", () => { + assert.strictEqual( + store.getLastTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + 1, + ).value, + "a", + ); + assert.strictEqual( + store.getLastTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + { skip: 2 }, + ).value, + "=", + ); + }); + + it("should return null if it's skipped beyond the right token", () => { + assert.strictEqual( + store.getLastTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + { skip: 3 }, + ), + null, + ); + assert.strictEqual( + store.getLastTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + { skip: 4 }, + ), + null, + ); + }); + + it("should retrieve the first matched token between non-adjacent nodes with filter option", () => { + assert.strictEqual( + store.getLastTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + { filter: t => t.type !== "Identifier" }, + ).value, + "*", + ); + }); + + it("should retrieve first token or comment between non-adjacent nodes with includeComments option", () => { + assert.strictEqual( + store.getLastTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + { includeComments: true }, + ).value, + "*", + ); + }); + + it("should retrieve first token or comment between non-adjacent nodes with includeComments and skip options", () => { + assert.strictEqual( + store.getLastTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + { includeComments: true, skip: 1 }, + ).value, + "D", + ); + }); + + it("should retrieve first token or comment between non-adjacent nodes with includeComments and skip and filter options", () => { + assert.strictEqual( + store.getLastTokenBetween( + VariableDeclarator.id, + BinaryExpression.right, + { + includeComments: true, + skip: 1, + filter: t => t.type !== "Punctuator", + }, + ).value, + "a", + ); + }); + }); + + describe("when calling getTokensBetween", () => { + it("should retrieve zero tokens between adjacent nodes", () => { + check(store.getTokensBetween(BinaryExpression, CallExpression), []); + }); + + it("should retrieve one token between nodes", () => { + check( + store.getTokensBetween( + BinaryExpression.left, + BinaryExpression.right, + ), + ["*"], + ); + }); + + it("should retrieve multiple tokens between non-adjacent nodes", () => { + check( + store.getTokensBetween( + VariableDeclarator.id, + BinaryExpression.right, + ), + ["=", "a", "*"], + ); + }); + + it("should retrieve surrounding tokens when asked for padding", () => { + check( + store.getTokensBetween( + VariableDeclarator.id, + BinaryExpression.left, + 2, + ), + ["var", "answer", "=", "a", "*"], + ); + }); + }); + + describe("when calling getTokenByRangeStart", () => { + it("should return identifier token", () => { + const result = store.getTokenByRangeStart(9); + + assert.strictEqual(result.type, "Identifier"); + assert.strictEqual(result.value, "answer"); + }); + + it("should return null when token doesn't exist", () => { + const result = store.getTokenByRangeStart(10); + + assert.isNull(result); + }); + + it("should return a comment token when includeComments is true", () => { + const result = store.getTokenByRangeStart(15, { + includeComments: true, + }); + + assert.strictEqual(result.type, "Block"); + assert.strictEqual(result.value, "B"); + }); + + it("should not return a comment token at the supplied index when includeComments is false", () => { + const result = store.getTokenByRangeStart(15, { + includeComments: false, + }); + + assert.isNull(result); + }); + + it("should not return comment tokens by default", () => { + const result = store.getTokenByRangeStart(15); + + assert.isNull(result); + }); + }); + + describe("when calling getTokenOrCommentBefore", () => { + it("should retrieve one token or comment before a node", () => { + assert.strictEqual( + store.getTokenOrCommentBefore(BinaryExpression).value, + "C", + ); + }); + + it("should skip a given number of tokens", () => { + assert.strictEqual( + store.getTokenOrCommentBefore(BinaryExpression, 1).value, + "=", + ); + assert.strictEqual( + store.getTokenOrCommentBefore(BinaryExpression, 2).value, + "B", + ); + }); + }); + + describe("when calling getTokenOrCommentAfter", () => { + it("should retrieve one token or comment after a node", () => { + assert.strictEqual( + store.getTokenOrCommentAfter(VariableDeclarator.id).value, + "B", + ); + }); + + it("should skip a given number of tokens", () => { + assert.strictEqual( + store.getTokenOrCommentAfter(VariableDeclarator.id, 1).value, + "=", + ); + assert.strictEqual( + store.getTokenOrCommentAfter(VariableDeclarator.id, 2).value, + "C", + ); + }); + }); + + describe("when calling getFirstToken & getTokenAfter", () => { + it("should retrieve all tokens and comments in the node", () => { + const code = "(function(a, /*b,*/ c){})"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const tokens = []; + let token = tokenStore.getFirstToken(ast); + + while (token) { + tokens.push(token); + token = tokenStore.getTokenAfter(token, { + includeComments: true, + }); + } + + check(tokens, [ + "(", + "function", + "(", + "a", + ",", + "b,", + "c", + ")", + "{", + "}", + ")", + ]); + }); + + it("should retrieve all tokens and comments in the node (no spaces)", () => { + const code = "(function(a,/*b,*/c){})"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const tokens = []; + let token = tokenStore.getFirstToken(ast); + + while (token) { + tokens.push(token); + token = tokenStore.getTokenAfter(token, { + includeComments: true, + }); + } + + check(tokens, [ + "(", + "function", + "(", + "a", + ",", + "b,", + "c", + ")", + "{", + "}", + ")", + ]); + }); + }); + + describe("when calling getLastToken & getTokenBefore", () => { + it("should retrieve all tokens and comments in the node", () => { + const code = "(function(a, /*b,*/ c){})"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const tokens = []; + let token = tokenStore.getLastToken(ast); + + while (token) { + tokens.push(token); + token = tokenStore.getTokenBefore(token, { + includeComments: true, + }); + } + + check(tokens.reverse(), [ + "(", + "function", + "(", + "a", + ",", + "b,", + "c", + ")", + "{", + "}", + ")", + ]); + }); + + it("should retrieve all tokens and comments in the node (no spaces)", () => { + const code = "(function(a,/*b,*/c){})"; + const ast = espree.parse(code, { + loc: true, + range: true, + tokens: true, + comment: true, + }); + const tokenStore = new TokenStore(ast.tokens, ast.comments); + const tokens = []; + let token = tokenStore.getLastToken(ast); + + while (token) { + tokens.push(token); + token = tokenStore.getTokenBefore(token, { + includeComments: true, + }); + } + + check(tokens.reverse(), [ + "(", + "function", + "(", + "a", + ",", + "b,", + "c", + ")", + "{", + "}", + ")", + ]); + }); + }); + + describe("when calling commentsExistBetween", () => { + it("should retrieve false if comments don't exist", () => { + assert.isFalse( + store.commentsExistBetween(AST.tokens[0], AST.tokens[1]), + ); + }); + + it("should retrieve true if comments exist", () => { + assert.isTrue( + store.commentsExistBetween(AST.tokens[1], AST.tokens[2]), + ); + }); + }); + + describe("getCommentsBefore", () => { + it("should retrieve comments before a node", () => { + assert.strictEqual( + store.getCommentsBefore(VariableDeclaration)[0].value, + "A", + ); + }); + + it("should retrieve comments before a token", () => { + assert.strictEqual( + store.getCommentsBefore(TOKENS[2] /* "=" token */)[0].value, + "B", + ); + }); + + it("should retrieve multiple comments before a node", () => { + const comments = store.getCommentsBefore(CallExpression); + + assert.strictEqual(comments.length, 2); + assert.strictEqual(comments[0].value, "E"); + assert.strictEqual(comments[1].value, "F"); + }); + + it("should retrieve comments before a Program node", () => { + assert.strictEqual(store.getCommentsBefore(Program)[0].value, "A"); + }); + + it("should return an empty array if there are no comments before a node or token", () => { + check(store.getCommentsBefore(BinaryExpression.right), []); + check(store.getCommentsBefore(TOKENS[1]), []); + }); + }); + + describe("getCommentsAfter", () => { + it("should retrieve comments after a node", () => { + assert.strictEqual( + store.getCommentsAfter(VariableDeclarator.id)[0].value, + "B", + ); + }); + + it("should retrieve comments after a token", () => { + assert.strictEqual( + store.getCommentsAfter(TOKENS[2] /* "=" token */)[0].value, + "C", + ); + }); + + it("should retrieve multiple comments after a node", () => { + const comments = store.getCommentsAfter(VariableDeclaration); + + assert.strictEqual(comments.length, 2); + assert.strictEqual(comments[0].value, "E"); + assert.strictEqual(comments[1].value, "F"); + }); + + it("should retrieve comments after a Program node", () => { + assert.strictEqual(store.getCommentsAfter(Program)[0].value, "Z"); + }); + + it("should return an empty array if there are no comments after a node or token", () => { + check(store.getCommentsAfter(CallExpression.callee), []); + check(store.getCommentsAfter(TOKENS[0]), []); + }); + }); + + describe("getCommentsInside", () => { + it("should retrieve comments inside a node", () => { + check(store.getCommentsInside(Program), ["B", "C", "D", "E", "F"]); + check(store.getCommentsInside(VariableDeclaration), [ + "B", + "C", + "D", + ]); + check(store.getCommentsInside(VariableDeclarator), ["B", "C", "D"]); + check(store.getCommentsInside(BinaryExpression), ["D"]); + }); + + it("should return an empty array if a node does not contain any comments", () => { + check(store.getCommentsInside(TOKENS[2]), []); + }); + }); +}); diff --git a/tests/lib/linter/apply-disable-directives.js b/tests/lib/linter/apply-disable-directives.js index d56bf5bc1b0f..6aabf84fab48 100644 --- a/tests/lib/linter/apply-disable-directives.js +++ b/tests/lib/linter/apply-disable-directives.js @@ -11,3266 +11,4228 @@ const assert = require("chai").assert; const applyDisableDirectives = require("../../../lib/linter/apply-disable-directives"); +const jslang = require("../../../lib/languages/js"); //----------------------------------------------------------------------------- // Helpers //----------------------------------------------------------------------------- /** - * Creates a ParentComment for a given range. + * Creates a ParentDirective for a given range. * @param {[number, number]} range total range of the comment - * @param {string} value String value of the comment + * @param {string} value String value of the directive * @param {string[]} ruleIds Rule IDs reported in the value - * @returns {ParentComment} Test-ready ParentComment object. + * @returns {ParentDirective} Test-ready ParentDirective object. */ -function createParentComment(range, value, ruleIds = []) { - return { - commentToken: { - range, - loc: { - start: { - line: 1, - column: 1 - }, - end: { - line: 1, - column: value ? value.length : 10 - } - }, - value - }, - ruleIds - }; +function createParentDirective(range, value, ruleIds = []) { + return { + node: { + range, + loc: { + start: { + line: 1, + column: 1, + }, + end: { + line: 1, + column: range[1] + 1, + }, + }, + }, + value, + ruleIds, + }; } +const sourceCode = { + getRange(node) { + return node.range; + }, + getLoc(node) { + return node.loc; + }, +}; + //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("apply-disable-directives", () => { - describe("/* eslint-disable */ comments without rules", () => { - it("keeps problems before the comment on the same line", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ parentComment: createParentComment([0, 7]), type: "disable", line: 1, column: 8, ruleId: null, justification: "justification" }], - problems: [{ line: 1, column: 7, ruleId: "foo" }] - }), - [{ line: 1, column: 7, ruleId: "foo" }] - ); - }); - - it("keeps problems on a previous line before the comment", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ parentComment: createParentComment([21, 27]), type: "disable", line: 2, column: 1, ruleId: null, justification: "justification" }], - problems: [{ line: 1, column: 10, ruleId: "foo" }] - }), - [{ line: 1, column: 10, ruleId: "foo" }] - ); - }); - - it("filters problems at the same location as the comment", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 8, ruleId: null, justification: "justification" }], - problems: [{ line: 1, column: 8, ruleId: null }] - }), - [{ line: 1, column: 8, ruleId: null, suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - - it("filters out problems after the comment on the same line", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 8, ruleId: null, justification: "justification" }], - problems: [{ line: 1, column: 10, ruleId: "foo" }] - }), - [{ line: 1, column: 10, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - - it("filters out problems on a later line than the comment", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 8, ruleId: null, justification: "justification" }], - problems: [{ line: 2, column: 3, ruleId: "foo" }] - }), - [{ line: 2, column: 3, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - }); - - describe("/* eslint-disable */ comments with rules", () => { - it("filters problems after the comment that have the same ruleId", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 8, ruleId: "foo", justification: "justification" }], - problems: [{ line: 2, column: 3, ruleId: "foo" }] - }), - [{ line: 2, column: 3, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - - it("filters problems in the same location as the comment that have the same ruleId", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 8, ruleId: "foo", justification: "justification" }], - problems: [{ line: 1, column: 8, ruleId: "foo" }] - }), - [{ line: 1, column: 8, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - - it("keeps problems after the comment that have a different ruleId", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([26, 29]), - type: "disable", - line: 1, - column: 1, - ruleId: "foo", - justification: "justification" - }], - problems: [{ line: 2, column: 3, ruleId: "not-foo" }] - }), - [{ line: 2, column: 3, ruleId: "not-foo" }] - ); - }); - - it("keeps problems before the comment that have the same ruleId", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([7, 31]), - type: "disable", - line: 1, - column: 8, - ruleId: "foo" - }], - problems: [{ line: 1, column: 7, ruleId: "foo" }] - }), - [{ line: 1, column: 7, ruleId: "foo" }] - ); - }); - }); - - describe("eslint-enable comments without rules", () => { - it("keeps problems after the eslint-enable comment", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 26]), - type: "disable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([27, 45]), - type: "enable", - line: 1, - column: 26, - ruleId: null, - justification: "j2" - } - ], - problems: [{ line: 1, column: 27, ruleId: "foo" }] - }), - [{ line: 1, column: 27, ruleId: "foo" }] - ); - }); - - it("keeps problems in the same location as the eslint-enable comment", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 25]), - type: "disable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([26, 40]), - type: "enable", - line: 1, - column: 26, - ruleId: null, - justification: "j2" - } - ], - problems: [{ line: 1, column: 26, ruleId: "foo" }] - }), - [{ line: 1, column: 26, ruleId: "foo" }] - ); - }); - - it("filters out problems before the eslint-enable comment", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { type: "disable", line: 1, column: 1, ruleId: null, justification: "j1" }, - { type: "enable", line: 1, column: 26, ruleId: null, justification: "j2" } - ], - problems: [{ line: 1, column: 3, ruleId: "foo" }] - }), - [{ line: 1, column: 3, ruleId: "foo", suppressions: [{ kind: "directive", justification: "j1" }] }] - ); - }); - - it("filter out problems if disable all then enable foo and then disable foo", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 20]), - type: "disable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([26, 44]), - type: "enable", - line: 1, - column: 26, - ruleId: "foo", - justification: "j2" - }, - { - parentComment: createParentComment([45, 63]), - type: "disable", - line: 2, - column: 1, - ruleId: "foo", - justification: "j3" - } - ], - problems: [{ line: 3, column: 3, ruleId: "foo" }] - }), - [{ line: 3, column: 3, ruleId: "foo", suppressions: [{ kind: "directive", justification: "j3" }] }] - ); - }); - - it("filter out problems if disable all then enable foo and then disable all", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 20]), - type: "disable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([21, 44]), - type: "enable", - line: 1, - column: 26, - ruleId: "foo", - justification: "j2" - }, - { - parentComment: createParentComment([45, 63]), - type: "disable", - line: 2, - column: 1, - ruleId: null, - justification: "j3" - } - ], - problems: [{ line: 3, column: 3, ruleId: "foo" }] - }), - [{ line: 3, column: 3, ruleId: "foo", suppressions: [{ kind: "directive", justification: "j3" }] }] - ); - }); - - it("keeps problems before the eslint-enable comment if there is no corresponding disable comment", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 20]), - type: "disable", - line: 1, - column: 1, - ruleId: "foo", - justification: "j1" - }, - { - parentComment: createParentComment([25, 44]), - type: "enable", - line: 1, - column: 26, - ruleId: null, - justification: "j2" - } - ], - problems: [{ line: 1, column: 3, ruleId: "not-foo" }] - }), - [{ line: 1, column: 3, ruleId: "not-foo" }] - ); - }); - }); - - describe("eslint-enable comments with rules", () => { - it("keeps problems after the comment that have the same ruleId as the eslint-enable comment", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 20]), - type: "disable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([21, 44]), - type: "enable", - line: 2, - column: 1, - ruleId: "foo", - justification: "j2" - } - ], - problems: [{ line: 2, column: 4, ruleId: "foo" }] - }), - [{ line: 2, column: 4, ruleId: "foo" }] - ); - }); - - it("keeps problems in the same location as the comment that have the same ruleId as the eslint-enable comment", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 20]), - type: "disable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([21, 44]), - type: "enable", - line: 2, - column: 1, - ruleId: "foo", - justification: "j2" - } - ], - problems: [{ line: 2, column: 1, ruleId: "foo" }] - }), - [{ line: 2, column: 1, ruleId: "foo" }] - ); - }); - - it("filters problems after the comment that have a different ruleId as the eslint-enable comment", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 20]), - type: "disable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([21, 44]), - type: "enable", - line: 2, - column: 1, - ruleId: "foo", - justification: "j2" - } - ], - problems: [{ line: 2, column: 4, ruleId: "not-foo" }] - }), - [{ line: 2, column: 4, ruleId: "not-foo", suppressions: [{ kind: "directive", justification: "j1" }] }] - ); - }); - - it("reenables reporting correctly even when followed by another enable comment", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { type: "disable", line: 1, column: 1, ruleId: null, justification: "j1" }, - { type: "enable", line: 1, column: 22, ruleId: "foo", justification: "j2" }, - { type: "enable", line: 1, column: 46, ruleId: "bar", justification: "j3" } - ], - problems: [ - { line: 1, column: 10, ruleId: "foo" }, - { line: 1, column: 10, ruleId: "bar" }, - { line: 1, column: 30, ruleId: "foo" }, - { line: 1, column: 30, ruleId: "bar" }, - { line: 1, column: 50, ruleId: "foo" }, - { line: 1, column: 50, ruleId: "bar" } - ] - }), - [ - { line: 1, column: 10, ruleId: "foo", suppressions: [{ kind: "directive", justification: "j1" }] }, - { line: 1, column: 10, ruleId: "bar", suppressions: [{ kind: "directive", justification: "j1" }] }, - { line: 1, column: 30, ruleId: "foo" }, - { line: 1, column: 30, ruleId: "bar", suppressions: [{ kind: "directive", justification: "j1" }] }, - { line: 1, column: 50, ruleId: "foo" }, - { line: 1, column: 50, ruleId: "bar" } - ] - ); - }); - }); - - describe("eslint-disable-line comments without rules", () => { - it("keeps problems on a previous line", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([6, 27]), - type: "disable-line", - line: 2, - column: 1, - ruleId: null, - justification: "justification" - }], - problems: [{ line: 1, column: 5, ruleId: "foo" }] - }), - [{ line: 1, column: 5, ruleId: "foo" }] - ); - }); - - it("filters problems before the comment on the same line", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([7, 28]), - type: "disable-line", - line: 1, - column: 8, - ruleId: null, - justification: "justification" - }], - problems: [{ line: 1, column: 1, ruleId: "foo" }] - }), - [{ line: 1, column: 1, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - - it("filters problems after the comment on the same line", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([7, 28]), - type: "disable-line", - line: 1, - column: 8, - ruleId: null, - justification: "justification" - }], - problems: [{ line: 1, column: 10, ruleId: "foo" }] - }), - [{ line: 1, column: 10, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - - it("keeps problems on a following line", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([7, 34]), - type: "disable-line", - line: 1, - column: 8, - ruleId: "foo", - justification: "justification" - }], - problems: [{ line: 2, column: 1, ruleId: "foo" }] - }), - [{ line: 2, column: 1, ruleId: "foo" }] - ); - }); - }); - - describe("eslint-disable-line comments with rules", () => { - it("filters problems on the current line that match the ruleId", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([7, 34]), - type: "disable-line", - line: 1, - column: 8, - ruleId: "foo", - justification: "justification" - }], - problems: [{ line: 1, column: 2, ruleId: "foo" }] - }), - [{ line: 1, column: 2, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - - it("keeps problems on the current line that do not match the ruleId", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 27]), - type: "disable-line", - line: 1, - column: 1, - ruleId: "foo", - justification: "justification" - }], - problems: [{ line: 1, column: 2, ruleId: "not-foo" }] - }), - [{ line: 1, column: 2, ruleId: "not-foo" }] - ); - }); - - it("filters problems on the current line that do not match the ruleId if preceded by a disable comment", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 21]), - type: "disable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([24, 28]), - type: "disable-line", - line: 1, - column: 22, - ruleId: "foo", - justification: "j2" - } - ], - problems: [{ line: 1, column: 5, ruleId: "not-foo" }] - }), - [{ line: 1, column: 5, ruleId: "not-foo", suppressions: [{ kind: "directive", justification: "j1" }] }] - ); - }); - - it("handles consecutive comments appropriately", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([7, 34]), - type: "disable-line", - line: 1, - column: 8, - ruleId: "foo", - justification: "j1" - }, - { - parentComment: createParentComment([38, 73]), - type: "disable-line", - line: 2, - column: 8, - ruleId: "foo", - justification: "j2" - }, - { - parentComment: createParentComment([76, 111]), - type: "disable-line", - line: 3, - column: 8, - ruleId: "foo", - justification: "j3" - }, - { - parentComment: createParentComment([114, 149]), - type: "disable-line", - line: 4, - column: 8, - ruleId: "foo", - justification: "j4" - }, - { - parentComment: createParentComment([152, 187]), - type: "disable-line", - line: 5, - column: 8, - ruleId: "foo", - justification: "j5" - }, - { - parentComment: createParentComment([190, 225]), - type: "disable-line", - line: 6, - column: 8, - ruleId: "foo", - justification: "j6" - } - ], - problems: [{ line: 2, column: 1, ruleId: "foo" }] - }), - [{ line: 2, column: 1, ruleId: "foo", suppressions: [{ kind: "directive", justification: "j2" }] }] - ); - }); - }); - - describe("eslint-disable-next-line comments without rules", () => { - it("filters problems on the next line", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 31]), - type: "disable-next-line", - line: 1, - column: 1, - ruleId: null, - justification: "justification" - }], - problems: [{ line: 2, column: 3, ruleId: "foo" }] - }), - [{ line: 2, column: 3, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - - it("keeps problems on the same line", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 31]), - type: "disable-next-line", - line: 1, - column: 1, - ruleId: null - }], - problems: [{ line: 1, column: 3, ruleId: "foo" }] - }), - [{ line: 1, column: 3, ruleId: "foo" }] - ); - }); - - it("keeps problems after the next line", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 31]), - type: "disable-next-line", - line: 1, - column: 1, - ruleId: null, - justification: "justification" - }], - problems: [{ line: 3, column: 3, ruleId: "foo" }] - }), - [{ line: 3, column: 3, ruleId: "foo" }] - ); - }); - - it("filters problems on the next line even if there is an eslint-enable comment on the same line", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { parentComment: createParentComment([0, 31]), type: "disable-next-line", line: 1, column: 1, ruleId: null, justification: "j1" }, - { parentComment: createParentComment([31, 50]), type: "enable", line: 1, column: 31, ruleId: null, justification: "j2" } - ], - problems: [{ line: 2, column: 2, ruleId: "foo" }] - }), - [{ line: 2, column: 2, ruleId: "foo", suppressions: [{ kind: "directive", justification: "j1" }] }] - ); - }); - }); - - describe("eslint-disable-next-line comments with rules", () => { - it("filters problems on the next line that match the ruleId", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: "foo", justification: "justification" }], - problems: [{ line: 2, column: 1, ruleId: "foo" }] - }), - [{ line: 2, column: 1, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - - it("keeps problems on the next line that do not match the ruleId", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 31]), - type: "disable-next-line", - line: 1, - column: 1, - ruleId: "foo", - justification: "justification" - }], - problems: [{ line: 2, column: 1, ruleId: "not-foo" }] - }), - [{ line: 2, column: 1, ruleId: "not-foo" }] - ); - }); - }); - - describe("unrecognized directive types", () => { - it("throws a TypeError when it encounters an unrecognized directive", () => { - assert.throws( - () => - applyDisableDirectives({ - directives: [{ type: "foo", line: 1, column: 4, ruleId: "foo", justification: "justification" }], - problems: [] - }), - "Unrecognized directive type 'foo'" - ); - }); - }); - - describe("unused directives", () => { - it("Adds a problem for /* eslint-disable */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 20]), - type: "disable", - line: 1, - column: 1, - justification: "justification" - }], - problems: [], - reportUnusedDisableDirectives: "error" - }), - [{ - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 2, - nodeType: null - }] - ); - }); - - it("Does not fix a problem for /* eslint-disable */ when disableFixes is enabled", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 20]), - type: "disable", - line: 1, - column: 1, - justification: "justification" - }], - disableFixes: true, - problems: [], - reportUnusedDisableDirectives: "error" - }), - [{ - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - severity: 2, - nodeType: null - }] - ); - }); - - it("Does not add a problem for /* eslint-disable */ /* (problem) */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 1, ruleId: null, justification: "justification" }], - problems: [{ line: 2, column: 1, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [{ line: 2, column: 1, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - - it("Adds a problem for /* eslint-disable foo */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 21]), - type: "disable", - line: 1, - column: 1, - ruleId: "foo", - justification: "justification" - }], - problems: [], - reportUnusedDisableDirectives: "error" - }), - [{ - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'foo').", - line: 1, - column: 1, - fix: { - range: [0, 21], - text: " " - }, - severity: 2, - nodeType: null - }] - ); - }); - - it("Adds a problem for /* eslint-disable foo */ /* (problem from another rule) */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 24]), - type: "disable", - line: 1, - column: 1, - ruleId: "foo", - justification: "justification" - }], - problems: [{ line: 1, column: 20, ruleId: "not-foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'foo').", - line: 1, - column: 1, - fix: { - range: [0, 24], - text: " " - }, - severity: 2, - nodeType: null - }, - { - ruleId: "not-foo", - line: 1, - column: 20 - } - ] - ); - }); - - it("Adds a problem for /* (problem from foo) */ /* eslint-disable */ /* eslint-enable foo */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 21]), - type: "disable", - line: 1, - column: 8, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([0, 21]), - type: "enable", - line: 1, - column: 24, - ruleId: "foo", - justification: "j2" - } - ], - problems: [{ line: 1, column: 2, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: "foo", - line: 1, - column: 2 - }, - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - fix: { - range: [0, 21], - text: " " - }, - line: 1, - column: 8, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable */ /* eslint-enable */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 20]), - type: "disable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([21, 41]), - type: "enable", - line: 1, - column: 12, - ruleId: null, - justification: "j2" - } - ], - problems: [], - reportUnusedDisableDirectives: "error" - }), - [{ - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 2, - nodeType: null - }] - ); - }); - - it("Adds two problems for /* eslint-disable */ /* eslint-disable */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 21]), - type: "disable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([21, 42]), - type: "disable", - line: 2, - column: 1, - ruleId: null, - justification: "j2" - } - ], - problems: [], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 21], - text: " " - }, - severity: 2, - nodeType: null - }, - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 2, - column: 1, - fix: { - range: [21, 42], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable */ /* eslint-disable */ /* (problem) */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 21]), - type: "disable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([22, 45]), - type: "disable", - line: 2, - column: 1, - ruleId: null, - justification: "j2" - } - ], - problems: [{ line: 3, column: 1, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 21], - text: " " - }, - severity: 2, - nodeType: null - }, - { - line: 3, - column: 1, - ruleId: "foo", - suppressions: [ - { kind: "directive", justification: "j1" }, - { kind: "directive", justification: "j2" } - ] - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable foo */ /* eslint-disable */ /* (problem from foo) */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 21]), - type: "disable", - line: 1, - column: 1, - ruleId: "foo", - justification: "j1" - }, - { - parentComment: createParentComment([22, 45]), - type: "disable", - line: 2, - column: 1, - ruleId: null, - justification: "j2" - } - ], - problems: [{ line: 3, column: 1, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'foo').", - line: 1, - column: 1, - fix: { - range: [0, 21], - text: " " - }, - severity: 2, - nodeType: null - }, - { - line: 3, - column: 1, - ruleId: "foo", - suppressions: [ - { kind: "directive", justification: "j1" }, - { kind: "directive", justification: "j2" } - ] - } - ] - ); - }); - - it("Does not add a problem for /* eslint-disable foo */ /* (problem from foo) */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ type: "disable", line: 1, column: 1, ruleId: "foo", justification: "justification" }], - problems: [{ line: 1, column: 6, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [{ line: 1, column: 6, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - - it("Adds a problem for /* eslint-disable */ /* eslint-disable foo */ /* (problem from foo) */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 21]), - type: "disable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([22, 45]), - type: "disable", - line: 2, - column: 1, - ruleId: "foo", - justification: "j2" - } - ], - problems: [{ line: 3, column: 1, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 21], - text: " " - }, - severity: 2, - nodeType: null - }, - { - line: 3, - column: 1, - ruleId: "foo", - suppressions: [ - { kind: "directive", justification: "j1" }, - { kind: "directive", justification: "j2" } - ] - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable */ /* eslint-disable foo */ /* (problem from another rule) */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 20]), - type: "disable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([21, 45]), - type: "disable", - line: 2, - column: 1, - ruleId: "foo", - justification: "j2" - } - ], - problems: [{ line: 3, column: 1, ruleId: "bar" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'foo').", - line: 2, - column: 1, - fix: { - range: [21, 45], - text: " " - }, - severity: 2, - nodeType: null - }, - { - line: 3, - column: 1, - ruleId: "bar", - suppressions: [{ kind: "directive", justification: "j1" }] - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable foo */ /* eslint-enable foo */ /* (problem from foo) */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 20]), - type: "disable", - line: 1, - column: 1, - ruleId: "foo", - justification: "j1" - }, - { - parentComment: createParentComment([25, 46]), - type: "enable", - line: 1, - column: 26, - ruleId: "foo", - justification: "j2" - } - ], - problems: [{ line: 1, column: 30, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'foo').", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 2, - nodeType: null - }, - { - ruleId: "foo", - line: 1, - column: 30 - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable foo */ /* eslint-enable */ /* (problem from foo) */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 24]), - type: "disable", - line: 1, - column: 1, - ruleId: "foo", - justification: "j1" - }, - { - parentComment: createParentComment([25, 49]), - type: "enable", - line: 1, - column: 26, - ruleId: null, - justification: "j2" - } - ], - problems: [{ line: 1, column: 30, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'foo').", - line: 1, - column: 1, - fix: { - range: [0, 24], - text: " " - }, - severity: 2, - nodeType: null - }, - { - ruleId: "foo", - line: 1, - column: 30 - } - ] - ); - }); - - it("Adds two problems for /* eslint-disable */ /* eslint-disable foo */ /* eslint-enable foo */ /* (problem from foo) */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 21]), - type: "disable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([22, 45]), - type: "disable", - line: 2, - column: 1, - ruleId: "foo", - justification: "j2" - }, - { - parentComment: createParentComment([46, 69]), - type: "enable", - line: 3, - column: 1, - ruleId: "foo", - justification: "j3" - } - ], - problems: [{ line: 4, column: 1, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 21], - text: " " - }, - severity: 2, - nodeType: null - }, - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'foo').", - line: 2, - column: 1, - fix: { - range: [22, 45], - text: " " - }, - severity: 2, - nodeType: null - }, - { - ruleId: "foo", - line: 4, - column: 1 - } - ] - ); - }); - - it("Adds a problem for /* eslint-enable */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 20]), - type: "enable", - line: 1, - column: 1, - justification: "justification" - }], - problems: [], - reportUnusedDisableDirectives: "error" - }), - [{ - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 2, - nodeType: null - }] - ); - }); - - it("Does not fix a problem for /* eslint-enable */ when disableFixes is enabled", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 20]), - type: "enable", - line: 1, - column: 1, - justification: "justification" - }], - disableFixes: true, - problems: [], - reportUnusedDisableDirectives: "error" - }), - [{ - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", - line: 1, - column: 1, - severity: 2, - nodeType: null - }] - ); - }); - - it("Does not add a problem for /* eslint-disable */ /* (problem) */ /* eslint-enable */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { type: "disable", line: 1, column: 1, ruleId: null, justification: "justification" }, - { type: "enable", line: 3, column: 1, ruleId: null, justification: "justification" } - ], - problems: [{ line: 2, column: 1, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [{ line: 2, column: 1, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - - it("Adds a problem for /* eslint-enable foo */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 21]), - type: "enable", - line: 1, - column: 1, - ruleId: "foo", - justification: "justification" - }], - problems: [], - reportUnusedDisableDirectives: "error" - }), - [{ - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", - line: 1, - column: 1, - fix: { - range: [0, 21], - text: " " - }, - severity: 2, - nodeType: null - }] - ); - }); - - it("Adds a problem for /* eslint-disable not-foo */ /* (problem from not-foo) */ /* eslint-enable foo */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 24]), - type: "disable", - line: 1, - column: 1, - ruleId: "not-foo", - justification: "justification" - }, - { - parentComment: createParentComment([48, 72]), - type: "enable", - line: 3, - column: 1, - ruleId: "foo", - justification: "justification" - } - ], - problems: [{ line: 2, column: 1, ruleId: "not-foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: "not-foo", - line: 2, - column: 1, - suppressions: [{ justification: "justification", kind: "directive" }] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", - line: 3, - column: 1, - fix: { - range: [48, 72], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable */ /* eslint-enable */ /* eslint-enable foo */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 21]), - type: "disable", - line: 1, - column: 1, - ruleId: "foo", - justification: "j1" - }, - { - parentComment: createParentComment([42, 63]), - type: "enable", - line: 3, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([63, 84]), - type: "enable", - line: 4, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([84, 105]), - type: "enable", - line: 5, - column: 1, - ruleId: "foo", - justification: "j2" - } - ], - problems: [{ line: 2, column: 1, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: "foo", - line: 2, - column: 1, - suppressions: [{ justification: "j1", kind: "directive" }] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", - fix: { - range: [63, 84], - text: " " - }, - line: 4, - column: 1, - severity: 2, - nodeType: null - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", - fix: { - range: [84, 105], - text: " " - }, - line: 5, - column: 1, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable */ /* eslint-enable */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 21]), - type: "disable", - line: 1, - column: 1, - ruleId: "foo", - justification: "j1" - }, - { - parentComment: createParentComment([42, 63]), - type: "enable", - line: 3, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([63, 84]), - type: "enable", - line: 4, - column: 1, - ruleId: null, - justification: "j1" - } - ], - problems: [{ line: 2, column: 1, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: "foo", - line: 2, - column: 1, - suppressions: [{ justification: "j1", kind: "directive" }] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", - fix: { - range: [63, 84], - text: " " - }, - line: 4, - column: 1, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds two problems for /* eslint-enable */ /* eslint-enable */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 21]), - type: "enable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([21, 42]), - type: "enable", - line: 2, - column: 1, - ruleId: null, - justification: "j2" - } - ], - problems: [], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", - line: 1, - column: 1, - fix: { - range: [0, 21], - text: " " - }, - severity: 2, - nodeType: null - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", - line: 2, - column: 1, - fix: { - range: [21, 42], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-enable */ /* eslint-disable */ /* (problem) */ /* eslint-enable */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 21]), - type: "enable", - line: 1, - column: 1, - ruleId: null, - justification: "j1" - }, - { - parentComment: createParentComment([21, 42]), - type: "disable", - line: 2, - column: 1, - ruleId: null, - justification: "j2" - }, - { - parentComment: createParentComment([63, 84]), - type: "enable", - line: 4, - column: 1, - ruleId: null, - justification: "j3" - } - ], - problems: [{ line: 3, column: 1, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", - line: 1, - column: 1, - fix: { - range: [0, 21], - text: " " - }, - severity: 2, - nodeType: null - }, - { - line: 3, - column: 1, - ruleId: "foo", - suppressions: [{ kind: "directive", justification: "j2" }] - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable */ /* eslint-enable foo */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 21]), - type: "disable", - line: 1, - column: 1, - ruleId: "foo", - justification: "j1" - }, - { - parentComment: createParentComment([42, 63]), - type: "enable", - line: 3, - column: 1, - ruleId: null, - justification: "j2" - }, - { - parentComment: createParentComment([63, 84]), - type: "enable", - line: 4, - column: 1, - ruleId: "foo", - justification: "j3" - } - ], - problems: [{ line: 2, column: 1, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - line: 2, - column: 1, - ruleId: "foo", - suppressions: [{ kind: "directive", justification: "j1" }] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", - line: 4, - column: 1, - fix: { - range: [63, 84], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Does not add a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable foo */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { type: "disable", line: 1, column: 1, ruleId: "foo", justification: "j1" }, - { type: "enable", line: 3, column: 1, ruleId: "foo", justification: "j2" } - ], - problems: [{ line: 2, column: 1, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [{ line: 2, column: 1, ruleId: "foo", suppressions: [{ kind: "directive", justification: "j1" }] }] - ); - }); - - it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable foo */ /* eslint-enable */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 21]), - type: "disable", - line: 1, - column: 1, - ruleId: "foo", - justification: "j1" - }, - { - parentComment: createParentComment([42, 63]), - type: "enable", - line: 3, - column: 1, - ruleId: "foo", - justification: "j2" - }, - { - parentComment: createParentComment([63, 84]), - type: "enable", - line: 4, - column: 1, - ruleId: null, - justification: "j3" - } - ], - problems: [{ line: 2, column: 1, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - line: 2, - column: 1, - ruleId: "foo", - suppressions: [ - { kind: "directive", justification: "j1" } - ] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", - line: 4, - column: 1, - fix: { - range: [63, 84], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable bar */ /* (problem from bar) */ /* eslint-enable foo */ /* eslint-enable */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 20]), - type: "disable", - line: 1, - column: 1, - ruleId: "bar", - justification: "j1" - }, - { - parentComment: createParentComment([60, 80]), - type: "enable", - line: 3, - column: 1, - ruleId: "foo", - justification: "j2" - }, - { - parentComment: createParentComment([80, 100]), - type: "enable", - line: 4, - column: 1, - ruleId: null, - justification: "j3" - } - ], - problems: [{ line: 2, column: 1, ruleId: "bar" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - line: 2, - column: 1, - ruleId: "bar", - suppressions: [{ kind: "directive", justification: "j1" }] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", - line: 3, - column: 1, - fix: { - range: [60, 80], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable foo */ /* eslint-enable */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 20]), - type: "disable", - line: 1, - column: 1, - ruleId: "foo", - justification: "j1" - }, - { - parentComment: createParentComment([60, 80]), - type: "enable", - line: 3, - column: 1, - ruleId: "foo", - justification: "j2" - }, - { - parentComment: createParentComment([80, 100]), - type: "enable", - line: 4, - column: 1, - ruleId: "foo", - justification: "j3" - } - ], - problems: [{ line: 2, column: 1, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - line: 2, - column: 1, - ruleId: "foo", - suppressions: [{ kind: "directive", justification: "j1" }] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", - line: 4, - column: 1, - fix: { - range: [80, 100], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds two problems for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable foo */ /* eslint-enable foo */ /* eslint-enable */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 20]), - type: "disable", - line: 1, - column: 1, - ruleId: "foo", - justification: "j1" - }, - { - parentComment: createParentComment([40, 60]), - type: "enable", - line: 3, - column: 1, - ruleId: "foo", - justification: "j2" - }, - { - parentComment: createParentComment([60, 80]), - type: "enable", - line: 4, - column: 1, - ruleId: "foo", - justification: "j3" - }, - { - parentComment: createParentComment([80, 100]), - type: "enable", - line: 5, - column: 1, - ruleId: null, - justification: "j4" - } - ], - problems: [{ line: 2, column: 1, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: "foo", - line: 2, - column: 1, - suppressions: [{ kind: "directive", justification: "j1" }] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", - line: 4, - column: 1, - fix: { - range: [60, 80], - text: " " - }, - severity: 2, - nodeType: null - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", - line: 5, - column: 1, - fix: { - range: [80, 100], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-disable used */ /* (problem from used) */ /* eslint-enable used */ /* eslint-enable */", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 20]), - ruleId: "used", - type: "disable", - line: 1, - column: 1, - justification: "j1" - }, - { - parentComment: createParentComment([40, 60]), - ruleId: "used", - type: "disable", - line: 3, - column: 1, - justification: "j2" - }, - { - parentComment: createParentComment([80, 100]), - ruleId: "used", - type: "enable", - line: 5, - column: 1, - justification: "j3" - }, - { - parentComment: createParentComment([100, 120]), - ruleId: null, - type: "enable", - line: 6, - column: 1, - justification: "j4" - } - ], - problems: [{ line: 2, column: 1, ruleId: "used" }, { line: 4, column: 1, ruleId: "used" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j1" }] - }, - { - line: 4, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j1" }, { kind: "directive", justification: "j2" }] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", - line: 6, - column: 1, - fix: { - range: [100, 120], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for // eslint-disable-line", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 22]), - type: "disable-line", - line: 1, - column: 1, - ruleId: null, - justification: "justification" - }], - problems: [], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 22], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - - it("Does not add a problem for // eslint-disable-line (problem)", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ type: "disable-line", line: 1, column: 1, ruleId: null, justification: "justification" }], - problems: [{ line: 1, column: 10, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [{ line: 1, column: 10, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - - it("Adds a problem for // eslint-disable-next-line", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 27]), - type: "disable-next-line", - line: 1, - column: 2, - ruleId: null, - justification: "justification" - }], - problems: [], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 2, - fix: { - range: [0, 27], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Does not add a problem for // eslint-disable-next-line \\n (problem)", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ type: "disable-next-line", line: 1, column: 1, ruleId: null, justification: "justification" }], - problems: [{ line: 2, column: 10, ruleId: "foo" }], - reportUnusedDisableDirectives: "error" - }), - [{ line: 2, column: 10, ruleId: "foo", suppressions: [{ kind: "directive", justification: "justification" }] }] - ); - }); - - it("adds two problems for /* eslint-disable */ // eslint-disable-line", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { parentComment: createParentComment([0, 20]), type: "disable", line: 1, column: 1, ruleId: null }, - { parentComment: createParentComment([20, 43]), type: "disable-line", line: 1, column: 22, ruleId: null } - ], - problems: [], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 2, - nodeType: null - }, - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 22, - fix: { - range: [20, 43], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Does not add problems when reportUnusedDisableDirectives: \"off\" is used", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [{ - parentComment: createParentComment([0, 27]), - type: "disable-next-line", - line: 1, - column: 1, - ruleId: null, - justification: "justification" - }], - problems: [], - reportUnusedDisableDirectives: "off" - }), - [] - ); - }); - }); - - describe("unused rules within directives", () => { - it("Adds a problem for /* eslint-disable used, unused */", () => { - const parentComment = createParentComment([0, 32], " eslint-disable used, unused ", ["used", "unused"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment, - ruleId: "used", - type: "disable", - line: 1, - column: 18, - justification: "j1" - }, - { - parentComment, - ruleId: "unused", - type: "disable", - line: 1, - column: 22, - justification: "j2" - } - ], - problems: [{ line: 2, column: 1, ruleId: "used" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused').", - line: 1, - column: 22, - fix: { - range: [22, 30], - text: "" - }, - severity: 2, - nodeType: null - }, - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j1" }] - } - ] - ); - }); - it("Adds a problem for /* eslint-disable used , unused , -- unused and used are ok */", () => { - const parentComment = createParentComment([0, 62], " eslint-disable used , unused , -- unused and used are ok ", ["used", "unused"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment, - ruleId: "used", - type: "disable", - line: 1, - column: 18, - justification: "j1" - }, - { - parentComment, - ruleId: "unused", - type: "disable", - line: 1, - column: 24, - justification: "j2" - } - ], - problems: [{ line: 2, column: 1, ruleId: "used" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused').", - line: 1, - column: 24, - fix: { - range: [23, 32], - text: "" - }, - severity: 2, - nodeType: null - }, - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j1" }] - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable unused, used */", () => { - const parentComment = createParentComment([0, 32], " eslint-disable unused, used ", ["unused", "used"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment, - ruleId: "unused", - type: "disable", - line: 1, - column: 18, - justification: "j1" - }, - { - parentComment, - ruleId: "used", - type: "disable", - line: 1, - column: 25, - justification: "j2" - } - ], - problems: [{ line: 2, column: 1, ruleId: "used" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused').", - line: 1, - column: 18, - fix: { - range: [18, 26], - text: "" - }, - severity: 2, - nodeType: null - }, - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j2" }] - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable unused,, ,, used */", () => { - const parentComment = createParentComment([0, 37], " eslint-disable unused,, ,, used ", ["unused", "used"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment, - ruleId: "unused", - type: "disable", - line: 1, - column: 18, - justification: "j1" - }, - { - parentComment, - ruleId: "used", - type: "disable", - line: 1, - column: 29, - justification: "j2" - } - ], - problems: [{ line: 2, column: 1, ruleId: "used" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused').", - line: 1, - column: 18, - fix: { - range: [18, 25], - text: "" - }, - severity: 2, - nodeType: null - }, - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j2" }] - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable unused-1, unused-2, used */", () => { - const parentComment = createParentComment([0, 45], " eslint-disable unused-1, unused-2, used ", ["unused-1", "unused-2", "used"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment, - ruleId: "unused-1", - type: "disable", - line: 1, - column: 18, - justification: "j1" - }, - { - parentComment, - ruleId: "unused-2", - type: "disable", - line: 1, - column: 28, - justification: "j2" - }, - { - parentComment, - ruleId: "used", - type: "disable", - line: 1, - column: 38, - justification: "j3" - } - ], - problems: [{ line: 2, column: 1, ruleId: "used" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused-1').", - line: 1, - column: 18, - fix: { - range: [18, 28], - text: "" - }, - severity: 2, - nodeType: null - }, - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused-2').", - line: 1, - column: 28, - fix: { - range: [26, 36], - text: "" - }, - severity: 2, - nodeType: null - }, - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j3" }] - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable unused-1, unused-2, used, unused-3 */", () => { - const parentComment = createParentComment([0, 55], " eslint-disable unused-1, unused-2, used, unused-3 ", ["unused-1", "unused-2", "used", "unused-3"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment, - ruleId: "unused-1", - type: "disable", - line: 1, - column: 18, - justification: "j1" - }, - { - parentComment, - ruleId: "unused-2", - type: "disable", - line: 1, - column: 28, - justification: "j2" - }, - { - parentComment, - ruleId: "used", - type: "disable", - line: 1, - column: 38, - justification: "j3" - }, - { - parentComment, - ruleId: "unused-3", - type: "disable", - line: 1, - column: 43, - justification: "j4" - } - ], - problems: [{ line: 2, column: 1, ruleId: "used" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused-1').", - line: 1, - column: 18, - fix: { - range: [18, 28], - text: "" - }, - severity: 2, - nodeType: null - }, - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused-2').", - line: 1, - column: 28, - fix: { - range: [26, 36], - text: "" - }, - severity: 2, - nodeType: null - }, - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused-3').", - line: 1, - column: 43, - fix: { - range: [42, 52], - text: "" - }, - severity: 2, - nodeType: null - }, - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j3" }] - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable unused-1, unused-2 */", () => { - const parentComment = createParentComment([0, 39], " eslint-disable unused-1, unused-2 ", ["unused-1", "unused-2"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment, - ruleId: "unused-1", - type: "disable", - line: 1, - column: 18, - justification: "j1" - }, - { - parentComment, - ruleId: "unused-2", - type: "disable", - line: 1, - column: 28, - justification: "j2" - } - ], - problems: [], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused-1' or 'unused-2').", - line: 1, - column: 18, - fix: { - range: [0, 39], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable unused-1, unused-2, unused-3 */", () => { - const parentComment = createParentComment([0, 49], " eslint-disable unused-1, unused-2, unused-3 ", ["unused-1", "unused-2", "unused-3"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment, - ruleId: "unused-1", - type: "disable", - line: 1, - column: 18 - }, - { - parentComment, - ruleId: "unused-2", - type: "disable", - line: 1, - column: 28 - }, - { - parentComment, - ruleId: "unused-3", - type: "disable", - line: 1, - column: 38 - } - ], - problems: [], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'unused-1', 'unused-2', or 'unused-3').", - line: 1, - column: 18, - fix: { - range: [0, 49], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable foo */ \\n (problem from foo and bar) // eslint-disable-line foo, bar", () => { - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), - ruleId: "foo", - type: "disable", - line: 1, - column: 1, - justification: "j1" - }, - { - parentComment: createParentComment([41, 81], " eslint-disable-line foo, bar", ["foo", "bar"]), - ruleId: "foo", - type: "disable-line", - line: 2, - column: 11, - justification: "j2" - }, - { - parentComment: createParentComment([41, 81], " eslint-disable-line foo, bar ", ["foo", "bar"]), - ruleId: "bar", - type: "disable-line", - line: 2, - column: 11, - justification: "j2" - } - ], - problems: [ - { line: 2, column: 1, ruleId: "bar" }, - { line: 2, column: 6, ruleId: "foo" } - ], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: "bar", - line: 2, - column: 1, - suppressions: [{ kind: "directive", justification: "j2" }] - }, - { - ruleId: "foo", - line: 2, - column: 6, - suppressions: [ - { kind: "directive", justification: "j1" }, - { kind: "directive", justification: "j2" } - ] - }, - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'foo').", - line: 2, - column: 11, - fix: { - range: [64, 69], - text: "" - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable used, unused */", () => { - const parentComment = createParentComment([0, 32], " eslint-enable used, unused ", ["used", "unused"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), - ruleId: "used", - type: "disable", - line: 1, - column: 1, - justification: "j1" - }, - { - parentComment, - ruleId: "used", - type: "enable", - line: 3, - column: 1, - justification: "j2" - }, - { - parentComment, - ruleId: "unused", - type: "enable", - line: 4, - column: 1, - justification: "j3" - } - ], - problems: [{ line: 2, column: 1, ruleId: "used" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j1" }] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused').", - line: 4, - column: 1, - fix: { - range: [21, 29], - text: "" - }, - severity: 2, - nodeType: null - } - ] - ); - }); - it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable used , unused , -- unused and used are ok */", () => { - const parentComment = createParentComment([0, 62], " eslint-enable used , unused , -- unused and used are ok ", ["used", "unused"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), - ruleId: "used", - type: "disable", - line: 1, - column: 1, - justification: "j1" - }, - { - parentComment, - ruleId: "used", - type: "enable", - line: 3, - column: 1, - justification: "j2" - }, - { - parentComment, - ruleId: "unused", - type: "enable", - line: 4, - column: 1, - justification: "j3" - } - ], - problems: [{ line: 2, column: 1, ruleId: "used" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j1" }] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused').", - line: 4, - column: 1, - fix: { - range: [22, 31], - text: "" - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable unused, used */", () => { - const parentComment = createParentComment([0, 32], " eslint-enable unused, used ", ["unused", "used"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), - ruleId: "used", - type: "disable", - line: 1, - column: 1, - justification: "j1" - }, - { - parentComment, - ruleId: "unused", - type: "enable", - line: 3, - column: 1, - justification: "j2" - }, - { - parentComment, - ruleId: "used", - type: "enable", - line: 4, - column: 1, - justification: "j3" - } - ], - problems: [{ line: 2, column: 1, ruleId: "used" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j1" }] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused').", - line: 3, - column: 1, - fix: { - range: [17, 25], - text: "" - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable unused,, ,, used */", () => { - const parentComment = createParentComment([0, 37], " eslint-enable unused,, ,, used ", ["unused", "used"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), - ruleId: "used", - type: "disable", - line: 1, - column: 1, - justification: "j1" - }, - { - parentComment, - ruleId: "unused", - type: "enable", - line: 3, - column: 1, - justification: "j2" - }, - { - parentComment, - ruleId: "used", - type: "enable", - line: 4, - column: 1, - justification: "j3" - } - ], - problems: [{ line: 2, column: 1, ruleId: "used" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j1" }] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused').", - line: 3, - column: 1, - fix: { - range: [17, 24], - text: "" - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable unused-1, unused-2, used */", () => { - const parentComment = createParentComment([0, 45], " eslint-enable unused-1, unused-2, used ", ["unused-1", "unused-2", "used"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), - ruleId: "used", - type: "disable", - line: 1, - column: 1, - justification: "j1" - }, - { - parentComment, - ruleId: "unused-1", - type: "enable", - line: 3, - column: 1, - justification: "j2" - }, - { - parentComment, - ruleId: "unused-2", - type: "enable", - line: 4, - column: 1, - justification: "j3" - }, - { - parentComment, - ruleId: "used", - type: "enable", - line: 5, - column: 1, - justification: "j4" - } - ], - problems: [{ line: 2, column: 1, ruleId: "used" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j1" }] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-1').", - line: 3, - column: 1, - fix: { - range: [17, 27], - text: "" - }, - severity: 2, - nodeType: null - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-2').", - line: 4, - column: 1, - fix: { - range: [25, 35], - text: "" - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable unused-1, unused-2, used, unused-3 */", () => { - const parentComment = createParentComment([0, 55], " eslint-enable unused-1, unused-2, used, unused-3 ", ["unused-1", "unused-2", "used", "unused-3"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment: createParentComment([0, 29], " eslint-disable foo ", ["foo"]), - ruleId: "used", - type: "disable", - line: 1, - column: 1, - justification: "j1" - }, - { - parentComment, - ruleId: "unused-1", - type: "enable", - line: 3, - column: 1, - justification: "j2" - }, - { - parentComment, - ruleId: "unused-2", - type: "enable", - line: 4, - column: 1, - justification: "j3" - }, - { - parentComment, - ruleId: "used", - type: "enable", - line: 5, - column: 1, - justification: "j4" - }, - { - parentComment, - ruleId: "unused-3", - type: "enable", - line: 6, - column: 1, - justification: "j5" - } - ], - problems: [{ line: 2, column: 1, ruleId: "used" }], - reportUnusedDisableDirectives: "error" - }), - [ - { - line: 2, - column: 1, - ruleId: "used", - suppressions: [{ kind: "directive", justification: "j1" }] - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-1').", - line: 3, - column: 1, - fix: { - range: [17, 27], - text: "" - }, - severity: 2, - nodeType: null - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-2').", - line: 4, - column: 1, - fix: { - range: [25, 35], - text: "" - }, - severity: 2, - nodeType: null - }, - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-3').", - line: 6, - column: 1, - fix: { - range: [41, 51], - text: "" - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-enable unused-1, unused-2 */", () => { - const parentComment = createParentComment([0, 39], " eslint-enable unused-1, unused-2 ", ["unused-1", "unused-2"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment, - ruleId: "unused-1", - type: "enable", - line: 1, - column: 18, - justification: "j1" - }, - { - parentComment, - ruleId: "unused-2", - type: "enable", - line: 1, - column: 28, - justification: "j2" - } - ], - problems: [], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-1' or 'unused-2').", - line: 1, - column: 18, - fix: { - range: [0, 39], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - - it("Adds a problem for /* eslint-enable unused-1, unused-2, unused-3 */", () => { - const parentComment = createParentComment([0, 49], " eslint-enable unused-1, unused-2, unused-3 ", ["unused-1", "unused-2", "unused-3"]); - - assert.deepStrictEqual( - applyDisableDirectives({ - directives: [ - { - parentComment, - ruleId: "unused-1", - type: "enable", - line: 1, - column: 18 - }, - { - parentComment, - ruleId: "unused-2", - type: "enable", - line: 1, - column: 28 - }, - { - parentComment, - ruleId: "unused-3", - type: "enable", - line: 1, - column: 38 - } - ], - problems: [], - reportUnusedDisableDirectives: "error" - }), - [ - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-1', 'unused-2', or 'unused-3').", - line: 1, - column: 18, - fix: { - range: [0, 49], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - }); - }); + describe("/* eslint-disable */ comments without rules", () => { + it("keeps problems before the comment on the same line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 7]), + type: "disable", + line: 1, + column: 8, + ruleId: null, + justification: "justification", + }, + ], + problems: [{ line: 1, column: 7, ruleId: "foo" }], + }), + [{ line: 1, column: 7, ruleId: "foo" }], + ); + }); + + it("keeps problems on a previous line before the comment", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([21, 27]), + type: "disable", + line: 2, + column: 1, + ruleId: null, + justification: "justification", + }, + ], + problems: [{ line: 1, column: 10, ruleId: "foo" }], + }), + [{ line: 1, column: 10, ruleId: "foo" }], + ); + }); + + it("filters problems at the same location as the comment", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + type: "disable", + line: 1, + column: 8, + ruleId: null, + justification: "justification", + }, + ], + problems: [{ line: 1, column: 8, ruleId: null }], + }), + [ + { + line: 1, + column: 8, + ruleId: null, + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + + it("filters out problems after the comment on the same line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + type: "disable", + line: 1, + column: 8, + ruleId: null, + justification: "justification", + }, + ], + problems: [{ line: 1, column: 10, ruleId: "foo" }], + }), + [ + { + line: 1, + column: 10, + ruleId: "foo", + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + + it("filters out problems on a later line than the comment", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + type: "disable", + line: 1, + column: 8, + ruleId: null, + justification: "justification", + }, + ], + problems: [{ line: 2, column: 3, ruleId: "foo" }], + }), + [ + { + line: 2, + column: 3, + ruleId: "foo", + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + }); + + describe("/* eslint-disable */ comments with rules", () => { + it("filters problems after the comment that have the same ruleId", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + type: "disable", + line: 1, + column: 8, + ruleId: "foo", + justification: "justification", + }, + ], + problems: [{ line: 2, column: 3, ruleId: "foo" }], + }), + [ + { + line: 2, + column: 3, + ruleId: "foo", + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + + it("filters problems in the same location as the comment that have the same ruleId", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + type: "disable", + line: 1, + column: 8, + ruleId: "foo", + justification: "justification", + }, + ], + problems: [{ line: 1, column: 8, ruleId: "foo" }], + }), + [ + { + line: 1, + column: 8, + ruleId: "foo", + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + + it("keeps problems after the comment that have a different ruleId", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([26, 29]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "justification", + }, + ], + problems: [{ line: 2, column: 3, ruleId: "not-foo" }], + }), + [{ line: 2, column: 3, ruleId: "not-foo" }], + ); + }); + + it("keeps problems before the comment that have the same ruleId", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([7, 31]), + type: "disable", + line: 1, + column: 8, + ruleId: "foo", + }, + ], + problems: [{ line: 1, column: 7, ruleId: "foo" }], + }), + [{ line: 1, column: 7, ruleId: "foo" }], + ); + }); + }); + + describe("eslint-enable comments without rules", () => { + it("keeps problems after the eslint-enable comment", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 26]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([27, 45]), + type: "enable", + line: 1, + column: 26, + ruleId: null, + justification: "j2", + }, + ], + problems: [{ line: 1, column: 27, ruleId: "foo" }], + }), + [{ line: 1, column: 27, ruleId: "foo" }], + ); + }); + + it("keeps problems in the same location as the eslint-enable comment", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 25]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([26, 40]), + type: "enable", + line: 1, + column: 26, + ruleId: null, + justification: "j2", + }, + ], + problems: [{ line: 1, column: 26, ruleId: "foo" }], + }), + [{ line: 1, column: 26, ruleId: "foo" }], + ); + }); + + it("filters out problems before the eslint-enable comment", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + type: "enable", + line: 1, + column: 26, + ruleId: null, + justification: "j2", + }, + ], + problems: [{ line: 1, column: 3, ruleId: "foo" }], + }), + [ + { + line: 1, + column: 3, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + ], + ); + }); + + it("filter out problems if disable all then enable foo and then disable foo", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([26, 44]), + type: "enable", + line: 1, + column: 26, + ruleId: "foo", + justification: "j2", + }, + { + parentDirective: createParentDirective([45, 63]), + type: "disable", + line: 2, + column: 1, + ruleId: "foo", + justification: "j3", + }, + ], + problems: [{ line: 3, column: 3, ruleId: "foo" }], + }), + [ + { + line: 3, + column: 3, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j3" }, + ], + }, + ], + ); + }); + + it("filter out problems if disable all then enable foo and then disable all", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([21, 44]), + type: "enable", + line: 1, + column: 26, + ruleId: "foo", + justification: "j2", + }, + { + parentDirective: createParentDirective([45, 63]), + type: "disable", + line: 2, + column: 1, + ruleId: null, + justification: "j3", + }, + ], + problems: [{ line: 3, column: 3, ruleId: "foo" }], + }), + [ + { + line: 3, + column: 3, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j3" }, + ], + }, + ], + ); + }); + + it("keeps problems before the eslint-enable comment if there is no corresponding disable comment", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1", + }, + { + parentDirective: createParentDirective([25, 44]), + type: "enable", + line: 1, + column: 26, + ruleId: null, + justification: "j2", + }, + ], + problems: [{ line: 1, column: 3, ruleId: "not-foo" }], + }), + [{ line: 1, column: 3, ruleId: "not-foo" }], + ); + }); + }); + + describe("eslint-enable comments with rules", () => { + it("keeps problems after the comment that have the same ruleId as the eslint-enable comment", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([21, 44]), + type: "enable", + line: 2, + column: 1, + ruleId: "foo", + justification: "j2", + }, + ], + problems: [{ line: 2, column: 4, ruleId: "foo" }], + }), + [{ line: 2, column: 4, ruleId: "foo" }], + ); + }); + + it("keeps problems in the same location as the comment that have the same ruleId as the eslint-enable comment", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([21, 44]), + type: "enable", + line: 2, + column: 1, + ruleId: "foo", + justification: "j2", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + }), + [{ line: 2, column: 1, ruleId: "foo" }], + ); + }); + + it("filters problems after the comment that have a different ruleId as the eslint-enable comment", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([21, 44]), + type: "enable", + line: 2, + column: 1, + ruleId: "foo", + justification: "j2", + }, + ], + problems: [{ line: 2, column: 4, ruleId: "not-foo" }], + }), + [ + { + line: 2, + column: 4, + ruleId: "not-foo", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + ], + ); + }); + + it("reenables reporting correctly even when followed by another enable comment", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + type: "enable", + line: 1, + column: 22, + ruleId: "foo", + justification: "j2", + }, + { + type: "enable", + line: 1, + column: 46, + ruleId: "bar", + justification: "j3", + }, + ], + problems: [ + { line: 1, column: 10, ruleId: "foo" }, + { line: 1, column: 10, ruleId: "bar" }, + { line: 1, column: 30, ruleId: "foo" }, + { line: 1, column: 30, ruleId: "bar" }, + { line: 1, column: 50, ruleId: "foo" }, + { line: 1, column: 50, ruleId: "bar" }, + ], + }), + [ + { + line: 1, + column: 10, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { + line: 1, + column: 10, + ruleId: "bar", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { line: 1, column: 30, ruleId: "foo" }, + { + line: 1, + column: 30, + ruleId: "bar", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { line: 1, column: 50, ruleId: "foo" }, + { line: 1, column: 50, ruleId: "bar" }, + ], + ); + }); + }); + + describe("eslint-disable-line comments without rules", () => { + it("keeps problems on a previous line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([6, 27]), + type: "disable-line", + line: 2, + column: 1, + ruleId: null, + justification: "justification", + }, + ], + problems: [{ line: 1, column: 5, ruleId: "foo" }], + }), + [{ line: 1, column: 5, ruleId: "foo" }], + ); + }); + + it("filters problems before the comment on the same line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([7, 28]), + type: "disable-line", + line: 1, + column: 8, + ruleId: null, + justification: "justification", + }, + ], + problems: [{ line: 1, column: 1, ruleId: "foo" }], + }), + [ + { + line: 1, + column: 1, + ruleId: "foo", + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + + it("filters problems after the comment on the same line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([7, 28]), + type: "disable-line", + line: 1, + column: 8, + ruleId: null, + justification: "justification", + }, + ], + problems: [{ line: 1, column: 10, ruleId: "foo" }], + }), + [ + { + line: 1, + column: 10, + ruleId: "foo", + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + + it("keeps problems on a following line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([7, 34]), + type: "disable-line", + line: 1, + column: 8, + ruleId: "foo", + justification: "justification", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + }), + [{ line: 2, column: 1, ruleId: "foo" }], + ); + }); + }); + + describe("eslint-disable-line comments with rules", () => { + it("filters problems on the current line that match the ruleId", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([7, 34]), + type: "disable-line", + line: 1, + column: 8, + ruleId: "foo", + justification: "justification", + }, + ], + problems: [{ line: 1, column: 2, ruleId: "foo" }], + }), + [ + { + line: 1, + column: 2, + ruleId: "foo", + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + + it("keeps problems on the current line that do not match the ruleId", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 27]), + type: "disable-line", + line: 1, + column: 1, + ruleId: "foo", + justification: "justification", + }, + ], + problems: [{ line: 1, column: 2, ruleId: "not-foo" }], + }), + [{ line: 1, column: 2, ruleId: "not-foo" }], + ); + }); + + it("filters problems on the current line that do not match the ruleId if preceded by a disable comment", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([24, 28]), + type: "disable-line", + line: 1, + column: 22, + ruleId: "foo", + justification: "j2", + }, + ], + problems: [{ line: 1, column: 5, ruleId: "not-foo" }], + }), + [ + { + line: 1, + column: 5, + ruleId: "not-foo", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + ], + ); + }); + + it("handles consecutive comments appropriately", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([7, 34]), + type: "disable-line", + line: 1, + column: 8, + ruleId: "foo", + justification: "j1", + }, + { + parentDirective: createParentDirective([38, 73]), + type: "disable-line", + line: 2, + column: 8, + ruleId: "foo", + justification: "j2", + }, + { + parentDirective: createParentDirective([76, 111]), + type: "disable-line", + line: 3, + column: 8, + ruleId: "foo", + justification: "j3", + }, + { + parentDirective: createParentDirective([114, 149]), + type: "disable-line", + line: 4, + column: 8, + ruleId: "foo", + justification: "j4", + }, + { + parentDirective: createParentDirective([152, 187]), + type: "disable-line", + line: 5, + column: 8, + ruleId: "foo", + justification: "j5", + }, + { + parentDirective: createParentDirective([190, 225]), + type: "disable-line", + line: 6, + column: 8, + ruleId: "foo", + justification: "j6", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + }), + [ + { + line: 2, + column: 1, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j2" }, + ], + }, + ], + ); + }); + }); + + describe("eslint-disable-next-line comments without rules", () => { + it("filters problems on the next line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 31]), + type: "disable-next-line", + line: 1, + column: 1, + ruleId: null, + justification: "justification", + }, + ], + problems: [{ line: 2, column: 3, ruleId: "foo" }], + }), + [ + { + line: 2, + column: 3, + ruleId: "foo", + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + + it("keeps problems on the same line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 31]), + type: "disable-next-line", + line: 1, + column: 1, + ruleId: null, + }, + ], + problems: [{ line: 1, column: 3, ruleId: "foo" }], + }), + [{ line: 1, column: 3, ruleId: "foo" }], + ); + }); + + it("keeps problems after the next line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 31]), + type: "disable-next-line", + line: 1, + column: 1, + ruleId: null, + justification: "justification", + }, + ], + problems: [{ line: 3, column: 3, ruleId: "foo" }], + }), + [{ line: 3, column: 3, ruleId: "foo" }], + ); + }); + + it("filters problems on the next line even if there is an eslint-enable comment on the same line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 31]), + type: "disable-next-line", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([31, 50]), + type: "enable", + line: 1, + column: 31, + ruleId: null, + justification: "j2", + }, + ], + problems: [{ line: 2, column: 2, ruleId: "foo" }], + }), + [ + { + line: 2, + column: 2, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + ], + ); + }); + }); + + describe("eslint-disable-next-line comments with rules", () => { + it("filters problems on the next line that match the ruleId", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + type: "disable-next-line", + line: 1, + column: 1, + ruleId: "foo", + justification: "justification", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + }), + [ + { + line: 2, + column: 1, + ruleId: "foo", + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + + it("keeps problems on the next line that do not match the ruleId", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 31]), + type: "disable-next-line", + line: 1, + column: 1, + ruleId: "foo", + justification: "justification", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "not-foo" }], + }), + [{ line: 2, column: 1, ruleId: "not-foo" }], + ); + }); + }); + + describe("unrecognized directive types", () => { + it("throws a TypeError when it encounters an unrecognized directive", () => { + assert.throws( + () => + applyDisableDirectives({ + directives: [ + { + type: "foo", + line: 1, + column: 4, + ruleId: "foo", + justification: "justification", + }, + ], + problems: [], + }), + "Unrecognized directive type 'foo'", + ); + }); + }); + + describe("unused directives", () => { + it("Adds a problem for /* eslint-disable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + justification: "justification", + }, + ], + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Does not fix a problem for /* eslint-disable */ when disableFixes is enabled", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + justification: "justification", + }, + ], + disableFixes: true, + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Does not add a problem for /* eslint-disable */ /* (problem) */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "justification", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 1, + ruleId: "foo", + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable foo */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "justification", + }, + ], + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'foo').", + line: 1, + column: 1, + fix: { + range: [0, 21], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable foo */ /* (problem from another rule) */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 24]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "justification", + }, + ], + problems: [{ line: 1, column: 20, ruleId: "not-foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'foo').", + line: 1, + column: 1, + fix: { + range: [0, 24], + text: " ", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: "not-foo", + line: 1, + column: 20, + }, + ], + ); + }); + + it("Adds a problem for /* (problem from foo) */ /* eslint-disable */ /* eslint-enable foo */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "disable", + line: 1, + column: 8, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([0, 21]), + type: "enable", + line: 1, + column: 24, + ruleId: "foo", + justification: "j2", + }, + ], + problems: [{ line: 1, column: 2, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: "foo", + line: 1, + column: 2, + }, + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + fix: { + range: [0, 21], + text: " ", + }, + line: 1, + column: 8, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([21, 41]), + type: "enable", + line: 1, + column: 12, + ruleId: null, + justification: "j2", + }, + ], + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds two problems for /* eslint-disable */ /* eslint-disable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([21, 42]), + type: "disable", + line: 2, + column: 1, + ruleId: null, + justification: "j2", + }, + ], + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 21], + text: " ", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 2, + column: 1, + fix: { + range: [21, 42], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable */ /* eslint-disable */ /* (problem) */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([22, 45]), + type: "disable", + line: 2, + column: 1, + ruleId: null, + justification: "j2", + }, + ], + problems: [{ line: 3, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 21], + text: " ", + }, + severity: 2, + nodeType: null, + }, + { + line: 3, + column: 1, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j1" }, + { kind: "directive", justification: "j2" }, + ], + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable foo */ /* eslint-disable */ /* (problem from foo) */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1", + }, + { + parentDirective: createParentDirective([22, 45]), + type: "disable", + line: 2, + column: 1, + ruleId: null, + justification: "j2", + }, + ], + problems: [{ line: 3, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'foo').", + line: 1, + column: 1, + fix: { + range: [0, 21], + text: " ", + }, + severity: 2, + nodeType: null, + }, + { + line: 3, + column: 1, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j1" }, + { kind: "directive", justification: "j2" }, + ], + }, + ], + ); + }); + + it("Does not add a problem for /* eslint-disable foo */ /* (problem from foo) */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "justification", + }, + ], + problems: [{ line: 1, column: 6, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 1, + column: 6, + ruleId: "foo", + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable */ /* eslint-disable foo */ /* (problem from foo) */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([22, 45]), + type: "disable", + line: 2, + column: 1, + ruleId: "foo", + justification: "j2", + }, + ], + problems: [{ line: 3, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 21], + text: " ", + }, + severity: 2, + nodeType: null, + }, + { + line: 3, + column: 1, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j1" }, + { kind: "directive", justification: "j2" }, + ], + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable */ /* eslint-disable foo */ /* (problem from another rule) */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([21, 45]), + type: "disable", + line: 2, + column: 1, + ruleId: "foo", + justification: "j2", + }, + ], + problems: [{ line: 3, column: 1, ruleId: "bar" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'foo').", + line: 2, + column: 1, + fix: { + range: [21, 45], + text: " ", + }, + severity: 2, + nodeType: null, + }, + { + line: 3, + column: 1, + ruleId: "bar", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable foo */ /* eslint-enable foo */ /* (problem from foo) */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1", + }, + { + parentDirective: createParentDirective([25, 46]), + type: "enable", + line: 1, + column: 26, + ruleId: "foo", + justification: "j2", + }, + ], + problems: [{ line: 1, column: 30, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'foo').", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: "foo", + line: 1, + column: 30, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable foo */ /* eslint-enable */ /* (problem from foo) */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 24]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1", + }, + { + parentDirective: createParentDirective([25, 49]), + type: "enable", + line: 1, + column: 26, + ruleId: null, + justification: "j2", + }, + ], + problems: [{ line: 1, column: 30, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'foo').", + line: 1, + column: 1, + fix: { + range: [0, 24], + text: " ", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: "foo", + line: 1, + column: 30, + }, + ], + ); + }); + + it("Adds two problems for /* eslint-disable */ /* eslint-disable foo */ /* eslint-enable foo */ /* (problem from foo) */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([22, 45]), + type: "disable", + line: 2, + column: 1, + ruleId: "foo", + justification: "j2", + }, + { + parentDirective: createParentDirective([46, 69]), + type: "enable", + line: 3, + column: 1, + ruleId: "foo", + justification: "j3", + }, + ], + problems: [{ line: 4, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 21], + text: " ", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'foo').", + line: 2, + column: 1, + fix: { + range: [22, 45], + text: " ", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: "foo", + line: 4, + column: 1, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "enable", + line: 1, + column: 1, + justification: "justification", + }, + ], + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Does not fix a problem for /* eslint-enable */ when disableFixes is enabled", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "enable", + line: 1, + column: 1, + justification: "justification", + }, + ], + disableFixes: true, + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 1, + column: 1, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Does not add a problem for /* eslint-disable */ /* (problem) */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + type: "disable", + line: 1, + column: 1, + ruleId: null, + justification: "justification", + }, + { + type: "enable", + line: 3, + column: 1, + ruleId: null, + justification: "justification", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 1, + ruleId: "foo", + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + + it("Adds a problem for /* eslint-enable foo */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "enable", + line: 1, + column: 1, + ruleId: "foo", + justification: "justification", + }, + ], + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", + line: 1, + column: 1, + fix: { + range: [0, 21], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable not-foo */ /* (problem from not-foo) */ /* eslint-enable foo */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 24]), + type: "disable", + line: 1, + column: 1, + ruleId: "not-foo", + justification: "justification", + }, + { + parentDirective: createParentDirective([48, 72]), + type: "enable", + line: 3, + column: 1, + ruleId: "foo", + justification: "justification", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "not-foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: "not-foo", + line: 2, + column: 1, + suppressions: [ + { + justification: "justification", + kind: "directive", + }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", + line: 3, + column: 1, + fix: { + range: [48, 72], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable */ /* eslint-enable */ /* eslint-enable foo */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1", + }, + { + parentDirective: createParentDirective([42, 63]), + type: "enable", + line: 3, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([63, 84]), + type: "enable", + line: 4, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([84, 105]), + type: "enable", + line: 5, + column: 1, + ruleId: "foo", + justification: "j2", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: "foo", + line: 2, + column: 1, + suppressions: [ + { justification: "j1", kind: "directive" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found).", + fix: { + range: [63, 84], + text: " ", + }, + line: 4, + column: 1, + severity: 2, + nodeType: null, + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", + fix: { + range: [84, 105], + text: " ", + }, + line: 5, + column: 1, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1", + }, + { + parentDirective: createParentDirective([42, 63]), + type: "enable", + line: 3, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([63, 84]), + type: "enable", + line: 4, + column: 1, + ruleId: null, + justification: "j1", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: "foo", + line: 2, + column: 1, + suppressions: [ + { justification: "j1", kind: "directive" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found).", + fix: { + range: [63, 84], + text: " ", + }, + line: 4, + column: 1, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds two problems for /* eslint-enable */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "enable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([21, 42]), + type: "enable", + line: 2, + column: 1, + ruleId: null, + justification: "j2", + }, + ], + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 1, + column: 1, + fix: { + range: [0, 21], + text: " ", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 2, + column: 1, + fix: { + range: [21, 42], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-enable */ /* eslint-disable */ /* (problem) */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "enable", + line: 1, + column: 1, + ruleId: null, + justification: "j1", + }, + { + parentDirective: createParentDirective([21, 42]), + type: "disable", + line: 2, + column: 1, + ruleId: null, + justification: "j2", + }, + { + parentDirective: createParentDirective([63, 84]), + type: "enable", + line: 4, + column: 1, + ruleId: null, + justification: "j3", + }, + ], + problems: [{ line: 3, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 1, + column: 1, + fix: { + range: [0, 21], + text: " ", + }, + severity: 2, + nodeType: null, + }, + { + line: 3, + column: 1, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j2" }, + ], + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable */ /* eslint-enable foo */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1", + }, + { + parentDirective: createParentDirective([42, 63]), + type: "enable", + line: 3, + column: 1, + ruleId: null, + justification: "j2", + }, + { + parentDirective: createParentDirective([63, 84]), + type: "enable", + line: 4, + column: 1, + ruleId: "foo", + justification: "j3", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 1, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", + line: 4, + column: 1, + fix: { + range: [63, 84], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Does not add a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable foo */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1", + }, + { + type: "enable", + line: 3, + column: 1, + ruleId: "foo", + justification: "j2", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 1, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable foo */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 21]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1", + }, + { + parentDirective: createParentDirective([42, 63]), + type: "enable", + line: 3, + column: 1, + ruleId: "foo", + justification: "j2", + }, + { + parentDirective: createParentDirective([63, 84]), + type: "enable", + line: 4, + column: 1, + ruleId: null, + justification: "j3", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 1, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 4, + column: 1, + fix: { + range: [63, 84], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable bar */ /* (problem from bar) */ /* eslint-enable foo */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: "bar", + justification: "j1", + }, + { + parentDirective: createParentDirective([60, 80]), + type: "enable", + line: 3, + column: 1, + ruleId: "foo", + justification: "j2", + }, + { + parentDirective: createParentDirective([80, 100]), + type: "enable", + line: 4, + column: 1, + ruleId: null, + justification: "j3", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "bar" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 1, + ruleId: "bar", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", + line: 3, + column: 1, + fix: { + range: [60, 80], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable foo */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1", + }, + { + parentDirective: createParentDirective([60, 80]), + type: "enable", + line: 3, + column: 1, + ruleId: "foo", + justification: "j2", + }, + { + parentDirective: createParentDirective([80, 100]), + type: "enable", + line: 4, + column: 1, + ruleId: "foo", + justification: "j3", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 1, + ruleId: "foo", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", + line: 4, + column: 1, + fix: { + range: [80, 100], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds two problems for /* eslint-disable foo */ /* (problem from foo) */ /* eslint-enable foo */ /* eslint-enable foo */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: "foo", + justification: "j1", + }, + { + parentDirective: createParentDirective([40, 60]), + type: "enable", + line: 3, + column: 1, + ruleId: "foo", + justification: "j2", + }, + { + parentDirective: createParentDirective([60, 80]), + type: "enable", + line: 4, + column: 1, + ruleId: "foo", + justification: "j3", + }, + { + parentDirective: createParentDirective([80, 100]), + type: "enable", + line: 5, + column: 1, + ruleId: null, + justification: "j4", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: "foo", + line: 2, + column: 1, + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'foo').", + line: 4, + column: 1, + fix: { + range: [60, 80], + text: " ", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 5, + column: 1, + fix: { + range: [80, 100], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-disable used */ /* (problem from used) */ /* eslint-enable used */ /* eslint-enable */", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + ruleId: "used", + type: "disable", + line: 1, + column: 1, + justification: "j1", + }, + { + parentDirective: createParentDirective([40, 60]), + ruleId: "used", + type: "disable", + line: 3, + column: 1, + justification: "j2", + }, + { + parentDirective: createParentDirective([80, 100]), + ruleId: "used", + type: "enable", + line: 5, + column: 1, + justification: "j3", + }, + { + parentDirective: createParentDirective([100, 120]), + ruleId: null, + type: "enable", + line: 6, + column: 1, + justification: "j4", + }, + ], + problems: [ + { line: 2, column: 1, ruleId: "used" }, + { line: 4, column: 1, ruleId: "used" }, + ], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { + line: 4, + column: 1, + ruleId: "used", + suppressions: [ + { kind: "directive", justification: "j1" }, + { kind: "directive", justification: "j2" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 6, + column: 1, + fix: { + range: [100, 120], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for // eslint-disable-line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 22]), + type: "disable-line", + line: 1, + column: 1, + ruleId: null, + justification: "justification", + }, + ], + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 22], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Does not add a problem for // eslint-disable-line (problem)", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + type: "disable-line", + line: 1, + column: 1, + ruleId: null, + justification: "justification", + }, + ], + problems: [{ line: 1, column: 10, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 1, + column: 10, + ruleId: "foo", + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + + it("Adds a problem for // eslint-disable-next-line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 27]), + type: "disable-next-line", + line: 1, + column: 2, + ruleId: null, + justification: "justification", + }, + ], + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 2, + fix: { + range: [0, 27], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Does not add a problem for // eslint-disable-next-line \\n (problem)", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + type: "disable-next-line", + line: 1, + column: 1, + ruleId: null, + justification: "justification", + }, + ], + problems: [{ line: 2, column: 10, ruleId: "foo" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 10, + ruleId: "foo", + suppressions: [ + { + kind: "directive", + justification: "justification", + }, + ], + }, + ], + ); + }); + + it("adds two problems for /* eslint-disable */ // eslint-disable-line", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 20]), + type: "disable", + line: 1, + column: 1, + ruleId: null, + }, + { + parentDirective: createParentDirective([20, 43]), + type: "disable-line", + line: 1, + column: 22, + ruleId: null, + }, + ], + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 22, + fix: { + range: [20, 43], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it('Does not add problems when reportUnusedDisableDirectives: "off" is used', () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective: createParentDirective([0, 27]), + type: "disable-next-line", + line: 1, + column: 1, + ruleId: null, + justification: "justification", + }, + ], + problems: [], + reportUnusedDisableDirectives: "off", + }), + [], + ); + }); + }); + + describe("unused rules within directives", () => { + it("Adds a problem for /* eslint-disable used, unused */", () => { + const parentDirective = createParentDirective( + [0, 32], + "used, unused", + ["used", "unused"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable used, unused */", + }, + directives: [ + { + parentDirective, + ruleId: "used", + type: "disable", + line: 1, + column: 18, + justification: "j1", + }, + { + parentDirective, + ruleId: "unused", + type: "disable", + line: 1, + column: 22, + justification: "j2", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'unused').", + line: 1, + column: 22, + fix: { + range: [22, 30], + text: "", + }, + severity: 2, + nodeType: null, + }, + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + ], + ); + }); + it("Adds a problem for /* eslint-disable used , unused , -- unused and used are ok */", () => { + const parentDirective = createParentDirective( + [0, 62], + "used , unused ,", + ["used", "unused"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable used , unused , -- unused and used are ok */", + }, + directives: [ + { + parentDirective, + ruleId: "used", + type: "disable", + line: 1, + column: 18, + justification: "j1", + }, + { + parentDirective, + ruleId: "unused", + type: "disable", + line: 1, + column: 24, + justification: "j2", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'unused').", + line: 1, + column: 24, + fix: { + range: [23, 32], + text: "", + }, + severity: 2, + nodeType: null, + }, + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable unused, used */", () => { + const parentDirective = createParentDirective( + [0, 32], + "unused, used", + ["unused", "used"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable unused, used */", + }, + directives: [ + { + parentDirective, + ruleId: "unused", + type: "disable", + line: 1, + column: 18, + justification: "j1", + }, + { + parentDirective, + ruleId: "used", + type: "disable", + line: 1, + column: 25, + justification: "j2", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'unused').", + line: 1, + column: 18, + fix: { + range: [18, 26], + text: "", + }, + severity: 2, + nodeType: null, + }, + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [ + { kind: "directive", justification: "j2" }, + ], + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable unused,, ,, used */", () => { + const parentDirective = createParentDirective( + [0, 37], + " unused,, ,, used ", + ["unused", "used"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable unused,, ,, used */", + }, + directives: [ + { + parentDirective, + ruleId: "unused", + type: "disable", + line: 1, + column: 18, + justification: "j1", + }, + { + parentDirective, + ruleId: "used", + type: "disable", + line: 1, + column: 29, + justification: "j2", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'unused').", + line: 1, + column: 18, + fix: { + range: [18, 25], + text: "", + }, + severity: 2, + nodeType: null, + }, + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [ + { kind: "directive", justification: "j2" }, + ], + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable unused-1, unused-2, used */", () => { + const parentDirective = createParentDirective( + [0, 45], + "unused-1, unused-2, used", + ["unused-1", "unused-2", "used"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable unused-1, unused-2, used */", + }, + directives: [ + { + parentDirective, + ruleId: "unused-1", + type: "disable", + line: 1, + column: 18, + justification: "j1", + }, + { + parentDirective, + ruleId: "unused-2", + type: "disable", + line: 1, + column: 28, + justification: "j2", + }, + { + parentDirective, + ruleId: "used", + type: "disable", + line: 1, + column: 38, + justification: "j3", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'unused-1').", + line: 1, + column: 18, + fix: { + range: [18, 28], + text: "", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'unused-2').", + line: 1, + column: 28, + fix: { + range: [26, 36], + text: "", + }, + severity: 2, + nodeType: null, + }, + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [ + { kind: "directive", justification: "j3" }, + ], + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable unused-1, unused-2, used, unused-3 */", () => { + const parentDirective = createParentDirective( + [0, 55], + "unused-1, unused-2, used, unused-3", + ["unused-1", "unused-2", "used", "unused-3"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable unused-1, unused-2, used, unused-3 */", + }, + directives: [ + { + parentDirective, + ruleId: "unused-1", + type: "disable", + line: 1, + column: 18, + justification: "j1", + }, + { + parentDirective, + ruleId: "unused-2", + type: "disable", + line: 1, + column: 28, + justification: "j2", + }, + { + parentDirective, + ruleId: "used", + type: "disable", + line: 1, + column: 38, + justification: "j3", + }, + { + parentDirective, + ruleId: "unused-3", + type: "disable", + line: 1, + column: 43, + justification: "j4", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'unused-1').", + line: 1, + column: 18, + fix: { + range: [18, 28], + text: "", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'unused-2').", + line: 1, + column: 28, + fix: { + range: [26, 36], + text: "", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'unused-3').", + line: 1, + column: 43, + fix: { + range: [42, 52], + text: "", + }, + severity: 2, + nodeType: null, + }, + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [ + { kind: "directive", justification: "j3" }, + ], + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable unused-1, unused-2 */", () => { + const parentDirective = createParentDirective( + [0, 39], + "unused-1, unused-2", + ["unused-1", "unused-2"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable unused-1, unused-2 */", + }, + directives: [ + { + parentDirective, + ruleId: "unused-1", + type: "disable", + line: 1, + column: 18, + justification: "j1", + }, + { + parentDirective, + ruleId: "unused-2", + type: "disable", + line: 1, + column: 28, + justification: "j2", + }, + ], + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'unused-1' or 'unused-2').", + line: 1, + column: 18, + fix: { + range: [0, 39], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable unused-1, unused-2, unused-3 */", () => { + const parentDirective = createParentDirective( + [0, 49], + "unused-1, unused-2, unused-3", + ["unused-1", "unused-2", "unused-3"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable unused-1, unused-2, unused-3 */", + }, + directives: [ + { + parentDirective, + ruleId: "unused-1", + type: "disable", + line: 1, + column: 18, + }, + { + parentDirective, + ruleId: "unused-2", + type: "disable", + line: 1, + column: 28, + }, + { + parentDirective, + ruleId: "unused-3", + type: "disable", + line: 1, + column: 38, + }, + ], + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'unused-1', 'unused-2', or 'unused-3').", + line: 1, + column: 18, + fix: { + range: [0, 49], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable foo */ \\n (problem from foo and bar) // eslint-disable-line foo, bar", () => { + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable foo */ \n (problem) // eslint-disable-line foo, bar", + }, + directives: [ + { + parentDirective: createParentDirective( + [0, 29], + "foo", + ["foo"], + ), + ruleId: "foo", + type: "disable", + line: 1, + column: 1, + justification: "j1", + }, + { + parentDirective: createParentDirective( + [41, 81], + "foo, bar", + ["foo", "bar"], + ), + ruleId: "foo", + type: "disable-line", + line: 2, + column: 11, + justification: "j2", + }, + { + parentDirective: createParentDirective( + [41, 81], + "foo, bar", + ["foo", "bar"], + ), + ruleId: "bar", + type: "disable-line", + line: 2, + column: 11, + justification: "j2", + }, + ], + problems: [ + { line: 2, column: 1, ruleId: "bar" }, + { line: 2, column: 6, ruleId: "foo" }, + ], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: "bar", + line: 2, + column: 1, + suppressions: [ + { kind: "directive", justification: "j2" }, + ], + }, + { + ruleId: "foo", + line: 2, + column: 6, + suppressions: [ + { kind: "directive", justification: "j1" }, + { kind: "directive", justification: "j2" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'foo').", + line: 2, + column: 11, + fix: { + range: [64, 69], + text: "", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable used, unused */", () => { + const parentDirective = createParentDirective( + [52, 84], + "used, unused", + ["used", "unused"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable used */\n/* (problem from used) */\n/* eslint-enable used, unused */", + }, + directives: [ + { + parentDirective: createParentDirective( + [0, 29], + "used", + ["used"], + ), + ruleId: "used", + type: "disable", + line: 1, + column: 1, + justification: "j1", + }, + { + parentDirective, + ruleId: "used", + type: "enable", + line: 3, + column: 1, + justification: "j2", + }, + { + parentDirective, + ruleId: "unused", + type: "enable", + line: 3, + column: 1, + justification: "j3", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused').", + line: 3, + column: 1, + fix: { + range: [73, 81], + text: "", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable used , unused , -- unused and used are ok */", () => { + const parentDirective = createParentDirective( + [52, 113], + " eslint-enable used , unused , -- unused and used are ok ", + ["used", "unused"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable used */\n/* (problem from used) */\n/* eslint-enable used , unused , -- unused and used are ok */", + }, + directives: [ + { + parentDirective: createParentDirective( + [0, 29], + "used", + ["used"], + ), + ruleId: "used", + type: "disable", + line: 1, + column: 1, + justification: "j1", + }, + { + parentDirective, + ruleId: "used", + type: "enable", + line: 3, + column: 1, + justification: "j2", + }, + { + parentDirective, + ruleId: "unused", + type: "enable", + line: 3, + column: 1, + justification: "j3", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused').", + line: 3, + column: 1, + fix: { + range: [74, 83], + text: "", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable unused, used */", () => { + const parentDirective = createParentDirective( + [52, 84], + "unused, used", + ["unused", "used"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable used */\n/* (problem from used) */\n/* eslint-enable unused, used */", + }, + directives: [ + { + parentDirective: createParentDirective( + [0, 29], + "used", + ["used"], + ), + ruleId: "used", + type: "disable", + line: 1, + column: 1, + justification: "j1", + }, + { + parentDirective, + ruleId: "unused", + type: "enable", + line: 3, + column: 1, + justification: "j2", + }, + { + parentDirective, + ruleId: "used", + type: "enable", + line: 3, + column: 1, + justification: "j3", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused').", + line: 3, + column: 1, + fix: { + range: [69, 77], + text: "", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable unused,, ,, used */", () => { + const parentDirective = createParentDirective( + [52, 88], + "unused,, ,, used", + ["unused", "used"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable used */\n/* (problem from used) */\n/* eslint-enable unused,, ,, used */", + }, + directives: [ + { + parentDirective: createParentDirective( + [0, 29], + "used", + ["used"], + ), + ruleId: "used", + type: "disable", + line: 1, + column: 1, + justification: "j1", + }, + { + parentDirective, + ruleId: "unused", + type: "enable", + line: 3, + column: 1, + justification: "j2", + }, + { + parentDirective, + ruleId: "used", + type: "enable", + line: 3, + column: 1, + justification: "j3", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused').", + line: 3, + column: 1, + fix: { + range: [69, 76], + text: "", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable unused-1, unused-2, used */", () => { + const parentDirective = createParentDirective( + [52, 96], + "unused-1, unused-2, used", + ["unused-1", "unused-2", "used"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable used */\n/* (problem from used) */\n/* eslint-enable unused-1, unused-2, used */", + }, + directives: [ + { + parentDirective: createParentDirective( + [0, 29], + "used", + ["used"], + ), + ruleId: "used", + type: "disable", + line: 1, + column: 1, + justification: "j1", + }, + { + parentDirective, + ruleId: "unused-1", + type: "enable", + line: 3, + column: 1, + justification: "j2", + }, + { + parentDirective, + ruleId: "unused-2", + type: "enable", + line: 3, + column: 1, + justification: "j3", + }, + { + parentDirective, + ruleId: "used", + type: "enable", + line: 3, + column: 1, + justification: "j4", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-1').", + line: 3, + column: 1, + fix: { + range: [69, 79], + text: "", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-2').", + line: 3, + column: 1, + fix: { + range: [77, 87], + text: "", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-disable used */ /* (problem from used) */ /* eslint-enable unused-1, unused-2, used, unused-3 */", () => { + const parentDirective = createParentDirective( + [52, 106], + "unused-1, unused-2, used, unused-3", + ["unused-1", "unused-2", "used", "unused-3"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode: { + ...sourceCode, + text: "/* eslint-disable used */\n/* (problem from used) */\n/* eslint-enable unused-1, unused-2, used, unused-3 */", + }, + directives: [ + { + parentDirective: createParentDirective( + [0, 29], + "used", + ["used"], + ), + ruleId: "used", + type: "disable", + line: 1, + column: 1, + justification: "j1", + }, + { + parentDirective, + ruleId: "unused-1", + type: "enable", + line: 3, + column: 1, + justification: "j2", + }, + { + parentDirective, + ruleId: "unused-2", + type: "enable", + line: 3, + column: 1, + justification: "j3", + }, + { + parentDirective, + ruleId: "used", + type: "enable", + line: 3, + column: 1, + justification: "j4", + }, + { + parentDirective, + ruleId: "unused-3", + type: "enable", + line: 3, + column: 1, + justification: "j5", + }, + ], + problems: [{ line: 2, column: 1, ruleId: "used" }], + reportUnusedDisableDirectives: "error", + }), + [ + { + line: 2, + column: 1, + ruleId: "used", + suppressions: [ + { kind: "directive", justification: "j1" }, + ], + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-1').", + line: 3, + column: 1, + fix: { + range: [69, 79], + text: "", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-2').", + line: 3, + column: 1, + fix: { + range: [77, 87], + text: "", + }, + severity: 2, + nodeType: null, + }, + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-3').", + line: 3, + column: 1, + fix: { + range: [93, 103], + text: "", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-enable unused-1, unused-2 */", () => { + const parentDirective = createParentDirective( + [0, 39], + " eslint-enable unused-1, unused-2 ", + ["unused-1", "unused-2"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective, + ruleId: "unused-1", + type: "enable", + line: 1, + column: 18, + justification: "j1", + }, + { + parentDirective, + ruleId: "unused-2", + type: "enable", + line: 1, + column: 28, + justification: "j2", + }, + ], + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-1' or 'unused-2').", + line: 1, + column: 18, + fix: { + range: [0, 39], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + + it("Adds a problem for /* eslint-enable unused-1, unused-2, unused-3 */", () => { + const parentDirective = createParentDirective( + [0, 49], + " eslint-enable unused-1, unused-2, unused-3 ", + ["unused-1", "unused-2", "unused-3"], + ); + + assert.deepStrictEqual( + applyDisableDirectives({ + language: jslang, + sourceCode, + directives: [ + { + parentDirective, + ruleId: "unused-1", + type: "enable", + line: 1, + column: 18, + }, + { + parentDirective, + ruleId: "unused-2", + type: "enable", + line: 1, + column: 28, + }, + { + parentDirective, + ruleId: "unused-3", + type: "enable", + line: 1, + column: 38, + }, + ], + problems: [], + reportUnusedDisableDirectives: "error", + }), + [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'unused-1', 'unused-2', or 'unused-3').", + line: 1, + column: 18, + fix: { + range: [0, 49], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ], + ); + }); + }); }); diff --git a/tests/lib/linter/code-path-analysis/code-path-analyzer.js b/tests/lib/linter/code-path-analysis/code-path-analyzer.js index dbed9b46194f..ba8a489be651 100644 --- a/tests/lib/linter/code-path-analysis/code-path-analyzer.js +++ b/tests/lib/linter/code-path-analysis/code-path-analyzer.js @@ -9,27 +9,20 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"), - fs = require("fs"), - path = require("path"), - vk = require("eslint-visitor-keys"), - { Linter } = require("../../../../lib/linter"), - EventGeneratorTester = require("../../../../tools/internal-testers/event-generator-tester"), - createEmitter = require("../../../../lib/linter/safe-emitter"), - debug = require("../../../../lib/linter/code-path-analysis/debug-helpers"), - CodePath = require("../../../../lib/linter/code-path-analysis/code-path"), - CodePathAnalyzer = require("../../../../lib/linter/code-path-analysis/code-path-analyzer"), - CodePathSegment = require("../../../../lib/linter/code-path-analysis/code-path-segment"), - NodeEventGenerator = require("../../../../lib/linter/node-event-generator"), - Traverser = require("../../../lib/shared/traverser"); +const assert = require("node:assert"), + fs = require("node:fs"), + path = require("node:path"), + { Linter } = require("../../../../lib/linter"), + debug = require("../../../../lib/linter/code-path-analysis/debug-helpers"), + CodePath = require("../../../../lib/linter/code-path-analysis/code-path"), + CodePathSegment = require("../../../../lib/linter/code-path-analysis/code-path-segment"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const STANDARD_ESQUERY_OPTION = { visitorKeys: vk.KEYS, fallback: Traverser.getKeys }; - const expectedPattern = /\/\*expected\s+((?:.|[\r\n])+?)\s*\*\//gu; +const languageOptionsPattern = /\/\*languageOptions\s+((?:.|[\r\n])+?)\s*\*\//u; const lineEndingPattern = /\r?\n/gu; const linter = new Linter(); @@ -40,16 +33,32 @@ const linter = new Linter(); * @returns {string[]} DOT arrows. */ function getExpectedDotArrows(source) { - expectedPattern.lastIndex = 0; + expectedPattern.lastIndex = 0; + + const retv = []; + let m; + + while ((m = expectedPattern.exec(source)) !== null) { + retv.push(m[1].replace(lineEndingPattern, "\n")); + } + + return retv; +} - const retv = []; - let m; +/** + * Extracts the content of a `/*languageOptions` comment from a given source code + * and parses it as JSON. + * @param {string} source A source code text. + * @returns {Object} languageOptions configuration for linting the source code text. + */ +function getLanguageOptions(source) { + const match = languageOptionsPattern.exec(source); - while ((m = expectedPattern.exec(source)) !== null) { - retv.push(m[1].replace(lineEndingPattern, "\n")); - } + if (match) { + return JSON.parse(match[1]); + } - return retv; + return {}; } //------------------------------------------------------------------------------ @@ -57,705 +66,1004 @@ function getExpectedDotArrows(source) { //------------------------------------------------------------------------------ describe("CodePathAnalyzer", () => { - EventGeneratorTester.testEventGeneratorInterface( - new CodePathAnalyzer(new NodeEventGenerator(createEmitter(), STANDARD_ESQUERY_OPTION)) - ); - - describe("interface of code paths", () => { - let actual = []; - - beforeEach(() => { - actual = []; - linter.defineRule("test", { - create: () => ({ - onCodePathStart(codePath) { - actual.push(codePath); - } - }) - }); - linter.verify( - "function foo(a) { if (a) return 0; else throw new Error(); }", - { rules: { test: 2 } } - ); - }); - - it("should have `id` as unique string", () => { - assert(typeof actual[0].id === "string"); - assert(typeof actual[1].id === "string"); - assert(actual[0].id !== actual[1].id); - }); - - it("should have `upper` as CodePath", () => { - assert(actual[0].upper === null); - assert(actual[1].upper === actual[0]); - }); - - it("should have `childCodePaths` as CodePath[]", () => { - assert(Array.isArray(actual[0].childCodePaths)); - assert(Array.isArray(actual[1].childCodePaths)); - assert(actual[0].childCodePaths.length === 1); - assert(actual[1].childCodePaths.length === 0); - assert(actual[0].childCodePaths[0] === actual[1]); - }); - - it("should have `initialSegment` as CodePathSegment", () => { - assert(actual[0].initialSegment instanceof CodePathSegment); - assert(actual[1].initialSegment instanceof CodePathSegment); - assert(actual[0].initialSegment.prevSegments.length === 0); - assert(actual[1].initialSegment.prevSegments.length === 0); - }); - - it("should have `finalSegments` as CodePathSegment[]", () => { - assert(Array.isArray(actual[0].finalSegments)); - assert(Array.isArray(actual[1].finalSegments)); - assert(actual[0].finalSegments.length === 1); - assert(actual[1].finalSegments.length === 2); - assert(actual[0].finalSegments[0].nextSegments.length === 0); - assert(actual[1].finalSegments[0].nextSegments.length === 0); - assert(actual[1].finalSegments[1].nextSegments.length === 0); - - // finalSegments should include returnedSegments and thrownSegments. - assert(actual[0].finalSegments[0] === actual[0].returnedSegments[0]); - assert(actual[1].finalSegments[0] === actual[1].returnedSegments[0]); - assert(actual[1].finalSegments[1] === actual[1].thrownSegments[0]); - }); - - it("should have `returnedSegments` as CodePathSegment[]", () => { - assert(Array.isArray(actual[0].returnedSegments)); - assert(Array.isArray(actual[1].returnedSegments)); - assert(actual[0].returnedSegments.length === 1); - assert(actual[1].returnedSegments.length === 1); - assert(actual[0].returnedSegments[0] instanceof CodePathSegment); - assert(actual[1].returnedSegments[0] instanceof CodePathSegment); - }); - - it("should have `thrownSegments` as CodePathSegment[]", () => { - assert(Array.isArray(actual[0].thrownSegments)); - assert(Array.isArray(actual[1].thrownSegments)); - assert(actual[0].thrownSegments.length === 0); - assert(actual[1].thrownSegments.length === 1); - assert(actual[1].thrownSegments[0] instanceof CodePathSegment); - }); - - it("should have `currentSegments` as CodePathSegment[]", () => { - assert(Array.isArray(actual[0].currentSegments)); - assert(Array.isArray(actual[1].currentSegments)); - assert(actual[0].currentSegments.length === 0); - assert(actual[1].currentSegments.length === 0); - - // there is the current segment in progress. - linter.defineRule("test", { - create() { - let codePath = null; - - return { - onCodePathStart(cp) { - codePath = cp; - }, - ReturnStatement() { - assert(codePath.currentSegments.length === 1); - assert(codePath.currentSegments[0] instanceof CodePathSegment); - }, - ThrowStatement() { - assert(codePath.currentSegments.length === 1); - assert(codePath.currentSegments[0] instanceof CodePathSegment); - } - }; - } - }); - linter.verify( - "function foo(a) { if (a) return 0; else throw new Error(); }", - { rules: { test: 2 } } - ); - }); - }); - - describe("interface of code path segments", () => { - let actual = []; - - beforeEach(() => { - actual = []; - linter.defineRule("test", { - create: () => ({ - onCodePathSegmentStart(segment) { - actual.push(segment); - } - }) - }); - linter.verify( - "function foo(a) { if (a) return 0; else throw new Error(); }", - { rules: { test: 2 } } - ); - }); - - it("should have `id` as unique string", () => { - assert(typeof actual[0].id === "string"); - assert(typeof actual[1].id === "string"); - assert(typeof actual[2].id === "string"); - assert(typeof actual[3].id === "string"); - assert(actual[0].id !== actual[1].id); - assert(actual[0].id !== actual[2].id); - assert(actual[0].id !== actual[3].id); - assert(actual[1].id !== actual[2].id); - assert(actual[1].id !== actual[3].id); - assert(actual[2].id !== actual[3].id); - }); - - it("should have `nextSegments` as CodePathSegment[]", () => { - assert(Array.isArray(actual[0].nextSegments)); - assert(Array.isArray(actual[1].nextSegments)); - assert(Array.isArray(actual[2].nextSegments)); - assert(Array.isArray(actual[3].nextSegments)); - assert(actual[0].nextSegments.length === 0); - assert(actual[1].nextSegments.length === 2); - assert(actual[2].nextSegments.length === 0); - assert(actual[3].nextSegments.length === 0); - assert(actual[1].nextSegments[0] === actual[2]); - assert(actual[1].nextSegments[1] === actual[3]); - }); - - it("should have `allNextSegments` as CodePathSegment[]", () => { - assert(Array.isArray(actual[0].allNextSegments)); - assert(Array.isArray(actual[1].allNextSegments)); - assert(Array.isArray(actual[2].allNextSegments)); - assert(Array.isArray(actual[3].allNextSegments)); - assert(actual[0].allNextSegments.length === 0); - assert(actual[1].allNextSegments.length === 2); - assert(actual[2].allNextSegments.length === 1); - assert(actual[3].allNextSegments.length === 1); - assert(actual[2].allNextSegments[0].reachable === false); - assert(actual[3].allNextSegments[0].reachable === false); - }); - - it("should have `prevSegments` as CodePathSegment[]", () => { - assert(Array.isArray(actual[0].prevSegments)); - assert(Array.isArray(actual[1].prevSegments)); - assert(Array.isArray(actual[2].prevSegments)); - assert(Array.isArray(actual[3].prevSegments)); - assert(actual[0].prevSegments.length === 0); - assert(actual[1].prevSegments.length === 0); - assert(actual[2].prevSegments.length === 1); - assert(actual[3].prevSegments.length === 1); - assert(actual[2].prevSegments[0] === actual[1]); - assert(actual[3].prevSegments[0] === actual[1]); - }); - - it("should have `allPrevSegments` as CodePathSegment[]", () => { - assert(Array.isArray(actual[0].allPrevSegments)); - assert(Array.isArray(actual[1].allPrevSegments)); - assert(Array.isArray(actual[2].allPrevSegments)); - assert(Array.isArray(actual[3].allPrevSegments)); - assert(actual[0].allPrevSegments.length === 0); - assert(actual[1].allPrevSegments.length === 0); - assert(actual[2].allPrevSegments.length === 1); - assert(actual[3].allPrevSegments.length === 1); - }); - - it("should have `reachable` as boolean", () => { - assert(actual[0].reachable === true); - assert(actual[1].reachable === true); - assert(actual[2].reachable === true); - assert(actual[3].reachable === true); - }); - }); - - describe("onCodePathStart", () => { - it("should be fired at the head of programs/functions", () => { - let count = 0; - let lastCodePathNodeType = null; - - linter.defineRule("test", { - create: () => ({ - onCodePathStart(cp, node) { - count += 1; - lastCodePathNodeType = node.type; - - assert(cp instanceof CodePath); - if (count === 1) { - assert(node.type === "Program"); - } else if (count === 2) { - assert(node.type === "FunctionDeclaration"); - } else if (count === 3) { - assert(node.type === "FunctionExpression"); - } else if (count === 4) { - assert(node.type === "ArrowFunctionExpression"); - } - }, - Program() { - assert(lastCodePathNodeType === "Program"); - }, - FunctionDeclaration() { - assert(lastCodePathNodeType === "FunctionDeclaration"); - }, - FunctionExpression() { - assert(lastCodePathNodeType === "FunctionExpression"); - }, - ArrowFunctionExpression() { - assert(lastCodePathNodeType === "ArrowFunctionExpression"); - } - }) - }); - linter.verify( - "foo(); function foo() {} var foo = function() {}; var foo = () => {};", - { rules: { test: 2 }, env: { es6: true } } - ); - - assert(count === 4); - }); - }); - - describe("onCodePathEnd", () => { - it("should be fired at the end of programs/functions", () => { - let count = 0; - let lastNodeType = null; - - linter.defineRule("test", { - create: () => ({ - onCodePathEnd(cp, node) { - count += 1; - - assert(cp instanceof CodePath); - if (count === 4) { - assert(node.type === "Program"); - } else if (count === 1) { - assert(node.type === "FunctionDeclaration"); - } else if (count === 2) { - assert(node.type === "FunctionExpression"); - } else if (count === 3) { - assert(node.type === "ArrowFunctionExpression"); - } - assert(node.type === lastNodeType); - }, - "Program:exit"() { - lastNodeType = "Program"; - }, - "FunctionDeclaration:exit"() { - lastNodeType = "FunctionDeclaration"; - }, - "FunctionExpression:exit"() { - lastNodeType = "FunctionExpression"; - }, - "ArrowFunctionExpression:exit"() { - lastNodeType = "ArrowFunctionExpression"; - } - }) - }); - linter.verify( - "foo(); function foo() {} var foo = function() {}; var foo = () => {};", - { rules: { test: 2 }, env: { es6: true } } - ); - - assert(count === 4); - }); - }); - - describe("onCodePathSegmentStart", () => { - it("should be fired at the head of programs/functions for the initial segment", () => { - let count = 0; - let lastCodePathNodeType = null; - - linter.defineRule("test", { - create: () => ({ - onCodePathSegmentStart(segment, node) { - count += 1; - lastCodePathNodeType = node.type; - - assert(segment instanceof CodePathSegment); - if (count === 1) { - assert(node.type === "Program"); - } else if (count === 2) { - assert(node.type === "FunctionDeclaration"); - } else if (count === 3) { - assert(node.type === "FunctionExpression"); - } else if (count === 4) { - assert(node.type === "ArrowFunctionExpression"); - } - }, - Program() { - assert(lastCodePathNodeType === "Program"); - }, - FunctionDeclaration() { - assert(lastCodePathNodeType === "FunctionDeclaration"); - }, - FunctionExpression() { - assert(lastCodePathNodeType === "FunctionExpression"); - }, - ArrowFunctionExpression() { - assert(lastCodePathNodeType === "ArrowFunctionExpression"); - } - }) - }); - linter.verify( - "foo(); function foo() {} var foo = function() {}; var foo = () => {};", - { rules: { test: 2 }, env: { es6: true } } - ); - - assert(count === 4); - }); - }); - - describe("onCodePathSegmentEnd", () => { - it("should be fired at the end of programs/functions for the final segment", () => { - let count = 0; - let lastNodeType = null; - - linter.defineRule("test", { - create: () => ({ - onCodePathSegmentEnd(cp, node) { - count += 1; - - assert(cp instanceof CodePathSegment); - if (count === 4) { - assert(node.type === "Program"); - } else if (count === 1) { - assert(node.type === "FunctionDeclaration"); - } else if (count === 2) { - assert(node.type === "FunctionExpression"); - } else if (count === 3) { - assert(node.type === "ArrowFunctionExpression"); - } - assert(node.type === lastNodeType); - }, - "Program:exit"() { - lastNodeType = "Program"; - }, - "FunctionDeclaration:exit"() { - lastNodeType = "FunctionDeclaration"; - }, - "FunctionExpression:exit"() { - lastNodeType = "FunctionExpression"; - }, - "ArrowFunctionExpression:exit"() { - lastNodeType = "ArrowFunctionExpression"; - } - }) - }); - linter.verify( - "foo(); function foo() {} var foo = function() {}; var foo = () => {};", - { rules: { test: 2 }, env: { es6: true } } - ); - - assert(count === 4); - }); - }); - - describe("onUnreachableCodePathSegmentStart", () => { - it("should be fired after a throw", () => { - let lastCodePathNodeType = null; - - linter.defineRule("test", { - create: () => ({ - onUnreachableCodePathSegmentStart(segment, node) { - lastCodePathNodeType = node.type; - - assert(segment instanceof CodePathSegment); - assert.strictEqual(node.type, "ExpressionStatement"); - }, - ExpressionStatement() { - assert.strictEqual(lastCodePathNodeType, "ExpressionStatement"); - } - }) - }); - linter.verify( - "throw 'boom'; foo();", - { rules: { test: 2 } } - ); - - }); - - it("should be fired after a return", () => { - let lastCodePathNodeType = null; - - linter.defineRule("test", { - create: () => ({ - onUnreachableCodePathSegmentStart(segment, node) { - lastCodePathNodeType = node.type; - - assert(segment instanceof CodePathSegment); - assert.strictEqual(node.type, "ExpressionStatement"); - }, - ExpressionStatement() { - assert.strictEqual(lastCodePathNodeType, "ExpressionStatement"); - } - }) - }); - linter.verify( - "function foo() { return; foo(); }", - { rules: { test: 2 } } - ); - - }); - }); - - describe("onUnreachableCodePathSegmentEnd", () => { - it("should be fired after a throw", () => { - let lastCodePathNodeType = null; - - linter.defineRule("test", { - create: () => ({ - onUnreachableCodePathSegmentEnd(segment, node) { - lastCodePathNodeType = node.type; - - assert(segment instanceof CodePathSegment); - assert.strictEqual(node.type, "Program"); - } - }) - }); - linter.verify( - "throw 'boom'; foo();", - { rules: { test: 2 } } - ); - - assert.strictEqual(lastCodePathNodeType, "Program"); - }); - - it("should be fired after a return", () => { - let lastCodePathNodeType = null; - - linter.defineRule("test", { - create: () => ({ - onUnreachableCodePathSegmentEnd(segment, node) { - lastCodePathNodeType = node.type; - assert(segment instanceof CodePathSegment); - assert.strictEqual(node.type, "FunctionDeclaration"); - }, - "Program:exit"() { - assert.strictEqual(lastCodePathNodeType, "FunctionDeclaration"); - } - }) - }); - linter.verify( - "function foo() { return; foo(); }", - { rules: { test: 2 } } - ); - - }); - - it("should be fired after a return inside of function and if statement", () => { - let lastCodePathNodeType = null; - - linter.defineRule("test", { - create: () => ({ - onUnreachableCodePathSegmentEnd(segment, node) { - lastCodePathNodeType = node.type; - assert(segment instanceof CodePathSegment); - assert.strictEqual(node.type, "BlockStatement"); - }, - "Program:exit"() { - assert.strictEqual(lastCodePathNodeType, "BlockStatement"); - } - }) - }); - linter.verify( - "function foo() { if (bar) { return; foo(); } else {} }", - { rules: { test: 2 } } - ); - - }); - - it("should be fired at the end of programs/functions for the final segment", () => { - let count = 0; - let lastNodeType = null; - - linter.defineRule("test", { - create: () => ({ - onUnreachableCodePathSegmentEnd(cp, node) { - count += 1; - - assert(cp instanceof CodePathSegment); - if (count === 4) { - assert(node.type === "Program"); - } else if (count === 1) { - assert(node.type === "FunctionDeclaration"); - } else if (count === 2) { - assert(node.type === "FunctionExpression"); - } else if (count === 3) { - assert(node.type === "ArrowFunctionExpression"); - } - assert(node.type === lastNodeType); - }, - "Program:exit"() { - lastNodeType = "Program"; - }, - "FunctionDeclaration:exit"() { - lastNodeType = "FunctionDeclaration"; - }, - "FunctionExpression:exit"() { - lastNodeType = "FunctionExpression"; - }, - "ArrowFunctionExpression:exit"() { - lastNodeType = "ArrowFunctionExpression"; - } - }) - }); - linter.verify( - "foo(); function foo() { return; } var foo = function() { return; }; var foo = () => { return; }; throw 'boom';", - { rules: { test: 2 }, env: { es6: true } } - ); - - assert(count === 4); - }); - }); - - describe("onCodePathSegmentLoop", () => { - it("should be fired in `while` loops", () => { - let count = 0; - - linter.defineRule("test", { - create: () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); - assert(node.type === "WhileStatement"); - } - }) - }); - linter.verify( - "while (a) { foo(); }", - { rules: { test: 2 } } - ); - - assert(count === 1); - }); - - it("should be fired in `do-while` loops", () => { - let count = 0; - - linter.defineRule("test", { - create: () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); - assert(node.type === "DoWhileStatement"); - } - }) - }); - linter.verify( - "do { foo(); } while (a);", - { rules: { test: 2 } } - ); - - assert(count === 1); - }); - - it("should be fired in `for` loops", () => { - let count = 0; - - linter.defineRule("test", { - create: () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); - - if (count === 1) { - - // connect path: "update" -> "test" - assert(node.parent.type === "ForStatement"); - } else if (count === 2) { - assert(node.type === "ForStatement"); - } - } - }) - }); - linter.verify( - "for (var i = 0; i < 10; ++i) { foo(); }", - { rules: { test: 2 } } - ); - - assert(count === 2); - }); - - it("should be fired in `for-in` loops", () => { - let count = 0; - - linter.defineRule("test", { - create: () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); - - if (count === 1) { - - // connect path: "right" -> "left" - assert(node.parent.type === "ForInStatement"); - } else if (count === 2) { - assert(node.type === "ForInStatement"); - } - } - }) - }); - linter.verify( - "for (var k in obj) { foo(); }", - { rules: { test: 2 } } - ); - - assert(count === 2); - }); - - it("should be fired in `for-of` loops", () => { - let count = 0; - - linter.defineRule("test", { - create: () => ({ - onCodePathSegmentLoop(fromSegment, toSegment, node) { - count += 1; - assert(fromSegment instanceof CodePathSegment); - assert(toSegment instanceof CodePathSegment); - - if (count === 1) { - - // connect path: "right" -> "left" - assert(node.parent.type === "ForOfStatement"); - } else if (count === 2) { - assert(node.type === "ForOfStatement"); - } - } - }) - }); - linter.verify( - "for (var x of xs) { foo(); }", - { rules: { test: 2 }, env: { es6: true } } - ); - - assert(count === 2); - }); - }); - - describe("completed code paths are correct", () => { - const testDataDir = path.join(__dirname, "../../../fixtures/code-path-analysis/"); - const testDataFiles = fs.readdirSync(testDataDir); - - testDataFiles.forEach(file => { - it(file, () => { - const source = fs.readFileSync(path.join(testDataDir, file), { encoding: "utf8" }); - const expected = getExpectedDotArrows(source); - const actual = []; - - assert(expected.length > 0, "/*expected */ comments not found."); - - linter.defineRule("test", { - create: () => ({ - onCodePathEnd(codePath) { - actual.push(debug.makeDotArrows(codePath)); - } - }) - }); - const messages = linter.verify(source, { - parserOptions: { ecmaVersion: 2022 }, - rules: { test: 2 } - }); - - assert.strictEqual(messages.length, 0, "Unexpected linting error in code."); - assert.strictEqual(actual.length, expected.length, "a count of code paths is wrong."); - - for (let i = 0; i < actual.length; ++i) { - assert.strictEqual(actual[i], expected[i]); - } - }); - }); - }); + describe("interface of code paths", () => { + let actual = []; + + beforeEach(() => { + actual = []; + linter.verify( + "function foo(a) { if (a) return 0; else throw new Error(); }", + { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onCodePathStart(codePath) { + actual.push(codePath); + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }, + ); + }); + + it("should have `id` as unique string", () => { + assert(typeof actual[0].id === "string"); + assert(typeof actual[1].id === "string"); + assert(actual[0].id !== actual[1].id); + }); + + it("should have `upper` as CodePath", () => { + assert(actual[0].upper === null); + assert(actual[1].upper === actual[0]); + }); + + it("should have `childCodePaths` as CodePath[]", () => { + assert(Array.isArray(actual[0].childCodePaths)); + assert(Array.isArray(actual[1].childCodePaths)); + assert(actual[0].childCodePaths.length === 1); + assert(actual[1].childCodePaths.length === 0); + assert(actual[0].childCodePaths[0] === actual[1]); + }); + + it("should have `initialSegment` as CodePathSegment", () => { + assert(actual[0].initialSegment instanceof CodePathSegment); + assert(actual[1].initialSegment instanceof CodePathSegment); + assert(actual[0].initialSegment.prevSegments.length === 0); + assert(actual[1].initialSegment.prevSegments.length === 0); + }); + + it("should have `finalSegments` as CodePathSegment[]", () => { + assert(Array.isArray(actual[0].finalSegments)); + assert(Array.isArray(actual[1].finalSegments)); + assert(actual[0].finalSegments.length === 1); + assert(actual[1].finalSegments.length === 2); + assert(actual[0].finalSegments[0].nextSegments.length === 0); + assert(actual[1].finalSegments[0].nextSegments.length === 0); + assert(actual[1].finalSegments[1].nextSegments.length === 0); + + // finalSegments should include returnedSegments and thrownSegments. + assert( + actual[0].finalSegments[0] === actual[0].returnedSegments[0], + ); + assert( + actual[1].finalSegments[0] === actual[1].returnedSegments[0], + ); + assert(actual[1].finalSegments[1] === actual[1].thrownSegments[0]); + }); + + it("should have `returnedSegments` as CodePathSegment[]", () => { + assert(Array.isArray(actual[0].returnedSegments)); + assert(Array.isArray(actual[1].returnedSegments)); + assert(actual[0].returnedSegments.length === 1); + assert(actual[1].returnedSegments.length === 1); + assert(actual[0].returnedSegments[0] instanceof CodePathSegment); + assert(actual[1].returnedSegments[0] instanceof CodePathSegment); + }); + + it("should have `thrownSegments` as CodePathSegment[]", () => { + assert(Array.isArray(actual[0].thrownSegments)); + assert(Array.isArray(actual[1].thrownSegments)); + assert(actual[0].thrownSegments.length === 0); + assert(actual[1].thrownSegments.length === 1); + assert(actual[1].thrownSegments[0] instanceof CodePathSegment); + }); + }); + + describe("interface of code path segments", () => { + let actual = []; + + beforeEach(() => { + actual = []; + linter.verify( + "function foo(a) { if (a) return 0; else throw new Error(); }", + { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onCodePathSegmentStart(segment) { + actual.push(segment); + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }, + ); + }); + + it("should have `id` as unique string", () => { + assert(typeof actual[0].id === "string"); + assert(typeof actual[1].id === "string"); + assert(typeof actual[2].id === "string"); + assert(typeof actual[3].id === "string"); + assert(actual[0].id !== actual[1].id); + assert(actual[0].id !== actual[2].id); + assert(actual[0].id !== actual[3].id); + assert(actual[1].id !== actual[2].id); + assert(actual[1].id !== actual[3].id); + assert(actual[2].id !== actual[3].id); + }); + + it("should have `nextSegments` as CodePathSegment[]", () => { + assert(Array.isArray(actual[0].nextSegments)); + assert(Array.isArray(actual[1].nextSegments)); + assert(Array.isArray(actual[2].nextSegments)); + assert(Array.isArray(actual[3].nextSegments)); + assert(actual[0].nextSegments.length === 0); + assert(actual[1].nextSegments.length === 2); + assert(actual[2].nextSegments.length === 0); + assert(actual[3].nextSegments.length === 0); + assert(actual[1].nextSegments[0] === actual[2]); + assert(actual[1].nextSegments[1] === actual[3]); + }); + + it("should have `allNextSegments` as CodePathSegment[]", () => { + assert(Array.isArray(actual[0].allNextSegments)); + assert(Array.isArray(actual[1].allNextSegments)); + assert(Array.isArray(actual[2].allNextSegments)); + assert(Array.isArray(actual[3].allNextSegments)); + assert(actual[0].allNextSegments.length === 0); + assert(actual[1].allNextSegments.length === 2); + assert(actual[2].allNextSegments.length === 1); + assert(actual[3].allNextSegments.length === 1); + assert(actual[2].allNextSegments[0].reachable === false); + assert(actual[3].allNextSegments[0].reachable === false); + }); + + it("should have `prevSegments` as CodePathSegment[]", () => { + assert(Array.isArray(actual[0].prevSegments)); + assert(Array.isArray(actual[1].prevSegments)); + assert(Array.isArray(actual[2].prevSegments)); + assert(Array.isArray(actual[3].prevSegments)); + assert(actual[0].prevSegments.length === 0); + assert(actual[1].prevSegments.length === 0); + assert(actual[2].prevSegments.length === 1); + assert(actual[3].prevSegments.length === 1); + assert(actual[2].prevSegments[0] === actual[1]); + assert(actual[3].prevSegments[0] === actual[1]); + }); + + it("should have `allPrevSegments` as CodePathSegment[]", () => { + assert(Array.isArray(actual[0].allPrevSegments)); + assert(Array.isArray(actual[1].allPrevSegments)); + assert(Array.isArray(actual[2].allPrevSegments)); + assert(Array.isArray(actual[3].allPrevSegments)); + assert(actual[0].allPrevSegments.length === 0); + assert(actual[1].allPrevSegments.length === 0); + assert(actual[2].allPrevSegments.length === 1); + assert(actual[3].allPrevSegments.length === 1); + }); + + it("should have `reachable` as boolean", () => { + assert(actual[0].reachable === true); + assert(actual[1].reachable === true); + assert(actual[2].reachable === true); + assert(actual[3].reachable === true); + }); + }); + + describe("onCodePathStart", () => { + it("should be fired at the head of programs/functions", () => { + let count = 0; + let lastCodePathNodeType = null; + + linter.verify( + "foo(); function bar() {} var baz = function() {}; var qux = () => {};", + { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onCodePathStart(cp, node) { + count += 1; + lastCodePathNodeType = node.type; + + assert(cp instanceof CodePath); + if (count === 1) { + assert(node.type === "Program"); + } else if (count === 2) { + assert( + node.type === + "FunctionDeclaration", + ); + } else if (count === 3) { + assert( + node.type === + "FunctionExpression", + ); + } else if (count === 4) { + assert( + node.type === + "ArrowFunctionExpression", + ); + } + }, + Program() { + assert( + lastCodePathNodeType === + "Program", + ); + }, + FunctionDeclaration() { + assert( + lastCodePathNodeType === + "FunctionDeclaration", + ); + }, + FunctionExpression() { + assert( + lastCodePathNodeType === + "FunctionExpression", + ); + }, + ArrowFunctionExpression() { + assert( + lastCodePathNodeType === + "ArrowFunctionExpression", + ); + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }, + ); + + assert(count === 4); + }); + }); + + describe("onCodePathEnd", () => { + it("should be fired at the end of programs/functions", () => { + let count = 0; + let lastNodeType = null; + + linter.verify( + "foo(); function bar() {} var baz = function() {}; var qux = () => {};", + { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onCodePathEnd(cp, node) { + count += 1; + + assert(cp instanceof CodePath); + if (count === 4) { + assert(node.type === "Program"); + } else if (count === 1) { + assert( + node.type === + "FunctionDeclaration", + ); + } else if (count === 2) { + assert( + node.type === + "FunctionExpression", + ); + } else if (count === 3) { + assert( + node.type === + "ArrowFunctionExpression", + ); + } + assert(node.type === lastNodeType); + }, + "Program:exit"() { + lastNodeType = "Program"; + }, + "FunctionDeclaration:exit"() { + lastNodeType = + "FunctionDeclaration"; + }, + "FunctionExpression:exit"() { + lastNodeType = "FunctionExpression"; + }, + "ArrowFunctionExpression:exit"() { + lastNodeType = + "ArrowFunctionExpression"; + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }, + ); + + assert(count === 4); + }); + }); + + describe("onCodePathSegmentStart", () => { + it("should be fired at the head of programs/functions for the initial segment", () => { + let count = 0; + let lastCodePathNodeType = null; + + linter.verify( + "foo(); function bar() {} var baz = function() {}; var qux = () => {};", + { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onCodePathSegmentStart(segment, node) { + count += 1; + lastCodePathNodeType = node.type; + + assert( + segment instanceof + CodePathSegment, + ); + if (count === 1) { + assert(node.type === "Program"); + } else if (count === 2) { + assert( + node.type === + "FunctionDeclaration", + ); + } else if (count === 3) { + assert( + node.type === + "FunctionExpression", + ); + } else if (count === 4) { + assert( + node.type === + "ArrowFunctionExpression", + ); + } + }, + Program() { + assert( + lastCodePathNodeType === + "Program", + ); + }, + FunctionDeclaration() { + assert( + lastCodePathNodeType === + "FunctionDeclaration", + ); + }, + FunctionExpression() { + assert( + lastCodePathNodeType === + "FunctionExpression", + ); + }, + ArrowFunctionExpression() { + assert( + lastCodePathNodeType === + "ArrowFunctionExpression", + ); + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }, + ); + + assert(count === 4); + }); + }); + + describe("onCodePathSegmentEnd", () => { + it("should be fired at the end of programs/functions for the final segment", () => { + let count = 0; + let lastNodeType = null; + + linter.verify( + "foo(); function bar() {} var baz = function() {}; var qux = () => {};", + { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onCodePathSegmentEnd(cp, node) { + count += 1; + + assert( + cp instanceof CodePathSegment, + ); + if (count === 4) { + assert(node.type === "Program"); + } else if (count === 1) { + assert( + node.type === + "FunctionDeclaration", + ); + } else if (count === 2) { + assert( + node.type === + "FunctionExpression", + ); + } else if (count === 3) { + assert( + node.type === + "ArrowFunctionExpression", + ); + } + assert(node.type === lastNodeType); + }, + "Program:exit"() { + lastNodeType = "Program"; + }, + "FunctionDeclaration:exit"() { + lastNodeType = + "FunctionDeclaration"; + }, + "FunctionExpression:exit"() { + lastNodeType = "FunctionExpression"; + }, + "ArrowFunctionExpression:exit"() { + lastNodeType = + "ArrowFunctionExpression"; + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }, + ); + + assert(count === 4); + }); + }); + + describe("onUnreachableCodePathSegmentStart", () => { + it("should be fired after a throw", () => { + let lastCodePathNodeType = null; + + linter.verify("throw 'boom'; foo();", { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onUnreachableCodePathSegmentStart( + segment, + node, + ) { + lastCodePathNodeType = node.type; + + assert( + segment instanceof CodePathSegment, + ); + assert.strictEqual( + node.type, + "ExpressionStatement", + ); + }, + ExpressionStatement() { + assert.strictEqual( + lastCodePathNodeType, + "ExpressionStatement", + ); + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }); + }); + + it("should be fired after a return", () => { + let lastCodePathNodeType = null; + + linter.verify("function foo() { return; foo(); }", { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onUnreachableCodePathSegmentStart( + segment, + node, + ) { + lastCodePathNodeType = node.type; + + assert( + segment instanceof CodePathSegment, + ); + assert.strictEqual( + node.type, + "ExpressionStatement", + ); + }, + ExpressionStatement() { + assert.strictEqual( + lastCodePathNodeType, + "ExpressionStatement", + ); + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }); + }); + }); + + describe("onUnreachableCodePathSegmentEnd", () => { + it("should be fired after a throw", () => { + let lastCodePathNodeType = null; + + linter.verify("throw 'boom'; foo();", { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onUnreachableCodePathSegmentEnd( + segment, + node, + ) { + lastCodePathNodeType = node.type; + + assert( + segment instanceof CodePathSegment, + ); + assert.strictEqual( + node.type, + "Program", + ); + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }); + + assert.strictEqual(lastCodePathNodeType, "Program"); + }); + + it("should be fired after a return", () => { + let lastCodePathNodeType = null; + + linter.verify("function foo() { return; foo(); }", { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onUnreachableCodePathSegmentEnd( + segment, + node, + ) { + lastCodePathNodeType = node.type; + assert( + segment instanceof CodePathSegment, + ); + assert.strictEqual( + node.type, + "FunctionDeclaration", + ); + }, + "Program:exit"() { + assert.strictEqual( + lastCodePathNodeType, + "FunctionDeclaration", + ); + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }); + }); + + it("should be fired after a return inside of function and if statement", () => { + let lastCodePathNodeType = null; + + linter.verify( + "function foo() { if (bar) { return; foo(); } else {} }", + { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onUnreachableCodePathSegmentEnd( + segment, + node, + ) { + lastCodePathNodeType = node.type; + assert( + segment instanceof + CodePathSegment, + ); + assert.strictEqual( + node.type, + "BlockStatement", + ); + }, + "Program:exit"() { + assert.strictEqual( + lastCodePathNodeType, + "BlockStatement", + ); + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }, + ); + }); + + it("should be fired at the end of programs/functions for the final segment", () => { + let count = 0; + let lastNodeType = null; + + linter.verify( + "foo(); function bar() { return; } var baz = function() { return; }; var qux = () => { return; }; throw 'boom';", + { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onUnreachableCodePathSegmentEnd( + cp, + node, + ) { + count += 1; + + assert( + cp instanceof CodePathSegment, + ); + if (count === 4) { + assert(node.type === "Program"); + } else if (count === 1) { + assert( + node.type === + "FunctionDeclaration", + ); + } else if (count === 2) { + assert( + node.type === + "FunctionExpression", + ); + } else if (count === 3) { + assert( + node.type === + "ArrowFunctionExpression", + ); + } + assert(node.type === lastNodeType); + }, + "Program:exit"() { + lastNodeType = "Program"; + }, + "FunctionDeclaration:exit"() { + lastNodeType = + "FunctionDeclaration"; + }, + "FunctionExpression:exit"() { + lastNodeType = "FunctionExpression"; + }, + "ArrowFunctionExpression:exit"() { + lastNodeType = + "ArrowFunctionExpression"; + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }, + ); + + assert(count === 4); + }); + }); + + describe("onCodePathSegmentLoop", () => { + it("should be fired in `while` loops", () => { + let count = 0; + + linter.verify("while (a) { foo(); }", { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onCodePathSegmentLoop( + fromSegment, + toSegment, + node, + ) { + count += 1; + assert( + fromSegment instanceof + CodePathSegment, + ); + assert( + toSegment instanceof + CodePathSegment, + ); + assert(node.type === "WhileStatement"); + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }); + + assert(count === 1); + }); + + it("should be fired in `do-while` loops", () => { + let count = 0; + + linter.verify("do { foo(); } while (a);", { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onCodePathSegmentLoop( + fromSegment, + toSegment, + node, + ) { + count += 1; + assert( + fromSegment instanceof + CodePathSegment, + ); + assert( + toSegment instanceof + CodePathSegment, + ); + assert( + node.type === "DoWhileStatement", + ); + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }); + + assert(count === 1); + }); + + it("should be fired in `for` loops", () => { + let count = 0; + + linter.verify("for (var i = 0; i < 10; ++i) { foo(); }", { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onCodePathSegmentLoop( + fromSegment, + toSegment, + node, + ) { + count += 1; + assert( + fromSegment instanceof + CodePathSegment, + ); + assert( + toSegment instanceof + CodePathSegment, + ); + + if (count === 1) { + // connect path: "update" -> "test" + assert( + node.parent.type === + "ForStatement", + ); + } else if (count === 2) { + assert( + node.type === "ForStatement", + ); + } + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }); + + assert(count === 2); + }); + + it("should be fired in `for-in` loops", () => { + let count = 0; + + linter.verify("for (var k in obj) { foo(); }", { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onCodePathSegmentLoop( + fromSegment, + toSegment, + node, + ) { + count += 1; + assert( + fromSegment instanceof + CodePathSegment, + ); + assert( + toSegment instanceof + CodePathSegment, + ); + + if (count === 1) { + // connect path: "right" -> "left" + assert( + node.parent.type === + "ForInStatement", + ); + } else if (count === 2) { + assert( + node.type === "ForInStatement", + ); + } + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }); + + assert(count === 2); + }); + + it("should be fired in `for-of` loops", () => { + let count = 0; + + linter.verify("for (var x of xs) { foo(); }", { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onCodePathSegmentLoop( + fromSegment, + toSegment, + node, + ) { + count += 1; + assert( + fromSegment instanceof + CodePathSegment, + ); + assert( + toSegment instanceof + CodePathSegment, + ); + + if (count === 1) { + // connect path: "right" -> "left" + assert( + node.parent.type === + "ForOfStatement", + ); + } else if (count === 2) { + assert( + node.type === "ForOfStatement", + ); + } + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }); + + assert(count === 2); + }); + }); + + describe("completed code paths are correct", () => { + const testDataDir = path.join( + __dirname, + "../../../fixtures/code-path-analysis/", + ); + const testDataFiles = fs.readdirSync(testDataDir); + + testDataFiles.forEach(file => { + it(file, () => { + const source = fs.readFileSync(path.join(testDataDir, file), { + encoding: "utf8", + }); + const expected = getExpectedDotArrows(source); + const actual = []; + + assert( + expected.length > 0, + "/*expected */ comments not found.", + ); + + const messages = linter.verify(source, { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onCodePathEnd(codePath) { + actual.push( + debug.makeDotArrows(codePath), + ); + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + languageOptions: getLanguageOptions(source), + }); + + assert.strictEqual( + messages.length, + 0, + "Unexpected linting error in code.", + ); + assert.strictEqual( + actual.length, + expected.length, + "a count of code paths is wrong.", + ); + + for (let i = 0; i < actual.length; ++i) { + assert.strictEqual(actual[i], expected[i]); + } + }); + }); + }); }); diff --git a/tests/lib/linter/code-path-analysis/code-path.js b/tests/lib/linter/code-path-analysis/code-path.js index 7f1d738f6e96..8ed438ad1b98 100644 --- a/tests/lib/linter/code-path-analysis/code-path.js +++ b/tests/lib/linter/code-path-analysis/code-path.js @@ -9,8 +9,8 @@ // Requirements //------------------------------------------------------------------------------ -const assert = require("assert"), - { Linter } = require("../../../../lib/linter"); +const assert = require("node:assert"), + { Linter } = require("../../../../lib/linter"); const linter = new Linter(); //------------------------------------------------------------------------------ @@ -23,22 +23,26 @@ const linter = new Linter(); * @returns {CodePath[]} A list of created code paths. */ function parseCodePaths(code) { - const retv = []; - - linter.defineRule("test", { - create: () => ({ - onCodePathStart(codePath) { - retv.push(codePath); - } - }) - }); - - linter.verify(code, { - rules: { test: 2 }, - parserOptions: { ecmaVersion: "latest" } - }); - - return retv; + const retv = []; + + linter.verify(code, { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: () => ({ + onCodePathStart(codePath) { + retv.push(codePath); + }, + }), + }, + }, + }, + }, + rules: { "test-plugin/test-rule": 2 }, + }); + + return retv; } /** @@ -51,16 +55,16 @@ function parseCodePaths(code) { * @returns {string[]} The list of segment's ids in the order traversed. */ function getOrderOfTraversing(codePath, options, callback) { - const retv = []; + const retv = []; - codePath.traverseSegments(options, (segment, controller) => { - retv.push(segment.id); - if (callback) { - callback(segment, controller); // eslint-disable-line n/callback-return -- At end of inner function - } - }); + codePath.traverseSegments(options, (segment, controller) => { + retv.push(segment.id); + if (callback) { + callback(segment, controller); // eslint-disable-line n/callback-return -- At end of inner function + } + }); - return retv; + return retv; } //------------------------------------------------------------------------------ @@ -68,65 +72,64 @@ function getOrderOfTraversing(codePath, options, callback) { //------------------------------------------------------------------------------ describe("CodePathAnalyzer", () => { - - /* - * If you need to output the code paths and DOT graph information for a - * particular piece of code, update and uncomment the following test and - * then run: - * DEBUG=eslint:code-path npx mocha tests/lib/linter/code-path-analysis/ - * - * All the information you need will be output to the console. - */ - /* - * it.only("test", () => { - * const codePaths = parseCodePaths("class Foo { a = () => b }"); - * }); - */ - - describe("CodePath#origin", () => { - - it("should be 'program' when code path starts at root node", () => { - const codePath = parseCodePaths("foo(); bar(); baz();")[0]; - - assert.strictEqual(codePath.origin, "program"); - }); - - it("should be 'function' when code path starts inside a function", () => { - const codePath = parseCodePaths("function foo() {}")[1]; - - assert.strictEqual(codePath.origin, "function"); - }); - - it("should be 'function' when code path starts inside an arrow function", () => { - const codePath = parseCodePaths("let foo = () => {}")[1]; - - assert.strictEqual(codePath.origin, "function"); - }); - - it("should be 'class-field-initializer' when code path starts inside a class field initializer", () => { - const codePath = parseCodePaths("class Foo { a=1; }")[1]; - - assert.strictEqual(codePath.origin, "class-field-initializer"); - }); - - it("should be 'class-static-block' when code path starts inside a class static block", () => { - const codePath = parseCodePaths("class Foo { static { this.a=1; } }")[1]; - - assert.strictEqual(codePath.origin, "class-static-block"); - }); - }); - - describe(".traverseSegments()", () => { - - describe("should traverse segments from the first to the end:", () => { - /* eslint-disable internal-rules/multiline-comment-style -- Commenting out */ - it("simple", () => { - const codePath = parseCodePaths("foo(); bar(); baz();")[0]; - const order = getOrderOfTraversing(codePath); - - assert.deepStrictEqual(order, ["s1_1"]); - - /* + /* + * If you need to output the code paths and DOT graph information for a + * particular piece of code, update and uncomment the following test and + * then run: + * DEBUG=eslint:code-path npx mocha tests/lib/linter/code-path-analysis/ + * + * All the information you need will be output to the console. + */ + /* + * it.only("test", () => { + * const codePaths = parseCodePaths("class Foo { a = () => b }"); + * }); + */ + + describe("CodePath#origin", () => { + it("should be 'program' when code path starts at root node", () => { + const codePath = parseCodePaths("foo(); bar(); baz();")[0]; + + assert.strictEqual(codePath.origin, "program"); + }); + + it("should be 'function' when code path starts inside a function", () => { + const codePath = parseCodePaths("function foo() {}")[1]; + + assert.strictEqual(codePath.origin, "function"); + }); + + it("should be 'function' when code path starts inside an arrow function", () => { + const codePath = parseCodePaths("let foo = () => {}")[1]; + + assert.strictEqual(codePath.origin, "function"); + }); + + it("should be 'class-field-initializer' when code path starts inside a class field initializer", () => { + const codePath = parseCodePaths("class Foo { a=1; }")[1]; + + assert.strictEqual(codePath.origin, "class-field-initializer"); + }); + + it("should be 'class-static-block' when code path starts inside a class static block", () => { + const codePath = parseCodePaths( + "class Foo { static { this.a=1; } }", + )[1]; + + assert.strictEqual(codePath.origin, "class-static-block"); + }); + }); + + describe(".traverseSegments()", () => { + describe("should traverse segments from the first to the end:", () => { + /* eslint-disable internal-rules/multiline-comment-style -- Commenting out */ + it("simple", () => { + const codePath = parseCodePaths("foo(); bar(); baz();")[0]; + const order = getOrderOfTraversing(codePath); + + assert.deepStrictEqual(order, ["s1_1"]); + + /* digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; @@ -135,15 +138,17 @@ describe("CodePathAnalyzer", () => { initial->s1_1->final; } */ - }); + }); - it("if", () => { - const codePath = parseCodePaths("if (a) foo(); else bar(); baz();")[0]; - const order = getOrderOfTraversing(codePath); + it("if", () => { + const codePath = parseCodePaths( + "if (a) foo(); else bar(); baz();", + )[0]; + const order = getOrderOfTraversing(codePath); - assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_3", "s1_4"]); + assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_3", "s1_4"]); - /* + /* digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; @@ -156,15 +161,23 @@ describe("CodePathAnalyzer", () => { s1_1->s1_3->s1_4->final; } */ - }); - - it("switch", () => { - const codePath = parseCodePaths("switch (a) { case 0: foo(); break; case 1: bar(); } baz();")[0]; - const order = getOrderOfTraversing(codePath); - - assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_4", "s1_5", "s1_6"]); - - /* + }); + + it("switch", () => { + const codePath = parseCodePaths( + "switch (a) { case 0: foo(); break; case 1: bar(); } baz();", + )[0]; + const order = getOrderOfTraversing(codePath); + + assert.deepStrictEqual(order, [ + "s1_1", + "s1_2", + "s1_4", + "s1_5", + "s1_6", + ]); + + /* digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; @@ -181,15 +194,15 @@ describe("CodePathAnalyzer", () => { s1_4->s1_6->final; } */ - }); + }); - it("while", () => { - const codePath = parseCodePaths("while (a) foo(); bar();")[0]; - const order = getOrderOfTraversing(codePath); + it("while", () => { + const codePath = parseCodePaths("while (a) foo(); bar();")[0]; + const order = getOrderOfTraversing(codePath); - assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_3", "s1_4"]); + assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_3", "s1_4"]); - /* + /* digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; @@ -201,15 +214,23 @@ describe("CodePathAnalyzer", () => { initial->s1_1->s1_2->s1_3->s1_2->s1_4->final; } */ - }); - - it("for", () => { - const codePath = parseCodePaths("for (var i = 0; i < 10; ++i) foo(i); bar();")[0]; - const order = getOrderOfTraversing(codePath); - - assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_3", "s1_4", "s1_5"]); - - /* + }); + + it("for", () => { + const codePath = parseCodePaths( + "for (var i = 0; i < 10; ++i) foo(i); bar();", + )[0]; + const order = getOrderOfTraversing(codePath); + + assert.deepStrictEqual(order, [ + "s1_1", + "s1_2", + "s1_3", + "s1_4", + "s1_5", + ]); + + /* digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; @@ -222,15 +243,23 @@ describe("CodePathAnalyzer", () => { initial->s1_1->s1_2->s1_3->s1_4->s1_2->s1_5->final; } */ - }); - - it("for-in", () => { - const codePath = parseCodePaths("for (var key in obj) foo(key); bar();")[0]; - const order = getOrderOfTraversing(codePath); - - assert.deepStrictEqual(order, ["s1_1", "s1_3", "s1_2", "s1_4", "s1_5"]); - - /* + }); + + it("for-in", () => { + const codePath = parseCodePaths( + "for (var key in obj) foo(key); bar();", + )[0]; + const order = getOrderOfTraversing(codePath); + + assert.deepStrictEqual(order, [ + "s1_1", + "s1_3", + "s1_2", + "s1_4", + "s1_5", + ]); + + /* digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; @@ -245,15 +274,17 @@ describe("CodePathAnalyzer", () => { s1_4->s1_5->final; } */ - }); + }); - it("try-catch", () => { - const codePath = parseCodePaths("try { foo(); } catch (e) { bar(); } baz();")[0]; - const order = getOrderOfTraversing(codePath); + it("try-catch", () => { + const codePath = parseCodePaths( + "try { foo(); } catch (e) { bar(); } baz();", + )[0]; + const order = getOrderOfTraversing(codePath); - assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_3", "s1_4"]); + assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_3", "s1_4"]); - /* + /* digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; @@ -267,19 +298,21 @@ describe("CodePathAnalyzer", () => { s1_2->s1_4->final; } */ - }); - }); + }); + }); - it("should traverse segments from `options.first` to `options.last`.", () => { - const codePath = parseCodePaths("if (a) { if (b) { foo(); } bar(); } else { out1(); } out2();")[0]; - const order = getOrderOfTraversing(codePath, { - first: codePath.initialSegment.nextSegments[0], - last: codePath.initialSegment.nextSegments[0].nextSegments[1] - }); + it("should traverse segments from `options.first` to `options.last`.", () => { + const codePath = parseCodePaths( + "if (a) { if (b) { foo(); } bar(); } else { out1(); } out2();", + )[0]; + const order = getOrderOfTraversing(codePath, { + first: codePath.initialSegment.nextSegments[0], + last: codePath.initialSegment.nextSegments[0].nextSegments[1], + }); - assert.deepStrictEqual(order, ["s1_2", "s1_3", "s1_4"]); + assert.deepStrictEqual(order, ["s1_2", "s1_3", "s1_4"]); - /* + /* digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; @@ -296,19 +329,25 @@ describe("CodePathAnalyzer", () => { s1_6->final; } */ - }); - - it("should stop immediately when 'controller.break()' was called.", () => { - const codePath = parseCodePaths("if (a) { if (b) { foo(); } bar(); } else { out1(); } out2();")[0]; - const order = getOrderOfTraversing(codePath, null, (segment, controller) => { - if (segment.id === "s1_2") { - controller.break(); - } - }); - - assert.deepStrictEqual(order, ["s1_1", "s1_2"]); - - /* + }); + + it("should stop immediately when 'controller.break()' was called.", () => { + const codePath = parseCodePaths( + "if (a) { if (b) { foo(); } bar(); } else { out1(); } out2();", + )[0]; + const order = getOrderOfTraversing( + codePath, + null, + (segment, controller) => { + if (segment.id === "s1_2") { + controller.break(); + } + }, + ); + + assert.deepStrictEqual(order, ["s1_1", "s1_2"]); + + /* digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; @@ -325,19 +364,25 @@ describe("CodePathAnalyzer", () => { s1_6->final; } */ - }); - - it("should skip the current branch when 'controller.skip()' was called.", () => { - const codePath = parseCodePaths("if (a) { if (b) { foo(); } bar(); } else { out1(); } out2();")[0]; - const order = getOrderOfTraversing(codePath, null, (segment, controller) => { - if (segment.id === "s1_2") { - controller.skip(); - } - }); - - assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_5", "s1_6"]); - - /* + }); + + it("should skip the current branch when 'controller.skip()' was called.", () => { + const codePath = parseCodePaths( + "if (a) { if (b) { foo(); } bar(); } else { out1(); } out2();", + )[0]; + const order = getOrderOfTraversing( + codePath, + null, + (segment, controller) => { + if (segment.id === "s1_2") { + controller.skip(); + } + }, + ); + + assert.deepStrictEqual(order, ["s1_1", "s1_2", "s1_5", "s1_6"]); + + /* digraph { node[shape=box,style="rounded,filled",fillcolor=white]; initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; @@ -354,8 +399,76 @@ describe("CodePathAnalyzer", () => { s1_6->final; } */ - }); + }); + + it("should not skip the next branch when 'controller.skip()' was called.", () => { + const codePath = parseCodePaths( + "if (a) { if (b) { foo(); } bar(); } out1();", + )[0]; + const order = getOrderOfTraversing( + codePath, + null, + (segment, controller) => { + if (segment.id === "s1_4") { + controller.skip(); // Since s1_5 is connected from s1_1, we expect it not to be skipped. + } + }, + ); + + assert.deepStrictEqual(order, [ + "s1_1", + "s1_2", + "s1_3", + "s1_4", + "s1_5", + ]); + + /* + digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nIfStatement:enter\nIdentifier (a)"]; + s1_2[label="BlockStatement:enter\nIfStatement:enter\nIdentifier (b)"]; + s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_4[label="IfStatement:exit\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_5[label="IfStatement:exit\nExpressionStatement:enter\nCallExpression:enter\nIdentifier (out1)\nCallExpression:exit\nExpressionStatement:exit\nProgram:exit"]; + initial->s1_1->s1_2->s1_3->s1_4->s1_5; + s1_1->s1_5; + s1_2->s1_4; + s1_5->final; + } + */ + }); + + it("should skip the next branch when 'controller.skip()' was called at top segment.", () => { + const codePath = parseCodePaths("a; while (b) { c; }")[0]; + + const order = getOrderOfTraversing( + codePath, + null, + (segment, controller) => { + if (segment.id === "s1_1") { + controller.skip(); + } + }, + ); + + assert.deepStrictEqual(order, ["s1_1"]); - /* eslint-enable internal-rules/multiline-comment-style -- Commenting out */ - }); + /* + digraph { + node[shape=box,style="rounded,filled",fillcolor=white]; + initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25]; + final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25]; + s1_1[label="Program:enter\nExpressionStatement:enter\nIdentifier (a)\nExpressionStatement:exit\nWhileStatement:enter"]; + s1_2[label="Identifier (b)"]; + s1_3[label="BlockStatement:enter\nExpressionStatement:enter\nIdentifier (c)\nExpressionStatement:exit\nBlockStatement:exit"]; + s1_4[label="WhileStatement:exit\nProgram:exit"]; + initial->s1_1->s1_2->s1_3->s1_2->s1_4->final; + } + */ + }); + /* eslint-enable internal-rules/multiline-comment-style -- Commenting out */ + }); }); diff --git a/tests/lib/linter/config-comment-parser.js b/tests/lib/linter/config-comment-parser.js deleted file mode 100644 index 98bbc5177550..000000000000 --- a/tests/lib/linter/config-comment-parser.js +++ /dev/null @@ -1,275 +0,0 @@ -/** - * @fileoverview Tests for ConfigCommentParser object. - * @author Nicholas C. Zakas - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert, - ConfigCommentParser = require("../../../lib/linter/config-comment-parser"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("ConfigCommentParser", () => { - - let commentParser; - const location = { - start: { - line: 1, - column: 0 - } - }; - - beforeEach(() => { - commentParser = new ConfigCommentParser(); - }); - - describe("parseJsonConfig", () => { - - it("should parse JSON config with one item", () => { - const code = "no-alert:0"; - const result = commentParser.parseJsonConfig(code, location); - - - assert.deepStrictEqual(result, { - success: true, - config: { - "no-alert": 0 - } - }); - }); - - it("should parse JSON config with two items", () => { - const code = "no-alert:0 semi: 2"; - const result = commentParser.parseJsonConfig(code, location); - - - assert.deepStrictEqual(result, { - success: true, - config: { - "no-alert": 0, - semi: 2 - } - }); - }); - - it("should parse JSON config with two comma-separated items", () => { - const code = "no-alert:0,semi: 2"; - const result = commentParser.parseJsonConfig(code, location); - - - assert.deepStrictEqual(result, { - success: true, - config: { - "no-alert": 0, - semi: 2 - } - }); - }); - - it("should parse JSON config with two items and a string severity", () => { - const code = "no-alert:off,semi: 2"; - const result = commentParser.parseJsonConfig(code, location); - - - assert.deepStrictEqual(result, { - success: true, - config: { - "no-alert": "off", - semi: 2 - } - }); - }); - - it("should parse JSON config with two items and options", () => { - const code = "no-alert:off, semi: [2, always]"; - const result = commentParser.parseJsonConfig(code, location); - - - assert.deepStrictEqual(result, { - success: true, - config: { - "no-alert": "off", - semi: [2, "always"] - } - }); - }); - - it("should parse JSON config with two items and options from plugins", () => { - const code = "plugin/no-alert:off, plugin/semi: [2, always]"; - const result = commentParser.parseJsonConfig(code, location); - - assert.deepStrictEqual(result, { - success: true, - config: { - "plugin/no-alert": "off", - "plugin/semi": [2, "always"] - } - }); - }); - - - }); - - describe("parseStringConfig", () => { - - const comment = {}; - - it("should parse String config with one item", () => { - const code = "a: true"; - const result = commentParser.parseStringConfig(code, comment); - - assert.deepStrictEqual(result, { - a: { - value: "true", - comment - } - }); - }); - - it("should parse String config with one item and no value", () => { - const code = "a"; - const result = commentParser.parseStringConfig(code, comment); - - assert.deepStrictEqual(result, { - a: { - value: null, - comment - } - }); - }); - - it("should parse String config with two items", () => { - const code = "a: five b:three"; - const result = commentParser.parseStringConfig(code, comment); - - assert.deepStrictEqual(result, { - a: { - value: "five", - comment - }, - b: { - value: "three", - comment - } - }); - }); - - it("should parse String config with two comma-separated items", () => { - const code = "a: seventy, b:ELEVENTEEN"; - const result = commentParser.parseStringConfig(code, comment); - - assert.deepStrictEqual(result, { - a: { - value: "seventy", - comment - }, - b: { - value: "ELEVENTEEN", - comment - } - }); - }); - - it("should parse String config with two comma-separated items and no values", () => { - const code = "a , b"; - const result = commentParser.parseStringConfig(code, comment); - - assert.deepStrictEqual(result, { - a: { - value: null, - comment - }, - b: { - value: null, - comment - } - }); - }); - }); - - describe("parseListConfig", () => { - - it("should parse list config with one item", () => { - const code = "a"; - const result = commentParser.parseListConfig(code); - - assert.deepStrictEqual(result, { - a: true - }); - }); - - it("should parse list config with two items", () => { - const code = "a, b"; - const result = commentParser.parseListConfig(code); - - assert.deepStrictEqual(result, { - a: true, - b: true - }); - }); - - it("should parse list config with two items and extra whitespace", () => { - const code = " a , b "; - const result = commentParser.parseListConfig(code); - - assert.deepStrictEqual(result, { - a: true, - b: true - }); - }); - - it("should parse list config with quoted items", () => { - const code = "'a', \"b\", 'c\", \"d'"; - const result = commentParser.parseListConfig(code); - - assert.deepStrictEqual(result, { - a: true, - b: true, - "\"d'": true, // This result is correct because used mismatched quotes. - "'c\"": true // This result is correct because used mismatched quotes. - }); - }); - it("should parse list config with spaced items", () => { - const code = " a b , 'c d' , \"e f\" "; - const result = commentParser.parseListConfig(code); - - assert.deepStrictEqual(result, { - "a b": true, - "c d": true, - "e f": true - }); - }); - }); - - describe("parseDirective", () => { - - it("should parse a directive comment with a justification", () => { - const comment = { value: " eslint no-unused-vars: error -- test " }; - const result = commentParser.parseDirective(comment); - - assert.deepStrictEqual(result, { - directiveText: "eslint", - directiveValue: " no-unused-vars: error" - }); - }); - - it("should parse a directive comment without a justification", () => { - const comment = { value: "global foo" }; - const result = commentParser.parseDirective(comment); - - assert.deepStrictEqual(result, { - directiveText: "global", - directiveValue: " foo" - }); - }); - - }); - -}); diff --git a/tests/lib/linter/esquery.js b/tests/lib/linter/esquery.js new file mode 100644 index 000000000000..95eee5b28b23 --- /dev/null +++ b/tests/lib/linter/esquery.js @@ -0,0 +1,311 @@ +/** + * @fileoverview Tests for esquery + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("chai").assert; +const sinon = require("sinon"); +const esquery = require("esquery"); +const { + parse, + matches, + ESQueryParsedSelector, +} = require("../../../lib/linter/esquery"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("esquery", () => { + describe("ESQueryParsedSelector", () => { + describe("constructor", () => { + it("should store properties correctly", () => { + const selector = new ESQueryParsedSelector( + "Identifier", + false, + { type: "identifier", value: "Identifier" }, + ["Identifier"], + 1, + 2, + ); + + assert.strictEqual(selector.source, "Identifier"); + assert.strictEqual(selector.isExit, false); + assert.deepStrictEqual(selector.root, { + type: "identifier", + value: "Identifier", + }); + assert.deepStrictEqual(selector.nodeTypes, ["Identifier"]); + assert.strictEqual(selector.attributeCount, 1); + assert.strictEqual(selector.identifierCount, 2); + }); + }); + + describe("compare", () => { + it("should compare based on attributeCount first", () => { + const selector1 = new ESQueryParsedSelector( + "", + false, + {}, + [], + 2, + 1, + ); + const selector2 = new ESQueryParsedSelector( + "", + false, + {}, + [], + 1, + 1, + ); + + assert.isAbove(selector1.compare(selector2), 0); + assert.isBelow(selector2.compare(selector1), 0); + }); + + it("should compare based on identifierCount if attributeCount is equal", () => { + const selector1 = new ESQueryParsedSelector( + "", + false, + {}, + [], + 1, + 2, + ); + const selector2 = new ESQueryParsedSelector( + "", + false, + {}, + [], + 1, + 1, + ); + + assert.isAbove(selector1.compare(selector2), 0); + assert.isBelow(selector2.compare(selector1), 0); + }); + + it("should compare based on source if attributeCount and identifierCount are equal", () => { + const selector1 = new ESQueryParsedSelector( + "B", + false, + {}, + [], + 1, + 1, + ); + const selector2 = new ESQueryParsedSelector( + "A", + false, + {}, + [], + 1, + 1, + ); + + assert.isAbove(selector1.compare(selector2), 0); + assert.isBelow(selector2.compare(selector1), 0); + }); + + it("should return -1 if sources are equal", () => { + const selector1 = new ESQueryParsedSelector( + "A", + false, + {}, + [], + 1, + 1, + ); + const selector2 = new ESQueryParsedSelector( + "A", + false, + {}, + [], + 1, + 1, + ); + + assert.strictEqual(selector1.compare(selector2), -1); + }); + }); + }); + + describe("parse", () => { + it("should parse a simple selector", () => { + const result = parse("Identifier"); + + assert.instanceOf(result, ESQueryParsedSelector); + assert.strictEqual(result.source, "Identifier"); + assert.strictEqual(result.isExit, false); + assert.deepStrictEqual(result.nodeTypes, ["Identifier"]); + assert.strictEqual(result.attributeCount, 0); + assert.strictEqual(result.identifierCount, 1); + }); + + it("should parse a wildcard selector", () => { + const result = parse("*"); + assert.instanceOf(result, ESQueryParsedSelector); + assert.strictEqual(result.source, "*"); + assert.strictEqual(result.isExit, false); + assert.strictEqual(result.nodeTypes, null); + assert.strictEqual(result.attributeCount, 0); + assert.strictEqual(result.identifierCount, 0); + }); + + it("should recognize exit selectors", () => { + const result = parse("Identifier:exit"); + + assert.strictEqual(result.isExit, true); + assert.deepStrictEqual(result.nodeTypes, ["Identifier"]); + assert.strictEqual(result.attributeCount, 0); + assert.strictEqual(result.identifierCount, 1); + }); + + it("should use cached results when parsing the same selector twice", () => { + const result1 = parse("Identifier"); + const result2 = parse("Identifier"); + + assert.strictEqual(result1, result2); + assert.strictEqual(result1.attributeCount, 0); + assert.strictEqual(result1.identifierCount, 1); + }); + + it("should throw a useful error when parsing fails", () => { + assert.throws( + () => parse("[invalid"), + SyntaxError, + /Syntax error in selector/u, + ); + }); + + it("should return null nodeTypes for selectors that match any type", () => { + const result = parse("[attr]"); + assert.strictEqual(result.nodeTypes, null); + assert.strictEqual(result.attributeCount, 1); + assert.strictEqual(result.identifierCount, 0); + }); + + it("should handle compound selectors", () => { + const result = parse("FunctionDeclaration[id.name='foo']"); + assert.deepStrictEqual(result.nodeTypes, ["FunctionDeclaration"]); + assert.strictEqual(result.attributeCount, 1); + assert.strictEqual(result.identifierCount, 1); + }); + + it("should handle class selectors for functions", () => { + const result = parse(":function"); + assert.includeMembers(result.nodeTypes, [ + "FunctionDeclaration", + "FunctionExpression", + "ArrowFunctionExpression", + ]); + assert.strictEqual(result.attributeCount, 0); + assert.strictEqual(result.identifierCount, 0); + }); + + it("should handle class selectors for statements", () => { + const result = parse(":statement"); + + assert.strictEqual(result.nodeTypes, null); + assert.strictEqual(result.attributeCount, 0); + assert.strictEqual(result.identifierCount, 0); + }); + + it("should handle child selector with attribute selector", () => { + const result = parse("BinaryExpression > *[name='foo']"); + + assert.strictEqual(result.isExit, false); + assert.strictEqual(result.nodeTypes, null); + assert.strictEqual(result.attributeCount, 1); + assert.strictEqual(result.identifierCount, 1); + }); + + it("should handle not selector", () => { + const result = parse("*:not(ExpressionStatement)"); + + assert.strictEqual(result.isExit, false); + assert.strictEqual(result.nodeTypes, null); + assert.strictEqual(result.attributeCount, 0); + assert.strictEqual(result.identifierCount, 1); + }); + + it("should handle compound selector with matches", () => { + const result = parse( + ":matches(Identifier[name='foo'], Identifier[name='bar'], Identifier[name='baz'])", + ); + + assert.strictEqual(result.isExit, false); + assert.deepStrictEqual(result.nodeTypes, ["Identifier"]); + assert.strictEqual(result.attributeCount, 3); + assert.strictEqual(result.identifierCount, 3); + }); + }); + + describe("matches()", () => { + it("should delegate to esquery.matches", () => { + const matchesSpy = sinon.stub(esquery, "matches").returns(true); + const node = { type: "Identifier", name: "foo" }; + const root = { type: "identifier", value: "Identifier" }; + const ancestry = []; + const options = {}; + + const result = matches(node, root, ancestry, options); + + assert.strictEqual( + matchesSpy.calledOnceWith(node, root, ancestry, options), + true, + ); + assert.strictEqual(result, true); + + matchesSpy.restore(); + }); + }); + + describe("compare()", () => { + [ + ["Identifier", "Identifier", -1], + ["Identifier", "Identifier:exit", -1], + ["Identifier:exit", "Identifier", 1], + ["Identifier:exit", "Identifier:exit", -1], + ["Identifier", "Literal", -1], + ["Literal", "Identifier", 1], + ["Literal[name='foo']", "Literal[name='bar']", 1], + ["Literal[name='bar']", "Literal[name='foo']", -1], + ["Literal[name='foo']", "Literal[name='foo']", -1], + [ + "FunctionDeclaration[id.name='foo']", + "FunctionDeclaration[id.name='bar']", + 1, + ], + [ + "FunctionDeclaration[id.name='bar']", + "FunctionDeclaration[id.name='foo']", + -1, + ], + [ + "FunctionDeclaration[id.name='foo']", + "FunctionDeclaration[id.name='foo']", + -1, + ], + ["Identifier", "[name]", -1], + ["Identifier", "Identifier[name]", -1], + ].forEach(([selectorA, selectorB, expected]) => { + it(`compare "${selectorA}" to "${selectorB}" should return ${expected}`, () => { + const parsedSelectorA = parse(selectorA); + const parsedSelectorB = parse(selectorB); + + assert.strictEqual( + parsedSelectorA.compare(parsedSelectorB), + expected, + ); + }); + }); + }); +}); diff --git a/tests/lib/linter/file-context.js b/tests/lib/linter/file-context.js new file mode 100644 index 000000000000..1b9469636e00 --- /dev/null +++ b/tests/lib/linter/file-context.js @@ -0,0 +1,177 @@ +/** + * @fileoverview Tests for FileContext class. + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("chai").assert; +const { FileContext } = require("../../../lib/linter/file-context"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("FileContext", () => { + const mockSourceCode = {}; + const defaultConfig = { + cwd: "/path/to/project", + filename: "test.js", + physicalFilename: "/path/to/project/test.js", + sourceCode: mockSourceCode, + parserOptions: { ecmaVersion: 2021 }, + parserPath: "/path/to/parser", + languageOptions: { ecmaVersion: 2022 }, + settings: { env: { es6: true } }, + }; + + describe("constructor", () => { + it("should create a frozen instance with all properties set", () => { + const context = new FileContext(defaultConfig); + + assert.strictEqual(context.cwd, defaultConfig.cwd); + assert.strictEqual(context.filename, defaultConfig.filename); + assert.strictEqual( + context.physicalFilename, + defaultConfig.physicalFilename, + ); + assert.strictEqual(context.sourceCode, defaultConfig.sourceCode); + assert.deepStrictEqual( + context.parserOptions, + defaultConfig.parserOptions, + ); + assert.strictEqual(context.parserPath, defaultConfig.parserPath); + assert.deepStrictEqual( + context.languageOptions, + defaultConfig.languageOptions, + ); + assert.deepStrictEqual(context.settings, defaultConfig.settings); + + // Verify the instance is frozen + assert.throws(() => { + context.cwd = "changed"; + }, TypeError); + }); + + it("should allow partial configuration", () => { + const partialConfig = { + cwd: "/path/to/project", + filename: "test.js", + physicalFilename: "/path/to/project/test.js", + sourceCode: mockSourceCode, + }; + + const context = new FileContext(partialConfig); + + assert.strictEqual(context.cwd, partialConfig.cwd); + assert.strictEqual(context.filename, partialConfig.filename); + assert.strictEqual( + context.physicalFilename, + partialConfig.physicalFilename, + ); + assert.strictEqual(context.sourceCode, partialConfig.sourceCode); + assert.isUndefined(context.parserOptions); + assert.isUndefined(context.parserPath); + assert.isUndefined(context.languageOptions); + assert.isUndefined(context.settings); + }); + }); + + describe("deprecated methods", () => { + let context; + + beforeEach(() => { + context = new FileContext(defaultConfig); + }); + + it("getCwd() should return the cwd property", () => { + assert.strictEqual(context.getCwd(), context.cwd); + assert.strictEqual(context.getCwd(), defaultConfig.cwd); + }); + + it("getFilename() should return the filename property", () => { + assert.strictEqual(context.getFilename(), context.filename); + assert.strictEqual(context.getFilename(), defaultConfig.filename); + }); + + it("getPhysicalFilename() should return the physicalFilename property", () => { + assert.strictEqual( + context.getPhysicalFilename(), + context.physicalFilename, + ); + assert.strictEqual( + context.getPhysicalFilename(), + defaultConfig.physicalFilename, + ); + }); + + it("getSourceCode() should return the sourceCode property", () => { + assert.strictEqual(context.getSourceCode(), context.sourceCode); + assert.strictEqual( + context.getSourceCode(), + defaultConfig.sourceCode, + ); + }); + }); + + describe("extend()", () => { + let context; + + beforeEach(() => { + context = new FileContext(defaultConfig); + }); + + it("should create a new object with the original as prototype", () => { + const extension = { extraProperty: "extra" }; + const extended = context.extend(extension); + + // Verify new properties + assert.strictEqual(extended.extraProperty, "extra"); + + // Verify inherited properties + assert.strictEqual(extended.cwd, context.cwd); + assert.strictEqual(extended.filename, context.filename); + assert.strictEqual( + extended.physicalFilename, + context.physicalFilename, + ); + assert.strictEqual(extended.sourceCode, context.sourceCode); + assert.deepStrictEqual( + extended.parserOptions, + context.parserOptions, + ); + assert.strictEqual(extended.parserPath, context.parserPath); + assert.deepStrictEqual( + extended.languageOptions, + context.languageOptions, + ); + assert.deepStrictEqual(extended.settings, context.settings); + }); + + it("should freeze the extended object", () => { + const extension = { extraProperty: "extra" }; + const extended = context.extend(extension); + + // Verify the extended object is frozen + assert.throws(() => { + extended.cwd = "changed"; + }, TypeError); + + assert.throws(() => { + extended.extraProperty = "changed"; + }, TypeError); + }); + + it("should throw an error when attempting to override existing properties", () => { + const extension = { cwd: "newCwd" }; + + assert.throws(() => { + context.extend(extension); + }, TypeError); + }); + }); +}); diff --git a/tests/lib/linter/interpolate.js b/tests/lib/linter/interpolate.js index 04e7140956b3..bfaff9c90c23 100644 --- a/tests/lib/linter/interpolate.js +++ b/tests/lib/linter/interpolate.js @@ -5,26 +5,60 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert; -const interpolate = require("../../../lib/linter/interpolate"); +const { + getPlaceholderMatcher, + interpolate, +} = require("../../../lib/linter/interpolate"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ +describe("getPlaceholderMatcher", () => { + it("returns a global regular expression", () => { + const matcher = getPlaceholderMatcher(); + + assert.strictEqual(matcher.global, true); + }); + + it("matches text with placeholders", () => { + const matcher = getPlaceholderMatcher(); + + assert.match("{{ placeholder }}", matcher); + }); + + it("does not match text without placeholders", () => { + const matcher = getPlaceholderMatcher(); + + assert.notMatch("no placeholders in sight", matcher); + }); + + it("captures the text inside the placeholder", () => { + const matcher = getPlaceholderMatcher(); + const text = "{{ placeholder }}"; + const matches = Array.from(text.matchAll(matcher)); + + assert.deepStrictEqual(matches, [[text, " placeholder "]]); + }); +}); + describe("interpolate()", () => { - it("passes through text without {{ }}", () => { - const message = "This is a very important message!"; - - assert.strictEqual(interpolate(message, {}), message); - }); - it("passes through text with {{ }} that don’t match a key", () => { - const message = "This is a very important {{ message }}!"; - - assert.strictEqual(interpolate(message, {}), message); - }); - it("Properly interpolates keys in {{ }}", () => { - assert.strictEqual(interpolate("This is a very important {{ message }}!", { - message: "test" - }), "This is a very important test!"); - }); + it("passes through text without {{ }}", () => { + const message = "This is a very important message!"; + + assert.strictEqual(interpolate(message, {}), message); + }); + it("passes through text with {{ }} that don’t match a key", () => { + const message = "This is a very important {{ message }}!"; + + assert.strictEqual(interpolate(message, {}), message); + }); + it("Properly interpolates keys in {{ }}", () => { + assert.strictEqual( + interpolate("This is a very important {{ message }}!", { + message: "test", + }), + "This is a very important test!", + ); + }); }); diff --git a/tests/lib/linter/linter.js b/tests/lib/linter/linter.js index dec2aabb67d5..eb4595b73dd8 100644 --- a/tests/lib/linter/linter.js +++ b/tests/lib/linter/linter.js @@ -10,20 +10,29 @@ //------------------------------------------------------------------------------ const { assert } = require("chai"), - sinon = require("sinon"), - espree = require("espree"), - esprima = require("esprima"), - testParsers = require("../../fixtures/parsers/linter-test-parsers"); + sinon = require("sinon"), + espree = require("espree"), + esprima = require("esprima"), + testParsers = require("../../fixtures/parsers/linter-test-parsers"); const { Linter } = require("../../../lib/linter"); const { FlatConfigArray } = require("../../../lib/config/flat-config-array"); +const { SourceCode } = require("../../../lib/languages/js/source-code"); +const jslang = require("../../../lib/languages/js"); +const Traverser = require("../../../lib/shared/traverser"); +const { WarningService } = require("../../../lib/services/warning-service"); +const { LATEST_ECMA_VERSION } = require("../../../conf/ecma-version"); + +// In Node.js, `jsonPluginPackage.default` is the plugin. In the browser test, `jsonPluginPackage` is the plugin. +const jsonPluginPackage = require("@eslint/json"); +const jsonPlugin = jsonPluginPackage.default || jsonPluginPackage; //------------------------------------------------------------------------------ // Constants //------------------------------------------------------------------------------ const TEST_CODE = "var answer = 6 * 7;", - BROKEN_TEST_CODE = "var;"; + BROKEN_TEST_CODE = "var;"; //------------------------------------------------------------------------------ // Helpers @@ -37,7 +46,7 @@ const TEST_CODE = "var answer = 6 * 7;", * @private */ function getVariable(scope, name) { - return scope.variables.find(v => v.name === name) || null; + return scope.variables.find(v => v.name === name) || null; } /** @@ -53,5213 +62,5372 @@ const ESLINT_ENV = "eslint-env"; //------------------------------------------------------------------------------ describe("Linter", () => { - const filename = "filename.js"; - - /** @type {InstanceType} */ - let linter; - - beforeEach(() => { - linter = new Linter(); - }); - - afterEach(() => { - sinon.verifyAndRestore(); - }); - - describe("Static Members", () => { - describe("version", () => { - it("should return same version as instance property", () => { - assert.strictEqual(Linter.version, linter.version); - }); - }); - }); - - describe("when using events", () => { - const code = TEST_CODE; - - it("an error should be thrown when an error occurs inside of an event handler", () => { - const config = { rules: { checker: "error" } }; - - linter.defineRule("checker", { - create: () => ({ - Program() { - throw new Error("Intentional error."); - } - }) - }); - - assert.throws(() => { - linter.verify(code, config, filename); - }, `Intentional error.\nOccurred while linting ${filename}:1\nRule: "checker"`); - }); - - it("does not call rule listeners with a `this` value", () => { - const spy = sinon.spy(); - - linter.defineRule("checker", { - create: () => ({ Program: spy }) - }); - linter.verify("foo", { rules: { checker: "error" } }); - assert(spy.calledOnce, "Rule should have been called"); - assert.strictEqual(spy.firstCall.thisValue, void 0, "this value should be undefined"); - }); - - it("does not allow listeners to use special EventEmitter values", () => { - const spy = sinon.spy(); - - linter.defineRule("checker", { - create: () => ({ newListener: spy }) - }); - linter.verify("foo", { rules: { checker: "error", "no-undef": "error" } }); - assert(spy.notCalled); - }); - - it("has all the `parent` properties on nodes when the rule listeners are created", () => { - const spy = sinon.spy(context => { - assert.strictEqual(context.getSourceCode(), context.sourceCode); - const ast = context.sourceCode.ast; - - assert.strictEqual(ast.body[0].parent, ast); - assert.strictEqual(ast.body[0].expression.parent, ast.body[0]); - assert.strictEqual(ast.body[0].expression.left.parent, ast.body[0].expression); - assert.strictEqual(ast.body[0].expression.right.parent, ast.body[0].expression); - - return {}; - }); - - linter.defineRule("checker", { create: spy }); - - linter.verify("foo + bar", { rules: { checker: "error" } }); - assert(spy.calledOnce); - }); - }); - - describe("context.getSourceLines()", () => { - - it("should get proper lines when using \\n as a line break", () => { - const code = "a;\nb;"; - const spy = sinon.spy(context => { - assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); - return {}; - }); - - linter.defineRule("checker", { create: spy }); - linter.verify(code, { rules: { checker: "error" } }); - assert(spy.calledOnce); - }); - - it("should get proper lines when using \\r\\n as a line break", () => { - const code = "a;\r\nb;"; - const spy = sinon.spy(context => { - assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); - return {}; - }); - - linter.defineRule("checker", { create: spy }); - linter.verify(code, { rules: { checker: "error" } }); - assert(spy.calledOnce); - }); - - it("should get proper lines when using \\r as a line break", () => { - const code = "a;\rb;"; - const spy = sinon.spy(context => { - assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); - return {}; - }); - - linter.defineRule("checker", { create: spy }); - linter.verify(code, { rules: { checker: "error" } }); - assert(spy.calledOnce); - }); - - it("should get proper lines when using \\u2028 as a line break", () => { - const code = "a;\u2028b;"; - const spy = sinon.spy(context => { - assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); - return {}; - }); - - linter.defineRule("checker", { create: spy }); - linter.verify(code, { rules: { checker: "error" } }); - assert(spy.calledOnce); - }); - - it("should get proper lines when using \\u2029 as a line break", () => { - const code = "a;\u2029b;"; - const spy = sinon.spy(context => { - assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); - return {}; - }); - - linter.defineRule("checker", { create: spy }); - linter.verify(code, { rules: { checker: "error" } }); - assert(spy.calledOnce); - }); - - - }); - - describe("getSourceCode()", () => { - const code = TEST_CODE; - - it("should retrieve SourceCode object after reset", () => { - linter.verify(code, {}, filename, true); - - const sourceCode = linter.getSourceCode(); - - assert.isObject(sourceCode); - assert.strictEqual(sourceCode.text, code); - assert.isObject(sourceCode.ast); - }); - - it("should retrieve SourceCode object without reset", () => { - linter.verify(code, {}, filename); - - const sourceCode = linter.getSourceCode(); - - assert.isObject(sourceCode); - assert.strictEqual(sourceCode.text, code); - assert.isObject(sourceCode.ast); - }); - - }); - - describe("getSuppressedMessages()", () => { - it("should have no suppressed messages", () => { - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should have a suppressed message", () => { - const code = "/* eslint-disable no-alert -- justification */\nalert(\"test\");"; - const config = { - rules: { "no-alert": 1 } - }; - const messages = linter.verify(code, config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.deepStrictEqual( - suppressedMessages[0].suppressions, - [{ kind: "directive", justification: "justification" }] - ); - }); - - it("should have a suppressed message", () => { - const code = [ - "/* eslint-disable no-alert --- j1", - " * --- j2", - " */", - "alert(\"test\");" - ].join("\n"); - const config = { - rules: { "no-alert": 1 } - }; - const messages = linter.verify(code, config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.deepStrictEqual( - suppressedMessages[0].suppressions, - [{ kind: "directive", justification: "j1\n * --- j2" }] - ); - }); - - it("should not report a lint message", () => { - const code = [ - "/* eslint-disable -- j1 */", - "// eslint-disable-next-line -- j2", - "alert(\"test\");" - ].join("\n"); - const config = { - rules: { "no-alert": 1 } - }; - const messages = linter.verify(code, config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.deepStrictEqual( - suppressedMessages[0].suppressions, - [ - { kind: "directive", justification: "j1" }, - { kind: "directive", justification: "j2" } - ] - ); - }); - - it("should not report a lint message", () => { - const code = [ - "/* eslint-disable -- j1 */", - "alert(\"test\"); // eslint-disable-line -- j2" - ].join("\n"); - const config = { - rules: { "no-alert": 1 } - }; - const messages = linter.verify(code, config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.deepStrictEqual( - suppressedMessages[0].suppressions, - [ - { kind: "directive", justification: "j1" }, - { kind: "directive", justification: "j2" } - ] - ); - }); - - it("should have a suppressed message with multiple suppressions", () => { - const code = [ - "/* eslint-disable no-alert -- j1 */", - "/* eslint-disable no-console -- unused */", - "/* eslint-disable-next-line no-alert -- j2 */", - "alert(\"test\"); // eslint-disable-line no-alert -- j3" - ].join("\n"); - const config = { - rules: { "no-alert": 1 } - }; - const messages = linter.verify(code, config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.deepStrictEqual( - suppressedMessages[0].suppressions, - [ - { kind: "directive", justification: "j1" }, - { kind: "directive", justification: "j2" }, - { kind: "directive", justification: "j3" } - ] - ); - }); - }); - - describe("context.getSource()", () => { - const code = TEST_CODE; - - it("should retrieve all text when used without parameters", () => { - - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - assert.strictEqual(context.getSource(), TEST_CODE); - }); - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve all text for root node", () => { - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), TEST_CODE); - }); - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should clamp to valid range when retrieving characters before start of source", () => { - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2, 0), TEST_CODE); - }); - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve all text for binary expression", () => { - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), "6 * 7"); - }); - return { BinaryExpression: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve all text plus two characters before for binary expression", () => { - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2), "= 6 * 7"); - }); - return { BinaryExpression: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve all text plus one character after for binary expression", () => { - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;"); - }); - return { BinaryExpression: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve all text plus two characters before and one character after for binary expression", () => { - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2, 1), "= 6 * 7;"); - }); - return { BinaryExpression: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - }); - - describe("when calling context.getAncestors", () => { - const code = TEST_CODE; - - it("should retrieve all ancestors when used", () => { - - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const ancestors = context.getAncestors(); - - assert.strictEqual(ancestors.length, 3); - }); - return { BinaryExpression: spy }; - } - }); - - linter.verify(code, config, filename, true); - assert(spy && spy.calledOnce); - }); - - it("should retrieve empty ancestors for root node", () => { - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const ancestors = context.getAncestors(); - - assert.strictEqual(ancestors.length, 0); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("when calling context.getNodeByRangeIndex", () => { - const code = TEST_CODE; - - it("should retrieve a node starting at the given index", () => { - const config = { rules: { checker: "error" } }; - const spy = sinon.spy(context => { - assert.strictEqual(context.getNodeByRangeIndex(4).type, "Identifier"); - return {}; - }); - - linter.defineRule("checker", { create: spy }); - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should retrieve a node containing the given index", () => { - const config = { rules: { checker: "error" } }; - const spy = sinon.spy(context => { - assert.strictEqual(context.getNodeByRangeIndex(6).type, "Identifier"); - return {}; - }); - - linter.defineRule("checker", { create: spy }); - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should retrieve a node that is exactly the given index", () => { - const config = { rules: { checker: "error" } }; - const spy = sinon.spy(context => { - const node = context.getNodeByRangeIndex(13); - - assert.strictEqual(node.type, "Literal"); - assert.strictEqual(node.value, 6); - return {}; - }); - - linter.defineRule("checker", { create: spy }); - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should retrieve a node ending with the given index", () => { - const config = { rules: { checker: "error" } }; - const spy = sinon.spy(context => { - assert.strictEqual(context.getNodeByRangeIndex(9).type, "Identifier"); - return {}; - }); - - linter.defineRule("checker", { create: spy }); - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should retrieve the deepest node containing the given index", () => { - const config = { rules: { checker: "error" } }; - const spy = sinon.spy(context => { - const node1 = context.getNodeByRangeIndex(14); - - assert.strictEqual(node1.type, "BinaryExpression"); - - const node2 = context.getNodeByRangeIndex(3); - - assert.strictEqual(node2.type, "VariableDeclaration"); - return {}; - }); - - linter.defineRule("checker", { create: spy }); - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should return null if the index is outside the range of any node", () => { - const config = { rules: { checker: "error" } }; - const spy = sinon.spy(context => { - const node1 = context.getNodeByRangeIndex(-1); - - assert.isNull(node1); - - const node2 = context.getNodeByRangeIndex(-99); - - assert.isNull(node2); - return {}; - }); - - linter.defineRule("checker", { create: spy }); - linter.verify(code, config); - assert(spy.calledOnce); - }); - }); - - - describe("when calling context.getScope", () => { - const code = "function foo() { q: for(;;) { break q; } } function bar () { var q = t; } var baz = (() => { return 1; });"; - - it("should retrieve the global scope correctly from a Program", () => { - const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "global"); - }); - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the function scope correctly from a FunctionDeclaration", () => { - const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "function"); - }); - return { FunctionDeclaration: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledTwice); - }); - - it("should retrieve the function scope correctly from a LabeledStatement", () => { - const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.id.name, "foo"); - }); - return { LabeledStatement: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the function scope correctly from within an ArrowFunctionExpression", () => { - const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "ArrowFunctionExpression"); - }); - - return { ReturnStatement: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the function scope correctly from within an SwitchStatement", () => { - const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "switch"); - assert.strictEqual(scope.block.type, "SwitchStatement"); - }); - - return { SwitchStatement: spy }; - } - }); - - linter.verify("switch(foo){ case 'a': var b = 'foo'; }", config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the function scope correctly from within a BlockStatement", () => { - const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.block.type, "BlockStatement"); - }); - - return { BlockStatement: spy }; - } - }); - - linter.verify("var x; {let y = 1}", config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the function scope correctly from within a nested block statement", () => { - const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.block.type, "BlockStatement"); - }); - - return { BlockStatement: spy }; - } - }); - - linter.verify("if (true) { let x = 1 }", config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the function scope correctly from within a FunctionDeclaration", () => { - const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "FunctionDeclaration"); - }); - - return { FunctionDeclaration: spy }; - } - }); - - linter.verify("function foo() {}", config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the function scope correctly from within a FunctionExpression", () => { - const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "FunctionExpression"); - }); - - return { FunctionExpression: spy }; - } - }); - - linter.verify("(function foo() {})();", config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the catch scope correctly from within a CatchClause", () => { - const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6 } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "catch"); - assert.strictEqual(scope.block.type, "CatchClause"); - }); - - return { CatchClause: spy }; - } - }); - - linter.verify("try {} catch (err) {}", config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve module scope correctly from an ES6 module", () => { - const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "module"); - }); - - return { AssignmentExpression: spy }; - } - }); - - linter.verify("var foo = {}; foo.bar = 1;", config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve function scope correctly when globalReturn is true", () => { - const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, ecmaFeatures: { globalReturn: true } } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "function"); - }); - - return { AssignmentExpression: spy }; - } - }); - - linter.verify("var foo = {}; foo.bar = 1;", config); - assert(spy && spy.calledOnce); - }); - }); - - describe("marking variables as used", () => { - it("should mark variables in current scope as used", () => { - const code = "var a = 1, b = 2;"; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); - - const scope = context.getScope(); - - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); - - return { "Program:exit": spy }; - } - }); - - linter.verify(code, { rules: { checker: "error" } }); - assert(spy && spy.calledOnce); - }); - it("should mark variables in function args as used", () => { - const code = "function abc(a, b) { return 1; }"; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); - - const scope = context.getScope(); - - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); - - return { ReturnStatement: spy }; - } - }); - - linter.verify(code, { rules: { checker: "error" } }); - assert(spy && spy.calledOnce); - }); - it("should mark variables in higher scopes as used", () => { - const code = "var a, b; function abc() { return 1; }"; - let returnSpy, exitSpy; - - linter.defineRule("checker", { - create(context) { - returnSpy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); - }); - exitSpy = sinon.spy(() => { - const scope = context.getScope(); - - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); - - return { ReturnStatement: returnSpy, "Program:exit": exitSpy }; - } - }); - - linter.verify(code, { rules: { checker: "error" } }); - assert(returnSpy && returnSpy.calledOnce); - assert(exitSpy && exitSpy.calledOnce); - }); - - it("should mark variables in Node.js environment as used", () => { - const code = "var a = 1, b = 2;"; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const globalScope = context.getScope(), - childScope = globalScope.childScopes[0]; - - assert.isTrue(context.markVariableAsUsed("a")); - - assert.isTrue(getVariable(childScope, "a").eslintUsed); - assert.isUndefined(getVariable(childScope, "b").eslintUsed); - }); - - return { "Program:exit": spy }; - } - }); - - linter.verify(code, { rules: { checker: "error" }, env: { node: true } }); - assert(spy && spy.calledOnce); - }); - - it("should mark variables in modules as used", () => { - const code = "var a = 1, b = 2;"; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const globalScope = context.getScope(), - childScope = globalScope.childScopes[0]; - - assert.isTrue(context.markVariableAsUsed("a")); - - assert.isTrue(getVariable(childScope, "a").eslintUsed); - assert.isUndefined(getVariable(childScope, "b").eslintUsed); - }); - - return { "Program:exit": spy }; - } - }); - - linter.verify(code, { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }, filename, true); - assert(spy && spy.calledOnce); - }); - - it("should return false if the given variable is not found", () => { - const code = "var a = 1, b = 2;"; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - assert.isFalse(context.markVariableAsUsed("c")); - }); - - return { "Program:exit": spy }; - } - }); - - linter.verify(code, { rules: { checker: "error" } }); - assert(spy && spy.calledOnce); - }); - }); - - describe("when evaluating code", () => { - const code = TEST_CODE; - - it("events for each node type should fire", () => { - const config = { rules: { checker: "error" } }; - - // spies for various AST node types - const spyLiteral = sinon.spy(), - spyVariableDeclarator = sinon.spy(), - spyVariableDeclaration = sinon.spy(), - spyIdentifier = sinon.spy(), - spyBinaryExpression = sinon.spy(); - - linter.defineRule("checker", { - create: () => ({ - Literal: spyLiteral, - VariableDeclarator: spyVariableDeclarator, - VariableDeclaration: spyVariableDeclaration, - Identifier: spyIdentifier, - BinaryExpression: spyBinaryExpression - }) - }); - - const messages = linter.verify(code, config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - sinon.assert.calledOnce(spyVariableDeclaration); - sinon.assert.calledOnce(spyVariableDeclarator); - sinon.assert.calledOnce(spyIdentifier); - sinon.assert.calledTwice(spyLiteral); - sinon.assert.calledOnce(spyBinaryExpression); - }); - - it("should throw an error if a rule reports a problem without a message", () => { - linter.defineRule("invalid-report", { - create: context => ({ - Program(node) { - context.report({ node }); - } - }) - }); - - assert.throws( - () => linter.verify("foo", { rules: { "invalid-report": "error" } }), - TypeError, - "Missing `message` property in report() call; add a message that describes the linting problem." - ); - }); - }); - - describe("when config has shared settings for rules", () => { - const code = "test-rule"; - - it("should pass settings to all rules", () => { - linter.defineRule(code, { - create: context => ({ - Literal(node) { - context.report(node, context.settings.info); - } - }) - }); - - const config = { rules: {}, settings: { info: "Hello" } }; - - config.rules[code] = 1; - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "Hello"); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not have any settings if they were not passed in", () => { - linter.defineRule(code, { - create: context => ({ - Literal(node) { - if (Object.getOwnPropertyNames(context.settings).length !== 0) { - context.report(node, "Settings should be empty"); - } - } - }) - }); - - const config = { rules: {} }; - - config.rules[code] = 1; - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when config has parseOptions", () => { - - it("should pass ecmaFeatures to all rules when provided on config", () => { - - const parserOptions = { - ecmaFeatures: { - jsx: true, - globalReturn: true - } - }; - - linter.defineRule("test-rule", { - create: sinon.mock().withArgs( - sinon.match({ parserOptions }) - ).returns({}) - }); - - const config = { rules: { "test-rule": 2 }, parserOptions }; - - linter.verify("0", config, filename); - }); - - it("should pass parserOptions to all rules when default parserOptions is used", () => { - - const parserOptions = {}; - - linter.defineRule("test-rule", { - create: sinon.mock().withArgs( - sinon.match({ parserOptions }) - ).returns({}) - }); - - const config = { rules: { "test-rule": 2 } }; - - linter.verify("0", config, filename); - }); - - }); - - describe("when a custom parser is defined using defineParser", () => { - - it("should be able to define a custom parser", () => { - const parser = { - parseForESLint: function parse(code, options) { - return { - ast: esprima.parse(code, options), - services: { - test: { - getMessage() { - return "Hi!"; - } - } - } - }; - } - }; - - linter.defineParser("test-parser", parser); - const config = { rules: {}, parser: "test-parser" }; - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - }); - - describe("when config has parser", () => { - - it("should pass parser as parserPath to all rules when provided on config", () => { - - const alternateParser = "esprima"; - - linter.defineParser("esprima", esprima); - linter.defineRule("test-rule", { - create: sinon.mock().withArgs( - sinon.match({ parserPath: alternateParser }) - ).returns({}) - }); - - const config = { rules: { "test-rule": 2 }, parser: alternateParser }; - - linter.verify("0", config, filename); - }); - - it("should use parseForESLint() in custom parser when custom parser is specified", () => { - const config = { rules: {}, parser: "enhanced-parser" }; - - linter.defineParser("enhanced-parser", testParsers.enhancedParser); - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should expose parser services when using parseForESLint() and services are specified", () => { - linter.defineParser("enhanced-parser", testParsers.enhancedParser); - linter.defineRule("test-service-rule", { - create: context => ({ - Literal(node) { - assert.strictEqual(context.parserServices, context.sourceCode.parserServices); - context.report({ - node, - message: context.sourceCode.parserServices.test.getMessage() - }); - } - }) - }); - - const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" }; - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "Hi!"); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should use the same parserServices if source code object is reused", () => { - linter.defineParser("enhanced-parser", testParsers.enhancedParser); - linter.defineRule("test-service-rule", { - create: context => ({ - Literal(node) { - assert.strictEqual(context.parserServices, context.sourceCode.parserServices); - context.report({ - node, - message: context.sourceCode.parserServices.test.getMessage() - }); - } - }) - }); - - const config = { rules: { "test-service-rule": 2 }, parser: "enhanced-parser" }; - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "Hi!"); - assert.strictEqual(suppressedMessages.length, 0); - - const messages2 = linter.verify(linter.getSourceCode(), config, filename); - const suppressedMessages2 = linter.getSuppressedMessages(); - - assert.strictEqual(messages2.length, 1); - assert.strictEqual(messages2[0].message, "Hi!"); - assert.strictEqual(suppressedMessages2.length, 0); - }); - - it("should pass parser as parserPath to all rules when default parser is used", () => { - linter.defineRule("test-rule", { - create: sinon.mock().withArgs( - sinon.match({ parserPath: "espree" }) - ).returns({}) - }); - - const config = { rules: { "test-rule": 2 } }; - - linter.verify("0", config, filename); - }); - - }); - - - describe("when passing in configuration values for rules", () => { - const code = "var answer = 6 * 7"; - - it("should be configurable by only setting the integer value", () => { - const rule = "semi", - config = { rules: {} }; - - config.rules[rule] = 1; - - const messages = linter.verify(code, config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, rule); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should be configurable by only setting the string value", () => { - const rule = "semi", - config = { rules: {} }; - - config.rules[rule] = "warn"; - - const messages = linter.verify(code, config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); - assert.strictEqual(messages[0].ruleId, rule); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should be configurable by passing in values as an array", () => { - const rule = "semi", - config = { rules: {} }; - - config.rules[rule] = [1]; - - const messages = linter.verify(code, config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, rule); - - assert.strictEqual(suppressedMessages.length, 0); - }); + const filename = "filename.js"; + + /** @type {InstanceType} */ + let linter; + + let warningService; + + beforeEach(() => { + warningService = sinon.stub(new WarningService()); + linter = new Linter({ configType: "eslintrc", warningService }); + }); + + afterEach(() => { + sinon.verifyAndRestore(); + }); + + describe("Static Members", () => { + describe("version", () => { + it("should return same version as instance property", () => { + assert.strictEqual(Linter.version, linter.version); + }); + }); + }); + + describe("when using events", () => { + const code = TEST_CODE; + + it("an error should be thrown when an error occurs inside of an event handler", () => { + const config = { rules: { checker: "error" } }; + + linter.defineRule("checker", { + create: () => ({ + Program() { + throw new Error("Intentional error."); + }, + }), + }); + + assert.throws(() => { + linter.verify(code, config, filename); + }, `Intentional error.\nOccurred while linting ${filename}:1\nRule: "checker"`); + }); + + it("does not call rule listeners with a `this` value", () => { + const spy = sinon.spy(); + + linter.defineRule("checker", { + create: () => ({ Program: spy }), + }); + linter.verify("foo", { rules: { checker: "error" } }); + assert(spy.calledOnce, "Rule should have been called"); + assert.strictEqual( + spy.firstCall.thisValue, + void 0, + "this value should be undefined", + ); + }); + + it("does not allow listeners to use special EventEmitter values", () => { + const spy = sinon.spy(); + + linter.defineRule("checker", { + create: () => ({ newListener: spy }), + }); + linter.verify("foo", { + rules: { checker: "error", "no-undef": "error" }, + }); + assert(spy.notCalled); + }); + + it("has all the `parent` properties on nodes when the rule listeners are created", () => { + const spy = sinon.spy(context => { + assert.strictEqual(context.getSourceCode(), context.sourceCode); + const ast = context.sourceCode.ast; + + assert.strictEqual(ast.body[0].parent, ast); + assert.strictEqual(ast.body[0].expression.parent, ast.body[0]); + assert.strictEqual( + ast.body[0].expression.left.parent, + ast.body[0].expression, + ); + assert.strictEqual( + ast.body[0].expression.right.parent, + ast.body[0].expression, + ); + + return {}; + }); + + linter.defineRule("checker", { create: spy }); + + linter.verify("foo + bar", { rules: { checker: "error" } }); + assert(spy.calledOnce); + }); + }); + + describe("getSourceCode()", () => { + const code = TEST_CODE; + + it("should retrieve SourceCode object after reset", () => { + linter.verify(code, {}, filename); + + const sourceCode = linter.getSourceCode(); + + assert.isObject(sourceCode); + assert.strictEqual(sourceCode.text, code); + assert.isObject(sourceCode.ast); + }); + + it("should retrieve SourceCode object without reset", () => { + linter.verify(code, {}, filename); + + const sourceCode = linter.getSourceCode(); + + assert.isObject(sourceCode); + assert.strictEqual(sourceCode.text, code); + assert.isObject(sourceCode.ast); + }); + }); + + describe("getSuppressedMessages()", () => { + it("should have no suppressed messages", () => { + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should have a suppressed message", () => { + const code = + '/* eslint-disable no-alert -- justification */\nalert("test");'; + const config = { + rules: { "no-alert": 1 }, + }; + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.deepStrictEqual(suppressedMessages[0].suppressions, [ + { kind: "directive", justification: "justification" }, + ]); + }); + + it("should have a suppressed message", () => { + const code = [ + "/* eslint-disable no-alert --- j1", + " * --- j2", + " */", + 'alert("test");', + ].join("\n"); + const config = { + rules: { "no-alert": 1 }, + }; + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.deepStrictEqual(suppressedMessages[0].suppressions, [ + { kind: "directive", justification: "j1\n * --- j2" }, + ]); + }); + + it("should not report a lint message", () => { + const code = [ + "/* eslint-disable -- j1 */", + "// eslint-disable-next-line -- j2", + 'alert("test");', + ].join("\n"); + const config = { + rules: { "no-alert": 1 }, + }; + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.deepStrictEqual(suppressedMessages[0].suppressions, [ + { kind: "directive", justification: "j1" }, + { kind: "directive", justification: "j2" }, + ]); + }); + + it("should not report a lint message", () => { + const code = [ + "/* eslint-disable -- j1 */", + 'alert("test"); // eslint-disable-line -- j2', + ].join("\n"); + const config = { + rules: { "no-alert": 1 }, + }; + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.deepStrictEqual(suppressedMessages[0].suppressions, [ + { kind: "directive", justification: "j1" }, + { kind: "directive", justification: "j2" }, + ]); + }); + + it("should have a suppressed message with multiple suppressions", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + "/* eslint-disable no-console -- unused */", + "/* eslint-disable-next-line no-alert -- j2 */", + 'alert("test"); // eslint-disable-line no-alert -- j3', + ].join("\n"); + const config = { + rules: { "no-alert": 1 }, + }; + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.deepStrictEqual(suppressedMessages[0].suppressions, [ + { kind: "directive", justification: "j1" }, + { kind: "directive", justification: "j2" }, + { kind: "directive", justification: "j3" }, + ]); + }); + }); + + describe("when evaluating code", () => { + const code = TEST_CODE; + + it("events for each node type should fire", () => { + const config = { rules: { checker: "error" } }; + + // spies for various AST node types + const spyLiteral = sinon.spy(), + spyVariableDeclarator = sinon.spy(), + spyVariableDeclaration = sinon.spy(), + spyIdentifier = sinon.spy(), + spyBinaryExpression = sinon.spy(); + + linter.defineRule("checker", { + create: () => ({ + Literal: spyLiteral, + VariableDeclarator: spyVariableDeclarator, + VariableDeclaration: spyVariableDeclaration, + Identifier: spyIdentifier, + BinaryExpression: spyBinaryExpression, + }), + }); + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + sinon.assert.calledOnce(spyVariableDeclaration); + sinon.assert.calledOnce(spyVariableDeclarator); + sinon.assert.calledOnce(spyIdentifier); + sinon.assert.calledTwice(spyLiteral); + sinon.assert.calledOnce(spyBinaryExpression); + }); + + it("should throw an error if a rule is a function", () => { + /** + * Legacy-format rule (a function instead of an object with `create` method). + * @param {RuleContext} context The ESLint rule context object. + * @returns {Object} Listeners. + */ + function functionStyleRule(context) { + return { + Program(node) { + context.report({ node, message: "bad" }); + }, + }; + } + + linter.defineRule("function-style-rule", functionStyleRule); + + assert.throws( + () => + linter.verify("foo", { + rules: { "function-style-rule": "error" }, + }), + TypeError, + "Error while loading rule 'function-style-rule': Rule must be an object with a `create` method", + ); + }); + + it("should throw an error if a rule is an object without 'create' method", () => { + const rule = { + create_(context) { + return { + Program(node) { + context.report({ node, message: "bad" }); + }, + }; + }, + }; + + linter.defineRule("object-rule-without-create", rule); + + assert.throws( + () => + linter.verify("foo", { + rules: { "object-rule-without-create": "error" }, + }), + TypeError, + "Error while loading rule 'object-rule-without-create': Rule must be an object with a `create` method", + ); + }); + + it("should throw an error if a rule with invalid `meta.schema` is enabled in a configuration comment", () => { + const rule = { + meta: { + schema: true, + }, + create() { + return {}; + }, + }; + + linter.defineRule("rule-with-invalid-schema", rule); + + assert.throws( + () => linter.verify("/* eslint rule-with-invalid-schema: 2 */"), + "Error while processing options validation schema of rule 'rule-with-invalid-schema': Rule's `meta.schema` must be an array or object", + ); + }); + + it("should throw an error if a rule reports a problem without a message", () => { + linter.defineRule("invalid-report", { + create: context => ({ + Program(node) { + context.report({ node }); + }, + }), + }); + + assert.throws( + () => + linter.verify("foo", { + rules: { "invalid-report": "error" }, + }), + TypeError, + "Missing `message` property in report() call; add a message that describes the linting problem.", + ); + }); + }); + + describe("when config has shared settings for rules", () => { + const code = "test-rule"; + + it("should pass settings to all rules", () => { + linter.defineRule(code, { + create: context => ({ + Literal(node) { + context.report(node, context.settings.info); + }, + }), + }); + + const config = { rules: {}, settings: { info: "Hello" } }; + + config.rules[code] = 1; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].message, "Hello"); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not have any settings if they were not passed in", () => { + linter.defineRule(code, { + create: context => ({ + Literal(node) { + if ( + Object.getOwnPropertyNames(context.settings) + .length !== 0 + ) { + context.report(node, "Settings should be empty"); + } + }, + }), + }); + + const config = { rules: {} }; + + config.rules[code] = 1; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when config has parseOptions", () => { + it("should pass ecmaFeatures to all rules when provided on config", () => { + const parserOptions = { + ecmaFeatures: { + jsx: true, + globalReturn: true, + }, + }; + + linter.defineRule("test-rule", { + create: sinon + .mock() + .withArgs(sinon.match({ parserOptions })) + .returns({}), + }); + + const config = { rules: { "test-rule": 2 }, parserOptions }; + + linter.verify("0", config, filename); + }); + + it("should pass parserOptions to all rules when default parserOptions is used", () => { + const parserOptions = {}; + + linter.defineRule("test-rule", { + create: sinon + .mock() + .withArgs(sinon.match({ parserOptions })) + .returns({}), + }); + + const config = { rules: { "test-rule": 2 } }; + + linter.verify("0", config, filename); + }); + }); + + describe("when a custom parser is defined using defineParser", () => { + it("should be able to define a custom parser", () => { + const parser = { + parseForESLint: function parse(code, options) { + return { + ast: esprima.parse(code, options), + services: { + test: { + getMessage() { + return "Hi!"; + }, + }, + }, + }; + }, + }; + + linter.defineParser("test-parser", parser); + const config = { rules: {}, parser: "test-parser" }; + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when config has parser", () => { + it("should pass parser as parserPath to all rules when provided on config", () => { + const alternateParser = "esprima"; + + linter.defineParser("esprima", esprima); + linter.defineRule("test-rule", { + create: sinon + .mock() + .withArgs(sinon.match({ parserPath: alternateParser })) + .returns({}), + }); + + const config = { + rules: { "test-rule": 2 }, + parser: alternateParser, + }; + + linter.verify("0", config, filename); + }); + + it("should use parseForESLint() in custom parser when custom parser is specified", () => { + const config = { rules: {}, parser: "enhanced-parser" }; + + linter.defineParser("enhanced-parser", testParsers.enhancedParser); + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should expose parser services when using parseForESLint() and services are specified", () => { + linter.defineParser("enhanced-parser", testParsers.enhancedParser); + linter.defineRule("test-service-rule", { + create: context => ({ + Literal(node) { + context.report({ + node, + message: + context.sourceCode.parserServices.test.getMessage(), + }); + }, + }), + }); + + const config = { + rules: { "test-service-rule": 2 }, + parser: "enhanced-parser", + }; + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].message, "Hi!"); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should use the same parserServices if source code object is reused", () => { + linter.defineParser("enhanced-parser", testParsers.enhancedParser); + linter.defineRule("test-service-rule", { + create: context => ({ + Literal(node) { + context.report({ + node, + message: + context.sourceCode.parserServices.test.getMessage(), + }); + }, + }), + }); + + const config = { + rules: { "test-service-rule": 2 }, + parser: "enhanced-parser", + }; + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].message, "Hi!"); + assert.strictEqual(suppressedMessages.length, 0); + + const messages2 = linter.verify( + linter.getSourceCode(), + config, + filename, + ); + const suppressedMessages2 = linter.getSuppressedMessages(); + + assert.strictEqual(messages2.length, 1); + assert.strictEqual(messages2[0].message, "Hi!"); + assert.strictEqual(suppressedMessages2.length, 0); + }); + + it("should pass parser as parserPath to all rules when default parser is used", () => { + linter.defineRule("test-rule", { + create: sinon + .mock() + .withArgs(sinon.match({ parserPath: "espree" })) + .returns({}), + }); + + const config = { rules: { "test-rule": 2 } }; + + linter.verify("0", config, filename); + }); + }); + + describe("when passing in configuration values for rules", () => { + const code = "var answer = 6 * 7"; + + it("should be configurable by only setting the integer value", () => { + const rule = "semi", + config = { rules: {} }; + + config.rules[rule] = 1; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, rule); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should be configurable by only setting the string value", () => { + const rule = "semi", + config = { rules: {} }; + + config.rules[rule] = "warn"; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].ruleId, rule); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should be configurable by passing in values as an array", () => { + const rule = "semi", + config = { rules: {} }; + + config.rules[rule] = [1]; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, rule); + + assert.strictEqual(suppressedMessages.length, 0); + }); - it("should be configurable by passing in string value as an array", () => { - const rule = "semi", - config = { rules: {} }; + it("should be configurable by passing in string value as an array", () => { + const rule = "semi", + config = { rules: {} }; - config.rules[rule] = ["warn"]; + config.rules[rule] = ["warn"]; - const messages = linter.verify(code, config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); - assert.strictEqual(messages[0].ruleId, rule); + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].ruleId, rule); - assert.strictEqual(suppressedMessages.length, 0); - }); + assert.strictEqual(suppressedMessages.length, 0); + }); - it("should not be configurable by setting other value", () => { - const rule = "semi", - config = { rules: {} }; + it("should not be configurable by setting other value", () => { + const rule = "semi", + config = { rules: {} }; - config.rules[rule] = "1"; + config.rules[rule] = "1"; - const messages = linter.verify(code, config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); - it("should process empty config", () => { - const config = {}; - const messages = linter.verify(code, config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); + it("should process empty config", () => { + const config = {}; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); - describe("when evaluating code containing /*global */ and /*globals */ blocks", () => { - - it("variables should be available in global scope", () => { - const config = { rules: { checker: "error" }, globals: { Array: "off", ConfigGlobal: "writeable" } }; - const code = ` + describe("when evaluating code containing /*global */ and /*globals */ blocks", () => { + it("variables should be available in global scope", () => { + const config = { + rules: { checker: "error" }, + globals: { Array: "off", ConfigGlobal: "writeable" }, + }; + const code = ` /*global a b:true c:false d:readable e:writeable Math:off */ function foo() {} /*globals f:true*/ /* global ConfigGlobal : readable */ `; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - const a = getVariable(scope, "a"), - b = getVariable(scope, "b"), - c = getVariable(scope, "c"), - d = getVariable(scope, "d"), - e = getVariable(scope, "e"), - f = getVariable(scope, "f"), - mathGlobal = getVariable(scope, "Math"), - arrayGlobal = getVariable(scope, "Array"), - configGlobal = getVariable(scope, "ConfigGlobal"); - - assert.strictEqual(a.name, "a"); - assert.strictEqual(a.writeable, false); - assert.strictEqual(b.name, "b"); - assert.strictEqual(b.writeable, true); - assert.strictEqual(c.name, "c"); - assert.strictEqual(c.writeable, false); - assert.strictEqual(d.name, "d"); - assert.strictEqual(d.writeable, false); - assert.strictEqual(e.name, "e"); - assert.strictEqual(e.writeable, true); - assert.strictEqual(f.name, "f"); - assert.strictEqual(f.writeable, true); - assert.strictEqual(mathGlobal, null); - assert.strictEqual(arrayGlobal, null); - assert.strictEqual(configGlobal.name, "ConfigGlobal"); - assert.strictEqual(configGlobal.writeable, false); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("when evaluating code containing a /*global */ block with sloppy whitespace", () => { - const code = "/* global a b : true c: false*/"; - - it("variables should be available in global scope", () => { - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - a = getVariable(scope, "a"), - b = getVariable(scope, "b"), - c = getVariable(scope, "c"); - - assert.strictEqual(a.name, "a"); - assert.strictEqual(a.writeable, false); - assert.strictEqual(b.name, "b"); - assert.strictEqual(b.writeable, true); - assert.strictEqual(c.name, "c"); - assert.strictEqual(c.writeable, false); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("when evaluating code containing a /*global */ block with specific variables", () => { - const code = "/* global toString hasOwnProperty valueOf: true */"; - - it("should not throw an error if comment block has global variables which are Object.prototype contains", () => { - const config = { rules: { checker: "error" } }; - - linter.verify(code, config); - }); - }); - - describe("when evaluating code containing /*eslint-env */ block", () => { - it("variables should be available in global scope", () => { - const code = `/*${ESLINT_ENV} node*/ function f() {} /*${ESLINT_ENV} browser, foo*/`; - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - exports = getVariable(scope, "exports"), - window = getVariable(scope, "window"); - - assert.strictEqual(exports.writeable, true); - assert.strictEqual(window.writeable, false); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("variables should be available in global scope with quoted items", () => { - const code = `/*${ESLINT_ENV} 'node'*/ function f() {} /*${ESLINT_ENV} "browser", "mocha"*/`; - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - exports = getVariable(scope, "exports"), - window = getVariable(scope, "window"), - it = getVariable(scope, "it"); - - assert.strictEqual(exports.writeable, true); - assert.strictEqual(window.writeable, false); - assert.strictEqual(it.writeable, false); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("when evaluating code containing /*eslint-env */ block with sloppy whitespace", () => { - const code = `/* ${ESLINT_ENV} ,, node , no-browser ,, */`; - - it("variables should be available in global scope", () => { - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - exports = getVariable(scope, "exports"), - window = getVariable(scope, "window"); - - assert.strictEqual(exports.writeable, true); - assert.strictEqual(window, null); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("when evaluating code containing /*exported */ block", () => { - - it("we should behave nicely when no matching variable is found", () => { - const code = "/* exported horse */"; - const config = { rules: {} }; - - linter.verify(code, config, filename, true); - }); - - it("variables should be exported", () => { - const code = "/* exported horse */\n\nvar horse = 'circus'"; - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); - - assert.strictEqual(horse.eslintUsed, true); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("undefined variables should not be exported", () => { - const code = "/* exported horse */\n\nhorse = 'circus'"; - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); - - assert.strictEqual(horse, null); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("variables should be exported in strict mode", () => { - const code = "/* exported horse */\n'use strict';\nvar horse = 'circus'"; - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); - - assert.strictEqual(horse.eslintUsed, true); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("variables should not be exported in the es6 module environment", () => { - const code = "/* exported horse */\nvar horse = 'circus'"; - const config = { rules: { checker: "error" }, parserOptions: { ecmaVersion: 6, sourceType: "module" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); - - assert.strictEqual(horse, null); // there is no global scope at all - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("variables should not be exported when in the node environment", () => { - const code = "/* exported horse */\nvar horse = 'circus'"; - const config = { rules: { checker: "error" }, env: { node: true } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); - - assert.strictEqual(horse, null); // there is no global scope at all - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("when evaluating code containing a line comment", () => { - const code = "//global a \n function f() {}"; - - it("should not introduce a global variable", () => { - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(getVariable(scope, "a"), null); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("when evaluating code containing normal block comments", () => { - const code = "/**/ /*a*/ /*b:true*/ /*foo c:false*/"; - - it("should not introduce a global variable", () => { - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(getVariable(scope, "a"), null); - assert.strictEqual(getVariable(scope, "b"), null); - assert.strictEqual(getVariable(scope, "foo"), null); - assert.strictEqual(getVariable(scope, "c"), null); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("when evaluating any code", () => { - const code = "x"; - - it("builtin global variables should be available in the global scope", () => { - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.notStrictEqual(getVariable(scope, "Object"), null); - assert.notStrictEqual(getVariable(scope, "Array"), null); - assert.notStrictEqual(getVariable(scope, "undefined"), null); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("ES6 global variables should not be available by default", () => { - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(getVariable(scope, "Promise"), null); - assert.strictEqual(getVariable(scope, "Symbol"), null); - assert.strictEqual(getVariable(scope, "WeakMap"), null); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("ES6 global variables should be available in the es6 environment", () => { - const config = { rules: { checker: "error" }, env: { es6: true } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.notStrictEqual(getVariable(scope, "Promise"), null); - assert.notStrictEqual(getVariable(scope, "Symbol"), null); - assert.notStrictEqual(getVariable(scope, "WeakMap"), null); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("ES6 global variables can be disabled when the es6 environment is enabled", () => { - const config = { rules: { checker: "error" }, globals: { Promise: "off", Symbol: "off", WeakMap: "off" }, env: { es6: true } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(getVariable(scope, "Promise"), null); - assert.strictEqual(getVariable(scope, "Symbol"), null); - assert.strictEqual(getVariable(scope, "WeakMap"), null); - }); - - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("at any time", () => { - const code = "new-rule"; - - it("can add a rule dynamically", () => { - linter.defineRule(code, { - create: context => ({ - Literal(node) { - context.report(node, "message"); - } - }) - }); - - const config = { rules: {} }; - - config.rules[code] = 1; - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, code); - assert.strictEqual(messages[0].nodeType, "Literal"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("at any time", () => { - const code = ["new-rule-0", "new-rule-1"]; - - it("can add multiple rules dynamically", () => { - const config = { rules: {} }; - const newRules = {}; - - code.forEach(item => { - config.rules[item] = 1; - newRules[item] = { - create(context) { - return { - Literal(node) { - context.report(node, "message"); - } - }; - } - }; - }); - linter.defineRules(newRules); - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, code.length); - code.forEach(item => { - assert.ok(messages.some(message => message.ruleId === item)); - }); - messages.forEach(message => { - assert.strictEqual(message.nodeType, "Literal"); - }); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("at any time", () => { - const code = "filename-rule"; - - it("has access to the filename", () => { - linter.defineRule(code, { - create: context => ({ - Literal(node) { - assert.strictEqual(context.getFilename(), context.filename); - context.report(node, context.filename); - } - }) - }); - - const config = { rules: {} }; - - config.rules[code] = 1; - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages[0].message, filename); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("has access to the physicalFilename", () => { - linter.defineRule(code, { - create: context => ({ - Literal(node) { - assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); - context.report(node, context.physicalFilename); - } - }) - }); - - const config = { rules: {} }; - - config.rules[code] = 1; - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages[0].message, filename); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("defaults filename to ''", () => { - linter.defineRule(code, { - create: context => ({ - Literal(node) { - assert.strictEqual(context.getFilename(), context.filename); - context.report(node, context.filename); - } - }) - }); - - const config = { rules: {} }; - - config.rules[code] = 1; - - const messages = linter.verify("0", config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages[0].message, ""); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to enable rules", () => { - - it("should report a violation", () => { - const code = "/*eslint no-alert:1*/ alert('test');"; - const config = { rules: {} }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].message, "Unexpected alert."); - assert.include(messages[0].nodeType, "CallExpression"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should enable rule configured using a string severity that contains uppercase letters", () => { - const code = "/*eslint no-alert: \"Error\"*/ alert('test');"; - const config = { rules: {} }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, "Unexpected alert."); - assert.include(messages[0].nodeType, "CallExpression"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("rules should not change initial config", () => { - const config = { rules: { strict: 2 } }; - const codeA = "/*eslint strict: 0*/ function bar() { return 2; }"; - const codeB = "function foo() { return 1; }"; - let messages = linter.verify(codeA, config, filename, false); - let suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - - messages = linter.verify(codeB, config, filename, false); - suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("rules should not change initial config", () => { - const config = { rules: { quotes: [2, "double"] } }; - const codeA = "/*eslint quotes: 0*/ function bar() { return '2'; }"; - const codeB = "function foo() { return '1'; }"; - let messages = linter.verify(codeA, config, filename, false); - let suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - - messages = linter.verify(codeB, config, filename, false); - suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("rules should not change initial config", () => { - const config = { rules: { quotes: [2, "double"] } }; - const codeA = "/*eslint quotes: [0, \"single\"]*/ function bar() { return '2'; }"; - const codeB = "function foo() { return '1'; }"; - - let messages = linter.verify(codeA, config, filename, false); - let suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - - messages = linter.verify(codeB, config, filename, false); - suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("rules should not change initial config", () => { - const config = { rules: { "no-unused-vars": [2, { vars: "all" }] } }; - const codeA = "/*eslint no-unused-vars: [0, {\"vars\": \"local\"}]*/ var a = 44;"; - const codeB = "var b = 55;"; - - let messages = linter.verify(codeA, config, filename, false); - let suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - - messages = linter.verify(codeB, config, filename, false); - suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with invalid comments to enable rules", () => { - it("should report a violation when the config is not a valid rule configuration", () => { - const messages = linter.verify("/*eslint no-alert:true*/ alert('test');", {}); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - severity: 2, - ruleId: "no-alert", - message: "Configuration for rule \"no-alert\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed 'true').\n", - line: 1, - column: 1, - endLine: 1, - endColumn: 25, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation when the config violates a rule's schema", () => { - const messages = linter.verify("/* eslint no-alert: [error, {nonExistentPropertyName: true}]*/", {}); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - severity: 2, - ruleId: "no-alert", - message: "Configuration for rule \"no-alert\" is invalid:\n\tValue [{\"nonExistentPropertyName\":true}] should NOT have more than 0 items.\n", - line: 1, - column: 1, - endLine: 1, - endColumn: 63, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should apply valid configuration even if there is an invalid configuration present", () => { - const code = [ - "/* eslint no-unused-vars: [ */ // <-- this one is invalid JSON", - "/* eslint no-undef: [\"error\"] */ // <-- this one is fine, and thus should apply", - "foo(); // <-- expected no-undef error here" - ].join("\n"); - - const messages = linter.verify(code); - const suppressedMessages = linter.getSuppressedMessages(); - - // different engines have different JSON parsing error messages - assert.match(messages[0].message, /Failed to parse JSON from ' "no-unused-vars": \['/u); - assert.strictEqual(messages[0].severity, 2); - assert.isTrue(messages[0].fatal); - assert.isNull(messages[0].ruleId); - assert.strictEqual(messages[0].line, 1); - assert.strictEqual(messages[0].column, 1); - assert.isNull(messages[0].nodeType); - - assert.deepStrictEqual( - messages[1], - { - severity: 2, - ruleId: "no-undef", - message: "'foo' is not defined.", - messageId: "undef", - line: 3, - column: 1, - endLine: 3, - endColumn: 4, - nodeType: "Identifier" - } - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - }); - - describe("when evaluating code with comments to disable rules", () => { - const code = "/*eslint no-alert:0*/ alert('test');"; - - it("should not report a violation", () => { - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to disable rules", () => { - let code, messages, suppressedMessages; - - it("should report an error when disabling a non-existent rule in inline comment", () => { - code = "/*eslint foo:0*/ ;"; - messages = linter.verify(code, {}, filename); - suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); - assert.strictEqual(suppressedMessages.length, 0); - - code = "/*eslint-disable foo*/ ;"; - messages = linter.verify(code, {}, filename); - suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); - assert.strictEqual(suppressedMessages.length, 0); - - code = "/*eslint-disable-line foo*/ ;"; - messages = linter.verify(code, {}, filename); - suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); - assert.strictEqual(suppressedMessages.length, 0); - - code = "/*eslint-disable-next-line foo*/ ;"; - messages = linter.verify(code, {}, filename); - suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report an error, when disabling a non-existent rule in config", () => { - messages = linter.verify("", { rules: { foo: 0 } }, filename); - suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report an error, when config a non-existent rule in config", () => { - messages = linter.verify("", { rules: { foo: 1 } }, filename); - suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); - assert.strictEqual(suppressedMessages.length, 0); - - messages = linter.verify("", { rules: { foo: 2 } }, filename); - suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to enable multiple rules", () => { - const code = "/*eslint no-alert:1 no-console:1*/ alert('test'); console.log('test');"; - - it("should report a violation", () => { - const config = { rules: {} }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].message, "Unexpected alert."); - assert.include(messages[0].nodeType, "CallExpression"); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to enable and disable multiple rules", () => { - const code = "/*eslint no-alert:1 no-console:0*/ alert('test'); console.log('test');"; - - it("should report a violation", () => { - const config = { rules: { "no-console": 1, "no-alert": 0 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].message, "Unexpected alert."); - assert.include(messages[0].nodeType, "CallExpression"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to disable and enable configurable rule as part of plugin", () => { - - beforeEach(() => { - linter.defineRule("test-plugin/test-rule", { - create(context) { - return { - Literal(node) { - if (node.value === "trigger violation") { - context.report(node, "Reporting violation."); - } - } - }; - } - }); - }); - - it("should not report a violation when inline comment enables plugin rule and there's no violation", () => { - const config = { rules: {} }; - const code = "/*eslint test-plugin/test-rule: 2*/ var a = \"no violation\";"; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report a violation when inline comment disables plugin rule", () => { - const code = "/*eslint test-plugin/test-rule:0*/ var a = \"trigger violation\""; - const config = { rules: { "test-plugin/test-rule": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation when the report is right before the comment", () => { - const code = " /* eslint-disable */ "; - - linter.defineRule("checker", { - create: context => ({ - Program() { - context.report({ loc: { line: 1, column: 0 }, message: "foo" }); - } - }) - }); - const problems = linter.verify(code, { rules: { checker: "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(problems.length, 1); - assert.strictEqual(problems[0].message, "foo"); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report a violation when the report is right at the start of the comment", () => { - const code = " /* eslint-disable */ "; - - linter.defineRule("checker", { - create: context => ({ - Program() { - context.report({ loc: { line: 1, column: 1 }, message: "foo" }); - } - }) - }); - const problems = linter.verify(code, { rules: { checker: "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(problems.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].message, "foo"); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - assert.strictEqual(suppressedMessages[0].suppressions[0].justification, ""); - }); - - it("rules should not change initial config", () => { - const config = { rules: { "test-plugin/test-rule": 2 } }; - const codeA = "/*eslint test-plugin/test-rule: 0*/ var a = \"trigger violation\";"; - const codeB = "var a = \"trigger violation\";"; - - let messages = linter.verify(codeA, config, filename, false); - let suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - - messages = linter.verify(codeB, config, filename, false); - suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to enable and disable all reporting", () => { - it("should report a violation", () => { - - const code = [ - "/*eslint-disable */", - "alert('test');", - "/*eslint-enable */", - "alert('test');" - ].join("\n"); - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].message, "Unexpected alert."); - assert.include(messages[0].nodeType, "CallExpression"); - assert.strictEqual(messages[0].line, 4); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].message, "Unexpected alert."); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - assert.strictEqual(suppressedMessages[0].suppressions[0].justification, ""); - }); - - it("should not report a violation", () => { - const code = [ - "/*eslint-disable */", - "alert('test');", - "alert('test');" - ].join("\n"); - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - assert.strictEqual(suppressedMessages[1].line, 3); - assert.strictEqual(suppressedMessages[1].suppressions.length, 1); - }); - - it("should not report a violation", () => { - const code = [ - " alert('test1');/*eslint-disable */\n", - "alert('test');", - " alert('test');\n", - "/*eslint-enable */alert('test2');" - ].join(""); - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].column, 21); - assert.strictEqual(messages[1].column, 19); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].column, 1); - assert.strictEqual(suppressedMessages[1].column, 56); - }); - - it("should report a violation", () => { - - const code = [ - "/*eslint-disable */", - "alert('test');", - "/*eslint-disable */", - "alert('test');", - "/*eslint-enable*/", - "alert('test');", - "/*eslint-enable*/" - ].join("\n"); - - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 2); - }); - - - it("should not report a violation", () => { - const code = [ - "/*eslint-disable */", - "(function(){ var b = 44;})()", - "/*eslint-enable */;any();" - ].join("\n"); - - const config = { rules: { "no-unused-vars": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 1); - }); - - it("should not report a violation", () => { - const code = [ - "(function(){ /*eslint-disable */ var b = 44;})()", - "/*eslint-enable */;any();" - ].join("\n"); - - const config = { rules: { "no-unused-vars": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 1); - }); - }); - - describe("when evaluating code with comments to ignore reporting on specific rules on a specific line", () => { - - describe("eslint-disable-line", () => { - it("should report a violation", () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert", - "console.log('test');" // here - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("should report a violation", () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert", - "console.log('test'); // eslint-disable-line no-console", - "alert('test');" // here - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - }); - - it("should report a violation if eslint-disable-line in a block comment is not on a single line", () => { - const code = [ - "/* eslint-disable-line", - "*", - "*/ console.log('test');" // here - ].join("\n"); - const config = { - rules: { - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[1].ruleId, "no-console"); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not disable rule and add an extra report if eslint-disable-line in a block comment is not on a single line", () => { - const code = [ - "alert('test'); /* eslint-disable-line ", - "no-alert */" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - - const messages = linter.verify(code, config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages, [ - { - ruleId: "no-alert", - severity: 1, - line: 1, - column: 1, - endLine: 1, - endColumn: 14, - message: "Unexpected alert.", - messageId: "unexpected", - nodeType: "CallExpression" - }, - { - ruleId: null, - severity: 2, - message: "eslint-disable-line comment should not span multiple lines.", - line: 1, - column: 16, - endLine: 2, - endColumn: 12, - nodeType: null - } - ]); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report a violation for eslint-disable-line in block comment", () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert", - "alert('test'); /*eslint-disable-line no-alert*/" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); - }); - - it("should not report a violation", () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert", - "console.log('test'); // eslint-disable-line no-console" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - }); - - it("should not report a violation", () => { - const code = [ - "alert('test') // eslint-disable-line no-alert, quotes, semi", - "console.log('test'); // eslint-disable-line" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "double"], - semi: [1, "always"], - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 5); - }); - - it("should not report a violation", () => { - const code = [ - "alert('test') /* eslint-disable-line no-alert, quotes, semi */", - "console.log('test'); /* eslint-disable-line */" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "double"], - semi: [1, "always"], - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 5); - }); - - it("should ignore violations of multiple rules when specified in mixed comments", () => { - const code = [ - " alert(\"test\"); /* eslint-disable-line no-alert */ // eslint-disable-line quotes" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "single"] - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("should report a violation with quoted rule names in eslint-disable-line", () => { - const code = [ - "alert('test'); // eslint-disable-line 'no-alert'", - "console.log('test');", // here - "alert('test'); // eslint-disable-line \"no-alert\"" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - assert.strictEqual(messages[0].line, 2); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 1); - assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].line, 3); - }); - }); - - describe("eslint-disable-next-line", () => { - it("should ignore violation of specified rule on next line", () => { - const code = [ - "// eslint-disable-next-line no-alert", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => { - const code = [ - "/* eslint-disable-next-line no-alert */", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => { - const code = [ - "/* eslint-disable-next-line no-alert */", - "alert('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("should not ignore violation if code is not on next line", () => { - const code = [ - "/* eslint-disable-next-line", - "no-alert */alert('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should ignore violation if block comment span multiple lines", () => { - const code = [ - "/* eslint-disable-next-line", - "no-alert */", - "alert('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("should ignore violations only of specified rule", () => { - const code = [ - "// eslint-disable-next-line no-console", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should ignore violations of multiple rules when specified", () => { - const code = [ - "// eslint-disable-next-line no-alert, quotes", - "alert(\"test\");", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "single"], - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].ruleId, "quotes"); - }); - - it("should ignore violations of multiple rules when specified in multiple lines", () => { - const code = [ - "/* eslint-disable-next-line", - "no-alert,", - "quotes", - "*/", - "alert(\"test\");", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "single"], - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - }); - - it("should ignore violations of multiple rules when specified in mixed comments", () => { - const code = [ - "/* eslint-disable-next-line no-alert */ // eslint-disable-next-line quotes", - "alert(\"test\");" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "single"] - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].ruleId, "quotes"); - }); - - it("should ignore violations of multiple rules when specified in mixed single line and multi line comments", () => { - const code = [ - "/* eslint-disable-next-line", - "no-alert", - "*/ // eslint-disable-next-line quotes", - "alert(\"test\");" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "single"] - } - }; - const messages = linter.verify(code, config, filename); - - assert.strictEqual(messages.length, 0); - }); - - it("should ignore violations of only the specified rule on next line", () => { - const code = [ - "// eslint-disable-next-line quotes", - "alert(\"test\");", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "single"], - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "quotes"); - }); - - it("should ignore violations of specified rule on next line only", () => { - const code = [ - "alert('test');", - "// eslint-disable-next-line no-alert", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("should ignore all rule violations on next line if none specified", () => { - const code = [ - "// eslint-disable-next-line", - "alert(\"test\");", - "console.log('test')" - ].join("\n"); - const config = { - rules: { - semi: [1, "never"], - quotes: [1, "single"], - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 3); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].ruleId, "quotes"); - assert.strictEqual(suppressedMessages[2].ruleId, "semi"); - }); - - it("should ignore violations if eslint-disable-next-line is a block comment", () => { - const code = [ - "alert('test');", - "/* eslint-disable-next-line no-alert */", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("should report a violation", () => { - const code = [ - "/* eslint-disable-next-line", - "*", - "*/", - "console.log('test');" // here - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not ignore violations if comment is of the type hashbang", () => { - const code = [ - "#! eslint-disable-next-line no-alert", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should ignore violation of specified rule on next line with quoted rule names", () => { - const code = [ - "// eslint-disable-next-line 'no-alert'", - "alert('test');", - "// eslint-disable-next-line \"no-alert\"", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); - }); - }); - }); - - describe("when evaluating code with comments to enable and disable reporting of specific rules", () => { - - it("should report a violation", () => { - const code = [ - "/*eslint-disable no-alert */", - "alert('test');", - "console.log('test');" // here - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("should report no violation", () => { - const code = [ - "/*eslint-disable no-unused-vars */", - "var foo; // eslint-disable-line no-unused-vars", - "var bar;", - "/* eslint-enable no-unused-vars */" // here - ].join("\n"); - const config = { rules: { "no-unused-vars": 2 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-unused-vars"); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[1].ruleId, "no-unused-vars"); - assert.strictEqual(suppressedMessages[1].line, 3); - }); - - it("should report no violation", () => { - const code = [ - "var foo1; // eslint-disable-line no-unused-vars", - "var foo2; // eslint-disable-line no-unused-vars", - "var foo3; // eslint-disable-line no-unused-vars", - "var foo4; // eslint-disable-line no-unused-vars", - "var foo5; // eslint-disable-line no-unused-vars" - ].join("\n"); - const config = { rules: { "no-unused-vars": 2 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 5); - }); - - it("should report no violation", () => { - const code = [ - "/* eslint-disable quotes */", - "console.log(\"foo\");", - "/* eslint-enable quotes */" - ].join("\n"); - const config = { rules: { quotes: 2 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation", () => { - const code = [ - "/*eslint-disable no-alert, no-console */", - "alert('test');", - "console.log('test');", - "/*eslint-enable*/", - - "alert('test');", // here - "console.log('test');" // here - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].line, 5); - assert.strictEqual(messages[1].ruleId, "no-console"); - assert.strictEqual(messages[1].line, 6); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[1].line, 3); - }); - - it("should report a violation", () => { - const code = [ - "/*eslint-disable no-alert */", - "alert('test');", - "console.log('test');", - "/*eslint-enable no-console */", - - "alert('test');" // here - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].line, 5); - }); - - - it("should report a violation", () => { - const code = [ - "/*eslint-disable no-alert, no-console */", - "alert('test');", - "console.log('test');", - "/*eslint-enable no-alert*/", - - "alert('test');", // here - "console.log('test');" - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].line, 5); - - assert.strictEqual(suppressedMessages.length, 3); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[1].line, 3); - assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[2].line, 6); - }); - - - it("should report a violation", () => { - const code = [ - "/*eslint-disable no-alert */", - - "/*eslint-disable no-console */", - "alert('test');", - "console.log('test');", - "/*eslint-enable */", - - "alert('test');", // here - "console.log('test');", // here - - "/*eslint-enable */", - - "alert('test');", // here - "console.log('test');", // here - - "/*eslint-enable*/" - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 4); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].line, 6); - assert.strictEqual(messages[1].ruleId, "no-console"); - assert.strictEqual(messages[1].line, 7); - assert.strictEqual(messages[2].ruleId, "no-alert"); - assert.strictEqual(messages[2].line, 9); - assert.strictEqual(messages[3].ruleId, "no-console"); - assert.strictEqual(messages[3].line, 10); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 3); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[1].line, 4); - }); - - it("should report a violation", () => { - const code = [ - "/*eslint-disable no-alert, no-console */", - "alert('test');", - "console.log('test');", - - "/*eslint-enable no-alert */", - - "alert('test');", // here - "console.log('test');", - - "/*eslint-enable no-console */", - - "alert('test');", // here - "console.log('test');", // here - "/*eslint-enable no-console */" - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 3); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].line, 5); - assert.strictEqual(messages[1].ruleId, "no-alert"); - assert.strictEqual(messages[1].line, 8); - assert.strictEqual(messages[2].ruleId, "no-console"); - assert.strictEqual(messages[2].line, 9); - - assert.strictEqual(suppressedMessages.length, 3); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[1].line, 3); - assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[2].line, 6); - }); - - it("should report a violation when severity is warn", () => { - const code = [ - "/*eslint-disable no-alert, no-console */", - "alert('test');", - "console.log('test');", - - "/*eslint-enable no-alert */", - - "alert('test');", // here - "console.log('test');", - - "/*eslint-enable no-console */", - - "alert('test');", // here - "console.log('test');", // here - "/*eslint-enable no-console */" - ].join("\n"); - const config = { rules: { "no-alert": "warn", "no-console": "warn" } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 3); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].line, 5); - assert.strictEqual(messages[1].ruleId, "no-alert"); - assert.strictEqual(messages[1].line, 8); - assert.strictEqual(messages[2].ruleId, "no-console"); - assert.strictEqual(messages[2].line, 9); - - assert.strictEqual(suppressedMessages.length, 3); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[1].line, 3); - assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[2].line, 6); - }); - - it("should report a violation with quoted rule names in eslint-disable", () => { - const code = [ - "/*eslint-disable 'no-alert' */", - "alert('test');", - "console.log('test');", // here - "/*eslint-enable */", - "/*eslint-disable \"no-console\" */", - "alert('test');", // here - "console.log('test');" - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-console"); - assert.strictEqual(messages[1].ruleId, "no-alert"); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - }); - - it("should report a violation with quoted rule names in eslint-enable", () => { - const code = [ - "/*eslint-disable no-alert, no-console */", - "alert('test');", - "console.log('test');", - "/*eslint-enable 'no-alert'*/", - "alert('test');", // here - "console.log('test');", - "/*eslint-enable \"no-console\"*/", - "console.log('test');" // here - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].line, 5); - assert.strictEqual(messages[1].ruleId, "no-console"); - assert.strictEqual(messages[1].line, 8); - - assert.strictEqual(suppressedMessages.length, 3); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[1].line, 3); - assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[2].line, 6); - }); - }); - - describe("when evaluating code with comments to enable and disable multiple comma separated rules", () => { - const code = "/*eslint no-alert:1, no-console:0*/ alert('test'); console.log('test');"; - - it("should report a violation", () => { - const config = { rules: { "no-console": 1, "no-alert": 0 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].message, "Unexpected alert."); - assert.include(messages[0].nodeType, "CallExpression"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to enable configurable rule", () => { - const code = "/*eslint quotes:[2, \"double\"]*/ alert('test');"; - - it("should report a violation", () => { - const config = { rules: { quotes: [2, "single"] } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "quotes"); - assert.strictEqual(messages[0].message, "Strings must use doublequote."); - assert.include(messages[0].nodeType, "Literal"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to enable configurable rule using string severity", () => { - const code = "/*eslint quotes:[\"error\", \"double\"]*/ alert('test');"; - - it("should report a violation", () => { - const config = { rules: { quotes: [2, "single"] } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "quotes"); - assert.strictEqual(messages[0].message, "Strings must use doublequote."); - assert.include(messages[0].nodeType, "Literal"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with incorrectly formatted comments to disable rule", () => { - it("should report a violation", () => { - const code = "/*eslint no-alert:'1'*/ alert('test');"; - - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - - /* - * Incorrectly formatted comment threw error; - * message from caught exception - * may differ amongst UAs, so verifying - * first part only as defined in the - * parseJsonConfig function in lib/eslint.js - */ - assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":'1'':/u); - assert.strictEqual(messages[0].line, 1); - assert.strictEqual(messages[0].column, 1); - - assert.strictEqual(messages[1].ruleId, "no-alert"); - assert.strictEqual(messages[1].message, "Unexpected alert."); - assert.include(messages[1].nodeType, "CallExpression"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation", () => { - const code = "/*eslint no-alert:abc*/ alert('test');"; - - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - - /* - * Incorrectly formatted comment threw error; - * message from caught exception - * may differ amongst UAs, so verifying - * first part only as defined in the - * parseJsonConfig function in lib/eslint.js - */ - assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":abc':/u); - assert.strictEqual(messages[0].line, 1); - assert.strictEqual(messages[0].column, 1); - - assert.strictEqual(messages[1].ruleId, "no-alert"); - assert.strictEqual(messages[1].message, "Unexpected alert."); - assert.include(messages[1].nodeType, "CallExpression"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation", () => { - const code = "/*eslint no-alert:0 2*/ alert('test');"; - - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - - /* - * Incorrectly formatted comment threw error; - * message from caught exception - * may differ amongst UAs, so verifying - * first part only as defined in the - * parseJsonConfig function in lib/eslint.js - */ - assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":0 2':/u); - assert.strictEqual(messages[0].line, 1); - assert.strictEqual(messages[0].column, 1); - - assert.strictEqual(messages[1].ruleId, "no-alert"); - assert.strictEqual(messages[1].message, "Unexpected alert."); - assert.include(messages[1].nodeType, "CallExpression"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments which have colon in its value", () => { - const code = String.raw` + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node); + const a = getVariable(scope, "a"), + b = getVariable(scope, "b"), + c = getVariable(scope, "c"), + d = getVariable(scope, "d"), + e = getVariable(scope, "e"), + f = getVariable(scope, "f"), + mathGlobal = getVariable(scope, "Math"), + arrayGlobal = getVariable(scope, "Array"), + configGlobal = getVariable(scope, "ConfigGlobal"); + + assert.strictEqual(a.name, "a"); + assert.strictEqual(a.writeable, false); + assert.strictEqual(b.name, "b"); + assert.strictEqual(b.writeable, true); + assert.strictEqual(c.name, "c"); + assert.strictEqual(c.writeable, false); + assert.strictEqual(d.name, "d"); + assert.strictEqual(d.writeable, false); + assert.strictEqual(e.name, "e"); + assert.strictEqual(e.writeable, true); + assert.strictEqual(f.name, "f"); + assert.strictEqual(f.writeable, true); + assert.strictEqual(mathGlobal, null); + assert.strictEqual(arrayGlobal, null); + assert.strictEqual(configGlobal.name, "ConfigGlobal"); + assert.strictEqual(configGlobal.writeable, false); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("when evaluating code containing a /*global */ block with sloppy whitespace", () => { + const code = "/* global a b : true c: false*/"; + + it("variables should be available in global scope", () => { + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node), + a = getVariable(scope, "a"), + b = getVariable(scope, "b"), + c = getVariable(scope, "c"); + + assert.strictEqual(a.name, "a"); + assert.strictEqual(a.writeable, false); + assert.strictEqual(b.name, "b"); + assert.strictEqual(b.writeable, true); + assert.strictEqual(c.name, "c"); + assert.strictEqual(c.writeable, false); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("when evaluating code containing a /*global */ block with specific variables", () => { + const code = "/* global toString hasOwnProperty valueOf: true */"; + + it("should not throw an error if comment block has global variables which are Object.prototype contains", () => { + const config = { rules: { checker: "error" } }; + + linter.verify(code, config); + }); + }); + + describe("when evaluating code containing /*eslint-env */ block", () => { + it("variables should be available in global scope", () => { + const code = `/*${ESLINT_ENV} node*/ function f() {} /*${ESLINT_ENV} browser, foo*/`; + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node), + exports = getVariable(scope, "exports"), + window = getVariable(scope, "window"); + + assert.strictEqual(exports.writeable, true); + assert.strictEqual(window.writeable, false); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("variables should be available in global scope with quoted items", () => { + const code = `/*${ESLINT_ENV} 'node'*/ function f() {} /*${ESLINT_ENV} "browser", "mocha"*/`; + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node), + exports = getVariable(scope, "exports"), + window = getVariable(scope, "window"), + it = getVariable(scope, "it"); + + assert.strictEqual(exports.writeable, true); + assert.strictEqual(window.writeable, false); + assert.strictEqual(it.writeable, false); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("when evaluating code containing /*eslint-env */ block with sloppy whitespace", () => { + const code = `/* ${ESLINT_ENV} ,, node , no-browser ,, */`; + + it("variables should be available in global scope", () => { + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node), + exports = getVariable(scope, "exports"), + window = getVariable(scope, "window"); + + assert.strictEqual(exports.writeable, true); + assert.strictEqual(window, null); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("when evaluating code containing /*exported */ block", () => { + it("we should behave nicely when no matching variable is found", () => { + const code = "/* exported horse */"; + const config = { rules: {} }; + + linter.verify(code, config, filename); + }); + + it("variable should be exported ", () => { + const code = "/* exported horse */\n\nvar horse;"; + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node), + horse = getVariable(scope, "horse"); + + assert.isTrue(horse.eslintUsed); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("`key: value` pair variable should not be exported", () => { + const code = "/* exported horse: true */\n\nvar horse;"; + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node), + horse = getVariable(scope, "horse"); + + assert.notOk(horse.eslintUsed); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("variables with comma should be exported", () => { + const code = "/* exported horse, dog */\n\nvar horse, dog;"; + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node); + + ["horse", "dog"].forEach(name => { + assert.isTrue(getVariable(scope, name).eslintUsed); + }); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("variables without comma should not be exported", () => { + const code = "/* exported horse dog */\n\nvar horse, dog;"; + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node); + + ["horse", "dog"].forEach(name => { + assert.notOk(getVariable(scope, name).eslintUsed); + }); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("variables should be exported", () => { + const code = "/* exported horse */\n\nvar horse = 'circus'"; + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node), + horse = getVariable(scope, "horse"); + + assert.strictEqual(horse.eslintUsed, true); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("undefined variables should not be exported", () => { + const code = "/* exported horse */\n\nhorse = 'circus'"; + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node), + horse = getVariable(scope, "horse"); + + assert.strictEqual(horse, null); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("variables should be exported in strict mode", () => { + const code = + "/* exported horse */\n'use strict';\nvar horse = 'circus'"; + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node), + horse = getVariable(scope, "horse"); + + assert.strictEqual(horse.eslintUsed, true); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("variables should not be exported in the es6 module environment", () => { + const code = "/* exported horse */\nvar horse = 'circus'"; + const config = { + rules: { checker: "error" }, + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node), + horse = getVariable(scope, "horse"); + + assert.strictEqual(horse, null); // there is no global scope at all + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("variables should not be exported when in the node environment", () => { + const code = "/* exported horse */\nvar horse = 'circus'"; + const config = { rules: { checker: "error" }, env: { node: true } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node), + horse = getVariable(scope, "horse"); + + assert.strictEqual(horse, null); // there is no global scope at all + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("when evaluating code containing a line comment", () => { + const code = "//global a \n function f() {}"; + + it("should not introduce a global variable", () => { + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node); + + assert.strictEqual(getVariable(scope, "a"), null); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("when evaluating code containing normal block comments", () => { + const code = "/**/ /*a*/ /*b:true*/ /*foo c:false*/"; + + it("should not introduce a global variable", () => { + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node); + + assert.strictEqual(getVariable(scope, "a"), null); + assert.strictEqual(getVariable(scope, "b"), null); + assert.strictEqual(getVariable(scope, "foo"), null); + assert.strictEqual(getVariable(scope, "c"), null); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("when evaluating any code", () => { + const code = "x"; + + it("builtin global variables should be available in the global scope", () => { + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node); + + assert.notStrictEqual( + getVariable(scope, "Object"), + null, + ); + assert.notStrictEqual( + getVariable(scope, "Array"), + null, + ); + assert.notStrictEqual( + getVariable(scope, "undefined"), + null, + ); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("ES6 global variables should not be available by default", () => { + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node); + + assert.strictEqual(getVariable(scope, "Promise"), null); + assert.strictEqual(getVariable(scope, "Symbol"), null); + assert.strictEqual(getVariable(scope, "WeakMap"), null); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("ES6 global variables should be available in the es6 environment", () => { + const config = { rules: { checker: "error" }, env: { es6: true } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node); + + assert.notStrictEqual( + getVariable(scope, "Promise"), + null, + ); + assert.notStrictEqual( + getVariable(scope, "Symbol"), + null, + ); + assert.notStrictEqual( + getVariable(scope, "WeakMap"), + null, + ); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("ES6 global variables can be disabled when the es6 environment is enabled", () => { + const config = { + rules: { checker: "error" }, + globals: { Promise: "off", Symbol: "off", WeakMap: "off" }, + env: { es6: true }, + }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + const scope = context.sourceCode.getScope(node); + + assert.strictEqual(getVariable(scope, "Promise"), null); + assert.strictEqual(getVariable(scope, "Symbol"), null); + assert.strictEqual(getVariable(scope, "WeakMap"), null); + }); + + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("at any time", () => { + const code = "new-rule"; + + it("can add a rule dynamically", () => { + linter.defineRule(code, { + create: context => ({ + Literal(node) { + context.report(node, "message"); + }, + }), + }); + + const config = { rules: {} }; + + config.rules[code] = 1; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, code); + assert.strictEqual(messages[0].nodeType, "Literal"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("at any time", () => { + const code = ["new-rule-0", "new-rule-1"]; + + it("can add multiple rules dynamically", () => { + const config = { rules: {} }; + const newRules = {}; + + code.forEach(item => { + config.rules[item] = 1; + newRules[item] = { + create(context) { + return { + Literal(node) { + context.report(node, "message"); + }, + }; + }, + }; + }); + linter.defineRules(newRules); + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, code.length); + code.forEach(item => { + assert.ok(messages.some(message => message.ruleId === item)); + }); + messages.forEach(message => { + assert.strictEqual(message.nodeType, "Literal"); + }); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("at any time", () => { + const code = "filename-rule"; + + it("has access to the filename", () => { + linter.defineRule(code, { + create: context => ({ + Literal(node) { + assert.strictEqual( + context.getFilename(), + context.filename, + ); + context.report(node, context.filename); + }, + }), + }); + + const config = { rules: {} }; + + config.rules[code] = 1; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages[0].message, filename); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("has access to the physicalFilename", () => { + linter.defineRule(code, { + create: context => ({ + Literal(node) { + assert.strictEqual( + context.getPhysicalFilename(), + context.physicalFilename, + ); + context.report(node, context.physicalFilename); + }, + }), + }); + + const config = { rules: {} }; + + config.rules[code] = 1; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages[0].message, filename); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("defaults filename to ''", () => { + linter.defineRule(code, { + create: context => ({ + Literal(node) { + assert.strictEqual( + context.getFilename(), + context.filename, + ); + context.report(node, context.filename); + }, + }), + }); + + const config = { rules: {} }; + + config.rules[code] = 1; + + const messages = linter.verify("0", config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages[0].message, ""); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to enable rules", () => { + it("should report a violation", () => { + const code = "/*eslint no-alert:1*/ alert('test');"; + const config = { rules: {} }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].message, "Unexpected alert."); + assert.include(messages[0].nodeType, "CallExpression"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should enable rule configured using a string severity that contains uppercase letters", () => { + const code = "/*eslint no-alert: \"Error\"*/ alert('test');"; + const config = { rules: {} }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual(messages[0].message, "Unexpected alert."); + assert.include(messages[0].nodeType, "CallExpression"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("rules should not change initial config", () => { + const config = { rules: { strict: 2 } }; + const codeA = "/*eslint strict: 0*/ function bar() { return 2; }"; + const codeB = "function foo() { return 1; }"; + let messages = linter.verify(codeA, config, filename); + let suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + + messages = linter.verify(codeB, config, filename); + suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(messages.length, 1); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("rules should not change initial config", () => { + const config = { rules: { quotes: [2, "double"] } }; + const codeA = "/*eslint quotes: 0*/ function bar() { return '2'; }"; + const codeB = "function foo() { return '1'; }"; + let messages = linter.verify(codeA, config, filename); + let suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + + messages = linter.verify(codeB, config, filename); + suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(messages.length, 1); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("rules should not change initial config", () => { + const config = { rules: { quotes: [2, "double"] } }; + const codeA = + "/*eslint quotes: [0, \"single\"]*/ function bar() { return '2'; }"; + const codeB = "function foo() { return '1'; }"; + + let messages = linter.verify(codeA, config, filename); + let suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + + messages = linter.verify(codeB, config, filename); + suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("rules should not change initial config", () => { + const config = { + rules: { "no-unused-vars": [2, { vars: "all" }] }, + }; + const codeA = + '/*eslint no-unused-vars: [0, {"vars": "local"}]*/ var a = 44;'; + const codeB = "var b = 55;"; + + let messages = linter.verify(codeA, config, filename); + let suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + + messages = linter.verify(codeB, config, filename); + suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("rules use the rule's config when it is present", () => { + const config = { + rules: { + "no-constant-condition": ["error", { checkLoops: "all" }], + }, + }; + const codeA = + "/*eslint no-constant-condition: error */ while (true) {}"; + const messages = linter.verify(codeA, config, filename); + + assert.deepStrictEqual(messages, [ + { + severity: 2, + ruleId: "no-constant-condition", + message: "Unexpected constant condition.", + messageId: "unexpected", + nodeType: "Literal", + line: 1, + column: 49, + endLine: 1, + endColumn: 53, + }, + ]); + }); + + it("rules should apply meta.defaultOptions when the rule is not configured", () => { + const config = { rules: {} }; + const codeA = + "/*eslint no-constant-condition: error */ while (true) {}"; + const messages = linter.verify(codeA, config, filename); + + assert.deepStrictEqual(messages, []); + }); + + describe("when the rule has default options and a schema", () => { + beforeEach(() => { + linter.defineRules({ + "with-default-option": { + meta: { + defaultOptions: ["default-rule-option"], + schema: { + items: [{ type: "string" }], + maxItems: 1, + minItems: 1, + type: "array", + }, + }, + create(context) { + const message = context.options[0]; + + return { + Identifier(node) { + context.report({ node, message }); + }, + }; + }, + }, + }); + }); + + it("preserves default options when the comment only has severity", () => { + const code = "/*eslint with-default-option: 'warn' */\nArray;"; + const messages = linter.verify(code); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].message, "default-rule-option"); + assert.strictEqual(messages[0].ruleId, "with-default-option"); + assert.strictEqual(messages[0].severity, 1); + }); + + it("overrides default options when the comment has severity and an option", () => { + const code = + "/*eslint with-default-option: ['warn', 'overridden-rule-option'] */\nArray;"; + const messages = linter.verify(code); + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "overridden-rule-option", + ); + assert.strictEqual(messages[0].ruleId, "with-default-option"); + assert.strictEqual(messages[0].severity, 1); + }); + + it("reports an error when the comment has an option that does not match the schema", () => { + const code = + "/*eslint with-default-option: ['warn', 123] */\nArray;"; + const messages = linter.verify(code); + + assert.strictEqual(messages.length, 1); + assert.match( + messages[0].message, + /Configuration for rule "with-default-option" is invalid/gu, + ); + assert.match( + messages[0].message, + /Value 123 should be string/gu, + ); + assert.strictEqual(messages[0].ruleId, "with-default-option"); + assert.strictEqual(messages[0].severity, 2); + }); + }); + + describe("when the rule has default options and schema: false", () => { + beforeEach(() => { + linter.defineRules({ + "with-default-option": { + meta: { + defaultOptions: ["default-rule-option"], + schema: false, + }, + create(context) { + const message = `${context.options[0]}`; + + return { + Identifier(node) { + context.report({ node, message }); + }, + }; + }, + }, + }); + }); + + it("preserves default options when the comment only has severity", () => { + const code = "/*eslint with-default-option: 'warn' */\nArray;"; + const messages = linter.verify(code); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].message, "default-rule-option"); + assert.strictEqual(messages[0].ruleId, "with-default-option"); + assert.strictEqual(messages[0].severity, 1); + }); + + it("overrides default options when the comment has severity and an option", () => { + const code = + "/*eslint with-default-option: ['warn', 'overridden-rule-option'] */\nArray;"; + const messages = linter.verify(code); + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "overridden-rule-option", + ); + assert.strictEqual(messages[0].ruleId, "with-default-option"); + assert.strictEqual(messages[0].severity, 1); + }); + + it("overrides default options error when the comment has an option that does not match the default type", () => { + const code = + "/*eslint with-default-option: ['warn', 123] */\nArray;"; + const messages = linter.verify(code); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].message, "123"); + assert.strictEqual(messages[0].ruleId, "with-default-option"); + assert.strictEqual(messages[0].severity, 1); + }); + }); + + describe("when the rule was already configured", () => { + beforeEach(() => { + linter.defineRules({ + "my-rule": { + meta: { + schema: [ + { + type: "string", + }, + ], + }, + create(context) { + const message = + context.options[0] ?? "option not provided"; + + return { + Program(node) { + context.report({ node, message }); + }, + }; + }, + }, + "has-default-options": { + meta: { + schema: [ + { + type: "string", + }, + ], + defaultOptions: ["option not provided"], + }, + create(context) { + const message = context.options[0]; + + return { + Identifier(node) { + context.report({ node, message }); + }, + }; + }, + }, + "requires-option": { + meta: { + schema: { + type: "array", + items: [ + { + type: "string", + }, + ], + minItems: 1, + }, + }, + create(context) { + const message = context.options[0]; + + return { + Identifier(node) { + context.report({ node, message }); + }, + }; + }, + }, + }); + }); + + [ + "off", + "error", + ["off"], + ["warn"], + ["error"], + ["off", "bar"], + ["warn", "bar"], + ["error", "bar"], + ].forEach(ruleConfig => { + const config = { + rules: { + "has-default-options": ruleConfig, + "my-rule": ruleConfig, + }, + }; + + it(`severity from the /*eslint*/ comment and options from the config should apply when the comment has only severity (original config: ${JSON.stringify(ruleConfig)})`, () => { + const code = + "/*eslint my-rule: 'warn', has-default-options: 'warn' */ id"; + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + const expectedMessage = + Array.isArray(ruleConfig) && ruleConfig.length > 1 + ? ruleConfig[1] + : "option not provided"; + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "my-rule"); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].message, expectedMessage); + assert.strictEqual( + messages[1].ruleId, + "has-default-options", + ); + assert.strictEqual(messages[1].severity, 1); + assert.strictEqual(messages[1].message, expectedMessage); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it(`severity from the /*eslint*/ comment and options from the config should apply when the comment has array with only severity (original config: ${JSON.stringify(ruleConfig)})`, () => { + const code = + "/*eslint my-rule: ['warn'], has-default-options: ['warn'] */ id"; + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + const expectedMessage = + Array.isArray(ruleConfig) && ruleConfig.length > 1 + ? ruleConfig[1] + : "option not provided"; + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "my-rule"); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].message, expectedMessage); + assert.strictEqual( + messages[1].ruleId, + "has-default-options", + ); + assert.strictEqual(messages[1].severity, 1); + assert.strictEqual(messages[1].message, expectedMessage); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it(`severity and options from the /*eslint*/ comment should apply when the comment includes options (original config: ${JSON.stringify(ruleConfig)})`, () => { + const code = + "/*eslint my-rule: ['warn', 'foo'], has-default-options: ['warn', 'foo'] */ id"; + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "my-rule"); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].message, "foo"); + assert.strictEqual( + messages[1].ruleId, + "has-default-options", + ); + assert.strictEqual(messages[1].severity, 1); + assert.strictEqual(messages[1].message, "foo"); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + it("should validate and use originally configured options when /*eslint*/ comment enables rule that was set to 'off' in the configuration", () => { + const code = + "/*eslint my-rule: ['warn'], requires-option: 'warn' */ foo;"; + const config = { + rules: { + "my-rule": ["off", true], // invalid options for this rule + "requires-option": ["off", "Don't use identifier"], // valid options for this rule + }, + }; + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "my-rule"); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + 'Configuration for rule "my-rule" is invalid:\n\tValue true should be string.\n', + ); + assert.strictEqual(messages[1].ruleId, "requires-option"); + assert.strictEqual(messages[1].severity, 1); + assert.strictEqual(messages[1].message, "Don't use identifier"); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + }); + + describe("when evaluating code with invalid comments to enable rules", () => { + it("should report a violation when the config is not a valid rule configuration", () => { + const messages = linter.verify( + "/*eslint no-alert:true*/ alert('test');", + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + severity: 2, + ruleId: "no-alert", + message: + "Configuration for rule \"no-alert\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed 'true').\n", + line: 1, + column: 1, + endLine: 1, + endColumn: 25, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation when the config violates a rule's schema", () => { + const messages = linter.verify( + "/* eslint no-alert: [error, {nonExistentPropertyName: true}]*/", + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + severity: 2, + ruleId: "no-alert", + message: + 'Configuration for rule "no-alert" is invalid:\n\tValue [{"nonExistentPropertyName":true}] should NOT have more than 0 items.\n', + line: 1, + column: 1, + endLine: 1, + endColumn: 63, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply valid configuration even if there is an invalid configuration present", () => { + const code = [ + "/* eslint no-unused-vars: [ */ // <-- this one is invalid JSON", + '/* eslint no-undef: ["error"] */ // <-- this one is fine, and thus should apply', + "foo(); // <-- expected no-undef error here", + ].join("\n"); + + const messages = linter.verify(code); + const suppressedMessages = linter.getSuppressedMessages(); + + // different engines have different JSON parsing error messages + assert.match( + messages[0].message, + /Failed to parse JSON from '"no-unused-vars": \['/u, + ); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.isNull(messages[0].ruleId); + assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages[0].column, 1); + assert.isNull(messages[0].nodeType); + + assert.deepStrictEqual(messages[1], { + severity: 2, + ruleId: "no-undef", + message: "'foo' is not defined.", + messageId: "undef", + line: 3, + column: 1, + endLine: 3, + endColumn: 4, + nodeType: "Identifier", + }); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to disable rules", () => { + const code = "/*eslint no-alert:0*/ alert('test');"; + + it("should not report a violation", () => { + const config = { rules: { "no-alert": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to disable rules", () => { + let code, messages, suppressedMessages; + + it("should report an error when disabling a non-existent rule in inline comment", () => { + code = "/*eslint foo:0*/ ;"; + messages = linter.verify(code, {}, filename); + suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "Definition for rule 'foo' was not found.", + ); + assert.strictEqual(suppressedMessages.length, 0); + + code = "/*eslint-disable foo*/ ;"; + messages = linter.verify(code, {}, filename); + suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "Definition for rule 'foo' was not found.", + ); + assert.strictEqual(suppressedMessages.length, 0); + + code = "/*eslint-disable-line foo*/ ;"; + messages = linter.verify(code, {}, filename); + suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "Definition for rule 'foo' was not found.", + ); + assert.strictEqual(suppressedMessages.length, 0); + + code = "/*eslint-disable-next-line foo*/ ;"; + messages = linter.verify(code, {}, filename); + suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "Definition for rule 'foo' was not found.", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report an error, when disabling a non-existent rule in config", () => { + messages = linter.verify("", { rules: { foo: 0 } }, filename); + suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report an error, when config a non-existent rule in config", () => { + messages = linter.verify("", { rules: { foo: 1 } }, filename); + suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + "Definition for rule 'foo' was not found.", + ); + assert.strictEqual(suppressedMessages.length, 0); + + messages = linter.verify("", { rules: { foo: 2 } }, filename); + suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + "Definition for rule 'foo' was not found.", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to enable multiple rules", () => { + const code = + "/*eslint no-alert:1 no-console:1*/ alert('test'); console.log('test');"; + + it("should report a violation", () => { + const config = { rules: {} }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].message, "Unexpected alert."); + assert.include(messages[0].nodeType, "CallExpression"); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to enable and disable multiple rules", () => { + const code = + "/*eslint no-alert:1 no-console:0*/ alert('test'); console.log('test');"; + + it("should report a violation", () => { + const config = { rules: { "no-console": 1, "no-alert": 0 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].message, "Unexpected alert."); + assert.include(messages[0].nodeType, "CallExpression"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to disable and enable configurable rule as part of plugin", () => { + beforeEach(() => { + linter.defineRule("test-plugin/test-rule", { + create(context) { + return { + Literal(node) { + if (node.value === "trigger violation") { + context.report(node, "Reporting violation."); + } + }, + }; + }, + }); + }); + + it("should not report a violation when inline comment enables plugin rule and there's no violation", () => { + const config = { rules: {} }; + const code = + '/*eslint test-plugin/test-rule: 2*/ var a = "no violation";'; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report a violation when inline comment disables plugin rule", () => { + const code = + '/*eslint test-plugin/test-rule:0*/ var a = "trigger violation"'; + const config = { rules: { "test-plugin/test-rule": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation when the report is right before the comment", () => { + const code = " /* eslint-disable */ "; + + linter.defineRule("checker", { + create: context => ({ + Program() { + context.report({ + loc: { line: 1, column: 0 }, + message: "foo", + }); + }, + }), + }); + const problems = linter.verify(code, { + rules: { checker: "error" }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(problems.length, 1); + assert.strictEqual(problems[0].message, "foo"); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report a violation when the report is right at the start of the comment", () => { + const code = " /* eslint-disable */ "; + + linter.defineRule("checker", { + create: context => ({ + Program() { + context.report({ + loc: { line: 1, column: 1 }, + message: "foo", + }); + }, + }), + }); + const problems = linter.verify(code, { + rules: { checker: "error" }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(problems.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].message, "foo"); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual( + suppressedMessages[0].suppressions[0].justification, + "", + ); + }); + + it("rules should not change initial config", () => { + const config = { rules: { "test-plugin/test-rule": 2 } }; + const codeA = + '/*eslint test-plugin/test-rule: 0*/ var a = "trigger violation";'; + const codeB = 'var a = "trigger violation";'; + + let messages = linter.verify(codeA, config, filename); + let suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + + messages = linter.verify(codeB, config, filename); + suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with multiple configuration comments for same rule", () => { + beforeEach(() => { + linter.defineRule("no-foo", { + meta: { + schema: [ + { + enum: ["bar", "baz", "qux"], + }, + ], + }, + create(context) { + const replacement = context.options[0] ?? "default"; + + return { + "Identifier[name='foo']"(node) { + context.report( + node, + `Replace 'foo' with '${replacement}'.`, + ); + }, + }; + }, + }); + }); + + it("should apply the first and report an error for the second when there are two", () => { + const code = + "/*eslint no-foo: ['error', 'bar']*/ /*eslint no-foo: ['error', 'baz']*/ foo;"; + + const messages = linter.verify(code); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + severity: 2, + message: + 'Rule "no-foo" is already configured by another configuration comment in the preceding code. This configuration is ignored.', + line: 1, + column: 37, + endLine: 1, + endColumn: 72, + nodeType: null, + }, + { + ruleId: "no-foo", + severity: 2, + message: "Replace 'foo' with 'bar'.", + line: 1, + column: 73, + endLine: 1, + endColumn: 76, + nodeType: "Identifier", + }, + ]); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply the first and report an error for each other when there are more than two", () => { + const code = + "/*eslint no-foo: ['error', 'bar']*/ /*eslint no-foo: ['error', 'baz']*/ /*eslint no-foo: ['error', 'qux']*/ foo;"; + + const messages = linter.verify(code); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + severity: 2, + message: + 'Rule "no-foo" is already configured by another configuration comment in the preceding code. This configuration is ignored.', + line: 1, + column: 37, + endLine: 1, + endColumn: 72, + nodeType: null, + }, + { + ruleId: null, + severity: 2, + message: + 'Rule "no-foo" is already configured by another configuration comment in the preceding code. This configuration is ignored.', + line: 1, + column: 73, + endLine: 1, + endColumn: 108, + nodeType: null, + }, + { + ruleId: "no-foo", + severity: 2, + message: "Replace 'foo' with 'bar'.", + line: 1, + column: 109, + endLine: 1, + endColumn: 112, + nodeType: "Identifier", + }, + ]); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply the first and report an error for the second when both just override severity", () => { + const code = + "/*eslint no-foo: 'warn'*/ /*eslint no-foo: 'error'*/ foo;"; + + const messages = linter.verify(code, { + rules: { "no-foo": ["error", "bar"] }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + severity: 2, + message: + 'Rule "no-foo" is already configured by another configuration comment in the preceding code. This configuration is ignored.', + line: 1, + column: 27, + endLine: 1, + endColumn: 53, + nodeType: null, + }, + { + ruleId: "no-foo", + severity: 1, + message: "Replace 'foo' with 'bar'.", + line: 1, + column: 54, + endLine: 1, + endColumn: 57, + nodeType: "Identifier", + }, + ]); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply the second if the first has an invalid configuration", () => { + const code = + "/*eslint no-foo: ['error', 'quux']*/ /*eslint no-foo: ['error', 'bar']*/ foo;"; + + const messages = linter.verify(code); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.include( + messages[0].message, + 'Configuration for rule "no-foo" is invalid', + ); + assert.strictEqual( + messages[1].message, + "Replace 'foo' with 'bar'.", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply configurations for other rules that are in the same comment as the duplicate", () => { + const code = + "/*eslint no-foo: ['error', 'bar']*/ /*eslint no-foo: ['error', 'baz'], no-alert: ['error']*/ foo; alert();"; + + const messages = linter.verify(code); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 3); + assert.strictEqual( + messages[0].message, + 'Rule "no-foo" is already configured by another configuration comment in the preceding code. This configuration is ignored.', + ); + assert.strictEqual( + messages[1].message, + "Replace 'foo' with 'bar'.", + ); + assert.strictEqual(messages[2].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to enable and disable all reporting", () => { + it("should report a violation", () => { + const code = [ + "/*eslint-disable */", + "alert('test');", + "/*eslint-enable */", + "alert('test');", + ].join("\n"); + const config = { rules: { "no-alert": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].message, "Unexpected alert."); + assert.include(messages[0].nodeType, "CallExpression"); + assert.strictEqual(messages[0].line, 4); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual( + suppressedMessages[0].message, + "Unexpected alert.", + ); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual( + suppressedMessages[0].suppressions[0].justification, + "", + ); + }); + + it("should not report a violation", () => { + const code = [ + "/*eslint-disable */", + "alert('test');", + "alert('test');", + ].join("\n"); + const config = { rules: { "no-alert": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual(suppressedMessages[1].line, 3); + assert.strictEqual(suppressedMessages[1].suppressions.length, 1); + }); + + it("should not report a violation", () => { + const code = [ + " alert('test1');/*eslint-disable */\n", + "alert('test');", + " alert('test');\n", + "/*eslint-enable */alert('test2');", + ].join(""); + const config = { rules: { "no-alert": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].column, 21); + assert.strictEqual(messages[1].column, 19); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].column, 1); + assert.strictEqual(suppressedMessages[1].column, 56); + }); + + it("should report a violation", () => { + const code = [ + "/*eslint-disable */", + "alert('test');", + "/*eslint-disable */", + "alert('test');", + "/*eslint-enable*/", + "alert('test');", + "/*eslint-enable*/", + ].join("\n"); + + const config = { rules: { "no-alert": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 2); + }); + + it("should not report a violation", () => { + const code = [ + "/*eslint-disable */", + "(function(){ var b = 44;})()", + "/*eslint-enable */;any();", + ].join("\n"); + + const config = { rules: { "no-unused-vars": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 1); + }); + + it("should not report a violation", () => { + const code = [ + "(function(){ /*eslint-disable */ var b = 44;})()", + "/*eslint-enable */;any();", + ].join("\n"); + + const config = { rules: { "no-unused-vars": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 1); + }); + }); + + describe("when evaluating code with comments to ignore reporting on specific rules on a specific line", () => { + describe("eslint-disable-line", () => { + it("should report a violation", () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + "console.log('test');", // here + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + }); + + it("should report a violation", () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + "console.log('test'); // eslint-disable-line no-console", + "alert('test');", // here + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); + }); + + it("should report a violation if eslint-disable-line in a block comment is not on a single line", () => { + const code = [ + "/* eslint-disable-line", + "*", + "*/ console.log('test');", // here + ].join("\n"); + const config = { + rules: { + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[1].ruleId, "no-console"); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not disable rule and add an extra report if eslint-disable-line in a block comment is not on a single line", () => { + const code = [ + "alert('test'); /* eslint-disable-line ", + "no-alert */", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: "no-alert", + severity: 1, + line: 1, + column: 1, + endLine: 1, + endColumn: 14, + message: "Unexpected alert.", + messageId: "unexpected", + nodeType: "CallExpression", + }, + { + ruleId: null, + severity: 2, + message: + "eslint-disable-line comment should not span multiple lines.", + line: 1, + column: 16, + endLine: 2, + endColumn: 12, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report a violation for eslint-disable-line in block comment", () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + "alert('test'); /*eslint-disable-line no-alert*/", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); + }); + + it("should not report a violation", () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + "console.log('test'); // eslint-disable-line no-console", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); + }); + + it("should not report a violation", () => { + const code = [ + "alert('test') // eslint-disable-line no-alert, quotes, semi", + "console.log('test'); // eslint-disable-line", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "double"], + semi: [1, "always"], + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 5); + }); + + it("should not report a violation", () => { + const code = [ + "alert('test') /* eslint-disable-line no-alert, quotes, semi */", + "console.log('test'); /* eslint-disable-line */", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "double"], + semi: [1, "always"], + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 5); + }); + + it("should ignore violations of multiple rules when specified in mixed comments", () => { + const code = [ + ' alert("test"); /* eslint-disable-line no-alert */ // eslint-disable-line quotes', + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "single"], + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + }); + + it("should report a violation with quoted rule names in eslint-disable-line", () => { + const code = [ + "alert('test'); // eslint-disable-line 'no-alert'", + "console.log('test');", // here + "alert('test'); // eslint-disable-line \"no-alert\"", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + assert.strictEqual(messages[0].line, 2); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[0].line, 1); + assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].line, 3); + }); + }); + + describe("eslint-disable-next-line", () => { + it("should ignore violation of specified rule on next line", () => { + const code = [ + "// eslint-disable-next-line no-alert", + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + }); + + it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => { + const code = [ + "/* eslint-disable-next-line no-alert */", + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + }); + it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => { + const code = [ + "/* eslint-disable-next-line no-alert */", + "alert('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + }); + + it("should not ignore violation if code is not on next line", () => { + const code = [ + "/* eslint-disable-next-line", + "no-alert */alert('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ignore violation if block comment span multiple lines", () => { + const code = [ + "/* eslint-disable-next-line", + "no-alert */", + "alert('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + }); + + it("should ignore violations only of specified rule", () => { + const code = [ + "// eslint-disable-next-line no-console", + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ignore violations of multiple rules when specified", () => { + const code = [ + "// eslint-disable-next-line no-alert, quotes", + 'alert("test");', + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "single"], + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].ruleId, "quotes"); + }); + + it("should ignore violations of multiple rules when specified in multiple lines", () => { + const code = [ + "/* eslint-disable-next-line", + "no-alert,", + "quotes", + "*/", + 'alert("test");', + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "single"], + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + }); + + it("should ignore violations of multiple rules when specified in mixed comments", () => { + const code = [ + "/* eslint-disable-next-line no-alert */ // eslint-disable-next-line quotes", + 'alert("test");', + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "single"], + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].ruleId, "quotes"); + }); + + it("should ignore violations of multiple rules when specified in mixed single line and multi line comments", () => { + const code = [ + "/* eslint-disable-next-line", + "no-alert", + "*/ // eslint-disable-next-line quotes", + 'alert("test");', + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "single"], + }, + }; + const messages = linter.verify(code, config, filename); + + assert.strictEqual(messages.length, 0); + }); + + it("should ignore violations of only the specified rule on next line", () => { + const code = [ + "// eslint-disable-next-line quotes", + 'alert("test");', + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "single"], + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "quotes"); + }); + + it("should ignore violations of specified rule on next line only", () => { + const code = [ + "alert('test');", + "// eslint-disable-next-line no-alert", + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + }); + + it("should ignore all rule violations on next line if none specified", () => { + const code = [ + "// eslint-disable-next-line", + 'alert("test");', + "console.log('test')", + ].join("\n"); + const config = { + rules: { + semi: [1, "never"], + quotes: [1, "single"], + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 3); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].ruleId, "quotes"); + assert.strictEqual(suppressedMessages[2].ruleId, "semi"); + }); + + it("should ignore violations if eslint-disable-next-line is a block comment", () => { + const code = [ + "alert('test');", + "/* eslint-disable-next-line no-alert */", + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + }); + + it("should report a violation", () => { + const code = [ + "/* eslint-disable-next-line", + "*", + "*/", + "console.log('test');", // here + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not ignore violations if comment is of the type hashbang", () => { + const code = [ + "#! eslint-disable-next-line no-alert", + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ignore violation of specified rule on next line with quoted rule names", () => { + const code = [ + "// eslint-disable-next-line 'no-alert'", + "alert('test');", + '// eslint-disable-next-line "no-alert"', + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); + }); + }); + }); + + describe("when evaluating code with comments to enable and disable reporting of specific rules", () => { + it("should report a violation", () => { + const code = [ + "/*eslint-disable no-alert */", + "alert('test');", + "console.log('test');", // here + ].join("\n"); + const config = { rules: { "no-alert": 1, "no-console": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + }); + + it("should report no violation", () => { + const code = [ + "/*eslint-disable no-unused-vars */", + "var foo; // eslint-disable-line no-unused-vars", + "var bar;", + "/* eslint-enable no-unused-vars */", // here + ].join("\n"); + const config = { rules: { "no-unused-vars": 2 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-unused-vars"); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual(suppressedMessages[1].ruleId, "no-unused-vars"); + assert.strictEqual(suppressedMessages[1].line, 3); + }); + + it("should report no violation", () => { + const code = [ + "var foo1; // eslint-disable-line no-unused-vars", + "var foo2; // eslint-disable-line no-unused-vars", + "var foo3; // eslint-disable-line no-unused-vars", + "var foo4; // eslint-disable-line no-unused-vars", + "var foo5; // eslint-disable-line no-unused-vars", + ].join("\n"); + const config = { rules: { "no-unused-vars": 2 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 5); + }); + + it("should report no violation", () => { + const code = [ + "/* eslint-disable quotes */", + 'console.log("foo");', + "/* eslint-enable quotes */", + ].join("\n"); + const config = { rules: { quotes: 2 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation", () => { + const code = [ + "/*eslint-disable no-alert, no-console */", + "alert('test');", + "console.log('test');", + "/*eslint-enable*/", + + "alert('test');", // here + "console.log('test');", // here + ].join("\n"); + const config = { rules: { "no-alert": 1, "no-console": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 5); + assert.strictEqual(messages[1].ruleId, "no-console"); + assert.strictEqual(messages[1].line, 6); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); + assert.strictEqual(suppressedMessages[1].line, 3); + }); + + it("should report a violation", () => { + const code = [ + "/*eslint-disable no-alert */", + "alert('test');", + "console.log('test');", + "/*eslint-enable no-console */", + + "alert('test');", // here + ].join("\n"); + const config = { rules: { "no-alert": 1, "no-console": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].line, 5); + }); + + it("should report a violation", () => { + const code = [ + "/*eslint-disable no-alert, no-console */", + "alert('test');", + "console.log('test');", + "/*eslint-enable no-alert*/", + + "alert('test');", // here + "console.log('test');", + ].join("\n"); + const config = { rules: { "no-alert": 1, "no-console": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 5); + + assert.strictEqual(suppressedMessages.length, 3); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); + assert.strictEqual(suppressedMessages[1].line, 3); + assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); + assert.strictEqual(suppressedMessages[2].line, 6); + }); + + it("should report a violation", () => { + const code = [ + "/*eslint-disable no-alert */", + + "/*eslint-disable no-console */", + "alert('test');", + "console.log('test');", + "/*eslint-enable */", + + "alert('test');", // here + "console.log('test');", // here + + "/*eslint-enable */", + + "alert('test');", // here + "console.log('test');", // here + + "/*eslint-enable*/", + ].join("\n"); + const config = { rules: { "no-alert": 1, "no-console": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 4); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 6); + assert.strictEqual(messages[1].ruleId, "no-console"); + assert.strictEqual(messages[1].line, 7); + assert.strictEqual(messages[2].ruleId, "no-alert"); + assert.strictEqual(messages[2].line, 9); + assert.strictEqual(messages[3].ruleId, "no-console"); + assert.strictEqual(messages[3].line, 10); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[0].line, 3); + assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); + assert.strictEqual(suppressedMessages[1].line, 4); + }); + + it("should report a violation", () => { + const code = [ + "/*eslint-disable no-alert, no-console */", + "alert('test');", + "console.log('test');", + + "/*eslint-enable no-alert */", + + "alert('test');", // here + "console.log('test');", + + "/*eslint-enable no-console */", + + "alert('test');", // here + "console.log('test');", // here + "/*eslint-enable no-console */", + ].join("\n"); + const config = { rules: { "no-alert": 1, "no-console": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 3); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 5); + assert.strictEqual(messages[1].ruleId, "no-alert"); + assert.strictEqual(messages[1].line, 8); + assert.strictEqual(messages[2].ruleId, "no-console"); + assert.strictEqual(messages[2].line, 9); + + assert.strictEqual(suppressedMessages.length, 3); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); + assert.strictEqual(suppressedMessages[1].line, 3); + assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); + assert.strictEqual(suppressedMessages[2].line, 6); + }); + + it("should report a violation when severity is warn", () => { + const code = [ + "/*eslint-disable no-alert, no-console */", + "alert('test');", + "console.log('test');", + + "/*eslint-enable no-alert */", + + "alert('test');", // here + "console.log('test');", + + "/*eslint-enable no-console */", + + "alert('test');", // here + "console.log('test');", // here + "/*eslint-enable no-console */", + ].join("\n"); + const config = { + rules: { "no-alert": "warn", "no-console": "warn" }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 3); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 5); + assert.strictEqual(messages[1].ruleId, "no-alert"); + assert.strictEqual(messages[1].line, 8); + assert.strictEqual(messages[2].ruleId, "no-console"); + assert.strictEqual(messages[2].line, 9); + + assert.strictEqual(suppressedMessages.length, 3); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); + assert.strictEqual(suppressedMessages[1].line, 3); + assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); + assert.strictEqual(suppressedMessages[2].line, 6); + }); + + it("should report a violation with quoted rule names in eslint-disable", () => { + const code = [ + "/*eslint-disable 'no-alert' */", + "alert('test');", + "console.log('test');", // here + "/*eslint-enable */", + '/*eslint-disable "no-console" */', + "alert('test');", // here + "console.log('test');", + ].join("\n"); + const config = { rules: { "no-alert": 1, "no-console": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-console"); + assert.strictEqual(messages[1].ruleId, "no-alert"); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); + }); + + it("should report a violation with quoted rule names in eslint-enable", () => { + const code = [ + "/*eslint-disable no-alert, no-console */", + "alert('test');", + "console.log('test');", + "/*eslint-enable 'no-alert'*/", + "alert('test');", // here + "console.log('test');", + '/*eslint-enable "no-console"*/', + "console.log('test');", // here + ].join("\n"); + const config = { rules: { "no-alert": 1, "no-console": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 5); + assert.strictEqual(messages[1].ruleId, "no-console"); + assert.strictEqual(messages[1].line, 8); + + assert.strictEqual(suppressedMessages.length, 3); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); + assert.strictEqual(suppressedMessages[1].line, 3); + assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); + assert.strictEqual(suppressedMessages[2].line, 6); + }); + }); + + describe("when evaluating code with comments to enable and disable multiple comma separated rules", () => { + const code = + "/*eslint no-alert:1, no-console:0*/ alert('test'); console.log('test');"; + + it("should report a violation", () => { + const config = { rules: { "no-console": 1, "no-alert": 0 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].message, "Unexpected alert."); + assert.include(messages[0].nodeType, "CallExpression"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to enable configurable rule", () => { + const code = "/*eslint quotes:[2, \"double\"]*/ alert('test');"; + + it("should report a violation", () => { + const config = { rules: { quotes: [2, "single"] } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "quotes"); + assert.strictEqual( + messages[0].message, + "Strings must use doublequote.", + ); + assert.include(messages[0].nodeType, "Literal"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to enable configurable rule using string severity", () => { + const code = '/*eslint quotes:["error", "double"]*/ alert(\'test\');'; + + it("should report a violation", () => { + const config = { rules: { quotes: [2, "single"] } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "quotes"); + assert.strictEqual( + messages[0].message, + "Strings must use doublequote.", + ); + assert.include(messages[0].nodeType, "Literal"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with incorrectly formatted comments to disable rule", () => { + it("should report a violation", () => { + const code = "/*eslint no-alert:'1'*/ alert('test');"; + + const config = { rules: { "no-alert": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + + /* + * Incorrectly formatted comment threw error; + * message from caught exception + * may differ amongst UAs, so verifying + * first part only as defined in the + * parseJsonConfig function in lib/eslint.js + */ + assert.match( + messages[0].message, + /^Failed to parse JSON from '"no-alert":'1'':/u, + ); + assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages[0].column, 1); + assert.strictEqual(messages[0].endLine, 1); + assert.strictEqual(messages[0].endColumn, 24); + assert.strictEqual(messages[0].ruleId, null); + assert.strictEqual(messages[0].fatal, true); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual(messages[0].nodeType, null); + + assert.strictEqual(messages[1].ruleId, "no-alert"); + assert.strictEqual(messages[1].message, "Unexpected alert."); + assert.include(messages[1].nodeType, "CallExpression"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation", () => { + const code = "/*eslint no-alert:abc*/ alert('test');"; + + const config = { rules: { "no-alert": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + + /* + * Incorrectly formatted comment threw error; + * message from caught exception + * may differ amongst UAs, so verifying + * first part only as defined in the + * parseJsonConfig function in lib/eslint.js + */ + assert.match( + messages[0].message, + /^Failed to parse JSON from '"no-alert":abc':/u, + ); + assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages[0].column, 1); + assert.strictEqual(messages[0].endLine, 1); + assert.strictEqual(messages[0].endColumn, 24); + assert.strictEqual(messages[0].ruleId, null); + assert.strictEqual(messages[0].fatal, true); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual(messages[0].nodeType, null); + + assert.strictEqual(messages[1].ruleId, "no-alert"); + assert.strictEqual(messages[1].message, "Unexpected alert."); + assert.include(messages[1].nodeType, "CallExpression"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation", () => { + const code = "\n\n\n /*eslint no-alert:0 2*/ alert('test');"; + + const config = { rules: { "no-alert": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + + /* + * Incorrectly formatted comment threw error; + * message from caught exception + * may differ amongst UAs, so verifying + * first part only as defined in the + * parseJsonConfig function in lib/eslint.js + */ + assert.match( + messages[0].message, + /^Failed to parse JSON from '"no-alert":0 2':/u, + ); + assert.strictEqual(messages[0].line, 4); + assert.strictEqual(messages[0].column, 5); + assert.strictEqual(messages[0].endLine, 4); + assert.strictEqual(messages[0].endColumn, 28); + assert.strictEqual(messages[0].ruleId, null); + assert.strictEqual(messages[0].fatal, true); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual(messages[0].nodeType, null); + + assert.strictEqual(messages[1].ruleId, "no-alert"); + assert.strictEqual(messages[1].message, "Unexpected alert."); + assert.include(messages[1].nodeType, "CallExpression"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments which have colon in its value", () => { + const code = String.raw` /* eslint max-len: [2, 100, 2, {ignoreUrls: true, ignorePattern: "data:image\\/|\\s*require\\s*\\(|^\\s*loader\\.lazy|-\\*-"}] */ alert('test'); `; - it("should not parse errors, should report a violation", () => { - const messages = linter.verify(code, {}, filename); - const suppressedMessages = linter.getSuppressedMessages(); + it("should not parse errors, should report a violation", () => { + const messages = linter.verify(code, {}, filename); + const suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "max-len"); - assert.strictEqual(messages[0].message, "This line has a length of 129. Maximum allowed is 100."); - assert.include(messages[0].nodeType, "Program"); + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "max-len"); + assert.strictEqual( + messages[0].message, + "This line has a length of 129. Maximum allowed is 100.", + ); + assert.include(messages[0].nodeType, "Program"); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); - describe("when evaluating code with comments that contain escape sequences", () => { - const code = String.raw` + describe("when evaluating code with comments that contain escape sequences", () => { + const code = String.raw` /* eslint max-len: ["error", 1, { ignoreComments: true, ignorePattern: "console\\.log\\(" }] */ console.log("test"); consolexlog("test2"); var a = "test2"; `; - it("should validate correctly", () => { - const config = { rules: {} }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - const [message1, message2] = messages; - - assert.strictEqual(messages.length, 2); - assert.strictEqual(message1.ruleId, "max-len"); - assert.strictEqual(message1.message, "This line has a length of 21. Maximum allowed is 1."); - assert.strictEqual(message1.line, 4); - assert.strictEqual(message1.column, 1); - assert.include(message1.nodeType, "Program"); - assert.strictEqual(message2.ruleId, "max-len"); - assert.strictEqual(message2.message, "This line has a length of 16. Maximum allowed is 1."); - assert.strictEqual(message2.line, 5); - assert.strictEqual(message2.column, 1); - assert.include(message2.nodeType, "Program"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating a file with a hashbang", () => { - - it("should preserve line numbers", () => { - const code = "#!bin/program\n\nvar foo;;"; - const config = { rules: { "no-extra-semi": 1 } }; - const messages = linter.verify(code, config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-extra-semi"); - assert.strictEqual(messages[0].nodeType, "EmptyStatement"); - assert.strictEqual(messages[0].line, 3); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should have a comment with the hashbang in it", () => { - const code = "#!bin/program\n\nvar foo;;"; - const config = { rules: { checker: "error" } }; - const spy = sinon.spy(context => { - const comments = context.getAllComments(); - - assert.strictEqual(comments.length, 1); - assert.strictEqual(comments[0].type, "Shebang"); - return {}; - }); - - linter.defineRule("checker", { create: spy }); - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should comment hashbang without breaking offset", () => { - const code = "#!/usr/bin/env node\n'123';"; - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), "'123';"); - }); - return { ExpressionStatement: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - }); - - describe("when evaluating broken code", () => { - const code = BROKEN_TEST_CODE; - - it("should report a violation with a useful parse error prefix", () => { - const messages = linter.verify(code); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.isNull(messages[0].ruleId); - assert.strictEqual(messages[0].line, 1); - assert.strictEqual(messages[0].column, 4); - assert.isTrue(messages[0].fatal); - assert.match(messages[0].message, /^Parsing error:/u); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report source code where the issue is present", () => { - const inValidCode = [ - "var x = 20;", - "if (x ==4 {", - " x++;", - "}" - ]; - const messages = linter.verify(inValidCode.join("\n")); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.isTrue(messages[0].fatal); - assert.match(messages[0].message, /^Parsing error:/u); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when using an invalid (undefined) rule", () => { - linter = new Linter(); - - const code = TEST_CODE; - let results, result, warningResult, arrayOptionResults, objectOptionResults, resultsMultiple; - - beforeEach(() => { - results = linter.verify(code, { rules: { foobar: 2 } }); - result = results[0]; - warningResult = linter.verify(code, { rules: { foobar: 1 } })[0]; - arrayOptionResults = linter.verify(code, { rules: { foobar: [2, "always"] } }); - objectOptionResults = linter.verify(code, { rules: { foobar: [1, { bar: false }] } }); - resultsMultiple = linter.verify(code, { rules: { foobar: 2, barfoo: 1 } }); - }); - - it("should report a problem", () => { - assert.isNotNull(result); - assert.isArray(results); - assert.isObject(result); - assert.property(result, "ruleId"); - assert.strictEqual(result.ruleId, "foobar"); - }); - - it("should report that the rule does not exist", () => { - assert.property(result, "message"); - assert.strictEqual(result.message, "Definition for rule 'foobar' was not found."); - }); - - it("should report at the correct severity", () => { - assert.property(result, "severity"); - assert.strictEqual(result.severity, 2); - assert.strictEqual(warningResult.severity, 2); // this is 2, since the rulename is very likely to be wrong - }); - - it("should accept any valid rule configuration", () => { - assert.isObject(arrayOptionResults[0]); - assert.isObject(objectOptionResults[0]); - }); - - it("should report multiple missing rules", () => { - assert.isArray(resultsMultiple); - - assert.deepStrictEqual( - resultsMultiple[1], - { - ruleId: "barfoo", - message: "Definition for rule 'barfoo' was not found.", - line: 1, - column: 1, - endLine: 1, - endColumn: 2, - severity: 2, - nodeType: null - } - ); - }); - }); - - describe("when using a rule which has been replaced", () => { - const code = TEST_CODE; - - it("should report the new rule", () => { - const results = linter.verify(code, { rules: { "no-comma-dangle": 2 } }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(results[0].ruleId, "no-comma-dangle"); - assert.strictEqual(results[0].message, "Rule 'no-comma-dangle' was removed and replaced by: comma-dangle"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when calling getRules", () => { - it("should return all loaded rules", () => { - const rules = linter.getRules(); - - assert.isAbove(rules.size, 230); - assert.isObject(rules.get("no-alert")); - }); - }); - - describe("when calling version", () => { - it("should return current version number", () => { - const version = linter.version; - - assert.isString(version); - assert.isTrue(parseInt(version[0], 10) >= 3); - }); - }); - - describe("when evaluating an empty string", () => { - it("runs rules", () => { - linter.defineRule("no-programs", { - create: context => ({ - Program(node) { - context.report({ node, message: "No programs allowed." }); - } - }) - }); - - assert.strictEqual( - linter.verify("", { rules: { "no-programs": "error" } }).length, - 1 - ); - }); - }); - - describe("when evaluating code without comments to environment", () => { - it("should report a violation when using typed array", () => { - const code = "var array = new Uint8Array();"; - - const config = { rules: { "no-undef": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation when using Promise", () => { - const code = "new Promise();"; - - const config = { rules: { "no-undef": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); + it("should validate correctly", () => { + const config = { rules: {} }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + const [message1, message2] = messages; + + assert.strictEqual(messages.length, 2); + assert.strictEqual(message1.ruleId, "max-len"); + assert.strictEqual( + message1.message, + "This line has a length of 21. Maximum allowed is 1.", + ); + assert.strictEqual(message1.line, 4); + assert.strictEqual(message1.column, 1); + assert.include(message1.nodeType, "Program"); + assert.strictEqual(message2.ruleId, "max-len"); + assert.strictEqual( + message2.message, + "This line has a length of 16. Maximum allowed is 1.", + ); + assert.strictEqual(message2.line, 5); + assert.strictEqual(message2.column, 1); + assert.include(message2.nodeType, "Program"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating a file with a hashbang", () => { + it("should preserve line numbers", () => { + const code = "#!bin/program\n\nvar foo;;"; + const config = { rules: { "no-extra-semi": 1 } }; + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-extra-semi"); + assert.strictEqual(messages[0].nodeType, "EmptyStatement"); + assert.strictEqual(messages[0].line, 3); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should have a comment with the hashbang in it", () => { + const code = "#!bin/program\n\nvar foo;;"; + const config = { rules: { checker: "error" } }; + const spy = sinon.spy(context => { + const comments = context.sourceCode.getAllComments(); + + assert.strictEqual(comments.length, 1); + assert.strictEqual(comments[0].type, "Shebang"); + return {}; + }); + + linter.defineRule("checker", { create: spy }); + linter.verify(code, config); + assert(spy.calledOnce); + }); + + it("should comment hashbang without breaking offset", () => { + const code = "#!/usr/bin/env node\n'123';"; + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual( + context.sourceCode.getText(node), + "'123';", + ); + }); + return { ExpressionStatement: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("when evaluating broken code", () => { + const code = BROKEN_TEST_CODE; + + it("should report a violation with a useful parse error prefix", () => { + const messages = linter.verify(code); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.isNull(messages[0].ruleId); + assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages[0].column, 4); + assert.isTrue(messages[0].fatal); + assert.match(messages[0].message, /^Parsing error:/u); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report source code where the issue is present", () => { + const inValidCode = ["var x = 20;", "if (x ==4 {", " x++;", "}"]; + const messages = linter.verify(inValidCode.join("\n")); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.match(messages[0].message, /^Parsing error:/u); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when using an invalid (undefined) rule", () => { + const code = TEST_CODE; + let results, + result, + warningResult, + arrayOptionResults, + objectOptionResults, + resultsMultiple; + + beforeEach(() => { + results = linter.verify(code, { rules: { foobar: 2 } }); + result = results[0]; + warningResult = linter.verify(code, { rules: { foobar: 1 } })[0]; + arrayOptionResults = linter.verify(code, { + rules: { foobar: [2, "always"] }, + }); + objectOptionResults = linter.verify(code, { + rules: { foobar: [1, { bar: false }] }, + }); + resultsMultiple = linter.verify(code, { + rules: { foobar: 2, barfoo: 1 }, + }); + }); + + it("should report a problem", () => { + assert.isNotNull(result); + assert.isArray(results); + assert.isObject(result); + assert.property(result, "ruleId"); + assert.strictEqual(result.ruleId, "foobar"); + }); + + it("should report that the rule does not exist", () => { + assert.property(result, "message"); + assert.strictEqual( + result.message, + "Definition for rule 'foobar' was not found.", + ); + }); + + it("should report at the correct severity", () => { + assert.property(result, "severity"); + assert.strictEqual(result.severity, 2); + assert.strictEqual(warningResult.severity, 2); // this is 2, since the rulename is very likely to be wrong + }); + + it("should accept any valid rule configuration", () => { + assert.isObject(arrayOptionResults[0]); + assert.isObject(objectOptionResults[0]); + }); + + it("should report multiple missing rules", () => { + assert.isArray(resultsMultiple); + + assert.deepStrictEqual(resultsMultiple[1], { + ruleId: "barfoo", + message: "Definition for rule 'barfoo' was not found.", + line: 1, + column: 1, + endLine: 1, + endColumn: 2, + severity: 2, + nodeType: null, + }); + }); + }); + + describe("when using a rule which has been replaced", () => { + const code = TEST_CODE; + + it("should report the new rule", () => { + const results = linter.verify(code, { + rules: { "no-comma-dangle": 2 }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(results[0].ruleId, "no-comma-dangle"); + assert.strictEqual( + results[0].message, + "Rule 'no-comma-dangle' was removed and replaced by: comma-dangle", + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when calling getRules", () => { + it("should return all loaded rules", () => { + const rules = linter.getRules(); + + assert.isAbove(rules.size, 230); + assert.isObject(rules.get("no-alert")); + }); + }); + + describe("when calling version", () => { + it("should return current version number", () => { + const version = linter.version; + + assert.isString(version); + assert.isTrue(parseInt(version[0], 10) >= 3); + }); + }); + + describe("when evaluating an empty string", () => { + it("runs rules", () => { + linter.defineRule("no-programs", { + create: context => ({ + Program(node) { + context.report({ + node, + message: "No programs allowed.", + }); + }, + }), + }); + + assert.strictEqual( + linter.verify("", { rules: { "no-programs": "error" } }).length, + 1, + ); + }); + }); + + describe("when evaluating code without comments to environment", () => { + it("should report a violation when using typed array", () => { + const code = "var array = new Uint8Array();"; + + const config = { rules: { "no-undef": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation when using Promise", () => { + const code = "new Promise();"; + + const config = { rules: { "no-undef": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); - describe("when evaluating code with comments to environment", () => { - it("should not support legacy config", () => { - const code = "/*jshint mocha:true */ describe();"; + describe("when evaluating code with comments to environment", () => { + it("should not support legacy config", () => { + const code = "/*jshint mocha:true */ describe();"; - const config = { rules: { "no-undef": 1 } }; + const config = { rules: { "no-undef": 1 } }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-undef"); - assert.strictEqual(messages[0].nodeType, "Identifier"); - assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-undef"); + assert.strictEqual(messages[0].nodeType, "Identifier"); + assert.strictEqual(messages[0].line, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); + assert.strictEqual(suppressedMessages.length, 0); + }); - it("should not report a violation", () => { - const code = "/*eslint-env es6 */ new Promise();"; + it("should not report a violation", () => { + const code = "/*eslint-env es6 */ new Promise();"; - const config = { rules: { "no-undef": 1 } }; + const config = { rules: { "no-undef": 1 } }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); - // https://github.com/eslint/eslint/issues/14652 - it("should not report a violation", () => { - const codes = [ - "/*eslint-env es6\n */ new Promise();", - "/*eslint-env browser,\nes6 */ window;Promise;", - "/*eslint-env\nbrowser,es6 */ window;Promise;" - ]; - const config = { rules: { "no-undef": 1 } }; + // https://github.com/eslint/eslint/issues/14652 + it("should not report a violation", () => { + const codes = [ + "/*eslint-env es6\n */ new Promise();", + "/*eslint-env browser,\nes6 */ window;Promise;", + "/*eslint-env\nbrowser,es6 */ window;Promise;", + ]; + const config = { rules: { "no-undef": 1 } }; - for (const code of codes) { - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); + for (const code of codes) { + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - } + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + } + }); - }); + it("should not report a violation", () => { + const code = `/*${ESLINT_ENV} mocha,node */ require();describe();`; - it("should not report a violation", () => { - const code = `/*${ESLINT_ENV} mocha,node */ require();describe();`; + const config = { rules: { "no-undef": 1 } }; - const config = { rules: { "no-undef": 1 } }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); + it("should not report a violation", () => { + const code = "/*eslint-env mocha */ suite();test();"; - it("should not report a violation", () => { - const code = "/*eslint-env mocha */ suite();test();"; + const config = { rules: { "no-undef": 1 } }; - const config = { rules: { "no-undef": 1 } }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); + it("should not report a violation", () => { + const code = `/*${ESLINT_ENV} amd */ define();require();`; - it("should not report a violation", () => { - const code = `/*${ESLINT_ENV} amd */ define();require();`; + const config = { rules: { "no-undef": 1 } }; - const config = { rules: { "no-undef": 1 } }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); + it("should not report a violation", () => { + const code = `/*${ESLINT_ENV} jasmine */ expect();spyOn();`; - it("should not report a violation", () => { - const code = `/*${ESLINT_ENV} jasmine */ expect();spyOn();`; + const config = { rules: { "no-undef": 1 } }; - const config = { rules: { "no-undef": 1 } }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); + it("should not report a violation", () => { + const code = `/*globals require: true */ /*${ESLINT_ENV} node */ require = 1;`; - it("should not report a violation", () => { - const code = `/*globals require: true */ /*${ESLINT_ENV} node */ require = 1;`; + const config = { rules: { "no-undef": 1 } }; - const config = { rules: { "no-undef": 1 } }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); + it("should not report a violation", () => { + const code = `/*${ESLINT_ENV} node */ process.exit();`; - it("should not report a violation", () => { - const code = `/*${ESLINT_ENV} node */ process.exit();`; + const config = { rules: {} }; - const config = { rules: {} }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); + it("should not report a violation", () => { + const code = `/*eslint no-process-exit: 0 */ /*${ESLINT_ENV} node */ process.exit();`; - it("should not report a violation", () => { - const code = `/*eslint no-process-exit: 0 */ /*${ESLINT_ENV} node */ process.exit();`; - - const config = { rules: { "no-undef": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to change config when allowInlineConfig is enabled", () => { - it("should report a violation for disabling rules", () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: false - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation for global variable declarations", () => { - const code = [ - "/* global foo */" - ].join("\n"); - const config = { - rules: { - test: 2 - } - }; - let ok = false; - - linter.defineRules({ - test: { - create: context => ({ - Program() { - const scope = context.getScope(); - const sourceCode = context.sourceCode; - const comments = sourceCode.getAllComments(); - - assert.strictEqual(context.getSourceCode(), sourceCode); - assert.strictEqual(1, comments.length); - - const foo = getVariable(scope, "foo"); - - assert.notOk(foo); - - ok = true; - } - }) - } - }); - - linter.verify(code, config, { allowInlineConfig: false }); - assert(ok); - }); - - it("should report a violation for eslint-disable", () => { - const code = [ - "/* eslint-disable */", - "alert('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: false - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report a violation for rule changes", () => { - const code = [ - "/*eslint no-alert:2*/", - "alert('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 0 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: false - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation for disable-line", () => { - const code = [ - "alert('test'); // eslint-disable-line" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: false - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation for env changes", () => { - const code = [ - `/*${ESLINT_ENV} browser*/ window` - ].join("\n"); - const config = { - rules: { - "no-undef": 2 - } - }; - const messages = linter.verify(code, config, { allowInlineConfig: false }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-undef"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with 'noInlineComment'", () => { - for (const directive of [ - "globals foo", - "global foo", - "exported foo", - "eslint eqeqeq: error", - "eslint-disable eqeqeq", - "eslint-disable-line eqeqeq", - "eslint-disable-next-line eqeqeq", - "eslint-enable eqeqeq", - "eslint-env es6" - ]) { - // eslint-disable-next-line no-loop-func -- No closures - it(`should warn '/* ${directive} */' if 'noInlineConfig' was given.`, () => { - const messages = linter.verify(`/* ${directive} */`, { noInlineConfig: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages.length, 1); - assert.deepStrictEqual(messages[0].fatal, void 0); - assert.deepStrictEqual(messages[0].ruleId, null); - assert.deepStrictEqual(messages[0].severity, 1); - assert.deepStrictEqual(messages[0].message, `'/*${directive.split(" ")[0]}*/' has no effect because you have 'noInlineConfig' setting in your config.`); - - assert.strictEqual(suppressedMessages.length, 0); - }); - } - - for (const directive of [ - "eslint-disable-line eqeqeq", - "eslint-disable-next-line eqeqeq" - ]) { - // eslint-disable-next-line no-loop-func -- No closures - it(`should warn '// ${directive}' if 'noInlineConfig' was given.`, () => { - const messages = linter.verify(`// ${directive}`, { noInlineConfig: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages.length, 1); - assert.deepStrictEqual(messages[0].fatal, void 0); - assert.deepStrictEqual(messages[0].ruleId, null); - assert.deepStrictEqual(messages[0].severity, 1); - assert.deepStrictEqual(messages[0].message, `'//${directive.split(" ")[0]}' has no effect because you have 'noInlineConfig' setting in your config.`); - - assert.strictEqual(suppressedMessages.length, 0); - }); - } - - it("should not warn if 'noInlineConfig' and '--no-inline-config' were given.", () => { - const messages = linter.verify("/* globals foo */", { noInlineConfig: true }, { allowInlineConfig: false }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when receiving cwd in options during instantiation", () => { - const code = "a;\nb;"; - const config = { rules: { checker: "error" } }; - - it("should get cwd correctly in the context", () => { - const cwd = "cwd"; - const linterWithOption = new Linter({ cwd }); - let spy; - - linterWithOption.defineRule("checker", { - create(context) { - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), context.cwd); - assert.strictEqual(context.cwd, cwd); - }); - return { Program: spy }; - } - }); - - linterWithOption.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should assign process.cwd() to it if cwd is undefined", () => { - let spy; - const linterWithOption = new Linter({ }); - - linterWithOption.defineRule("checker", { - create(context) { - - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), context.cwd); - assert.strictEqual(context.cwd, process.cwd()); - }); - return { Program: spy }; - } - }); - - linterWithOption.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should assign process.cwd() to it if the option is undefined", () => { - let spy; - - linter.defineRule("checker", { - create(context) { - - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), context.cwd); - assert.strictEqual(context.cwd, process.cwd()); - }); - return { Program: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("reportUnusedDisable option", () => { - it("reports problems for unused eslint-disable comments", () => { - const messages = linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("reports problems for multiple eslint-disable comments, including unused ones", () => { - const code = [ - "/* eslint-disable no-alert -- j1 */", - "alert(\"test\"); //eslint-disable-line no-alert -- j2" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].suppressions.length, 2); - }); - - it("reports problems for eslint-disable-line and eslint-disable-next-line comments, including unused ones", () => { - const code = [ - "// eslint-disable-next-line no-alert -- j1 */", - "alert(\"test\"); //eslint-disable-line no-alert -- j2" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].suppressions.length, 2); - }); - - it("reports problems for multiple unused eslint-disable comments with multiple ruleIds", () => { - const code = [ - "/* eslint no-undef: 2, no-void: 2 */", - "/* eslint-disable no-undef -- j1 */", - "void foo; //eslint-disable-line no-undef, no-void -- j2" - ].join("\n"); - const config = { - rules: { - "no-undef": 2, - "no-void": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-void"); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - assert.strictEqual(suppressedMessages[1].ruleId, "no-undef"); - assert.strictEqual(suppressedMessages[1].suppressions.length, 2); - }); - - it("reports problems for unused eslint-disable comments (error)", () => { - const messages = linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: "error" }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("reports problems for unused eslint-disable comments (warn)", () => { - const messages = linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: "warn" }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 1, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("reports problems for unused eslint-enable comments", () => { - const messages = linter.verify("/* eslint-enable */", {}, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", - line: 1, - column: 1, - fix: { - range: [0, 19], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("reports problems for unused eslint-enable comments with ruleId", () => { - const messages = linter.verify("/* eslint-enable no-alert */", {}, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'no-alert').", - line: 1, - column: 1, - fix: { - range: [0, 28], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("reports problems for unused eslint-enable comments with mismatch ruleId", () => { - const code = [ - "/* eslint-disable no-alert */", - "alert(\"test\");", - "/* eslint-enable no-console */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'no-console').", - line: 3, - column: 1, - fix: { - range: [45, 75], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 1); - }); - - it("reports problems for unused eslint-enable comments with used eslint-enable comments", () => { - const code = [ - "/* eslint-disable no-alert -- j1 */", - "alert(\"test\");", - "/* eslint-disable no-alert -- j2 */", - "alert(\"test\");", - "/* eslint-enable no-alert -- j3 */", - "/* eslint-enable -- j4 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", - line: 6, - column: 1, - fix: { - range: [137, 162], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - assert.strictEqual(suppressedMessages[1].suppressions.length, 2); - }); - - it("reports problems for unused eslint-disable comments with used eslint-enable comments", () => { - const code = [ - "/* eslint-disable no-alert -- j1 */", - "console.log(\"test\"); //", - "/* eslint-enable no-alert -- j2 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'no-alert').", - line: 1, - column: 1, - fix: { - range: [0, 35], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("reports problems for unused eslint-disable comments (in config)", () => { - const messages = linter.verify("/* eslint-disable */", { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 1, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("reports problems for partially unused eslint-disable comments (in config)", () => { - const code = "alert('test'); // eslint-disable-line no-alert, no-redeclare"; - const config = { - reportUnusedDisableDirectives: true, - rules: { - "no-alert": 1, - "no-redeclare": 1 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: true - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'no-redeclare').", - line: 1, - column: 16, - fix: { - range: [46, 60], - text: "" - }, - severity: 1, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("reports no problems for no-fallthrough despite comment pattern match", () => { - const code = "switch (foo) { case 0: a(); \n// eslint-disable-next-line no-fallthrough\n case 1: }"; - const config = { - reportUnusedDisableDirectives: true, - rules: { - "no-fallthrough": 2 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: true - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-fallthrough"); - }); - - it("reports problems for multiple eslint-enable comments with same ruleId", () => { - const code = [ - "/* eslint-disable no-alert -- j1 */", - "alert(\"test\"); //", - "/* eslint-enable no-alert -- j2 */", - "/* eslint-enable no-alert -- j3 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].line, 4); - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - }); - - it("reports problems for multiple eslint-enable comments without ruleId (Rule is already enabled)", () => { - const code = [ - "/* eslint-disable no-alert -- j1 */", - "alert(\"test\"); //", - "/* eslint-enable no-alert -- j2 */", - "/* eslint-enable -- j3 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].line, 4); - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - }); - - it("reports problems for multiple eslint-enable comments with ruleId (Rule is already enabled by eslint-enable comments without ruleId)", () => { - const code = [ - "/* eslint-disable no-alert -- j1 */", - "alert(\"test\"); //", - "/* eslint-enable -- j3 */", - "/* eslint-enable no-alert -- j2 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].line, 4); - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - }); - - it("reports problems for eslint-enable comments without ruleId (Two rules are already enabled)", () => { - const code = [ - "/* eslint-disable no-alert, no-console -- j1 */", - "alert(\"test\"); //", - "console.log(\"test\"); //", - "/* eslint-enable no-alert -- j2 */", - "/* eslint-enable no-console -- j3 */", - "/* eslint-enable -- j4 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2, - "no-console": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].line, 6); - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - assert.strictEqual(suppressedMessages[1].suppressions.length, 1); - }); - - it("reports problems for multiple eslint-enable comments with ruleId (Two rules are already enabled by eslint-enable comments without ruleId)", () => { - const code = [ - "/* eslint-disable no-alert, no-console -- j1 */", - "alert(\"test\"); //", - "console.log(\"test\"); //", - "/* eslint-enable -- j2 */", - "/* eslint-enable no-console -- j3 */", - "/* eslint-enable no-alert -- j4 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2, - "no-console": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].line, 5); - assert.strictEqual(messages[1].line, 6); - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - assert.strictEqual(suppressedMessages[1].suppressions.length, 1); - }); - - it("reports problems for multiple eslint-enable comments", () => { - const code = [ - "/* eslint-disable no-alert, no-console -- j1 */", - "alert(\"test\"); //", - "console.log(\"test\"); //", - "/* eslint-enable no-console -- j2 */", - "/* eslint-enable -- j3 */", - "/* eslint-enable no-alert -- j4 */", - "/* eslint-enable -- j5 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2, - "no-console": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].line, 6); - assert.strictEqual(messages[1].line, 7); - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - assert.strictEqual(suppressedMessages[1].suppressions.length, 1); - }); - - describe("autofix", () => { - const alwaysReportsRule = { - create(context) { - return { - Program(node) { - context.report({ message: "bad code", loc: node.loc.end }); - }, - "Identifier[name=bad]"(node) { - context.report({ message: "bad id", loc: node.loc }); - } - }; - } - }; - - const neverReportsRule = { - create() { - return {}; - } - }; - - const ruleCount = 3; - const usedRules = Array.from( - { length: ruleCount }, - (_, index) => `used${index ? `-${index}` : ""}` // "used", "used-1", "used-2" - ); - const unusedRules = usedRules.map(name => `un${name}`); // "unused", "unused-1", "unused-2" - - const config = { - reportUnusedDisableDirectives: true, - rules: { - ...Object.fromEntries(usedRules.map(name => [name, "error"])), - ...Object.fromEntries(unusedRules.map(name => [name, "error"])) - } - }; - - beforeEach(() => { - linter.defineRules(Object.fromEntries(usedRules.map(name => [name, alwaysReportsRule]))); - linter.defineRules(Object.fromEntries(unusedRules.map(name => [name, neverReportsRule]))); - }); - - const tests = [ - - //----------------------------------------------- - // Removing the entire comment - //----------------------------------------------- - - { - code: "// eslint-disable-line unused", - output: " " - }, - { - code: "foo// eslint-disable-line unused", - output: "foo " - }, - { - code: "// eslint-disable-line ,unused,", - output: " " - }, - { - code: "// eslint-disable-line unused-1, unused-2", - output: " " - }, - { - code: "// eslint-disable-line ,unused-1,, unused-2,, -- comment", - output: " " - }, - { - code: "// eslint-disable-next-line unused\n", - output: " \n" - }, - { - code: "// eslint-disable-next-line unused\nfoo", - output: " \nfoo" - }, - { - code: "/* eslint-disable \nunused\n*/", - output: " " - }, - { - code: "/* eslint-enable \nunused\n*/", - output: " " - }, - - //----------------------------------------------- - // Removing only individual rules - //----------------------------------------------- - - // content before the first rule should not be changed - { - code: "//eslint-disable-line unused, used", - output: "//eslint-disable-line used" - }, - { - code: "// eslint-disable-line unused, used", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line unused, used", - output: "// eslint-disable-line used" - }, - { - code: "/*\neslint-disable unused, used*/", - output: "/*\neslint-disable used*/" - }, - { - code: "/*\n eslint-disable unused, used*/", - output: "/*\n eslint-disable used*/" - }, - { - code: "/*\r\neslint-disable unused, used*/", - output: "/*\r\neslint-disable used*/" - }, - { - code: "/*\u2028eslint-disable unused, used*/", - output: "/*\u2028eslint-disable used*/" - }, - { - code: "/*\u00A0eslint-disable unused, used*/", - output: "/*\u00A0eslint-disable used*/" - }, - { - code: "/* eslint-disable used*/ bad /*\neslint-enable unused, used*/", - output: "/* eslint-disable used*/ bad /*\neslint-enable used*/" - }, - { - code: "/* eslint-disable used*/ bad /*\n eslint-enable unused, used*/", - output: "/* eslint-disable used*/ bad /*\n eslint-enable used*/" - }, - { - code: "/* eslint-disable used*/ bad /*\r\neslint-enable unused, used*/", - output: "/* eslint-disable used*/ bad /*\r\neslint-enable used*/" - }, - { - code: "/* eslint-disable used*/ bad /*\u2028eslint-enable unused, used*/", - output: "/* eslint-disable used*/ bad /*\u2028eslint-enable used*/" - }, - { - code: "/* eslint-disable used*/ bad /*\u00A0eslint-enable unused, used*/", - output: "/* eslint-disable used*/ bad /*\u00A0eslint-enable used*/" - }, - { - code: "// eslint-disable-line unused, used", - output: "// eslint-disable-line used" - }, - { - code: "/* eslint-disable\nunused, used*/", - output: "/* eslint-disable\nused*/" - }, - { - code: "/* eslint-disable\n unused, used*/", - output: "/* eslint-disable\n used*/" - }, - { - code: "/* eslint-disable\r\nunused, used*/", - output: "/* eslint-disable\r\nused*/" - }, - { - code: "/* eslint-disable\u2028unused, used*/", - output: "/* eslint-disable\u2028used*/" - }, - { - code: "/* eslint-disable\u00A0unused, used*/", - output: "/* eslint-disable\u00A0used*/" - }, - { - code: "/* eslint-disable used*/ bad /* eslint-enable\nunused, used*/", - output: "/* eslint-disable used*/ bad /* eslint-enable\nused*/" - }, - { - code: "/* eslint-disable used*/ bad /* eslint-enable\n unused, used*/", - output: "/* eslint-disable used*/ bad /* eslint-enable\n used*/" - }, - { - code: "/* eslint-disable used*/ bad /* eslint-enable\r\nunused, used*/", - output: "/* eslint-disable used*/ bad /* eslint-enable\r\nused*/" - }, - { - code: "/* eslint-disable used*/ bad /* eslint-enable\u2028unused, used*/", - output: "/* eslint-disable used*/ bad /* eslint-enable\u2028used*/" - }, - { - code: "/* eslint-disable used*/ bad /* eslint-enable\u00A0unused, used*/", - output: "/* eslint-disable used*/ bad /* eslint-enable\u00A0used*/" - }, - - // when removing the first rule, the comma and all whitespace up to the next rule (or next lone comma) should also be removed - { - code: "// eslint-disable-line unused,used", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line unused, used", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line unused , used", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line unused, used", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line unused ,used", - output: "// eslint-disable-line used" - }, - { - code: "/* eslint-disable unused\n,\nused */", - output: "/* eslint-disable used */" - }, - { - code: "/* eslint-disable unused \n \n,\n\n used */", - output: "/* eslint-disable used */" - }, - { - code: "/* eslint-disable unused\u2028,\u2028used */", - output: "/* eslint-disable used */" - }, - { - code: "/* eslint-disable used*/ bad /* eslint-enable unused\n,\nused */", - output: "/* eslint-disable used*/ bad /* eslint-enable used */" - }, - { - code: "/* eslint-disable used*/ bad /* eslint-enable unused \n \n,\n\n used */", - output: "/* eslint-disable used*/ bad /* eslint-enable used */" - }, - { - code: "/* eslint-disable used*/ bad /* eslint-enable unused\u2028,\u2028used */", - output: "/* eslint-disable used*/ bad /* eslint-enable used */" - }, - { - code: "// eslint-disable-line unused\u00A0,\u00A0used", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line unused,,used", - output: "// eslint-disable-line ,used" - }, - { - code: "// eslint-disable-line unused, ,used", - output: "// eslint-disable-line ,used" - }, - { - code: "// eslint-disable-line unused,, used", - output: "// eslint-disable-line , used" - }, - { - code: "// eslint-disable-line unused,used ", - output: "// eslint-disable-line used " - }, - { - code: "// eslint-disable-next-line unused,used\n", - output: "// eslint-disable-next-line used\n" - }, - - // when removing a rule in the middle, one comma and all whitespace between commas should also be removed - { - code: "// eslint-disable-line used-1,unused,used-2", - output: "// eslint-disable-line used-1,used-2" - }, - { - code: "// eslint-disable-line used-1, unused,used-2", - output: "// eslint-disable-line used-1,used-2" - }, - { - code: "// eslint-disable-line used-1,unused ,used-2", - output: "// eslint-disable-line used-1,used-2" - }, - { - code: "// eslint-disable-line used-1, unused ,used-2", - output: "// eslint-disable-line used-1,used-2" - }, - { - code: "/* eslint-disable used-1,\nunused\n,used-2 */", - output: "/* eslint-disable used-1,used-2 */" - }, - { - code: "/* eslint-disable used-1,\n\n unused \n \n ,used-2 */", - output: "/* eslint-disable used-1,used-2 */" - }, - { - code: "/* eslint-disable used-1,\u2028unused\u2028,used-2 */", - output: "/* eslint-disable used-1,used-2 */" - }, - { - code: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,\nunused\n,used-2 */", - output: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,used-2 */" - }, - { - code: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,\n\n unused \n \n ,used-2 */", - output: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,used-2 */" - }, - { - code: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,\u2028unused\u2028,used-2 */", - output: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,used-2 */" - }, - { - code: "// eslint-disable-line used-1,\u00A0unused\u00A0,used-2", - output: "// eslint-disable-line used-1,used-2" - }, - - // when removing a rule in the middle, content around commas should not be changed - { - code: "// eslint-disable-line used-1, unused ,used-2", - output: "// eslint-disable-line used-1,used-2" - }, - { - code: "// eslint-disable-line used-1,unused, used-2", - output: "// eslint-disable-line used-1, used-2" - }, - { - code: "// eslint-disable-line used-1 ,unused,used-2", - output: "// eslint-disable-line used-1 ,used-2" - }, - { - code: "// eslint-disable-line used-1 ,unused, used-2", - output: "// eslint-disable-line used-1 , used-2" - }, - { - code: "// eslint-disable-line used-1 , unused , used-2", - output: "// eslint-disable-line used-1 , used-2" - }, - { - code: "/* eslint-disable used-1\n,unused,\nused-2 */", - output: "/* eslint-disable used-1\n,\nused-2 */" - }, - { - code: "/* eslint-disable used-1\u2028,unused,\u2028used-2 */", - output: "/* eslint-disable used-1\u2028,\u2028used-2 */" - }, - { - code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1\n,unused,\nused-2 */", - output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1\n,\nused-2 */" - }, - { - code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1\u2028,unused,\u2028used-2 */", - output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1\u2028,\u2028used-2 */" - }, - { - code: "// eslint-disable-line used-1\u00A0,unused,\u00A0used-2", - output: "// eslint-disable-line used-1\u00A0,\u00A0used-2" - }, - { - code: "// eslint-disable-line , unused ,used", - output: "// eslint-disable-line ,used" - }, - { - code: "/* eslint-disable\n, unused ,used */", - output: "/* eslint-disable\n,used */" - }, - { - code: "/* eslint-disable used-1,\n,unused,used-2 */", - output: "/* eslint-disable used-1,\n,used-2 */" - }, - { - code: "/* eslint-disable used-1,unused,\n,used-2 */", - output: "/* eslint-disable used-1,\n,used-2 */" - }, - { - code: "/* eslint-disable used-1,\n,unused,\n,used-2 */", - output: "/* eslint-disable used-1,\n,\n,used-2 */" - }, - { - code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,unused,used-2 */", - output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,used-2 */" - }, - { - code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,unused,\n,used-2 */", - output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,used-2 */" - }, - { - code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,unused,\n,used-2 */", - output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,\n,used-2 */" - }, - { - code: "// eslint-disable-line used, unused,", - output: "// eslint-disable-line used," - }, - { - code: "// eslint-disable-next-line used, unused,\n", - output: "// eslint-disable-next-line used,\n" - }, - { - code: "// eslint-disable-line used, unused, ", - output: "// eslint-disable-line used, " - }, - { - code: "// eslint-disable-line used, unused, -- comment", - output: "// eslint-disable-line used, -- comment" - }, - { - code: "/* eslint-disable used, unused,\n*/", - output: "/* eslint-disable used,\n*/" - }, - { - code: "/* eslint-disable used */ bad /* eslint-enable used, unused,\n*/", - output: "/* eslint-disable used */ bad /* eslint-enable used,\n*/" - }, - - // when removing the last rule, the comma and all whitespace up to the previous rule (or previous lone comma) should also be removed - { - code: "// eslint-disable-line used,unused", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line used, unused", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line used ,unused", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line used , unused", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line used, unused", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line used ,unused", - output: "// eslint-disable-line used" - }, - { - code: "/* eslint-disable used\n,\nunused */", - output: "/* eslint-disable used */" - }, - { - code: "/* eslint-disable used \n \n,\n\n unused */", - output: "/* eslint-disable used */" - }, - { - code: "/* eslint-disable used\u2028,\u2028unused */", - output: "/* eslint-disable used */" - }, - { - code: "/* eslint-disable used*/ bad /* eslint-enable used\n,\nunused */", - output: "/* eslint-disable used*/ bad /* eslint-enable used */" - }, - { - code: "/* eslint-disable used*/ bad /* eslint-enable used \n \n,\n\n unused */", - output: "/* eslint-disable used*/ bad /* eslint-enable used */" - }, - { - code: "/* eslint-disable used*/ bad /* eslint-enable used\u2028,\u2028unused */", - output: "/* eslint-disable used*/ bad /* eslint-enable used */" - }, - { - code: "// eslint-disable-line used\u00A0,\u00A0unused", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line used,,unused", - output: "// eslint-disable-line used," - }, - { - code: "// eslint-disable-line used, ,unused", - output: "// eslint-disable-line used," - }, - { - code: "/* eslint-disable used,\n,unused */", - output: "/* eslint-disable used, */" - }, - { - code: "/* eslint-disable used\n, ,unused */", - output: "/* eslint-disable used\n, */" - }, - { - code: "/* eslint-disable used*/ bad /* eslint-enable used,\n,unused */", - output: "/* eslint-disable used*/ bad /* eslint-enable used, */" - }, - { - code: "/* eslint-disable used*/ bad /* eslint-enable used\n, ,unused */", - output: "/* eslint-disable used*/ bad /* eslint-enable used\n, */" - }, - - // content after the last rule should not be changed - { - code: "// eslint-disable-line used,unused", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line used,unused ", - output: "// eslint-disable-line used " - }, - { - code: "// eslint-disable-line used,unused ", - output: "// eslint-disable-line used " - }, - { - code: "// eslint-disable-line used,unused -- comment", - output: "// eslint-disable-line used -- comment" - }, - { - code: "// eslint-disable-next-line used,unused\n", - output: "// eslint-disable-next-line used\n" - }, - { - code: "// eslint-disable-next-line used,unused \n", - output: "// eslint-disable-next-line used \n" - }, - { - code: "/* eslint-disable used,unused\u2028*/", - output: "/* eslint-disable used\u2028*/" - }, - { - code: "/* eslint-disable used*/ bad /* eslint-enable used,unused\u2028*/", - output: "/* eslint-disable used*/ bad /* eslint-enable used\u2028*/" - }, - { - code: "// eslint-disable-line used,unused\u00A0", - output: "// eslint-disable-line used\u00A0" - }, - - // multiply rules to remove - { - code: "// eslint-disable-line used, unused-1, unused-2", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line unused-1, used, unused-2", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line unused-1, unused-2, used", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line used-1, unused-1, used-2, unused-2", - output: "// eslint-disable-line used-1, used-2" - }, - { - code: "// eslint-disable-line unused-1, used-1, unused-2, used-2", - output: "// eslint-disable-line used-1, used-2" - }, - { - code: ` + const config = { rules: { "no-undef": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to change config when allowInlineConfig is enabled", () => { + it("should report a violation for disabling rules", () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation for global variable declarations", () => { + const code = ["/* global foo */"].join("\n"); + const config = { + rules: { + test: 2, + }, + }; + let ok = false; + + linter.defineRules({ + test: { + create: context => ({ + Program(node) { + const scope = context.sourceCode.getScope(node); + const sourceCode = context.sourceCode; + const comments = sourceCode.getAllComments(); + + assert.strictEqual( + context.getSourceCode(), + sourceCode, + ); + assert.strictEqual(1, comments.length); + + const foo = getVariable(scope, "foo"); + + assert.notOk(foo); + + ok = true; + }, + }), + }, + }); + + linter.verify(code, config, { allowInlineConfig: false }); + assert(ok); + }); + + it("should report a violation for eslint-disable", () => { + const code = ["/* eslint-disable */", "alert('test');"].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report a violation for rule changes", () => { + const code = ["/*eslint no-alert:2*/", "alert('test');"].join("\n"); + const config = { + rules: { + "no-alert": 0, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation for disable-line", () => { + const code = ["alert('test'); // eslint-disable-line"].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation for env changes", () => { + const code = [`/*${ESLINT_ENV} browser*/ window`].join("\n"); + const config = { + rules: { + "no-undef": 2, + }, + }; + const messages = linter.verify(code, config, { + allowInlineConfig: false, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-undef"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with 'noInlineComment'", () => { + for (const directive of [ + "globals foo", + "global foo", + "exported foo", + "eslint eqeqeq: error", + "eslint-disable eqeqeq", + "eslint-disable-line eqeqeq", + "eslint-disable-next-line eqeqeq", + "eslint-enable eqeqeq", + "eslint-env es6", + ]) { + // eslint-disable-next-line no-loop-func -- No closures + it(`should warn '/* ${directive} */' if 'noInlineConfig' was given.`, () => { + const messages = linter.verify(`/* ${directive} */`, { + noInlineConfig: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages.length, 1); + assert.deepStrictEqual(messages[0].fatal, void 0); + assert.deepStrictEqual(messages[0].ruleId, null); + assert.deepStrictEqual(messages[0].severity, 1); + assert.deepStrictEqual( + messages[0].message, + `'/*${directive.split(" ")[0]}*/' has no effect because you have 'noInlineConfig' setting in your config.`, + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + } + + for (const directive of [ + "eslint-disable-line eqeqeq", + "eslint-disable-next-line eqeqeq", + ]) { + // eslint-disable-next-line no-loop-func -- No closures + it(`should warn '// ${directive}' if 'noInlineConfig' was given.`, () => { + const messages = linter.verify(`// ${directive}`, { + noInlineConfig: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages.length, 1); + assert.deepStrictEqual(messages[0].fatal, void 0); + assert.deepStrictEqual(messages[0].ruleId, null); + assert.deepStrictEqual(messages[0].severity, 1); + assert.deepStrictEqual( + messages[0].message, + `'//${directive.split(" ")[0]}' has no effect because you have 'noInlineConfig' setting in your config.`, + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + } + + it("should not warn if 'noInlineConfig' and '--no-inline-config' were given.", () => { + const messages = linter.verify( + "/* globals foo */", + { noInlineConfig: true }, + { allowInlineConfig: false }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when receiving cwd in options during instantiation", () => { + const code = "a;\nb;"; + const config = { rules: { checker: "error" } }; + + it("should get cwd correctly in the context", () => { + const cwd = "/cwd"; + const linterWithOption = new Linter({ + cwd, + configType: "eslintrc", + }); + let spy; + + linterWithOption.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), context.cwd); + assert.strictEqual(context.cwd, cwd); + }); + return { Program: spy }; + }, + }); + + linterWithOption.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("should assign process.cwd() to it if cwd is undefined", () => { + let spy; + const linterWithOption = new Linter({ configType: "eslintrc" }); + + linterWithOption.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), context.cwd); + assert.strictEqual(context.cwd, process.cwd()); + }); + return { Program: spy }; + }, + }); + + linterWithOption.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("should assign process.cwd() to it if the option is undefined", () => { + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual(context.getCwd(), context.cwd); + assert.strictEqual(context.cwd, process.cwd()); + }); + return { Program: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("reportUnusedDisable option", () => { + it("reports problems for unused eslint-disable comments", () => { + const messages = linter.verify( + "/* eslint-disable */", + {}, + { reportUnusedDisableDirectives: true }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for multiple eslint-disable comments, including unused ones", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + 'alert("test"); //eslint-disable-line no-alert -- j2', + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].suppressions.length, 2); + }); + + it("reports problems for eslint-disable-line and eslint-disable-next-line comments, including unused ones", () => { + const code = [ + "// eslint-disable-next-line no-alert -- j1 */", + 'alert("test"); //eslint-disable-line no-alert -- j2', + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].suppressions.length, 2); + }); + + it("reports problems for multiple unused eslint-disable comments with multiple ruleIds", () => { + const code = [ + "/* eslint no-undef: 2, no-void: 2 */", + "/* eslint-disable no-undef -- j1 */", + "void foo; //eslint-disable-line no-undef, no-void -- j2", + ].join("\n"); + const config = { + rules: { + "no-undef": 2, + "no-void": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].ruleId, "no-void"); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual(suppressedMessages[1].ruleId, "no-undef"); + assert.strictEqual(suppressedMessages[1].suppressions.length, 2); + }); + + it("reports problems for unused eslint-disable comments (error)", () => { + const messages = linter.verify( + "/* eslint-disable */", + {}, + { reportUnusedDisableDirectives: "error" }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-disable comments (warn)", () => { + const messages = linter.verify( + "/* eslint-disable */", + {}, + { reportUnusedDisableDirectives: "warn" }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 1, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-enable comments", () => { + const messages = linter.verify( + "/* eslint-enable */", + {}, + { reportUnusedDisableDirectives: true }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 1, + column: 1, + fix: { + range: [0, 19], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-enable comments with ruleId", () => { + const messages = linter.verify( + "/* eslint-enable no-alert */", + {}, + { reportUnusedDisableDirectives: true }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'no-alert').", + line: 1, + column: 1, + fix: { + range: [0, 28], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-enable comments with mismatch ruleId", () => { + const code = [ + "/* eslint-disable no-alert */", + 'alert("test");', + "/* eslint-enable no-console */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'no-console').", + line: 3, + column: 1, + fix: { + range: [45, 75], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 1); + }); + + it("reports problems for unused eslint-enable comments with used eslint-enable comments", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + 'alert("test");', + "/* eslint-disable no-alert -- j2 */", + 'alert("test");', + "/* eslint-enable no-alert -- j3 */", + "/* eslint-enable -- j4 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 6, + column: 1, + fix: { + range: [137, 162], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual(suppressedMessages[1].suppressions.length, 2); + }); + + it("reports problems for unused eslint-disable comments with used eslint-enable comments", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + 'console.log("test"); //', + "/* eslint-enable no-alert -- j2 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'no-alert').", + line: 1, + column: 1, + fix: { + range: [0, 35], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-disable comments (in config)", () => { + const messages = linter.verify("/* eslint-disable */", { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 1, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for partially unused eslint-disable comments (in config)", () => { + const code = + "alert('test'); // eslint-disable-line no-alert, no-redeclare"; + const config = { + reportUnusedDisableDirectives: true, + rules: { + "no-alert": 1, + "no-redeclare": 1, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'no-redeclare').", + line: 1, + column: 16, + fix: { + range: [46, 60], + text: "", + }, + severity: 1, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + }); + + it("reports no problems for no-fallthrough despite comment pattern match", () => { + const code = + "switch (foo) { case 0: a(); \n// eslint-disable-next-line no-fallthrough\n case 1: }"; + const config = { + reportUnusedDisableDirectives: true, + rules: { + "no-fallthrough": 2, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-fallthrough"); + }); + + it("reports problems for multiple eslint-enable comments with same ruleId", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + 'alert("test"); //', + "/* eslint-enable no-alert -- j2 */", + "/* eslint-enable no-alert -- j3 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 4); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + }); + + it("reports problems for multiple eslint-enable comments without ruleId (Rule is already enabled)", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + 'alert("test"); //', + "/* eslint-enable no-alert -- j2 */", + "/* eslint-enable -- j3 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 4); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + }); + + it("reports problems for multiple eslint-enable comments with ruleId (Rule is already enabled by eslint-enable comments without ruleId)", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + 'alert("test"); //', + "/* eslint-enable -- j3 */", + "/* eslint-enable no-alert -- j2 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 4); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + }); + + it("reports problems for eslint-enable comments without ruleId (Two rules are already enabled)", () => { + const code = [ + "/* eslint-disable no-alert, no-console -- j1 */", + 'alert("test"); //', + 'console.log("test"); //', + "/* eslint-enable no-alert -- j2 */", + "/* eslint-enable no-console -- j3 */", + "/* eslint-enable -- j4 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + "no-console": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 6); + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual(suppressedMessages[1].suppressions.length, 1); + }); + + it("reports problems for multiple eslint-enable comments with ruleId (Two rules are already enabled by eslint-enable comments without ruleId)", () => { + const code = [ + "/* eslint-disable no-alert, no-console -- j1 */", + 'alert("test"); //', + 'console.log("test"); //', + "/* eslint-enable -- j2 */", + "/* eslint-enable no-console -- j3 */", + "/* eslint-enable no-alert -- j4 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + "no-console": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].line, 5); + assert.strictEqual(messages[1].line, 6); + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual(suppressedMessages[1].suppressions.length, 1); + }); + + it("reports problems for multiple eslint-enable comments", () => { + const code = [ + "/* eslint-disable no-alert, no-console -- j1 */", + 'alert("test"); //', + 'console.log("test"); //', + "/* eslint-enable no-console -- j2 */", + "/* eslint-enable -- j3 */", + "/* eslint-enable no-alert -- j4 */", + "/* eslint-enable -- j5 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + "no-console": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].line, 6); + assert.strictEqual(messages[1].line, 7); + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].suppressions.length, 1); + assert.strictEqual(suppressedMessages[1].suppressions.length, 1); + }); + + describe("autofix", () => { + const alwaysReportsRule = { + create(context) { + return { + Program(node) { + context.report({ + message: "bad code", + loc: node.loc.end, + }); + }, + "Identifier[name=bad]"(node) { + context.report({ + message: "bad id", + loc: node.loc, + }); + }, + }; + }, + }; + + const neverReportsRule = { + create() { + return {}; + }, + }; + + const ruleCount = 3; + const usedRules = Array.from( + { length: ruleCount }, + (_, index) => `used${index ? `-${index}` : ""}`, // "used", "used-1", "used-2" + ); + const unusedRules = usedRules.map(name => `un${name}`); // "unused", "unused-1", "unused-2" + + const config = { + reportUnusedDisableDirectives: true, + rules: { + ...Object.fromEntries( + usedRules.map(name => [name, "error"]), + ), + ...Object.fromEntries( + unusedRules.map(name => [name, "error"]), + ), + }, + }; + + beforeEach(() => { + linter.defineRules( + Object.fromEntries( + usedRules.map(name => [name, alwaysReportsRule]), + ), + ); + linter.defineRules( + Object.fromEntries( + unusedRules.map(name => [name, neverReportsRule]), + ), + ); + }); + + const tests = [ + //----------------------------------------------- + // Removing the entire comment + //----------------------------------------------- + + { + code: "// eslint-disable-line unused", + output: " ", + }, + { + code: "foo// eslint-disable-line unused", + output: "foo ", + }, + { + code: "// eslint-disable-line ,unused,", + output: " ", + }, + { + code: "// eslint-disable-line unused-1, unused-2", + output: " ", + }, + { + code: "// eslint-disable-line ,unused-1,, unused-2,, -- comment", + output: " ", + }, + { + code: "// eslint-disable-next-line unused\n", + output: " \n", + }, + { + code: "// eslint-disable-next-line unused\nfoo", + output: " \nfoo", + }, + { + code: "/* eslint-disable \nunused\n*/", + output: " ", + }, + { + code: "/* eslint-enable \nunused\n*/", + output: " ", + }, + + //----------------------------------------------- + // Removing only individual rules + //----------------------------------------------- + + // content before the first rule should not be changed + { + code: "//eslint-disable-line unused, used", + output: "//eslint-disable-line used", + }, + { + code: "// eslint-disable-line unused, used", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line unused, used", + output: "// eslint-disable-line used", + }, + { + code: "/*\neslint-disable unused, used*/", + output: "/*\neslint-disable used*/", + }, + { + code: "/*\n eslint-disable unused, used*/", + output: "/*\n eslint-disable used*/", + }, + { + code: "/*\r\neslint-disable unused, used*/", + output: "/*\r\neslint-disable used*/", + }, + { + code: "/*\u2028eslint-disable unused, used*/", + output: "/*\u2028eslint-disable used*/", + }, + { + code: "/*\u00A0eslint-disable unused, used*/", + output: "/*\u00A0eslint-disable used*/", + }, + { + code: "/* eslint-disable used*/ bad /*\neslint-enable unused, used*/", + output: "/* eslint-disable used*/ bad /*\neslint-enable used*/", + }, + { + code: "/* eslint-disable used*/ bad /*\n eslint-enable unused, used*/", + output: "/* eslint-disable used*/ bad /*\n eslint-enable used*/", + }, + { + code: "/* eslint-disable used*/ bad /*\r\neslint-enable unused, used*/", + output: "/* eslint-disable used*/ bad /*\r\neslint-enable used*/", + }, + { + code: "/* eslint-disable used*/ bad /*\u2028eslint-enable unused, used*/", + output: "/* eslint-disable used*/ bad /*\u2028eslint-enable used*/", + }, + { + code: "/* eslint-disable used*/ bad /*\u00A0eslint-enable unused, used*/", + output: "/* eslint-disable used*/ bad /*\u00A0eslint-enable used*/", + }, + { + code: "// eslint-disable-line unused, used", + output: "// eslint-disable-line used", + }, + { + code: "/* eslint-disable\nunused, used*/", + output: "/* eslint-disable\nused*/", + }, + { + code: "/* eslint-disable\n unused, used*/", + output: "/* eslint-disable\n used*/", + }, + { + code: "/* eslint-disable\r\nunused, used*/", + output: "/* eslint-disable\r\nused*/", + }, + { + code: "/* eslint-disable\u2028unused, used*/", + output: "/* eslint-disable\u2028used*/", + }, + { + code: "/* eslint-disable\u00A0unused, used*/", + output: "/* eslint-disable\u00A0used*/", + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable\nunused, used*/", + output: "/* eslint-disable used*/ bad /* eslint-enable\nused*/", + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable\n unused, used*/", + output: "/* eslint-disable used*/ bad /* eslint-enable\n used*/", + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable\r\nunused, used*/", + output: "/* eslint-disable used*/ bad /* eslint-enable\r\nused*/", + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable\u2028unused, used*/", + output: "/* eslint-disable used*/ bad /* eslint-enable\u2028used*/", + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable\u00A0unused, used*/", + output: "/* eslint-disable used*/ bad /* eslint-enable\u00A0used*/", + }, + + // when removing the first rule, the comma and all whitespace up to the next rule (or next lone comma) should also be removed + { + code: "// eslint-disable-line unused,used", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line unused, used", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line unused , used", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line unused, used", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line unused ,used", + output: "// eslint-disable-line used", + }, + { + code: "/* eslint-disable unused\n,\nused */", + output: "/* eslint-disable used */", + }, + { + code: "/* eslint-disable unused \n \n,\n\n used */", + output: "/* eslint-disable used */", + }, + { + code: "/* eslint-disable unused\u2028,\u2028used */", + output: "/* eslint-disable used */", + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable unused\n,\nused */", + output: "/* eslint-disable used*/ bad /* eslint-enable used */", + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable unused \n \n,\n\n used */", + output: "/* eslint-disable used*/ bad /* eslint-enable used */", + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable unused\u2028,\u2028used */", + output: "/* eslint-disable used*/ bad /* eslint-enable used */", + }, + { + code: "// eslint-disable-line unused\u00A0,\u00A0used", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line unused,,used", + output: "// eslint-disable-line ,used", + }, + { + code: "// eslint-disable-line unused, ,used", + output: "// eslint-disable-line ,used", + }, + { + code: "// eslint-disable-line unused,, used", + output: "// eslint-disable-line , used", + }, + { + code: "// eslint-disable-line unused,used ", + output: "// eslint-disable-line used ", + }, + { + code: "// eslint-disable-next-line unused,used\n", + output: "// eslint-disable-next-line used\n", + }, + + // when removing a rule in the middle, one comma and all whitespace between commas should also be removed + { + code: "// eslint-disable-line used-1,unused,used-2", + output: "// eslint-disable-line used-1,used-2", + }, + { + code: "// eslint-disable-line used-1, unused,used-2", + output: "// eslint-disable-line used-1,used-2", + }, + { + code: "// eslint-disable-line used-1,unused ,used-2", + output: "// eslint-disable-line used-1,used-2", + }, + { + code: "// eslint-disable-line used-1, unused ,used-2", + output: "// eslint-disable-line used-1,used-2", + }, + { + code: "/* eslint-disable used-1,\nunused\n,used-2 */", + output: "/* eslint-disable used-1,used-2 */", + }, + { + code: "/* eslint-disable used-1,\n\n unused \n \n ,used-2 */", + output: "/* eslint-disable used-1,used-2 */", + }, + { + code: "/* eslint-disable used-1,\u2028unused\u2028,used-2 */", + output: "/* eslint-disable used-1,used-2 */", + }, + { + code: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,\nunused\n,used-2 */", + output: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,used-2 */", + }, + { + code: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,\n\n unused \n \n ,used-2 */", + output: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,used-2 */", + }, + { + code: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,\u2028unused\u2028,used-2 */", + output: "/* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1,used-2 */", + }, + { + code: "// eslint-disable-line used-1,\u00A0unused\u00A0,used-2", + output: "// eslint-disable-line used-1,used-2", + }, + + // when removing a rule in the middle, content around commas should not be changed + { + code: "// eslint-disable-line used-1, unused ,used-2", + output: "// eslint-disable-line used-1,used-2", + }, + { + code: "// eslint-disable-line used-1,unused, used-2", + output: "// eslint-disable-line used-1, used-2", + }, + { + code: "// eslint-disable-line used-1 ,unused,used-2", + output: "// eslint-disable-line used-1 ,used-2", + }, + { + code: "// eslint-disable-line used-1 ,unused, used-2", + output: "// eslint-disable-line used-1 , used-2", + }, + { + code: "// eslint-disable-line used-1 , unused , used-2", + output: "// eslint-disable-line used-1 , used-2", + }, + { + code: "/* eslint-disable used-1\n,unused,\nused-2 */", + output: "/* eslint-disable used-1\n,\nused-2 */", + }, + { + code: "/* eslint-disable used-1\u2028,unused,\u2028used-2 */", + output: "/* eslint-disable used-1\u2028,\u2028used-2 */", + }, + { + code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1\n,unused,\nused-2 */", + output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1\n,\nused-2 */", + }, + { + code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1\u2028,unused,\u2028used-2 */", + output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1\u2028,\u2028used-2 */", + }, + { + code: "// eslint-disable-line used-1\u00A0,unused,\u00A0used-2", + output: "// eslint-disable-line used-1\u00A0,\u00A0used-2", + }, + { + code: "// eslint-disable-line , unused ,used", + output: "// eslint-disable-line ,used", + }, + { + code: "/* eslint-disable\n, unused ,used */", + output: "/* eslint-disable\n,used */", + }, + { + code: "/* eslint-disable used-1,\n,unused,used-2 */", + output: "/* eslint-disable used-1,\n,used-2 */", + }, + { + code: "/* eslint-disable used-1,unused,\n,used-2 */", + output: "/* eslint-disable used-1,\n,used-2 */", + }, + { + code: "/* eslint-disable used-1,\n,unused,\n,used-2 */", + output: "/* eslint-disable used-1,\n,\n,used-2 */", + }, + { + code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,unused,used-2 */", + output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,used-2 */", + }, + { + code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,unused,\n,used-2 */", + output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,used-2 */", + }, + { + code: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,unused,\n,used-2 */", + output: "/* eslint-disable used-1, used-2 */ bad /* eslint-enable used-1,\n,\n,used-2 */", + }, + { + code: "// eslint-disable-line used, unused,", + output: "// eslint-disable-line used,", + }, + { + code: "// eslint-disable-next-line used, unused,\n", + output: "// eslint-disable-next-line used,\n", + }, + { + code: "// eslint-disable-line used, unused, ", + output: "// eslint-disable-line used, ", + }, + { + code: "// eslint-disable-line used, unused, -- comment", + output: "// eslint-disable-line used, -- comment", + }, + { + code: "/* eslint-disable used, unused,\n*/", + output: "/* eslint-disable used,\n*/", + }, + { + code: "/* eslint-disable used */ bad /* eslint-enable used, unused,\n*/", + output: "/* eslint-disable used */ bad /* eslint-enable used,\n*/", + }, + + // when removing the last rule, the comma and all whitespace up to the previous rule (or previous lone comma) should also be removed + { + code: "// eslint-disable-line used,unused", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line used, unused", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line used ,unused", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line used , unused", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line used, unused", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line used ,unused", + output: "// eslint-disable-line used", + }, + { + code: "/* eslint-disable used\n,\nunused */", + output: "/* eslint-disable used */", + }, + { + code: "/* eslint-disable used \n \n,\n\n unused */", + output: "/* eslint-disable used */", + }, + { + code: "/* eslint-disable used\u2028,\u2028unused */", + output: "/* eslint-disable used */", + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable used\n,\nunused */", + output: "/* eslint-disable used*/ bad /* eslint-enable used */", + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable used \n \n,\n\n unused */", + output: "/* eslint-disable used*/ bad /* eslint-enable used */", + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable used\u2028,\u2028unused */", + output: "/* eslint-disable used*/ bad /* eslint-enable used */", + }, + { + code: "// eslint-disable-line used\u00A0,\u00A0unused", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line used,,unused", + output: "// eslint-disable-line used,", + }, + { + code: "// eslint-disable-line used, ,unused", + output: "// eslint-disable-line used,", + }, + { + code: "/* eslint-disable used,\n,unused */", + output: "/* eslint-disable used, */", + }, + { + code: "/* eslint-disable used\n, ,unused */", + output: "/* eslint-disable used\n, */", + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable used,\n,unused */", + output: "/* eslint-disable used*/ bad /* eslint-enable used, */", + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable used\n, ,unused */", + output: "/* eslint-disable used*/ bad /* eslint-enable used\n, */", + }, + + // content after the last rule should not be changed + { + code: "// eslint-disable-line used,unused", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line used,unused ", + output: "// eslint-disable-line used ", + }, + { + code: "// eslint-disable-line used,unused ", + output: "// eslint-disable-line used ", + }, + { + code: "// eslint-disable-line used,unused -- comment", + output: "// eslint-disable-line used -- comment", + }, + { + code: "// eslint-disable-next-line used,unused\n", + output: "// eslint-disable-next-line used\n", + }, + { + code: "// eslint-disable-next-line used,unused \n", + output: "// eslint-disable-next-line used \n", + }, + { + code: "/* eslint-disable used,unused\u2028*/", + output: "/* eslint-disable used\u2028*/", + }, + { + code: "/* eslint-disable used*/ bad /* eslint-enable used,unused\u2028*/", + output: "/* eslint-disable used*/ bad /* eslint-enable used\u2028*/", + }, + { + code: "// eslint-disable-line used,unused\u00A0", + output: "// eslint-disable-line used\u00A0", + }, + + // multiply rules to remove + { + code: "// eslint-disable-line used, unused-1, unused-2", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line unused-1, used, unused-2", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line unused-1, unused-2, used", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line used-1, unused-1, used-2, unused-2", + output: "// eslint-disable-line used-1, used-2", + }, + { + code: "// eslint-disable-line unused-1, used-1, unused-2, used-2", + output: "// eslint-disable-line used-1, used-2", + }, + { + code: ` /* eslint-disable unused-1, used-1, unused-2, used-2 */ `, - output: ` + output: ` /* eslint-disable used-1, used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable unused-1, used-1, @@ -5267,15 +5435,15 @@ var a = "test2"; used-2 */ `, - output: ` + output: ` /* eslint-disable used-1, used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable used-1, unused-1, @@ -5283,15 +5451,15 @@ var a = "test2"; unused-2 */ `, - output: ` + output: ` /* eslint-disable used-1, used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable used-1, unused-1, @@ -5299,15 +5467,15 @@ var a = "test2"; unused-2, */ `, - output: ` + output: ` /* eslint-disable used-1, used-2, */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable ,unused-1 ,used-1 @@ -5315,15 +5483,15 @@ var a = "test2"; ,used-2 */ `, - output: ` + output: ` /* eslint-disable ,used-1 ,used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable ,used-1 ,unused-1 @@ -5331,15 +5499,15 @@ var a = "test2"; ,unused-2 */ `, - output: ` + output: ` /* eslint-disable ,used-1 ,used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable used-1, unused-1, @@ -5349,17 +5517,17 @@ var a = "test2"; -- comment */ `, - output: ` + output: ` /* eslint-disable used-1, used-2 -- comment */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable unused-1, @@ -5368,16 +5536,16 @@ var a = "test2"; used-2 */ `, - output: ` + output: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1, used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable unused-1, @@ -5386,16 +5554,16 @@ var a = "test2"; used-2 */ `, - output: ` + output: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1, used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable @@ -5405,17 +5573,17 @@ var a = "test2"; used-2 */ `, - output: ` + output: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1, used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable @@ -5425,17 +5593,17 @@ var a = "test2"; unused-2 */ `, - output: ` + output: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1, used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable @@ -5445,17 +5613,17 @@ var a = "test2"; unused-2, */ `, - output: ` + output: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable used-1, used-2, */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable @@ -5465,17 +5633,17 @@ var a = "test2"; ,used-2 */ `, - output: ` + output: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable ,used-1 ,used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable @@ -5485,17 +5653,17 @@ var a = "test2"; ,unused-2 */ `, - output: ` + output: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable ,used-1 ,used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable @@ -5507,7 +5675,7 @@ var a = "test2"; -- comment */ `, - output: ` + output: ` /* eslint-disable used-1, used-2*/ bad /* eslint-enable @@ -5516,11177 +5684,12281 @@ var a = "test2"; -- comment */ - ` - }, - - // duplicates in the list - { - code: "// eslint-disable-line unused, unused, used", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line unused, used, unused", - output: "// eslint-disable-line used" - }, - { - code: "// eslint-disable-line used, unused, unused, used", - output: "// eslint-disable-line used, used" - } - ]; - - for (const { code, output } of tests) { - // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach() - it(code, () => { - assert.strictEqual( - linter.verifyAndFix(code, config).output, - output - ); - }); - - // Test for quoted rule names - for (const testcaseForLiteral of [ - { code: code.replace(/((?:un)?used[\w-]*)/gu, '"$1"'), output: output.replace(/((?:un)?used[\w-]*)/gu, '"$1"') }, - { code: code.replace(/((?:un)?used[\w-]*)/gu, "'$1'"), output: output.replace(/((?:un)?used[\w-]*)/gu, "'$1'") } - ]) { - // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach() - it(testcaseForLiteral.code, () => { - assert.strictEqual( - linter.verifyAndFix(testcaseForLiteral.code, config).output, - testcaseForLiteral.output - ); - }); - } - } - }); - }); - - describe("config.noInlineConfig + options.allowInlineConfig", () => { - - it("should report both a rule violation and a warning about inline config", () => { - const code = [ - "/* eslint-disable */ // <-- this should be inline config warning", - "foo(); // <-- this should be no-undef error" - ].join("\n"); - const config = { - rules: { - "no-undef": 2 - }, - noInlineConfig: true - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: true - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "'/*eslint-disable*/' has no effect because you have 'noInlineConfig' setting in your config.", - line: 1, - column: 1, - endLine: 1, - endColumn: 21, - severity: 1, - nodeType: null - }, - { - ruleId: "no-undef", - messageId: "undef", - message: "'foo' is not defined.", - line: 2, - endLine: 2, - column: 1, - endColumn: 4, - severity: 2, - nodeType: "Identifier" - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report both a rule violation without warning about inline config when noInlineConfig is true and allowInlineConfig is false", () => { - const code = [ - "/* eslint-disable */ // <-- this should be inline config warning", - "foo(); // <-- this should be no-undef error" - ].join("\n"); - const config = { - rules: { - "no-undef": 2 - }, - noInlineConfig: true - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: false - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.deepStrictEqual( - messages, - [ - { - ruleId: "no-undef", - messageId: "undef", - message: "'foo' is not defined.", - line: 2, - endLine: 2, - column: 1, - endColumn: 4, - severity: 2, - nodeType: "Identifier" - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report both a rule violation without warning about inline config when both are false", () => { - const code = [ - "/* eslint-disable */ // <-- this should be inline config warning", - "foo(); // <-- this should be no-undef error" - ].join("\n"); - const config = { - rules: { - "no-undef": 2 - }, - noInlineConfig: false - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: false - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.deepStrictEqual( - messages, - [ - { - ruleId: "no-undef", - messageId: "undef", - message: "'foo' is not defined.", - line: 2, - endLine: 2, - column: 1, - endColumn: 4, - severity: 2, - nodeType: "Identifier" - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report one suppresed problem when noInlineConfig is false and allowInlineConfig is true", () => { - const code = [ - "/* eslint-disable */ // <-- this should be inline config warning", - "foo(); // <-- this should be no-undef error" - ].join("\n"); - const config = { - rules: { - "no-undef": 2 - }, - noInlineConfig: false - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: true - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 1); - assert.deepStrictEqual( - suppressedMessages, - [ - { - ruleId: "no-undef", - messageId: "undef", - message: "'foo' is not defined.", - line: 2, - endLine: 2, - column: 1, - endColumn: 4, - severity: 2, - nodeType: "Identifier", - suppressions: [ - { - justification: "", - kind: "directive" - } - ] - } - ] - ); - - }); - }); - - - describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { - it("should not report a violation", () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: true - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - }); - - describe("when evaluating code with hashbang", () => { - it("should comment hashbang without breaking offset", () => { - const code = "#!/usr/bin/env node\n'123';"; - const config = { rules: { checker: "error" } }; - let spy; - - linter.defineRule("checker", { - create(context) { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), "'123';"); - }); - return { ExpressionStatement: spy }; - } - }); - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("verify()", () => { - describe("filenames", () => { - it("should allow filename to be passed on options object", () => { - const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.filename, "foo.js"); - return {}; - }); - - linter.defineRule("checker", { create: filenameChecker }); - linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" }); - assert(filenameChecker.calledOnce); - }); - - it("should allow filename to be passed as third argument", () => { - const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.filename, "bar.js"); - return {}; - }); - - linter.defineRule("checker", { create: filenameChecker }); - linter.verify("foo;", { rules: { checker: "error" } }, "bar.js"); - assert(filenameChecker.calledOnce); - }); - - it("should default filename to when options object doesn't have filename", () => { - const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.filename, ""); - return {}; - }); - - linter.defineRule("checker", { create: filenameChecker }); - linter.verify("foo;", { rules: { checker: "error" } }, {}); - assert(filenameChecker.calledOnce); - }); - - it("should default filename to when only two arguments are passed", () => { - const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.filename, ""); - return {}; - }); - - linter.defineRule("checker", { create: filenameChecker }); - linter.verify("foo;", { rules: { checker: "error" } }); - assert(filenameChecker.calledOnce); - }); - }); - - describe("physicalFilenames", () => { - it("should be same as `filename` passed on options object, if no processors are used", () => { - const physicalFilenameChecker = sinon.spy(context => { - assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); - assert.strictEqual(context.physicalFilename, "foo.js"); - return {}; - }); - - linter.defineRule("checker", { create: physicalFilenameChecker }); - linter.verify("foo;", { rules: { checker: "error" } }, { filename: "foo.js" }); - assert(physicalFilenameChecker.calledOnce); - }); - - it("should default physicalFilename to when options object doesn't have filename", () => { - const physicalFilenameChecker = sinon.spy(context => { - assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); - assert.strictEqual(context.physicalFilename, ""); - return {}; - }); - - linter.defineRule("checker", { create: physicalFilenameChecker }); - linter.verify("foo;", { rules: { checker: "error" } }, {}); - assert(physicalFilenameChecker.calledOnce); - }); - - it("should default physicalFilename to when only two arguments are passed", () => { - const physicalFilenameChecker = sinon.spy(context => { - assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); - assert.strictEqual(context.physicalFilename, ""); - return {}; - }); - - linter.defineRule("checker", { create: physicalFilenameChecker }); - linter.verify("foo;", { rules: { checker: "error" } }); - assert(physicalFilenameChecker.calledOnce); - }); - }); - - it("should report warnings in order by line and column when called", () => { - - const code = "foo()\n alert('test')"; - const config = { rules: { "no-mixed-spaces-and-tabs": 1, "eol-last": 1, semi: [1, "always"] } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 3); - assert.strictEqual(messages[0].line, 1); - assert.strictEqual(messages[0].column, 6); - assert.strictEqual(messages[1].line, 2); - assert.strictEqual(messages[1].column, 18); - assert.strictEqual(messages[2].line, 2); - assert.strictEqual(messages[2].column, 18); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - describe("ecmaVersion", () => { - - it("should not support ES6 when no ecmaVersion provided", () => { - const messages = linter.verify("let x = 0;"); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("supports ECMAScript version 'latest'", () => { - const messages = linter.verify("let x = /[\\q{abc|d}&&[A--B]]/v;", { - parserOptions: { ecmaVersion: "latest" } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("the 'latest' is equal to espree.latestEcmaVersion", () => { - let ecmaVersion = null; - const config = { rules: { "ecma-version": 2 }, parserOptions: { ecmaVersion: "latest" } }; - - linter.defineRule("ecma-version", { - create: context => ({ - Program() { - ecmaVersion = context.parserOptions.ecmaVersion; - } - }) - }); - linter.verify("", config); - assert.strictEqual(ecmaVersion, espree.latestEcmaVersion, "ecmaVersion should be 13"); - }); - - it("the 'latest' is not normalized for custom parsers", () => { - let ecmaVersion = null; - const config = { rules: { "ecma-version": 2 }, parser: "custom-parser", parserOptions: { ecmaVersion: "latest" } }; - - linter.defineParser("custom-parser", testParsers.enhancedParser); - linter.defineRule("ecma-version", { - create: context => ({ - Program() { - ecmaVersion = context.parserOptions.ecmaVersion; - } - }) - }); - linter.verify("", config); - assert.strictEqual(ecmaVersion, "latest", "ecmaVersion should be latest"); - }); - - it("the 'latest' is equal to espree.latestEcmaVersion on languageOptions", () => { - let ecmaVersion = null; - const config = { rules: { "ecma-version": 2 }, parserOptions: { ecmaVersion: "latest" } }; - - linter.defineRule("ecma-version", { - create: context => ({ - Program() { - ecmaVersion = context.languageOptions.ecmaVersion; - } - }) - }); - linter.verify("", config); - assert.strictEqual(ecmaVersion, espree.latestEcmaVersion + 2009, "ecmaVersion should be 2022"); - }); - - it("the 'next' is equal to espree.latestEcmaVersion on languageOptions with custom parser", () => { - let ecmaVersion = null; - const config = { rules: { "ecma-version": 2 }, parser: "custom-parser", parserOptions: { ecmaVersion: "next" } }; - - linter.defineParser("custom-parser", testParsers.stubParser); - linter.defineRule("ecma-version", { - create: context => ({ - Program() { - ecmaVersion = context.languageOptions.ecmaVersion; - } - }) - }); - linter.verify("", config); - assert.strictEqual(ecmaVersion, espree.latestEcmaVersion + 2009, "ecmaVersion should be 2022"); - }); - - it("missing ecmaVersion is equal to 5 on languageOptions with custom parser", () => { - let ecmaVersion = null; - const config = { rules: { "ecma-version": 2 }, parser: "custom-parser" }; - - linter.defineParser("custom-parser", testParsers.enhancedParser); - linter.defineRule("ecma-version", { - create: context => ({ - Program() { - ecmaVersion = context.languageOptions.ecmaVersion; - } - }) - }); - linter.verify("", config); - assert.strictEqual(ecmaVersion, 5, "ecmaVersion should be 5"); - }); - - it("should pass normalized ecmaVersion to eslint-scope", () => { - let blockScope = null; - - linter.defineRule("block-scope", { - create: context => ({ - BlockStatement() { - blockScope = context.getScope(); - } - }) - }); - linter.defineParser("custom-parser", { - parse: (...args) => espree.parse(...args) - }); - - // Use standard parser - linter.verify("{}", { - rules: { "block-scope": 2 }, - parserOptions: { ecmaVersion: "latest" } - }); - - assert.strictEqual(blockScope.type, "block"); - - linter.verify("{}", { - rules: { "block-scope": 2 }, - parserOptions: {} // ecmaVersion defaults to 5 - }); - assert.strictEqual(blockScope.type, "global"); - - // Use custom parser - linter.verify("{}", { - rules: { "block-scope": 2 }, - parser: "custom-parser", - parserOptions: { ecmaVersion: "latest" } - }); - - assert.strictEqual(blockScope.type, "block"); - - linter.verify("{}", { - rules: { "block-scope": 2 }, - parser: "custom-parser", - parserOptions: {} // ecmaVersion defaults to 5 - }); - assert.strictEqual(blockScope.type, "global"); - }); - - describe("it should properly parse let declaration when", () => { - it("the ECMAScript version number is 6", () => { - const messages = linter.verify("let x = 5;", { - parserOptions: { - ecmaVersion: 6 - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("the ECMAScript version number is 2015", () => { - const messages = linter.verify("let x = 5;", { - parserOptions: { - ecmaVersion: 2015 - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - it("should fail to parse exponentiation operator when the ECMAScript version number is 2015", () => { - const messages = linter.verify("x ** y;", { - parserOptions: { - ecmaVersion: 2015 - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); - - describe("should properly parse exponentiation operator when", () => { - it("the ECMAScript version number is 7", () => { - const messages = linter.verify("x ** y;", { - parserOptions: { - ecmaVersion: 7 - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("the ECMAScript version number is 2016", () => { - const messages = linter.verify("x ** y;", { - parserOptions: { - ecmaVersion: 2016 - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - }); - - it("should properly parse object spread when ecmaVersion is 2018", () => { - - const messages = linter.verify("var x = { ...y };", { - parserOptions: { - ecmaVersion: 2018 - } - }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should properly parse global return when passed ecmaFeatures", () => { - - const messages = linter.verify("return;", { - parserOptions: { - ecmaFeatures: { - globalReturn: true - } - } - }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should properly parse global return when in Node.js environment", () => { - - const messages = linter.verify("return;", { - env: { - node: true - } - }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not parse global return when in Node.js environment with globalReturn explicitly off", () => { - - const messages = linter.verify("return;", { - env: { - node: true - }, - parserOptions: { - ecmaFeatures: { - globalReturn: false - } - } - }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "Parsing error: 'return' outside of function"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not parse global return when Node.js environment is false", () => { - - const messages = linter.verify("return;", {}, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "Parsing error: 'return' outside of function"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should properly parse sloppy-mode code when impliedStrict is false", () => { - - const messages = linter.verify("var private;", {}, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not parse sloppy-mode code when impliedStrict is true", () => { - - const messages = linter.verify("var private;", { - parserOptions: { - ecmaFeatures: { - impliedStrict: true - } - } - }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "Parsing error: The keyword 'private' is reserved"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should properly parse valid code when impliedStrict is true", () => { - - const messages = linter.verify("var foo;", { - parserOptions: { - ecmaFeatures: { - impliedStrict: true - } - } - }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should properly parse JSX when passed ecmaFeatures", () => { - - const messages = linter.verify("var x =
;", { - parserOptions: { - ecmaFeatures: { - jsx: true - } - } - }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report an error when JSX code is encountered and JSX is not enabled", () => { - const code = "var myDivElement =
;"; - const messages = linter.verify(code, {}, "filename"); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].line, 1); - assert.strictEqual(messages[0].column, 20); - assert.strictEqual(messages[0].message, "Parsing error: Unexpected token <"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report an error when JSX code is encountered and JSX is enabled", () => { - const code = "var myDivElement =
;"; - const messages = linter.verify(code, { parserOptions: { ecmaFeatures: { jsx: true } } }, "filename"); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => { - const code = "var myDivElement =
;"; - const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }, "filename"); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not allow the use of reserved words as variable names in ES3", () => { - const code = "var char;"; - const messages = linter.verify(code, { parserOptions: { ecmaVersion: 3 } }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.isTrue(messages[0].fatal); - assert.match(messages[0].message, /^Parsing error:.*'char'/u); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not allow the use of reserved words as property names in member expressions in ES3", () => { - const code = "obj.char;"; - const messages = linter.verify(code, { parserOptions: { ecmaVersion: 3 } }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.isTrue(messages[0].fatal); - assert.match(messages[0].message, /^Parsing error:.*'char'/u); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not allow the use of reserved words as property names in object literals in ES3", () => { - const code = "var obj = { char: 1 };"; - const messages = linter.verify(code, { parserOptions: { ecmaVersion: 3 } }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.isTrue(messages[0].fatal); - assert.match(messages[0].message, /^Parsing error:.*'char'/u); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should allow the use of reserved words as variable and property names in ES3 when allowReserved is true", () => { - const code = "var char; obj.char; var obj = { char: 1 };"; - const messages = linter.verify(code, { parserOptions: { ecmaVersion: 3, allowReserved: true } }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not allow the use of reserved words as variable names in ES > 3", () => { - const ecmaVersions = [void 0, ...espree.supportedEcmaVersions.filter(ecmaVersion => ecmaVersion > 3)]; - - ecmaVersions.forEach(ecmaVersion => { - const code = "var enum;"; - const messages = linter.verify(code, { parserOptions: { ecmaVersion } }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.isTrue(messages[0].fatal); - assert.match(messages[0].message, /^Parsing error:.*'enum'/u); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - it("should allow the use of reserved words as property names in ES > 3", () => { - const ecmaVersions = [void 0, ...espree.supportedEcmaVersions.filter(ecmaVersion => ecmaVersion > 3)]; - - ecmaVersions.forEach(ecmaVersion => { - const code = "obj.enum; obj.function; var obj = { enum: 1, function: 2 };"; - const messages = linter.verify(code, { parserOptions: { ecmaVersion } }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - it("should not allow `allowReserved: true` in ES > 3", () => { - const ecmaVersions = [void 0, ...espree.supportedEcmaVersions.filter(ecmaVersion => ecmaVersion > 3)]; - - ecmaVersions.forEach(ecmaVersion => { - const code = ""; - const messages = linter.verify(code, { parserOptions: { ecmaVersion, allowReserved: true } }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.isTrue(messages[0].fatal); - assert.match(messages[0].message, /^Parsing error:.*allowReserved/u); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - it("should be able to use es6 features if there is a comment which has \"eslint-env es6\"", () => { - const code = [ - "/* eslint-env es6 */", - "var arrow = () => 0;", - "var binary = 0b1010;", - "{ let a = 0; const b = 1; }", - "class A {}", - "function defaultParams(a = 0) {}", - "var {a = 1, b = 2} = {};", - "for (var a of []) {}", - "function* generator() { yield 0; }", - "var computed = {[a]: 0};", - "var duplicate = {dup: 0, dup: 1};", - "var method = {foo() {}};", - "var property = {a, b};", - "var octal = 0o755;", - "var u = /^.$/u.test('𠎡');", - "var y = /hello/y.test('hello');", - "function restParam(a, ...rest) {}", - "class B { superInFunc() { super.foo(); } }", - "var template = `hello, ${a}`;", - "var unicode = '\\u{20BB7}';" - ].join("\n"); - - const messages = linter.verify(code, null, "eslint-env es6"); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should be able to return in global if there is a comment which enables the node environment with a comment", () => { - const messages = linter.verify(`/* ${ESLINT_ENV} node */ return;`, null, "node environment"); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should attach a \"/*global\" comment node to declared variables", () => { - const code = "/* global foo */\n/* global bar, baz */"; - let ok = false; - - linter.defineRules({ - test: { - create: context => ({ - Program() { - const scope = context.getScope(); - const sourceCode = context.sourceCode; - const comments = sourceCode.getAllComments(); - - assert.strictEqual(context.getSourceCode(), sourceCode); - assert.strictEqual(2, comments.length); - - const foo = getVariable(scope, "foo"); - - assert.strictEqual(foo.eslintExplicitGlobal, true); - assert.strictEqual(foo.eslintExplicitGlobalComments[0], comments[0]); - - const bar = getVariable(scope, "bar"); - - assert.strictEqual(bar.eslintExplicitGlobal, true); - assert.strictEqual(bar.eslintExplicitGlobalComments[0], comments[1]); - - const baz = getVariable(scope, "baz"); - - assert.strictEqual(baz.eslintExplicitGlobal, true); - assert.strictEqual(baz.eslintExplicitGlobalComments[0], comments[1]); - - ok = true; - } - }) - } - }); - - linter.verify(code, { rules: { test: 2 } }); - assert(ok); - }); - - it("should report a linting error when a global is set to an invalid value", () => { - const results = linter.verify("/* global foo: AAAAA, bar: readonly */\nfoo;\nbar;", { rules: { "no-undef": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(results, [ - { - ruleId: null, - severity: 2, - message: "'AAAAA' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')", - line: 1, - column: 1, - endLine: 1, - endColumn: 39, - nodeType: null - }, - { - ruleId: "no-undef", - messageId: "undef", - severity: 2, - message: "'foo' is not defined.", - line: 2, - column: 1, - endLine: 2, - endColumn: 4, - nodeType: "Identifier" - } - ]); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not crash when we reuse the SourceCode object", () => { - linter.verify("function render() { return
{hello}
}", { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }); - linter.verify(linter.getSourceCode(), { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } }); - }); - - it("should reuse the SourceCode object", () => { - let ast1 = null, - ast2 = null; - - linter.defineRule("save-ast1", { - create: () => ({ - Program(node) { - ast1 = node; - } - }) - }); - linter.defineRule("save-ast2", { - create: () => ({ - Program(node) { - ast2 = node; - } - }) - }); - - linter.verify("function render() { return
{hello}
}", { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, rules: { "save-ast1": 2 } }); - linter.verify(linter.getSourceCode(), { parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, rules: { "save-ast2": 2 } }); - - assert(ast1 !== null); - assert(ast2 !== null); - assert(ast1 === ast2); - }); - - it("should allow 'await' as a property name in modules", () => { - const result = linter.verify( - "obj.await", - { parserOptions: { ecmaVersion: 6, sourceType: "module" } } - ); - const suppressedMessages = linter.getSuppressedMessages(); - - assert(result.length === 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - - it("should not modify config object passed as argument", () => { - const config = {}; - - Object.freeze(config); - linter.verify("var", config); - }); - - it("should pass 'id' to rule contexts with the rule id", () => { - const spy = sinon.spy(context => { - assert.strictEqual(context.id, "foo-bar-baz"); - return {}; - }); - - linter.defineRule("foo-bar-baz", { create: spy }); - linter.verify("x", { rules: { "foo-bar-baz": "error" } }); - assert(spy.calledOnce); - }); - - describe("descriptions in directive comments", () => { - it("should ignore the part preceded by '--' in '/*eslint*/'.", () => { - const aaa = sinon.stub().returns({}); - const bbb = sinon.stub().returns({}); - - linter.defineRule("aaa", { create: aaa }); - linter.defineRule("bbb", { create: bbb }); - const messages = linter.verify(` + `, + }, + + // duplicates in the list + { + code: "// eslint-disable-line unused, unused, used", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line unused, used, unused", + output: "// eslint-disable-line used", + }, + { + code: "// eslint-disable-line used, unused, unused, used", + output: "// eslint-disable-line used, used", + }, + ]; + + for (const { code, output } of tests) { + // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach() + it(code, () => { + assert.strictEqual( + linter.verifyAndFix(code, config).output, + output, + ); + }); + + // Test for quoted rule names + for (const testcaseForLiteral of [ + { + code: code.replace(/((?:un)?used[\w-]*)/gu, '"$1"'), + output: output.replace(/((?:un)?used[\w-]*)/gu, '"$1"'), + }, + { + code: code.replace(/((?:un)?used[\w-]*)/gu, "'$1'"), + output: output.replace(/((?:un)?used[\w-]*)/gu, "'$1'"), + }, + ]) { + // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach() + it(testcaseForLiteral.code, () => { + assert.strictEqual( + linter.verifyAndFix(testcaseForLiteral.code, config) + .output, + testcaseForLiteral.output, + ); + }); + } + } + }); + }); + + describe("config.noInlineConfig + options.allowInlineConfig", () => { + it("should report both a rule violation and a warning about inline config", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error", + ].join("\n"); + const config = { + rules: { + "no-undef": 2, + }, + noInlineConfig: true, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "'/*eslint-disable*/' has no effect because you have 'noInlineConfig' setting in your config.", + line: 1, + column: 1, + endLine: 1, + endColumn: 21, + severity: 1, + nodeType: null, + }, + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier", + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report both a rule violation without warning about inline config when noInlineConfig is true and allowInlineConfig is false", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error", + ].join("\n"); + const config = { + rules: { + "no-undef": 2, + }, + noInlineConfig: true, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual(messages, [ + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier", + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report both a rule violation without warning about inline config when both are false", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error", + ].join("\n"); + const config = { + rules: { + "no-undef": 2, + }, + noInlineConfig: false, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual(messages, [ + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier", + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report one suppressed problem when noInlineConfig is false and allowInlineConfig is true", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error", + ].join("\n"); + const config = { + rules: { + "no-undef": 2, + }, + noInlineConfig: false, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 1); + assert.deepStrictEqual(suppressedMessages, [ + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier", + suppressions: [ + { + justification: "", + kind: "directive", + }, + ], + }, + ]); + }); + }); + + describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { + it("should not report a violation", () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + }); + }); + + describe("when evaluating code with hashbang", () => { + it("should comment hashbang without breaking offset", () => { + const code = "#!/usr/bin/env node\n'123';"; + const config = { rules: { checker: "error" } }; + let spy; + + linter.defineRule("checker", { + create(context) { + spy = sinon.spy(node => { + assert.strictEqual( + context.sourceCode.getText(node), + "'123';", + ); + }); + return { ExpressionStatement: spy }; + }, + }); + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("verify()", () => { + describe("filenames", () => { + it("should allow filename to be passed on options object", () => { + const filenameChecker = sinon.spy(context => { + assert.strictEqual(context.filename, "foo.js"); + return {}; + }); + + linter.defineRule("checker", { create: filenameChecker }); + linter.verify( + "foo;", + { rules: { checker: "error" } }, + { filename: "foo.js" }, + ); + assert(filenameChecker.calledOnce); + }); + + it("should allow filename to be passed as third argument", () => { + const filenameChecker = sinon.spy(context => { + assert.strictEqual(context.filename, "bar.js"); + return {}; + }); + + linter.defineRule("checker", { create: filenameChecker }); + linter.verify( + "foo;", + { rules: { checker: "error" } }, + "bar.js", + ); + assert(filenameChecker.calledOnce); + }); + + it("should default filename to when options object doesn't have filename", () => { + const filenameChecker = sinon.spy(context => { + assert.strictEqual(context.filename, ""); + return {}; + }); + + linter.defineRule("checker", { create: filenameChecker }); + linter.verify("foo;", { rules: { checker: "error" } }, {}); + assert(filenameChecker.calledOnce); + }); + + it("should default filename to when only two arguments are passed", () => { + const filenameChecker = sinon.spy(context => { + assert.strictEqual(context.filename, ""); + return {}; + }); + + linter.defineRule("checker", { create: filenameChecker }); + linter.verify("foo;", { rules: { checker: "error" } }); + assert(filenameChecker.calledOnce); + }); + }); + + describe("physicalFilenames", () => { + it("should be same as `filename` passed on options object, if no processors are used", () => { + const physicalFilenameChecker = sinon.spy(context => { + assert.strictEqual( + context.getPhysicalFilename(), + context.physicalFilename, + ); + assert.strictEqual(context.physicalFilename, "foo.js"); + return {}; + }); + + linter.defineRule("checker", { + create: physicalFilenameChecker, + }); + linter.verify( + "foo;", + { rules: { checker: "error" } }, + { filename: "foo.js" }, + ); + assert(physicalFilenameChecker.calledOnce); + }); + + it("should default physicalFilename to when options object doesn't have filename", () => { + const physicalFilenameChecker = sinon.spy(context => { + assert.strictEqual( + context.getPhysicalFilename(), + context.physicalFilename, + ); + assert.strictEqual(context.physicalFilename, ""); + return {}; + }); + + linter.defineRule("checker", { + create: physicalFilenameChecker, + }); + linter.verify("foo;", { rules: { checker: "error" } }, {}); + assert(physicalFilenameChecker.calledOnce); + }); + + it("should default physicalFilename to when only two arguments are passed", () => { + const physicalFilenameChecker = sinon.spy(context => { + assert.strictEqual( + context.getPhysicalFilename(), + context.physicalFilename, + ); + assert.strictEqual(context.physicalFilename, ""); + return {}; + }); + + linter.defineRule("checker", { + create: physicalFilenameChecker, + }); + linter.verify("foo;", { rules: { checker: "error" } }); + assert(physicalFilenameChecker.calledOnce); + }); + }); + + it("should report warnings in order by line and column when called", () => { + const code = "foo()\n alert('test')"; + const config = { + rules: { + "no-mixed-spaces-and-tabs": 1, + "eol-last": 1, + semi: [1, "always"], + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 3); + assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages[0].column, 6); + assert.strictEqual(messages[1].line, 2); + assert.strictEqual(messages[1].column, 18); + assert.strictEqual(messages[2].line, 2); + assert.strictEqual(messages[2].column, 18); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + describe("ecmaVersion", () => { + it("should not support ES6 when no ecmaVersion provided", () => { + const messages = linter.verify("let x = 0;"); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("supports ECMAScript version 'latest'", () => { + const messages = linter.verify("{ using x = foo(); }", { + parserOptions: { ecmaVersion: "latest" }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("the 'latest' is equal to espree.latestEcmaVersion", () => { + let ecmaVersion = null; + const config = { + rules: { "ecma-version": 2 }, + parserOptions: { ecmaVersion: "latest" }, + }; + + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.parserOptions.ecmaVersion; + }, + }), + }); + linter.verify("", config); + assert.strictEqual( + ecmaVersion, + espree.latestEcmaVersion, + "ecmaVersion should be 13", + ); + }); + + it("the 'latest' is not normalized for custom parsers", () => { + let ecmaVersion = null; + const config = { + rules: { "ecma-version": 2 }, + parser: "custom-parser", + parserOptions: { ecmaVersion: "latest" }, + }; + + linter.defineParser( + "custom-parser", + testParsers.enhancedParser, + ); + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.parserOptions.ecmaVersion; + }, + }), + }); + linter.verify("", config); + assert.strictEqual( + ecmaVersion, + "latest", + "ecmaVersion should be latest", + ); + }); + + it("the 'latest' is equal to espree.latestEcmaVersion on languageOptions", () => { + let ecmaVersion = null; + const config = { + rules: { "ecma-version": 2 }, + parserOptions: { ecmaVersion: "latest" }, + }; + + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.languageOptions.ecmaVersion; + }, + }), + }); + linter.verify("", config); + assert.strictEqual( + ecmaVersion, + espree.latestEcmaVersion + 2009, + "ecmaVersion should be 2022", + ); + }); + + it("the 'next' is equal to ESLint's latest ECMA version on languageOptions with custom parser", () => { + let ecmaVersion = null; + const config = { + rules: { "ecma-version": 2 }, + parser: "custom-parser", + parserOptions: { ecmaVersion: "next" }, + }; + + linter.defineParser("custom-parser", testParsers.stubParser); + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.languageOptions.ecmaVersion; + }, + }), + }); + linter.verify("", config); + assert.strictEqual( + ecmaVersion, + LATEST_ECMA_VERSION, + `ecmaVersion should be ${LATEST_ECMA_VERSION}`, + ); + }); + + it("missing ecmaVersion is equal to 5 on languageOptions with custom parser", () => { + let ecmaVersion = null; + const config = { + rules: { "ecma-version": 2 }, + parser: "custom-parser", + }; + + linter.defineParser( + "custom-parser", + testParsers.enhancedParser, + ); + linter.defineRule("ecma-version", { + create: context => ({ + Program() { + ecmaVersion = context.languageOptions.ecmaVersion; + }, + }), + }); + linter.verify("", config); + assert.strictEqual(ecmaVersion, 5, "ecmaVersion should be 5"); + }); + + it("should pass normalized ecmaVersion to eslint-scope", () => { + let blockScope = null; + + linter.defineRule("block-scope", { + create: context => ({ + BlockStatement(node) { + blockScope = context.sourceCode.getScope(node); + }, + }), + }); + linter.defineParser("custom-parser", { + parse: (...args) => espree.parse(...args), + }); + + // Use standard parser + linter.verify("{}", { + rules: { "block-scope": 2 }, + parserOptions: { ecmaVersion: "latest" }, + }); + + assert.strictEqual(blockScope.type, "block"); + + linter.verify("{}", { + rules: { "block-scope": 2 }, + parserOptions: {}, // ecmaVersion defaults to 5 + }); + assert.strictEqual(blockScope.type, "global"); + + // Use custom parser + linter.verify("{}", { + rules: { "block-scope": 2 }, + parser: "custom-parser", + parserOptions: { ecmaVersion: "latest" }, + }); + + assert.strictEqual(blockScope.type, "block"); + + linter.verify("{}", { + rules: { "block-scope": 2 }, + parser: "custom-parser", + parserOptions: {}, // ecmaVersion defaults to 5 + }); + assert.strictEqual(blockScope.type, "global"); + }); + + describe("it should properly parse let declaration when", () => { + it("the ECMAScript version number is 6", () => { + const messages = linter.verify("let x = 5;", { + parserOptions: { + ecmaVersion: 6, + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("the ECMAScript version number is 2015", () => { + const messages = linter.verify("let x = 5;", { + parserOptions: { + ecmaVersion: 2015, + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + it("should fail to parse exponentiation operator when the ECMAScript version number is 2015", () => { + const messages = linter.verify("x ** y;", { + parserOptions: { + ecmaVersion: 2015, + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 0); + }); + + describe("should properly parse exponentiation operator when", () => { + it("the ECMAScript version number is 7", () => { + const messages = linter.verify("x ** y;", { + parserOptions: { + ecmaVersion: 7, + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("the ECMAScript version number is 2016", () => { + const messages = linter.verify("x ** y;", { + parserOptions: { + ecmaVersion: 2016, + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + }); + + it("should properly parse object spread when ecmaVersion is 2018", () => { + const messages = linter.verify( + "var x = { ...y };", + { + parserOptions: { + ecmaVersion: 2018, + }, + }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should properly parse global return when passed ecmaFeatures", () => { + const messages = linter.verify( + "return;", + { + parserOptions: { + ecmaFeatures: { + globalReturn: true, + }, + }, + }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should properly parse global return when in Node.js environment", () => { + const messages = linter.verify( + "return;", + { + env: { + node: true, + }, + }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not parse global return when in Node.js environment with globalReturn explicitly off", () => { + const messages = linter.verify( + "return;", + { + env: { + node: true, + }, + parserOptions: { + ecmaFeatures: { + globalReturn: false, + }, + }, + }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "Parsing error: 'return' outside of function", + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not parse global return when Node.js environment is false", () => { + const messages = linter.verify("return;", {}, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "Parsing error: 'return' outside of function", + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should properly parse sloppy-mode code when impliedStrict is false", () => { + const messages = linter.verify("var private;", {}, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not parse sloppy-mode code when impliedStrict is true", () => { + const messages = linter.verify( + "var private;", + { + parserOptions: { + ecmaFeatures: { + impliedStrict: true, + }, + }, + }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "Parsing error: The keyword 'private' is reserved", + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should properly parse valid code when impliedStrict is true", () => { + const messages = linter.verify( + "var foo;", + { + parserOptions: { + ecmaFeatures: { + impliedStrict: true, + }, + }, + }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should properly parse JSX when passed ecmaFeatures", () => { + const messages = linter.verify( + "var x =
;", + { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report an error when JSX code is encountered and JSX is not enabled", () => { + const code = 'var myDivElement =
;'; + const messages = linter.verify(code, {}, "filename"); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages[0].column, 20); + assert.strictEqual( + messages[0].message, + "Parsing error: Unexpected token <", + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report an error when JSX code is encountered and JSX is enabled", () => { + const code = 'var myDivElement =
;'; + const messages = linter.verify( + code, + { parserOptions: { ecmaFeatures: { jsx: true } } }, + "filename", + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => { + const code = "var myDivElement =
;"; + const messages = linter.verify( + code, + { + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { jsx: true }, + }, + }, + "filename", + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not allow the use of reserved words as variable names in ES3", () => { + const code = "var char;"; + const messages = linter.verify( + code, + { parserOptions: { ecmaVersion: 3 } }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.match(messages[0].message, /^Parsing error:.*'char'/u); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not allow the use of reserved words as property names in member expressions in ES3", () => { + const code = "obj.char;"; + const messages = linter.verify( + code, + { parserOptions: { ecmaVersion: 3 } }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.match(messages[0].message, /^Parsing error:.*'char'/u); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not allow the use of reserved words as property names in object literals in ES3", () => { + const code = "var obj = { char: 1 };"; + const messages = linter.verify( + code, + { parserOptions: { ecmaVersion: 3 } }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.match(messages[0].message, /^Parsing error:.*'char'/u); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should allow the use of reserved words as variable and property names in ES3 when allowReserved is true", () => { + const code = "var char; obj.char; var obj = { char: 1 };"; + const messages = linter.verify( + code, + { parserOptions: { ecmaVersion: 3, allowReserved: true } }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not allow the use of reserved words as variable names in ES > 3", () => { + const ecmaVersions = [ + void 0, + ...espree.supportedEcmaVersions.filter( + ecmaVersion => ecmaVersion > 3, + ), + ]; + + ecmaVersions.forEach(ecmaVersion => { + const code = "var enum;"; + const messages = linter.verify( + code, + { parserOptions: { ecmaVersion } }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.match(messages[0].message, /^Parsing error:.*'enum'/u); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + it("should allow the use of reserved words as property names in ES > 3", () => { + const ecmaVersions = [ + void 0, + ...espree.supportedEcmaVersions.filter( + ecmaVersion => ecmaVersion > 3, + ), + ]; + + ecmaVersions.forEach(ecmaVersion => { + const code = + "obj.enum; obj.function; var obj = { enum: 1, function: 2 };"; + const messages = linter.verify( + code, + { parserOptions: { ecmaVersion } }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + it("should not allow `allowReserved: true` in ES > 3", () => { + const ecmaVersions = [ + void 0, + ...espree.supportedEcmaVersions.filter( + ecmaVersion => ecmaVersion > 3, + ), + ]; + + ecmaVersions.forEach(ecmaVersion => { + const code = ""; + const messages = linter.verify( + code, + { parserOptions: { ecmaVersion, allowReserved: true } }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.match( + messages[0].message, + /^Parsing error:.*allowReserved/u, + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + it('should be able to use es6 features if there is a comment which has "eslint-env es6"', () => { + const code = [ + "/* eslint-env es6 */", + "var arrow = () => 0;", + "var binary = 0b1010;", + "{ let a = 0; const b = 1; }", + "class A {}", + "function defaultParams(a = 0) {}", + "var {a = 1, b = 2} = {};", + "for (var a of []) {}", + "function* generator() { yield 0; }", + "var computed = {[a]: 0};", + "var duplicate = {dup: 0, dup: 1};", + "var method = {foo() {}};", + "var property = {a, b};", + "var octal = 0o755;", + "var u = /^.$/u.test('𠎡');", + "var y = /hello/y.test('hello');", + "function restParam(a, ...rest) {}", + "class B { superInFunc() { super.foo(); } }", + "var template = `hello, ${a}`;", + "var unicode = '\\u{20BB7}';", + ].join("\n"); + + const messages = linter.verify(code, null, "eslint-env es6"); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should be able to return in global if there is a comment which enables the node environment with a comment", () => { + const messages = linter.verify( + `/* ${ESLINT_ENV} node */ return;`, + null, + "node environment", + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it('should attach a "/*global" comment node to declared variables', () => { + const code = "/* global foo */\n/* global bar, baz */"; + let ok = false; + + linter.defineRules({ + test: { + create: context => ({ + Program(node) { + const scope = context.sourceCode.getScope(node); + const sourceCode = context.sourceCode; + const comments = sourceCode.getAllComments(); + + assert.strictEqual( + context.getSourceCode(), + sourceCode, + ); + assert.strictEqual(2, comments.length); + + const foo = getVariable(scope, "foo"); + + assert.strictEqual(foo.eslintExplicitGlobal, true); + assert.strictEqual( + foo.eslintExplicitGlobalComments[0], + comments[0], + ); + + const bar = getVariable(scope, "bar"); + + assert.strictEqual(bar.eslintExplicitGlobal, true); + assert.strictEqual( + bar.eslintExplicitGlobalComments[0], + comments[1], + ); + + const baz = getVariable(scope, "baz"); + + assert.strictEqual(baz.eslintExplicitGlobal, true); + assert.strictEqual( + baz.eslintExplicitGlobalComments[0], + comments[1], + ); + + ok = true; + }, + }), + }, + }); + + linter.verify(code, { rules: { test: 2 } }); + assert(ok); + }); + + it("should report a linting error when a global is set to an invalid value", () => { + const results = linter.verify( + "/* global foo: AAAAA, bar: readonly */\nfoo;\nbar;", + { rules: { "no-undef": "error" } }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(results, [ + { + ruleId: null, + severity: 2, + message: + "'AAAAA' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')", + line: 1, + column: 1, + endLine: 1, + endColumn: 39, + nodeType: null, + }, + { + ruleId: "no-undef", + messageId: "undef", + severity: 2, + message: "'foo' is not defined.", + line: 2, + column: 1, + endLine: 2, + endColumn: 4, + nodeType: "Identifier", + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not crash when we reuse the SourceCode object", () => { + linter.verify( + "function render() { return
{hello}
}", + { + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { jsx: true }, + }, + }, + ); + linter.verify(linter.getSourceCode(), { + parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, + }); + }); + + it("should verify a SourceCode object created with the constructor", () => { + const text = "var foo = bar;"; + const sourceCode = new SourceCode({ + text, + ast: espree.parse(text, { + loc: true, + range: true, + tokens: true, + comment: true, + }), + }); + const messages = linter.verify(sourceCode, { + rules: { "no-undef": "error" }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].message, "'bar' is not defined."); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ensure that SourceCode properties are copied over during linting", () => { + const text = "var foo = bar;"; + const sourceCode = new SourceCode({ + text, + ast: espree.parse(text, { + loc: true, + range: true, + tokens: true, + comment: true, + }), + hasBOM: true, + }); + + linter.verify(sourceCode, { rules: { "no-undef": "error" } }); + const resultSourceCode = linter.getSourceCode(); + + assert.strictEqual(resultSourceCode.text, text); + assert.strictEqual(resultSourceCode.ast, sourceCode.ast); + assert.strictEqual(resultSourceCode.hasBOM, true); + }); + + it("should reuse the SourceCode object", () => { + let ast1 = null, + ast2 = null; + + linter.defineRule("save-ast1", { + create: () => ({ + Program(node) { + ast1 = node; + }, + }), + }); + linter.defineRule("save-ast2", { + create: () => ({ + Program(node) { + ast2 = node; + }, + }), + }); + + linter.verify( + "function render() { return
{hello}
}", + { + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { jsx: true }, + }, + rules: { "save-ast1": 2 }, + }, + ); + linter.verify(linter.getSourceCode(), { + parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } }, + rules: { "save-ast2": 2 }, + }); + + assert(ast1 !== null); + assert(ast2 !== null); + assert(ast1 === ast2); + }); + + it("should allow 'await' as a property name in modules", () => { + const result = linter.verify("obj.await", { + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert(result.length === 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not modify config object passed as argument", () => { + const config = {}; + + Object.freeze(config); + linter.verify("var", config); + }); + + it("should pass 'id' to rule contexts with the rule id", () => { + const spy = sinon.spy(context => { + assert.strictEqual(context.id, "foo-bar-baz"); + return {}; + }); + + linter.defineRule("foo-bar-baz", { create: spy }); + linter.verify("x", { rules: { "foo-bar-baz": "error" } }); + assert(spy.calledOnce); + }); + + describe("descriptions in directive comments", () => { + it("should ignore the part preceded by '--' in '/*eslint*/'.", () => { + const aaa = sinon.stub().returns({}); + const bbb = sinon.stub().returns({}); + + linter.defineRule("aaa", { create: aaa }); + linter.defineRule("bbb", { create: bbb }); + const messages = linter.verify( + ` /*eslint aaa:error -- bbb:error */ console.log("hello") - `, {}); - const suppressedMessages = linter.getSuppressedMessages(); + `, + {}, + ); + const suppressedMessages = linter.getSuppressedMessages(); - // Don't include syntax error of the comment. - assert.deepStrictEqual(messages, []); + // Don't include syntax error of the comment. + assert.deepStrictEqual(messages, []); - // Use only `aaa`. - assert.strictEqual(aaa.callCount, 1); - assert.strictEqual(bbb.callCount, 0); + // Use only `aaa`. + assert.strictEqual(aaa.callCount, 1); + assert.strictEqual(bbb.callCount, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); + assert.strictEqual(suppressedMessages.length, 0); + }); - it("should ignore the part preceded by '--' in '/*eslint-env*/'.", () => { - const messages = linter.verify(` + it("should ignore the part preceded by '--' in '/*eslint-env*/'.", () => { + const messages = linter.verify( + ` /*eslint-env es2015 -- es2017 */ var Promise = {} var Atomics = {} - `, { rules: { "no-redeclare": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Don't include `Atomics` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endColumn: 32, - endLine: 3, - line: 3, - message: "'Promise' is already defined as a built-in global variable.", - messageId: "redeclaredAsBuiltin", - nodeType: "Identifier", - ruleId: "no-redeclare", - severity: 2 - }] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should ignore the part preceded by '--' in '/*global*/'.", () => { - const messages = linter.verify(` + `, + { rules: { "no-redeclare": "error" } }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Don't include `Atomics` + assert.deepStrictEqual(messages, [ + { + column: 25, + endColumn: 32, + endLine: 3, + line: 3, + message: + "'Promise' is already defined as a built-in global variable.", + messageId: "redeclaredAsBuiltin", + nodeType: "Identifier", + ruleId: "no-redeclare", + severity: 2, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ignore the part preceded by '--' in '/*global*/'.", () => { + const messages = linter.verify( + ` /*global aaa -- bbb */ var aaa = {} var bbb = {} - `, { rules: { "no-redeclare": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Don't include `bbb` - assert.deepStrictEqual( - messages, - [{ - column: 30, - endColumn: 33, - line: 2, - endLine: 2, - message: "'aaa' is already defined by a variable declaration.", - messageId: "redeclaredBySyntax", - nodeType: "Block", - ruleId: "no-redeclare", - severity: 2 - }] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should ignore the part preceded by '--' in '/*globals*/'.", () => { - const messages = linter.verify(` + `, + { rules: { "no-redeclare": "error" } }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Don't include `bbb` + assert.deepStrictEqual(messages, [ + { + column: 30, + endColumn: 33, + line: 2, + endLine: 2, + message: + "'aaa' is already defined by a variable declaration.", + messageId: "redeclaredBySyntax", + nodeType: "Block", + ruleId: "no-redeclare", + severity: 2, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ignore the part preceded by '--' in '/*globals*/'.", () => { + const messages = linter.verify( + ` /*globals aaa -- bbb */ var aaa = {} var bbb = {} - `, { rules: { "no-redeclare": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Don't include `bbb` - assert.deepStrictEqual( - messages, - [{ - column: 31, - endColumn: 34, - line: 2, - endLine: 2, - message: "'aaa' is already defined by a variable declaration.", - messageId: "redeclaredBySyntax", - nodeType: "Block", - ruleId: "no-redeclare", - severity: 2 - }] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should ignore the part preceded by '--' in '/*exported*/'.", () => { - const messages = linter.verify(` + `, + { rules: { "no-redeclare": "error" } }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Don't include `bbb` + assert.deepStrictEqual(messages, [ + { + column: 31, + endColumn: 34, + line: 2, + endLine: 2, + message: + "'aaa' is already defined by a variable declaration.", + messageId: "redeclaredBySyntax", + nodeType: "Block", + ruleId: "no-redeclare", + severity: 2, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ignore the part preceded by '--' in '/*exported*/'.", () => { + const messages = linter.verify( + ` /*exported aaa -- bbb */ var aaa = {} var bbb = {} - `, { rules: { "no-unused-vars": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Don't include `aaa` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endColumn: 28, - endLine: 4, - line: 4, - message: "'bbb' is assigned a value but never used.", - messageId: "unusedVar", - nodeType: "Identifier", - ruleId: "no-unused-vars", - severity: 2 - }] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should ignore the part preceded by '--' in '/*eslint-disable*/'.", () => { - const messages = linter.verify(` + `, + { rules: { "no-unused-vars": "error" } }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Don't include `aaa` + assert.deepStrictEqual(messages, [ + { + column: 25, + endColumn: 28, + endLine: 4, + line: 4, + message: "'bbb' is assigned a value but never used.", + messageId: "unusedVar", + nodeType: "Identifier", + ruleId: "no-unused-vars", + severity: 2, + suggestions: [ + { + data: { + varName: "bbb", + }, + desc: "Remove unused variable 'bbb'.", + fix: { + range: [99, 111], + text: "", + }, + messageId: "removeVar", + }, + ], + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ignore the part preceded by '--' in '/*eslint-disable*/'.", () => { + const messages = linter.verify( + ` /*eslint-disable no-redeclare -- no-unused-vars */ var aaa = {} var aaa = {} - `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Do include `no-unused-vars` but not `no-redeclare` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endLine: 4, - endColumn: 28, - line: 4, - message: "'aaa' is assigned a value but never used.", - messageId: "unusedVar", - nodeType: "Identifier", - ruleId: "no-unused-vars", - severity: 2 - }] - ); - - assert.deepStrictEqual( - suppressedMessages, - [{ - column: 25, - endLine: 4, - endColumn: 28, - line: 4, - message: "'aaa' is already defined.", - messageId: "redeclared", - nodeType: "Identifier", - ruleId: "no-redeclare", - severity: 2, - suppressions: [{ kind: "directive", justification: "no-unused-vars" }] - }] - ); - }); - - it("should ignore the part preceded by '--' in '/*eslint-enable*/'.", () => { - const messages = linter.verify(` + `, + { + rules: { + "no-redeclare": "error", + "no-unused-vars": "error", + }, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Do include `no-unused-vars` but not `no-redeclare` + assert.deepStrictEqual(messages, [ + { + column: 25, + endLine: 4, + endColumn: 28, + line: 4, + message: "'aaa' is assigned a value but never used.", + messageId: "unusedVar", + nodeType: "Identifier", + ruleId: "no-unused-vars", + severity: 2, + }, + ]); + + assert.deepStrictEqual(suppressedMessages, [ + { + column: 25, + endLine: 4, + endColumn: 28, + line: 4, + message: "'aaa' is already defined.", + messageId: "redeclared", + nodeType: "Identifier", + ruleId: "no-redeclare", + severity: 2, + suppressions: [ + { + kind: "directive", + justification: "no-unused-vars", + }, + ], + }, + ]); + }); + + it("should ignore the part preceded by '--' in '/*eslint-enable*/'.", () => { + const messages = linter.verify( + ` /*eslint-disable no-redeclare, no-unused-vars */ /*eslint-enable no-redeclare -- no-unused-vars */ var aaa = {} var aaa = {} - `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Do include `no-redeclare` but not `no-unused-vars` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endLine: 5, - endColumn: 28, - line: 5, - message: "'aaa' is already defined.", - messageId: "redeclared", - nodeType: "Identifier", - ruleId: "no-redeclare", - severity: 2 - }] - ); - - assert.deepStrictEqual( - suppressedMessages, - [{ - column: 25, - endLine: 5, - endColumn: 28, - line: 5, - message: "'aaa' is assigned a value but never used.", - messageId: "unusedVar", - nodeType: "Identifier", - ruleId: "no-unused-vars", - severity: 2, - suppressions: [{ kind: "directive", justification: "" }] - }] - ); - }); - - it("should ignore the part preceded by '--' in '//eslint-disable-line'.", () => { - const messages = linter.verify(` + `, + { + rules: { + "no-redeclare": "error", + "no-unused-vars": "error", + }, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Do include `no-redeclare` but not `no-unused-vars` + assert.deepStrictEqual(messages, [ + { + column: 25, + endLine: 5, + endColumn: 28, + line: 5, + message: "'aaa' is already defined.", + messageId: "redeclared", + nodeType: "Identifier", + ruleId: "no-redeclare", + severity: 2, + }, + ]); + + assert.deepStrictEqual(suppressedMessages, [ + { + column: 25, + endLine: 5, + endColumn: 28, + line: 5, + message: "'aaa' is assigned a value but never used.", + messageId: "unusedVar", + nodeType: "Identifier", + ruleId: "no-unused-vars", + severity: 2, + suppressions: [ + { kind: "directive", justification: "" }, + ], + }, + ]); + }); + + it("should ignore the part preceded by '--' in '//eslint-disable-line'.", () => { + const messages = linter.verify( + ` var aaa = {} //eslint-disable-line no-redeclare -- no-unused-vars var aaa = {} //eslint-disable-line no-redeclare -- no-unused-vars - `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Do include `no-unused-vars` but not `no-redeclare` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endLine: 3, - endColumn: 28, - line: 3, - message: "'aaa' is assigned a value but never used.", - messageId: "unusedVar", - nodeType: "Identifier", - ruleId: "no-unused-vars", - severity: 2 - }] - ); - - assert.deepStrictEqual( - suppressedMessages, - [{ - column: 25, - endLine: 3, - endColumn: 28, - line: 3, - message: "'aaa' is already defined.", - messageId: "redeclared", - nodeType: "Identifier", - ruleId: "no-redeclare", - severity: 2, - suppressions: [{ kind: "directive", justification: "no-unused-vars" }] - }] - ); - }); - - it("should ignore the part preceded by '--' in '/*eslint-disable-line*/'.", () => { - const messages = linter.verify(` + `, + { + rules: { + "no-redeclare": "error", + "no-unused-vars": "error", + }, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Do include `no-unused-vars` but not `no-redeclare` + assert.deepStrictEqual(messages, [ + { + column: 25, + endLine: 3, + endColumn: 28, + line: 3, + message: "'aaa' is assigned a value but never used.", + messageId: "unusedVar", + nodeType: "Identifier", + ruleId: "no-unused-vars", + severity: 2, + }, + ]); + + assert.deepStrictEqual(suppressedMessages, [ + { + column: 25, + endLine: 3, + endColumn: 28, + line: 3, + message: "'aaa' is already defined.", + messageId: "redeclared", + nodeType: "Identifier", + ruleId: "no-redeclare", + severity: 2, + suppressions: [ + { + kind: "directive", + justification: "no-unused-vars", + }, + ], + }, + ]); + }); + + it("should ignore the part preceded by '--' in '/*eslint-disable-line*/'.", () => { + const messages = linter.verify( + ` var aaa = {} /*eslint-disable-line no-redeclare -- no-unused-vars */ var aaa = {} /*eslint-disable-line no-redeclare -- no-unused-vars */ - `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Do include `no-unused-vars` but not `no-redeclare` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endLine: 3, - endColumn: 28, - line: 3, - message: "'aaa' is assigned a value but never used.", - messageId: "unusedVar", - nodeType: "Identifier", - ruleId: "no-unused-vars", - severity: 2 - }] - ); - - assert.deepStrictEqual( - suppressedMessages, - [{ - column: 25, - endLine: 3, - endColumn: 28, - line: 3, - message: "'aaa' is already defined.", - messageId: "redeclared", - nodeType: "Identifier", - ruleId: "no-redeclare", - severity: 2, - suppressions: [{ kind: "directive", justification: "no-unused-vars" }] - }] - ); - }); - - it("should ignore the part preceded by '--' in '//eslint-disable-next-line'.", () => { - const messages = linter.verify(` + `, + { + rules: { + "no-redeclare": "error", + "no-unused-vars": "error", + }, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Do include `no-unused-vars` but not `no-redeclare` + assert.deepStrictEqual(messages, [ + { + column: 25, + endLine: 3, + endColumn: 28, + line: 3, + message: "'aaa' is assigned a value but never used.", + messageId: "unusedVar", + nodeType: "Identifier", + ruleId: "no-unused-vars", + severity: 2, + }, + ]); + + assert.deepStrictEqual(suppressedMessages, [ + { + column: 25, + endLine: 3, + endColumn: 28, + line: 3, + message: "'aaa' is already defined.", + messageId: "redeclared", + nodeType: "Identifier", + ruleId: "no-redeclare", + severity: 2, + suppressions: [ + { + kind: "directive", + justification: "no-unused-vars", + }, + ], + }, + ]); + }); + + it("should ignore the part preceded by '--' in '//eslint-disable-next-line'.", () => { + const messages = linter.verify( + ` //eslint-disable-next-line no-redeclare -- no-unused-vars var aaa = {} //eslint-disable-next-line no-redeclare -- no-unused-vars var aaa = {} - `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Do include `no-unused-vars` but not `no-redeclare` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endLine: 5, - endColumn: 28, - line: 5, - message: "'aaa' is assigned a value but never used.", - messageId: "unusedVar", - nodeType: "Identifier", - ruleId: "no-unused-vars", - severity: 2 - }] - ); - - assert.deepStrictEqual( - suppressedMessages, - [{ - column: 25, - endLine: 5, - endColumn: 28, - line: 5, - message: "'aaa' is already defined.", - messageId: "redeclared", - nodeType: "Identifier", - ruleId: "no-redeclare", - severity: 2, - suppressions: [{ kind: "directive", justification: "no-unused-vars" }] - }] - ); - }); - - it("should ignore the part preceded by '--' in '/*eslint-disable-next-line*/'.", () => { - const messages = linter.verify(` + `, + { + rules: { + "no-redeclare": "error", + "no-unused-vars": "error", + }, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Do include `no-unused-vars` but not `no-redeclare` + assert.deepStrictEqual(messages, [ + { + column: 25, + endLine: 5, + endColumn: 28, + line: 5, + message: "'aaa' is assigned a value but never used.", + messageId: "unusedVar", + nodeType: "Identifier", + ruleId: "no-unused-vars", + severity: 2, + }, + ]); + + assert.deepStrictEqual(suppressedMessages, [ + { + column: 25, + endLine: 5, + endColumn: 28, + line: 5, + message: "'aaa' is already defined.", + messageId: "redeclared", + nodeType: "Identifier", + ruleId: "no-redeclare", + severity: 2, + suppressions: [ + { + kind: "directive", + justification: "no-unused-vars", + }, + ], + }, + ]); + }); + + it("should ignore the part preceded by '--' in '/*eslint-disable-next-line*/'.", () => { + const messages = linter.verify( + ` /*eslint-disable-next-line no-redeclare -- no-unused-vars */ var aaa = {} /*eslint-disable-next-line no-redeclare -- no-unused-vars */ var aaa = {} - `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Do include `no-unused-vars` but not `no-redeclare` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endLine: 5, - endColumn: 28, - line: 5, - message: "'aaa' is assigned a value but never used.", - messageId: "unusedVar", - nodeType: "Identifier", - ruleId: "no-unused-vars", - severity: 2 - }] - ); - - assert.deepStrictEqual( - suppressedMessages, - [{ - column: 25, - endLine: 5, - endColumn: 28, - line: 5, - message: "'aaa' is already defined.", - messageId: "redeclared", - nodeType: "Identifier", - ruleId: "no-redeclare", - severity: 2, - suppressions: [{ kind: "directive", justification: "no-unused-vars" }] - }] - ); - }); - - it("should not ignore the part preceded by '--' if the '--' is not surrounded by whitespaces.", () => { - const rule = sinon.stub().returns({}); - - linter.defineRule("a--rule", { create: rule }); - const messages = linter.verify(` + `, + { + rules: { + "no-redeclare": "error", + "no-unused-vars": "error", + }, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Do include `no-unused-vars` but not `no-redeclare` + assert.deepStrictEqual(messages, [ + { + column: 25, + endLine: 5, + endColumn: 28, + line: 5, + message: "'aaa' is assigned a value but never used.", + messageId: "unusedVar", + nodeType: "Identifier", + ruleId: "no-unused-vars", + severity: 2, + }, + ]); + + assert.deepStrictEqual(suppressedMessages, [ + { + column: 25, + endLine: 5, + endColumn: 28, + line: 5, + message: "'aaa' is already defined.", + messageId: "redeclared", + nodeType: "Identifier", + ruleId: "no-redeclare", + severity: 2, + suppressions: [ + { + kind: "directive", + justification: "no-unused-vars", + }, + ], + }, + ]); + }); + + it("should not ignore the part preceded by '--' if the '--' is not surrounded by whitespaces.", () => { + const rule = sinon.stub().returns({}); + + linter.defineRule("a--rule", { create: rule }); + const messages = linter.verify( + ` /*eslint a--rule:error */ console.log("hello") - `, {}); - const suppressedMessages = linter.getSuppressedMessages(); + `, + {}, + ); + const suppressedMessages = linter.getSuppressedMessages(); - // Don't include syntax error of the comment. - assert.deepStrictEqual(messages, []); + // Don't include syntax error of the comment. + assert.deepStrictEqual(messages, []); - // Use `a--rule`. - assert.strictEqual(rule.callCount, 1); + // Use `a--rule`. + assert.strictEqual(rule.callCount, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); + assert.strictEqual(suppressedMessages.length, 0); + }); - it("should ignore the part preceded by '--' even if the '--' is longer than 2.", () => { - const aaa = sinon.stub().returns({}); - const bbb = sinon.stub().returns({}); + it("should ignore the part preceded by '--' even if the '--' is longer than 2.", () => { + const aaa = sinon.stub().returns({}); + const bbb = sinon.stub().returns({}); - linter.defineRule("aaa", { create: aaa }); - linter.defineRule("bbb", { create: bbb }); - const messages = linter.verify(` + linter.defineRule("aaa", { create: aaa }); + linter.defineRule("bbb", { create: bbb }); + const messages = linter.verify( + ` /*eslint aaa:error -------- bbb:error */ console.log("hello") - `, {}); - const suppressedMessages = linter.getSuppressedMessages(); + `, + {}, + ); + const suppressedMessages = linter.getSuppressedMessages(); - // Don't include syntax error of the comment. - assert.deepStrictEqual(messages, []); + // Don't include syntax error of the comment. + assert.deepStrictEqual(messages, []); - // Use only `aaa`. - assert.strictEqual(aaa.callCount, 1); - assert.strictEqual(bbb.callCount, 0); + // Use only `aaa`. + assert.strictEqual(aaa.callCount, 1); + assert.strictEqual(bbb.callCount, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); + assert.strictEqual(suppressedMessages.length, 0); + }); - it("should ignore the part preceded by '--' with line breaks.", () => { - const aaa = sinon.stub().returns({}); - const bbb = sinon.stub().returns({}); + it("should ignore the part preceded by '--' with line breaks.", () => { + const aaa = sinon.stub().returns({}); + const bbb = sinon.stub().returns({}); - linter.defineRule("aaa", { create: aaa }); - linter.defineRule("bbb", { create: bbb }); - const messages = linter.verify(` + linter.defineRule("aaa", { create: aaa }); + linter.defineRule("bbb", { create: bbb }); + const messages = linter.verify( + ` /*eslint aaa:error -------- bbb:error */ console.log("hello") - `, {}); - const suppressedMessages = linter.getSuppressedMessages(); - - // Don't include syntax error of the comment. - assert.deepStrictEqual(messages, []); - - // Use only `aaa`. - assert.strictEqual(aaa.callCount, 1); - assert.strictEqual(bbb.callCount, 0); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - }); - - describe("context.getScope()", () => { - - /** - * Get the scope on the node `astSelector` specified. - * @param {string} code The source code to verify. - * @param {string} astSelector The AST selector to get scope. - * @param {number} [ecmaVersion=5] The ECMAScript version. - * @returns {{node: ASTNode, scope: escope.Scope}} Gotten scope. - */ - function getScope(code, astSelector, ecmaVersion = 5) { - let node, scope; - - linter.defineRule("get-scope", { - create: context => ({ - [astSelector](node0) { - node = node0; - scope = context.getScope(); - } - }) - }); - linter.verify( - code, - { - parserOptions: { ecmaVersion }, - rules: { "get-scope": 2 } - } - ); - - return { node, scope }; - } - - it("should return 'function' scope on FunctionDeclaration (ES5)", () => { - const { node, scope } = getScope("function f() {}", "FunctionDeclaration"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node); - }); - - it("should return 'function' scope on FunctionExpression (ES5)", () => { - const { node, scope } = getScope("!function f() {}", "FunctionExpression"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node); - }); - - it("should return 'function' scope on the body of FunctionDeclaration (ES5)", () => { - const { node, scope } = getScope("function f() {}", "BlockStatement"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent); - }); - - it("should return 'function' scope on the body of FunctionDeclaration (ES2015)", () => { - const { node, scope } = getScope("function f() {}", "BlockStatement", 2015); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent); - }); - - it("should return 'function' scope on BlockStatement in functions (ES5)", () => { - const { node, scope } = getScope("function f() { { var b; } }", "BlockStatement > BlockStatement"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); - }); - - it("should return 'block' scope on BlockStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { { let a; var b; } }", "BlockStatement > BlockStatement", 2015); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.upper.type, "function"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]); - assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "b"]); - }); - - it("should return 'block' scope on nested BlockStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { { let a; { let b; var c; } } }", "BlockStatement > BlockStatement > BlockStatement", 2015); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.upper.type, "block"); - assert.strictEqual(scope.upper.upper.type, "function"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); - assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["a"]); - assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "c"]); - }); - - it("should return 'function' scope on SwitchStatement in functions (ES5)", () => { - const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchStatement"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); - }); - - it("should return 'switch' scope on SwitchStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchStatement", 2015); - - assert.strictEqual(scope.type, "switch"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); - }); - - it("should return 'function' scope on SwitchCase in functions (ES5)", () => { - const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchCase"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent.parent.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); - }); - - it("should return 'switch' scope on SwitchCase in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchCase", 2015); - - assert.strictEqual(scope.type, "switch"); - assert.strictEqual(scope.block, node.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); - }); - - it("should return 'catch' scope on CatchClause in functions (ES5)", () => { - const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause"); - - assert.strictEqual(scope.type, "catch"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); - }); - - it("should return 'catch' scope on CatchClause in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause", 2015); - - assert.strictEqual(scope.type, "catch"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); - }); - - it("should return 'catch' scope on the block of CatchClause in functions (ES5)", () => { - const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause > BlockStatement"); - - assert.strictEqual(scope.type, "catch"); - assert.strictEqual(scope.block, node.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); - }); - - it("should return 'block' scope on the block of CatchClause in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause > BlockStatement", 2015); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]); - }); - - it("should return 'function' scope on ForStatement in functions (ES5)", () => { - const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]); - }); - - it("should return 'for' scope on ForStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement", 2015); - - assert.strictEqual(scope.type, "for"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["i"]); - }); - - it("should return 'function' scope on the block body of ForStatement in functions (ES5)", () => { - const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent.parent.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]); - }); - - it("should return 'block' scope on the block body of ForStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement", 2015); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.upper.type, "for"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), []); - assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["i"]); - }); - - it("should return 'function' scope on ForInStatement in functions (ES5)", () => { - const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]); - }); - - it("should return 'for' scope on ForInStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement", 2015); - - assert.strictEqual(scope.type, "for"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["key"]); - }); - - it("should return 'function' scope on the block body of ForInStatement in functions (ES5)", () => { - const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement > BlockStatement"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent.parent.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]); - }); - - it("should return 'block' scope on the block body of ForInStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement > BlockStatement", 2015); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.upper.type, "for"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), []); - assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["key"]); - }); - - it("should return 'for' scope on ForOfStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement", 2015); - - assert.strictEqual(scope.type, "for"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["x"]); - }); - - it("should return 'block' scope on the block body of ForOfStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement > BlockStatement", 2015); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.upper.type, "for"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), []); - assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["x"]); - }); - - it("should shadow the same name variable by the iteration variable.", () => { - const { node, scope } = getScope("let x; for (let x of x) {}", "ForOfStatement", 2015); - - assert.strictEqual(scope.type, "for"); - assert.strictEqual(scope.upper.type, "global"); - assert.strictEqual(scope.block, node); - assert.strictEqual(scope.upper.variables[0].references.length, 0); - assert.strictEqual(scope.references[0].identifier, node.left.declarations[0].id); - assert.strictEqual(scope.references[1].identifier, node.right); - assert.strictEqual(scope.references[1].resolved, scope.variables[0]); - }); - }); - - describe("Variables and references", () => { - const code = [ - "a;", - "function foo() { b; }", - "Object;", - "foo;", - "var c;", - "c;", - "/* global d */", - "d;", - "e;", - "f;" - ].join("\n"); - let scope = null; - - beforeEach(() => { - let ok = false; - - linter.defineRules({ - test: { - create: context => ({ - Program() { - scope = context.getScope(); - ok = true; - } - }) - } - }); - linter.verify(code, { rules: { test: 2 }, globals: { e: true, f: false } }); - assert(ok); - }); - - afterEach(() => { - scope = null; - }); - - it("Scope#through should contain references of undefined variables", () => { - assert.strictEqual(scope.through.length, 2); - assert.strictEqual(scope.through[0].identifier.name, "a"); - assert.strictEqual(scope.through[0].identifier.loc.start.line, 1); - assert.strictEqual(scope.through[0].resolved, null); - assert.strictEqual(scope.through[1].identifier.name, "b"); - assert.strictEqual(scope.through[1].identifier.loc.start.line, 2); - assert.strictEqual(scope.through[1].resolved, null); - }); - - it("Scope#variables should contain global variables", () => { - assert(scope.variables.some(v => v.name === "Object")); - assert(scope.variables.some(v => v.name === "foo")); - assert(scope.variables.some(v => v.name === "c")); - assert(scope.variables.some(v => v.name === "d")); - assert(scope.variables.some(v => v.name === "e")); - assert(scope.variables.some(v => v.name === "f")); - }); - - it("Scope#set should contain global variables", () => { - assert(scope.set.get("Object")); - assert(scope.set.get("foo")); - assert(scope.set.get("c")); - assert(scope.set.get("d")); - assert(scope.set.get("e")); - assert(scope.set.get("f")); - }); - - it("Variables#references should contain their references", () => { - assert.strictEqual(scope.set.get("Object").references.length, 1); - assert.strictEqual(scope.set.get("Object").references[0].identifier.name, "Object"); - assert.strictEqual(scope.set.get("Object").references[0].identifier.loc.start.line, 3); - assert.strictEqual(scope.set.get("Object").references[0].resolved, scope.set.get("Object")); - assert.strictEqual(scope.set.get("foo").references.length, 1); - assert.strictEqual(scope.set.get("foo").references[0].identifier.name, "foo"); - assert.strictEqual(scope.set.get("foo").references[0].identifier.loc.start.line, 4); - assert.strictEqual(scope.set.get("foo").references[0].resolved, scope.set.get("foo")); - assert.strictEqual(scope.set.get("c").references.length, 1); - assert.strictEqual(scope.set.get("c").references[0].identifier.name, "c"); - assert.strictEqual(scope.set.get("c").references[0].identifier.loc.start.line, 6); - assert.strictEqual(scope.set.get("c").references[0].resolved, scope.set.get("c")); - assert.strictEqual(scope.set.get("d").references.length, 1); - assert.strictEqual(scope.set.get("d").references[0].identifier.name, "d"); - assert.strictEqual(scope.set.get("d").references[0].identifier.loc.start.line, 8); - assert.strictEqual(scope.set.get("d").references[0].resolved, scope.set.get("d")); - assert.strictEqual(scope.set.get("e").references.length, 1); - assert.strictEqual(scope.set.get("e").references[0].identifier.name, "e"); - assert.strictEqual(scope.set.get("e").references[0].identifier.loc.start.line, 9); - assert.strictEqual(scope.set.get("e").references[0].resolved, scope.set.get("e")); - assert.strictEqual(scope.set.get("f").references.length, 1); - assert.strictEqual(scope.set.get("f").references[0].identifier.name, "f"); - assert.strictEqual(scope.set.get("f").references[0].identifier.loc.start.line, 10); - assert.strictEqual(scope.set.get("f").references[0].resolved, scope.set.get("f")); - }); - - it("Reference#resolved should be their variable", () => { - assert.strictEqual(scope.set.get("Object").references[0].resolved, scope.set.get("Object")); - assert.strictEqual(scope.set.get("foo").references[0].resolved, scope.set.get("foo")); - assert.strictEqual(scope.set.get("c").references[0].resolved, scope.set.get("c")); - assert.strictEqual(scope.set.get("d").references[0].resolved, scope.set.get("d")); - assert.strictEqual(scope.set.get("e").references[0].resolved, scope.set.get("e")); - assert.strictEqual(scope.set.get("f").references[0].resolved, scope.set.get("f")); - }); - }); - - describe("context.getDeclaredVariables(node)", () => { - - /** - * Assert `context.getDeclaredVariables(node)` is valid. - * @param {string} code A code to check. - * @param {string} type A type string of ASTNode. This method checks variables on the node of the type. - * @param {Array>} expectedNamesList An array of expected variable names. The expected variable names is an array of string. - * @returns {void} - */ - function verify(code, type, expectedNamesList) { - linter.defineRules({ - test: { - create(context) { - - /** - * Assert `context.getDeclaredVariables(node)` is empty. - * @param {ASTNode} node A node to check. - * @returns {void} - */ - function checkEmpty(node) { - assert.strictEqual(0, context.getDeclaredVariables(node).length); - } - const rule = { - Program: checkEmpty, - EmptyStatement: checkEmpty, - BlockStatement: checkEmpty, - ExpressionStatement: checkEmpty, - LabeledStatement: checkEmpty, - BreakStatement: checkEmpty, - ContinueStatement: checkEmpty, - WithStatement: checkEmpty, - SwitchStatement: checkEmpty, - ReturnStatement: checkEmpty, - ThrowStatement: checkEmpty, - TryStatement: checkEmpty, - WhileStatement: checkEmpty, - DoWhileStatement: checkEmpty, - ForStatement: checkEmpty, - ForInStatement: checkEmpty, - DebuggerStatement: checkEmpty, - ThisExpression: checkEmpty, - ArrayExpression: checkEmpty, - ObjectExpression: checkEmpty, - Property: checkEmpty, - SequenceExpression: checkEmpty, - UnaryExpression: checkEmpty, - BinaryExpression: checkEmpty, - AssignmentExpression: checkEmpty, - UpdateExpression: checkEmpty, - LogicalExpression: checkEmpty, - ConditionalExpression: checkEmpty, - CallExpression: checkEmpty, - NewExpression: checkEmpty, - MemberExpression: checkEmpty, - SwitchCase: checkEmpty, - Identifier: checkEmpty, - Literal: checkEmpty, - ForOfStatement: checkEmpty, - ArrowFunctionExpression: checkEmpty, - YieldExpression: checkEmpty, - TemplateLiteral: checkEmpty, - TaggedTemplateExpression: checkEmpty, - TemplateElement: checkEmpty, - ObjectPattern: checkEmpty, - ArrayPattern: checkEmpty, - RestElement: checkEmpty, - AssignmentPattern: checkEmpty, - ClassBody: checkEmpty, - MethodDefinition: checkEmpty, - MetaProperty: checkEmpty - }; - - rule[type] = function(node) { - const expectedNames = expectedNamesList.shift(); - const variables = context.getDeclaredVariables(node); - - assert(Array.isArray(expectedNames)); - assert(Array.isArray(variables)); - assert.strictEqual(expectedNames.length, variables.length); - for (let i = variables.length - 1; i >= 0; i--) { - assert.strictEqual(expectedNames[i], variables[i].name); - } - }; - return rule; - } - } - }); - linter.verify(code, { - rules: { test: 2 }, - parserOptions: { - ecmaVersion: 6, - sourceType: "module" - } - }); - - // Check all expected names are asserted. - assert.strictEqual(0, expectedNamesList.length); - } - - it("VariableDeclaration", () => { - const code = "\n var {a, x: [b], y: {c = 0}} = foo;\n let {d, x: [e], y: {f = 0}} = foo;\n const {g, x: [h], y: {i = 0}} = foo, {j, k = function(z) { let l; }} = bar;\n "; - const namesList = [ - ["a", "b", "c"], - ["d", "e", "f"], - ["g", "h", "i", "j", "k"], - ["l"] - ]; - - verify(code, "VariableDeclaration", namesList); - }); - - it("VariableDeclaration (on for-in/of loop)", () => { - - // TDZ scope is created here, so tests to exclude those. - const code = "\n for (var {a, x: [b], y: {c = 0}} in foo) {\n let g;\n }\n for (let {d, x: [e], y: {f = 0}} of foo) {\n let h;\n }\n "; - const namesList = [ - ["a", "b", "c"], - ["g"], - ["d", "e", "f"], - ["h"] - ]; - - verify(code, "VariableDeclaration", namesList); - }); - - it("VariableDeclarator", () => { - - // TDZ scope is created here, so tests to exclude those. - const code = "\n var {a, x: [b], y: {c = 0}} = foo;\n let {d, x: [e], y: {f = 0}} = foo;\n const {g, x: [h], y: {i = 0}} = foo, {j, k = function(z) { let l; }} = bar;\n "; - const namesList = [ - ["a", "b", "c"], - ["d", "e", "f"], - ["g", "h", "i"], - ["j", "k"], - ["l"] - ]; - - verify(code, "VariableDeclarator", namesList); - }); - - it("FunctionDeclaration", () => { - const code = "\n function foo({a, x: [b], y: {c = 0}}, [d, e]) {\n let z;\n }\n function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) {\n let z;\n }\n "; - const namesList = [ - ["foo", "a", "b", "c", "d", "e"], - ["bar", "f", "g", "h", "i", "j"] - ]; - - verify(code, "FunctionDeclaration", namesList); - }); - - it("FunctionExpression", () => { - const code = "\n (function foo({a, x: [b], y: {c = 0}}, [d, e]) {\n let z;\n });\n (function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) {\n let z;\n });\n "; - const namesList = [ - ["foo", "a", "b", "c", "d", "e"], - ["bar", "f", "g", "h", "i", "j"], - ["q"] - ]; - - verify(code, "FunctionExpression", namesList); - }); - - it("ArrowFunctionExpression", () => { - const code = "\n (({a, x: [b], y: {c = 0}}, [d, e]) => {\n let z;\n });\n (({f, x: [g], y: {h = 0}}, [i, j]) => {\n let z;\n });\n "; - const namesList = [ - ["a", "b", "c", "d", "e"], - ["f", "g", "h", "i", "j"] - ]; - - verify(code, "ArrowFunctionExpression", namesList); - }); - - it("ClassDeclaration", () => { - const code = "\n class A { foo(x) { let y; } }\n class B { foo(x) { let y; } }\n "; - const namesList = [ - ["A", "A"], // outer scope's and inner scope's. - ["B", "B"] - ]; - - verify(code, "ClassDeclaration", namesList); - }); - - it("ClassExpression", () => { - const code = "\n (class A { foo(x) { let y; } });\n (class B { foo(x) { let y; } });\n "; - const namesList = [ - ["A"], - ["B"] - ]; - - verify(code, "ClassExpression", namesList); - }); - - it("CatchClause", () => { - const code = "\n try {} catch ({a, b}) {\n let x;\n try {} catch ({c, d}) {\n let y;\n }\n }\n "; - const namesList = [ - ["a", "b"], - ["c", "d"] - ]; - - verify(code, "CatchClause", namesList); - }); - - it("ImportDeclaration", () => { - const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; - const namesList = [ - [], - ["a"], - ["b", "c", "d"] - ]; - - verify(code, "ImportDeclaration", namesList); - }); - - it("ImportSpecifier", () => { - const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; - const namesList = [ - ["c"], - ["d"] - ]; - - verify(code, "ImportSpecifier", namesList); - }); - - it("ImportDefaultSpecifier", () => { - const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; - const namesList = [ - ["b"] - ]; - - verify(code, "ImportDefaultSpecifier", namesList); - }); - - it("ImportNamespaceSpecifier", () => { - const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; - const namesList = [ - ["a"] - ]; - - verify(code, "ImportNamespaceSpecifier", namesList); - }); - }); - - describe("suggestions", () => { - it("provides suggestion information for tools to use", () => { - linter.defineRule("rule-with-suggestions", { - meta: { hasSuggestions: true }, - create: context => ({ - Program(node) { - context.report({ - node, - message: "Incorrect spacing", - suggest: [{ - desc: "Insert space at the beginning", - fix: fixer => fixer.insertTextBefore(node, " ") - }, { - desc: "Insert space at the end", - fix: fixer => fixer.insertTextAfter(node, " ") - }] - }); - } - }) - }); - - const messages = linter.verify("var a = 1;", { rules: { "rule-with-suggestions": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages[0].suggestions, [{ - desc: "Insert space at the beginning", - fix: { - range: [0, 0], - text: " " - } - }, { - desc: "Insert space at the end", - fix: { - range: [10, 10], - text: " " - } - }]); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("supports messageIds for suggestions", () => { - linter.defineRule("rule-with-suggestions", { - meta: { - messages: { - suggestion1: "Insert space at the beginning", - suggestion2: "Insert space at the end" - }, - hasSuggestions: true - }, - create: context => ({ - Program(node) { - context.report({ - node, - message: "Incorrect spacing", - suggest: [{ - messageId: "suggestion1", - fix: fixer => fixer.insertTextBefore(node, " ") - }, { - messageId: "suggestion2", - fix: fixer => fixer.insertTextAfter(node, " ") - }] - }); - } - }) - }); - - const messages = linter.verify("var a = 1;", { rules: { "rule-with-suggestions": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages[0].suggestions, [{ - messageId: "suggestion1", - desc: "Insert space at the beginning", - fix: { - range: [0, 0], - text: " " - } - }, { - messageId: "suggestion2", - desc: "Insert space at the end", - fix: { - range: [10, 10], - text: " " - } - }]); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled", () => { - linter.defineRule("rule-with-suggestions", { - meta: { docs: {}, schema: [] }, - create: context => ({ - Program(node) { - context.report({ - node, - message: "hello world", - suggest: [{ desc: "convert to foo", fix: fixer => fixer.insertTextBefore(node, " ") }] - }); - } - }) - }); - - assert.throws(() => { - linter.verify("0", { rules: { "rule-with-suggestions": "error" } }); - }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`."); - }); - - it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled and the rule has the obsolete `meta.docs.suggestion` property", () => { - linter.defineRule("rule-with-meta-docs-suggestion", { - meta: { docs: { suggestion: true }, schema: [] }, - create: context => ({ - Program(node) { - context.report({ - node, - message: "hello world", - suggest: [{ desc: "convert to foo", fix: fixer => fixer.insertTextBefore(node, " ") }] - }); - } - }) - }); - - assert.throws(() => { - linter.verify("0", { rules: { "rule-with-meta-docs-suggestion": "error" } }); - }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint."); - }); - }); - - describe("mutability", () => { - let linter1 = null; - let linter2 = null; - - beforeEach(() => { - linter1 = new Linter(); - linter2 = new Linter(); - }); - - describe("rules", () => { - it("with no changes, same rules are loaded", () => { - assert.sameDeepMembers(Array.from(linter1.getRules().keys()), Array.from(linter2.getRules().keys())); - }); - - it("loading rule in one doesn't change the other", () => { - linter1.defineRule("mock-rule", { - create: () => ({}) - }); - - assert.isTrue(linter1.getRules().has("mock-rule"), "mock rule is present"); - assert.isFalse(linter2.getRules().has("mock-rule"), "mock rule is not present"); - }); - }); - }); - - describe("processors", () => { - let receivedFilenames = []; - let receivedPhysicalFilenames = []; - - beforeEach(() => { - receivedFilenames = []; - receivedPhysicalFilenames = []; - - // A rule that always reports the AST with a message equal to the source text - linter.defineRule("report-original-text", { - create: context => ({ - Program(ast) { - assert.strictEqual(context.getFilename(), context.filename); - assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); - - receivedFilenames.push(context.filename); - receivedPhysicalFilenames.push(context.physicalFilename); - - context.report({ node: ast, message: context.sourceCode.text }); - } - }) - }); - }); - - describe("preprocessors", () => { - it("should receive text and filename.", () => { - const code = "foo bar baz"; - const preprocess = sinon.spy(text => text.split(" ")); - - linter.verify(code, {}, { filename, preprocess }); - - assert.strictEqual(preprocess.calledOnce, true); - assert.deepStrictEqual(preprocess.args[0], [code, filename]); - }); - - it("should apply a preprocessor to the code, and lint each code sample separately", () => { - const code = "foo bar baz"; - const problems = linter.verify( - code, - { rules: { "report-original-text": "error" } }, - { - - // Apply a preprocessor that splits the source text into spaces and lints each word individually - preprocess(input) { - return input.split(" "); - } - } - ); - - assert.strictEqual(problems.length, 3); - assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]); - }); - - it("should apply a preprocessor to the code even if the preprocessor returned code block objects.", () => { - const code = "foo bar baz"; - const problems = linter.verify( - code, - { rules: { "report-original-text": "error" } }, - { - filename, - - // Apply a preprocessor that splits the source text into spaces and lints each word individually - preprocess(input) { - return input.split(" ").map(text => ({ - filename: "block.js", - text - })); - } - } - ); - - assert.strictEqual(problems.length, 3); - assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]); - - // filename - assert.strictEqual(receivedFilenames.length, 3); - assert(/^filename\.js[/\\]0_block\.js/u.test(receivedFilenames[0])); - assert(/^filename\.js[/\\]1_block\.js/u.test(receivedFilenames[1])); - assert(/^filename\.js[/\\]2_block\.js/u.test(receivedFilenames[2])); - - // physical filename - assert.strictEqual(receivedPhysicalFilenames.length, 3); - assert.strictEqual(receivedPhysicalFilenames.every(name => name === filename), true); - }); - - it("should receive text even if a SourceCode object was given.", () => { - const code = "foo"; - const preprocess = sinon.spy(text => text.split(" ")); - - linter.verify(code, {}); - const sourceCode = linter.getSourceCode(); - - linter.verify(sourceCode, {}, { filename, preprocess }); - - assert.strictEqual(preprocess.calledOnce, true); - assert.deepStrictEqual(preprocess.args[0], [code, filename]); - }); - - it("should receive text even if a SourceCode object was given (with BOM).", () => { - const code = "\uFEFFfoo"; - const preprocess = sinon.spy(text => text.split(" ")); - - linter.verify(code, {}); - const sourceCode = linter.getSourceCode(); - - linter.verify(sourceCode, {}, { filename, preprocess }); - - assert.strictEqual(preprocess.calledOnce, true); - assert.deepStrictEqual(preprocess.args[0], [code, filename]); - }); - - it("should catch preprocess error.", () => { - const code = "foo"; - const preprocess = sinon.spy(() => { - throw Object.assign(new SyntaxError("Invalid syntax"), { - lineNumber: 1, - column: 1 - }); - }); - - const messages = linter.verify(code, {}, { filename, preprocess }); - - assert.strictEqual(preprocess.calledOnce, true); - assert.deepStrictEqual(preprocess.args[0], [code, filename]); - assert.deepStrictEqual(messages, [ - { - ruleId: null, - fatal: true, - severity: 2, - message: "Preprocessing error: Invalid syntax", - line: 1, - column: 1, - nodeType: null - } - ]); - }); - }); - - describe("postprocessors", () => { - it("should receive result and filename.", () => { - const code = "foo bar baz"; - const preprocess = sinon.spy(text => text.split(" ")); - const postprocess = sinon.spy(text => [text]); - - linter.verify(code, {}, { filename, postprocess, preprocess }); - - assert.strictEqual(postprocess.calledOnce, true); - assert.deepStrictEqual(postprocess.args[0], [[[], [], []], filename]); - }); - - it("should apply a postprocessor to the reported messages", () => { - const code = "foo bar baz"; - - const problems = linter.verify( - code, - { rules: { "report-original-text": "error" } }, - { - preprocess: input => input.split(" "), - - /* - * Apply a postprocessor that updates the locations of the reported problems - * to make sure they correspond to the locations in the original text. - */ - postprocess(problemLists) { - problemLists.forEach(problemList => assert.strictEqual(problemList.length, 1)); - return problemLists.reduce( - (combinedList, problemList, index) => - combinedList.concat( - problemList.map( - problem => - Object.assign( - {}, - problem, - { - message: problem.message.toUpperCase(), - column: problem.column + index * 4 - } - ) - ) - ), - [] - ); - } - } - ); - - assert.strictEqual(problems.length, 3); - assert.deepStrictEqual(problems.map(problem => problem.message), ["FOO", "BAR", "BAZ"]); - assert.deepStrictEqual(problems.map(problem => problem.column), [1, 5, 9]); - }); - - it("should use postprocessed problem ranges when applying autofixes", () => { - const code = "foo bar baz"; - - linter.defineRule("capitalize-identifiers", { - meta: { - fixable: "code" - }, - create(context) { - return { - Identifier(node) { - if (node.name !== node.name.toUpperCase()) { - context.report({ - node, - message: "Capitalize this identifier", - fix: fixer => fixer.replaceText(node, node.name.toUpperCase()) - }); - } - } - }; - } - }); - - const fixResult = linter.verifyAndFix( - code, - { rules: { "capitalize-identifiers": "error" } }, - { - - /* - * Apply a postprocessor that updates the locations of autofixes - * to make sure they correspond to locations in the original text. - */ - preprocess: input => input.split(" "), - postprocess(problemLists) { - return problemLists.reduce( - (combinedProblems, problemList, blockIndex) => - combinedProblems.concat( - problemList.map(problem => - Object.assign(problem, { - fix: { - text: problem.fix.text, - range: problem.fix.range.map( - rangeIndex => rangeIndex + blockIndex * 4 - ) - } - })) - ), - [] - ); - } - } - ); - - assert.strictEqual(fixResult.fixed, true); - assert.strictEqual(fixResult.messages.length, 0); - assert.strictEqual(fixResult.output, "FOO BAR BAZ"); - }); - }); - }); - - describe("verifyAndFix", () => { - it("Fixes the code", () => { - const messages = linter.verifyAndFix("var a", { - rules: { - semi: 2 - } - }, { filename: "test.js" }); - - assert.strictEqual(messages.output, "var a;", "Fixes were applied correctly"); - assert.isTrue(messages.fixed); - }); - - it("does not require a third argument", () => { - const fixResult = linter.verifyAndFix("var a", { - rules: { - semi: 2 - } - }); - - assert.deepStrictEqual(fixResult, { - fixed: true, - messages: [], - output: "var a;" - }); - }); - - it("does not include suggestions in autofix results", () => { - const fixResult = linter.verifyAndFix("var foo = /\\#/", { - rules: { - semi: 2, - "no-useless-escape": 2 - } - }); - - assert.strictEqual(fixResult.output, "var foo = /\\#/;"); - assert.strictEqual(fixResult.fixed, true); - assert.strictEqual(fixResult.messages[0].suggestions.length > 0, true); - }); - - it("does not apply autofixes when fix argument is `false`", () => { - const fixResult = linter.verifyAndFix("var a", { - rules: { - semi: 2 - } - }, { fix: false }); - - assert.strictEqual(fixResult.fixed, false); - }); - - it("stops fixing after 10 passes", () => { - - linter.defineRule("add-spaces", { - meta: { - fixable: "whitespace" - }, - create(context) { - return { - Program(node) { - context.report({ - node, - message: "Add a space before this node.", - fix: fixer => fixer.insertTextBefore(node, " ") - }); - } - }; - } - }); - - const fixResult = linter.verifyAndFix("a", { rules: { "add-spaces": "error" } }); - - assert.strictEqual(fixResult.fixed, true); - assert.strictEqual(fixResult.output, `${" ".repeat(10)}a`); - assert.strictEqual(fixResult.messages.length, 1); - }); - - it("should throw an error if fix is passed but meta has no `fixable` property", () => { - linter.defineRule("test-rule", { - meta: { - docs: {}, - schema: [] - }, - create: context => ({ - Program(node) { - context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); - } - }) - }); - - assert.throws(() => { - linter.verify("0", { rules: { "test-rule": "error" } }); - }, /Fixable rules must set the `meta\.fixable` property to "code" or "whitespace".\nOccurred while linting :1\nRule: "test-rule"$/u); - }); - - it("should throw an error if fix is passed and there is no metadata", () => { - linter.defineRule("test-rule", { - create: context => ({ - Program(node) { - context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); - } - }) - }); - - assert.throws(() => { - linter.verify("0", { rules: { "test-rule": "error" } }); - }, /Fixable rules must set the `meta\.fixable` property/u); - }); - - it("should throw an error if fix is passed from a legacy-format rule", () => { - linter.defineRule("test-rule", { - create: context => ({ - Program(node) { - context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); - } - }) - }); - - assert.throws(() => { - linter.verify("0", { rules: { "test-rule": "error" } }); - }, /Fixable rules must set the `meta\.fixable` property/u); - }); - }); - - describe("Edge cases", () => { - - it("should properly parse import statements when sourceType is module", () => { - const code = "import foo from 'foo';"; - const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, sourceType: "module" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should properly parse import all statements when sourceType is module", () => { - const code = "import * as foo from 'foo';"; - const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, sourceType: "module" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should properly parse default export statements when sourceType is module", () => { - const code = "export default function initialize() {}"; - const messages = linter.verify(code, { parserOptions: { ecmaVersion: 6, sourceType: "module" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - // https://github.com/eslint/eslint/issues/9687 - it("should report an error when invalid parserOptions found", () => { - let messages = linter.verify("", { parserOptions: { ecmaVersion: 222 } }); - let suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages.length, 1); - assert.ok(messages[0].message.includes("Invalid ecmaVersion")); - assert.strictEqual(suppressedMessages.length, 0); - - messages = linter.verify("", { parserOptions: { sourceType: "foo" } }); - suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages.length, 1); - assert.ok(messages[0].message.includes("Invalid sourceType")); - assert.strictEqual(suppressedMessages.length, 0); - - messages = linter.verify("", { parserOptions: { ecmaVersion: 5, sourceType: "module" } }); - suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages.length, 1); - assert.ok(messages[0].message.includes("sourceType 'module' is not supported when ecmaVersion < 2015")); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not crash when invalid parentheses syntax is encountered", () => { - linter.verify("left = (aSize.width/2) - ()"); - }); - - it("should not crash when let is used inside of switch case", () => { - linter.verify("switch(foo) { case 1: let bar=2; }", { parserOptions: { ecmaVersion: 6 } }); - }); - - it("should not crash when parsing destructured assignment", () => { - linter.verify("var { a='a' } = {};", { parserOptions: { ecmaVersion: 6 } }); - }); - - it("should report syntax error when a keyword exists in object property shorthand", () => { - const messages = linter.verify("let a = {this}", { parserOptions: { ecmaVersion: 6 } }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].fatal, true); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not rewrite env setting in core (https://github.com/eslint/eslint/issues/4814)", () => { - - /* - * This test focuses on the instance of https://github.com/eslint/eslint/blob/v2.0.0-alpha-2/conf/environments.js#L26-L28 - * This `verify()` takes the instance and runs https://github.com/eslint/eslint/blob/v2.0.0-alpha-2/lib/eslint.js#L416 - */ - linter.defineRule("test", { - create: () => ({}) - }); - linter.verify("var a = 0;", { - env: { node: true }, - parserOptions: { ecmaVersion: 6, sourceType: "module" }, - rules: { test: 2 } - }); - - // This `verify()` takes the instance and tests that the instance was not modified. - let ok = false; - - linter.defineRule("test", { - create(context) { - assert( - context.parserOptions.ecmaFeatures.globalReturn, - "`ecmaFeatures.globalReturn` of the node environment should not be modified." - ); - ok = true; - return {}; - } - }); - linter.verify("var a = 0;", { - env: { node: true }, - rules: { test: 2 } - }); - - assert(ok); - }); - - it("should throw when rule's create() function does not return an object", () => { - const config = { rules: { checker: "error" } }; - - linter.defineRule("checker", { - create: () => null - }); // returns null - - assert.throws(() => { - linter.verify("abc", config, filename); - }, "The create() function for rule 'checker' did not return an object."); - - linter.defineRule("checker", { - create() {} - }); // returns undefined - - assert.throws(() => { - linter.verify("abc", config, filename); - }, "The create() function for rule 'checker' did not return an object."); - }); - }); - - describe("Custom parser", () => { - - const errorPrefix = "Parsing error: "; - - it("should have file path passed to it", () => { - const code = "/* this is code */"; - const parseSpy = { parse: sinon.spy() }; - - linter.defineParser("stub-parser", parseSpy); - linter.verify(code, { parser: "stub-parser" }, filename, true); - - sinon.assert.calledWithMatch(parseSpy.parse, "", { filePath: filename }); - }); - - it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => { - const code = "var myDivElement =
;"; - - linter.defineParser("esprima", esprima); - const messages = linter.verify(code, { parser: "esprima", parserOptions: { jsx: true } }, "filename"); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should return an error when the custom parser can't be found", () => { - const code = "var myDivElement =
;"; - const messages = linter.verify(code, { parser: "esprima-xyz" }, "filename"); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, "Configured parser 'esprima-xyz' was not found."); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not throw or report errors when the custom parser returns unrecognized operators (https://github.com/eslint/eslint/issues/10475)", () => { - const code = "null %% 'foo'"; - - linter.defineParser("unknown-logical-operator", testParsers.unknownLogicalOperator); - - // This shouldn't throw - const messages = linter.verify(code, { parser: "unknown-logical-operator" }, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not throw or report errors when the custom parser returns nested unrecognized operators (https://github.com/eslint/eslint/issues/10560)", () => { - const code = "foo && bar %% baz"; - - linter.defineParser("unknown-logical-operator-nested", testParsers.unknownLogicalOperatorNested); - - // This shouldn't throw - const messages = linter.verify(code, { parser: "unknown-logical-operator-nested" }, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not throw or return errors when the custom parser returns unknown AST nodes", () => { - const code = "foo && bar %% baz"; - - const nodes = []; - - linter.defineRule("collect-node-types", { - create: () => ({ - "*"(node) { - nodes.push(node.type); - } - }) - }); - - linter.defineParser("non-js-parser", testParsers.nonJSParser); - - const messages = linter.verify(code, { - parser: "non-js-parser", - rules: { - "collect-node-types": "error" - } - }, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.isTrue(nodes.length > 0); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should strip leading line: prefix from parser error", () => { - linter.defineParser("line-error", testParsers.lineError); - const messages = linter.verify(";", { parser: "line-error" }, "filename"); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, errorPrefix + testParsers.lineError.expectedError); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not modify a parser error message without a leading line: prefix", () => { - linter.defineParser("no-line-error", testParsers.noLineError); - const messages = linter.verify(";", { parser: "no-line-error" }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, errorPrefix + testParsers.noLineError.expectedError); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - describe("if a parser provides 'visitorKeys'", () => { - let types = []; - let sourceCode; - let scopeManager; - let firstChildNodes = []; - - beforeEach(() => { - types = []; - firstChildNodes = []; - linter.defineRule("collect-node-types", { - create: () => ({ - "*"(node) { - types.push(node.type); - } - }) - }); - linter.defineRule("save-scope-manager", { - create(context) { - scopeManager = context.sourceCode.scopeManager; - - return {}; - } - }); - linter.defineRule("esquery-option", { - create: () => ({ - ":first-child"(node) { - firstChildNodes.push(node); - } - }) - }); - linter.defineParser("enhanced-parser2", testParsers.enhancedParser2); - linter.verify("@foo class A {}", { - parser: "enhanced-parser2", - rules: { - "collect-node-types": "error", - "save-scope-manager": "error", - "esquery-option": "error" - } - }); - - sourceCode = linter.getSourceCode(); - }); - - it("Traverser should use the visitorKeys (so 'types' includes 'Decorator')", () => { - assert.deepStrictEqual( - types, - ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"] - ); - }); - - it("eslint-scope should use the visitorKeys (so 'childVisitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => { - assert.deepStrictEqual( - scopeManager.__options.childVisitorKeys.ClassDeclaration, // eslint-disable-line no-underscore-dangle -- ScopeManager API - ["experimentalDecorators", "id", "superClass", "body"] - ); - }); - - it("should use the same visitorKeys if the source code object is reused", () => { - const types2 = []; - - linter.defineRule("collect-node-types", { - create: () => ({ - "*"(node) { - types2.push(node.type); - } - }) - }); - linter.verify(sourceCode, { - rules: { - "collect-node-types": "error" - } - }); - - assert.deepStrictEqual( - types2, - ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"] - ); - }); - - it("esquery should use the visitorKeys (so 'visitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => { - assert.deepStrictEqual( - firstChildNodes, - [sourceCode.ast.body[0], sourceCode.ast.body[0].experimentalDecorators[0]] - ); - }); - }); - - describe("if a parser provides 'scope'", () => { - let scope = null; - let sourceCode = null; - - beforeEach(() => { - linter.defineParser("enhanced-parser3", testParsers.enhancedParser3); - linter.defineRule("save-scope1", { - create: context => ({ - Program() { - scope = context.getScope(); - } - }) - }); - linter.verify("@foo class A {}", { parser: "enhanced-parser3", rules: { "save-scope1": 2 } }); - - sourceCode = linter.getSourceCode(); - }); - - it("should use the scope (so the global scope has the reference of '@foo')", () => { - assert.strictEqual(scope.references.length, 1); - assert.deepStrictEqual( - scope.references[0].identifier.name, - "foo" - ); - }); - - it("should use the same scope if the source code object is reused", () => { - let scope2 = null; - - linter.defineRule("save-scope2", { - create: context => ({ - Program() { - scope2 = context.getScope(); - } - }) - }); - linter.verify(sourceCode, { rules: { "save-scope2": 2 } }, "test.js"); - - assert(scope2 !== null); - assert(scope2 === scope); - }); - }); - - it("should not pass any default parserOptions to the parser", () => { - linter.defineParser("throws-with-options", testParsers.throwsWithOptions); - const messages = linter.verify(";", { parser: "throws-with-options" }, "filename"); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("merging 'parserOptions'", () => { - it("should deeply merge 'parserOptions' from an environment with 'parserOptions' from the provided config", () => { - const code = "return
"; - const config = { - env: { - node: true // ecmaFeatures: { globalReturn: true } - }, - parserOptions: { - ecmaFeatures: { - jsx: true - } - } - }; - - const messages = linter.verify(code, config); - const suppressedMessages = linter.getSuppressedMessages(); - - // no parsing errors - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); + `, + {}, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Don't include syntax error of the comment. + assert.deepStrictEqual(messages, []); + + // Use only `aaa`. + assert.strictEqual(aaa.callCount, 1); + assert.strictEqual(bbb.callCount, 0); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + }); + + describe("Variables and references", () => { + const code = [ + "a;", + "function foo() { b; }", + "Object;", + "foo;", + "var c;", + "c;", + "/* global d */", + "d;", + "e;", + "f;", + ].join("\n"); + let scope = null; + + beforeEach(() => { + let ok = false; + + linter.defineRules({ + test: { + create: context => ({ + Program(node) { + scope = context.sourceCode.getScope(node); + ok = true; + }, + }), + }, + }); + linter.verify(code, { + rules: { test: 2 }, + globals: { e: true, f: false }, + }); + assert(ok); + }); + + afterEach(() => { + scope = null; + }); + + it("Scope#through should contain references of undefined variables", () => { + assert.strictEqual(scope.through.length, 2); + assert.strictEqual(scope.through[0].identifier.name, "a"); + assert.strictEqual(scope.through[0].identifier.loc.start.line, 1); + assert.strictEqual(scope.through[0].resolved, null); + assert.strictEqual(scope.through[1].identifier.name, "b"); + assert.strictEqual(scope.through[1].identifier.loc.start.line, 2); + assert.strictEqual(scope.through[1].resolved, null); + }); + + it("Scope#variables should contain global variables", () => { + assert(scope.variables.some(v => v.name === "Object")); + assert(scope.variables.some(v => v.name === "foo")); + assert(scope.variables.some(v => v.name === "c")); + assert(scope.variables.some(v => v.name === "d")); + assert(scope.variables.some(v => v.name === "e")); + assert(scope.variables.some(v => v.name === "f")); + }); + + it("Scope#set should contain global variables", () => { + assert(scope.set.get("Object")); + assert(scope.set.get("foo")); + assert(scope.set.get("c")); + assert(scope.set.get("d")); + assert(scope.set.get("e")); + assert(scope.set.get("f")); + }); + + it("Variables#references should contain their references", () => { + assert.strictEqual(scope.set.get("Object").references.length, 1); + assert.strictEqual( + scope.set.get("Object").references[0].identifier.name, + "Object", + ); + assert.strictEqual( + scope.set.get("Object").references[0].identifier.loc.start.line, + 3, + ); + assert.strictEqual( + scope.set.get("Object").references[0].resolved, + scope.set.get("Object"), + ); + assert.strictEqual(scope.set.get("foo").references.length, 1); + assert.strictEqual( + scope.set.get("foo").references[0].identifier.name, + "foo", + ); + assert.strictEqual( + scope.set.get("foo").references[0].identifier.loc.start.line, + 4, + ); + assert.strictEqual( + scope.set.get("foo").references[0].resolved, + scope.set.get("foo"), + ); + assert.strictEqual(scope.set.get("c").references.length, 1); + assert.strictEqual( + scope.set.get("c").references[0].identifier.name, + "c", + ); + assert.strictEqual( + scope.set.get("c").references[0].identifier.loc.start.line, + 6, + ); + assert.strictEqual( + scope.set.get("c").references[0].resolved, + scope.set.get("c"), + ); + assert.strictEqual(scope.set.get("d").references.length, 1); + assert.strictEqual( + scope.set.get("d").references[0].identifier.name, + "d", + ); + assert.strictEqual( + scope.set.get("d").references[0].identifier.loc.start.line, + 8, + ); + assert.strictEqual( + scope.set.get("d").references[0].resolved, + scope.set.get("d"), + ); + assert.strictEqual(scope.set.get("e").references.length, 1); + assert.strictEqual( + scope.set.get("e").references[0].identifier.name, + "e", + ); + assert.strictEqual( + scope.set.get("e").references[0].identifier.loc.start.line, + 9, + ); + assert.strictEqual( + scope.set.get("e").references[0].resolved, + scope.set.get("e"), + ); + assert.strictEqual(scope.set.get("f").references.length, 1); + assert.strictEqual( + scope.set.get("f").references[0].identifier.name, + "f", + ); + assert.strictEqual( + scope.set.get("f").references[0].identifier.loc.start.line, + 10, + ); + assert.strictEqual( + scope.set.get("f").references[0].resolved, + scope.set.get("f"), + ); + }); + + it("Reference#resolved should be their variable", () => { + assert.strictEqual( + scope.set.get("Object").references[0].resolved, + scope.set.get("Object"), + ); + assert.strictEqual( + scope.set.get("foo").references[0].resolved, + scope.set.get("foo"), + ); + assert.strictEqual( + scope.set.get("c").references[0].resolved, + scope.set.get("c"), + ); + assert.strictEqual( + scope.set.get("d").references[0].resolved, + scope.set.get("d"), + ); + assert.strictEqual( + scope.set.get("e").references[0].resolved, + scope.set.get("e"), + ); + assert.strictEqual( + scope.set.get("f").references[0].resolved, + scope.set.get("f"), + ); + }); + }); + + describe("suggestions", () => { + it("provides suggestion information for tools to use", () => { + linter.defineRule("rule-with-suggestions", { + meta: { hasSuggestions: true }, + create: context => ({ + Program(node) { + context.report({ + node, + message: "Incorrect spacing", + suggest: [ + { + desc: "Insert space at the beginning", + fix: fixer => + fixer.insertTextBefore(node, " "), + }, + { + desc: "Insert space at the end", + fix: fixer => + fixer.insertTextAfter(node, " "), + }, + ], + }); + }, + }), + }); + + const messages = linter.verify("var a = 1;", { + rules: { "rule-with-suggestions": "error" }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages[0].suggestions, [ + { + desc: "Insert space at the beginning", + fix: { + range: [0, 0], + text: " ", + }, + }, + { + desc: "Insert space at the end", + fix: { + range: [10, 10], + text: " ", + }, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("supports messageIds for suggestions", () => { + linter.defineRule("rule-with-suggestions", { + meta: { + messages: { + suggestion1: "Insert space at the beginning", + suggestion2: "Insert space at the end", + }, + hasSuggestions: true, + }, + create: context => ({ + Program(node) { + context.report({ + node, + message: "Incorrect spacing", + suggest: [ + { + messageId: "suggestion1", + fix: fixer => + fixer.insertTextBefore(node, " "), + }, + { + messageId: "suggestion2", + fix: fixer => + fixer.insertTextAfter(node, " "), + }, + ], + }); + }, + }), + }); + + const messages = linter.verify("var a = 1;", { + rules: { "rule-with-suggestions": "error" }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages[0].suggestions, [ + { + messageId: "suggestion1", + desc: "Insert space at the beginning", + fix: { + range: [0, 0], + text: " ", + }, + }, + { + messageId: "suggestion2", + desc: "Insert space at the end", + fix: { + range: [10, 10], + text: " ", + }, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled", () => { + linter.defineRule("rule-with-suggestions", { + meta: { docs: {}, schema: [] }, + create: context => ({ + Program(node) { + context.report({ + node, + message: "hello world", + suggest: [ + { + desc: "convert to foo", + fix: fixer => + fixer.insertTextBefore(node, " "), + }, + ], + }); + }, + }), + }); + + assert.throws(() => { + linter.verify("0", { + rules: { "rule-with-suggestions": "error" }, + }); + }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`."); + }); + + it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled and the rule has the obsolete `meta.docs.suggestion` property", () => { + linter.defineRule("rule-with-meta-docs-suggestion", { + meta: { docs: { suggestion: true }, schema: [] }, + create: context => ({ + Program(node) { + context.report({ + node, + message: "hello world", + suggest: [ + { + desc: "convert to foo", + fix: fixer => + fixer.insertTextBefore(node, " "), + }, + ], + }); + }, + }), + }); + + assert.throws(() => { + linter.verify("0", { + rules: { "rule-with-meta-docs-suggestion": "error" }, + }); + }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint."); + }); + }); + + describe("mutability", () => { + let linter1 = null; + let linter2 = null; + + beforeEach(() => { + linter1 = new Linter({ configType: "eslintrc" }); + linter2 = new Linter({ configType: "eslintrc" }); + }); + + describe("rules", () => { + it("with no changes, same rules are loaded", () => { + assert.sameDeepMembers( + Array.from(linter1.getRules().keys()), + Array.from(linter2.getRules().keys()), + ); + }); + + it("loading rule in one doesn't change the other", () => { + linter1.defineRule("mock-rule", { + create: () => ({}), + }); + + assert.isTrue( + linter1.getRules().has("mock-rule"), + "mock rule is present", + ); + assert.isFalse( + linter2.getRules().has("mock-rule"), + "mock rule is not present", + ); + }); + }); + }); + + describe("options", () => { + it("rules should apply meta.defaultOptions and ignore schema defaults", () => { + linter.defineRule("my-rule", { + meta: { + defaultOptions: [ + { + inBoth: "from-default-options", + inDefaultOptions: "from-default-options", + }, + ], + schema: { + type: "object", + properties: { + inBoth: { default: "from-schema", type: "string" }, + inDefaultOptions: { type: "string" }, + inSchema: { + default: "from-schema", + type: "string", + }, + }, + additionalProperties: false, + }, + }, + create(context) { + return { + Program(node) { + context.report({ + message: JSON.stringify(context.options[0]), + node, + }); + }, + }; + }, + }); + + const config = { + rules: { + "my-rule": "error", + }, + }; + + const code = ""; + const messages = linter.verify(code, config); + + assert.deepStrictEqual(JSON.parse(messages[0].message), { + inBoth: "from-default-options", + inDefaultOptions: "from-default-options", + }); + }); + }); + + describe("processors", () => { + let receivedFilenames = []; + let receivedPhysicalFilenames = []; + + beforeEach(() => { + receivedFilenames = []; + receivedPhysicalFilenames = []; + + // A rule that always reports the AST with a message equal to the source text + linter.defineRule("report-original-text", { + create: context => ({ + Program(ast) { + assert.strictEqual( + context.getFilename(), + context.filename, + ); + assert.strictEqual( + context.getPhysicalFilename(), + context.physicalFilename, + ); + + receivedFilenames.push(context.filename); + receivedPhysicalFilenames.push( + context.physicalFilename, + ); + + context.report({ + node: ast, + message: context.sourceCode.text, + }); + }, + }), + }); + }); + + describe("preprocessors", () => { + it("should receive text and filename.", () => { + const code = "foo bar baz"; + const preprocess = sinon.spy(text => text.split(" ")); + + linter.verify(code, {}, { filename, preprocess }); + + assert.strictEqual(preprocess.calledOnce, true); + assert.deepStrictEqual(preprocess.args[0], [code, filename]); + }); + + it("should apply a preprocessor to the code, and lint each code sample separately", () => { + const code = "foo bar baz"; + const problems = linter.verify( + code, + { rules: { "report-original-text": "error" } }, + { + // Apply a preprocessor that splits the source text into spaces and lints each word individually + preprocess(input) { + return input.split(" "); + }, + }, + ); + + assert.strictEqual(problems.length, 3); + assert.deepStrictEqual( + problems.map(problem => problem.message), + ["foo", "bar", "baz"], + ); + }); + + it("should apply a preprocessor to the code even if the preprocessor returned code block objects.", () => { + const code = "foo bar baz"; + const problems = linter.verify( + code, + { rules: { "report-original-text": "error" } }, + { + filename, + + // Apply a preprocessor that splits the source text into spaces and lints each word individually + preprocess(input) { + return input.split(" ").map(text => ({ + filename: "block.js", + text, + })); + }, + }, + ); + + assert.strictEqual(problems.length, 3); + assert.deepStrictEqual( + problems.map(problem => problem.message), + ["foo", "bar", "baz"], + ); + + // filename + assert.strictEqual(receivedFilenames.length, 3); + assert( + /^filename\.js[/\\]0_block\.js/u.test(receivedFilenames[0]), + ); + assert( + /^filename\.js[/\\]1_block\.js/u.test(receivedFilenames[1]), + ); + assert( + /^filename\.js[/\\]2_block\.js/u.test(receivedFilenames[2]), + ); + + // physical filename + assert.strictEqual(receivedPhysicalFilenames.length, 3); + assert.strictEqual( + receivedPhysicalFilenames.every(name => name === filename), + true, + ); + }); + + it("should receive text even if a SourceCode object was given.", () => { + const code = "foo"; + const preprocess = sinon.spy(text => text.split(" ")); + + linter.verify(code, {}); + const sourceCode = linter.getSourceCode(); + + linter.verify(sourceCode, {}, { filename, preprocess }); + + assert.strictEqual(preprocess.calledOnce, true); + assert.deepStrictEqual(preprocess.args[0], [code, filename]); + }); + + it("should receive text even if a SourceCode object was given (with BOM).", () => { + const code = "\uFEFFfoo"; + const preprocess = sinon.spy(text => text.split(" ")); + + linter.verify(code, {}); + const sourceCode = linter.getSourceCode(); + + linter.verify(sourceCode, {}, { filename, preprocess }); + + assert.strictEqual(preprocess.calledOnce, true); + assert.deepStrictEqual(preprocess.args[0], [code, filename]); + }); + + it("should catch preprocess error.", () => { + const code = "foo"; + const preprocess = sinon.spy(() => { + throw Object.assign(new SyntaxError("Invalid syntax"), { + lineNumber: 1, + column: 1, + }); + }); + + const messages = linter.verify( + code, + {}, + { filename, preprocess }, + ); + + assert.strictEqual(preprocess.calledOnce, true); + assert.deepStrictEqual(preprocess.args[0], [code, filename]); + assert.deepStrictEqual(messages, [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Preprocessing error: Invalid syntax", + line: 1, + column: 1, + nodeType: null, + }, + ]); + }); + }); + + describe("postprocessors", () => { + it("should receive result and filename.", () => { + const code = "foo bar baz"; + const preprocess = sinon.spy(text => text.split(" ")); + const postprocess = sinon.spy(text => [text]); + + linter.verify(code, {}, { filename, postprocess, preprocess }); + + assert.strictEqual(postprocess.calledOnce, true); + assert.deepStrictEqual(postprocess.args[0], [ + [[], [], []], + filename, + ]); + }); + + it("should apply a postprocessor to the reported messages", () => { + const code = "foo bar baz"; + + const problems = linter.verify( + code, + { rules: { "report-original-text": "error" } }, + { + preprocess: input => input.split(" "), + + /* + * Apply a postprocessor that updates the locations of the reported problems + * to make sure they correspond to the locations in the original text. + */ + postprocess(problemLists) { + problemLists.forEach(problemList => + assert.strictEqual(problemList.length, 1), + ); + return problemLists.reduce( + (combinedList, problemList, index) => + combinedList.concat( + problemList.map(problem => + Object.assign({}, problem, { + message: + problem.message.toUpperCase(), + column: + problem.column + index * 4, + }), + ), + ), + [], + ); + }, + }, + ); + + assert.strictEqual(problems.length, 3); + assert.deepStrictEqual( + problems.map(problem => problem.message), + ["FOO", "BAR", "BAZ"], + ); + assert.deepStrictEqual( + problems.map(problem => problem.column), + [1, 5, 9], + ); + }); + + it("should use postprocessed problem ranges when applying autofixes", () => { + const code = "foo bar baz"; + + linter.defineRule("capitalize-identifiers", { + meta: { + fixable: "code", + }, + create(context) { + return { + Identifier(node) { + if (node.name !== node.name.toUpperCase()) { + context.report({ + node, + message: "Capitalize this identifier", + fix: fixer => + fixer.replaceText( + node, + node.name.toUpperCase(), + ), + }); + } + }, + }; + }, + }); + + const fixResult = linter.verifyAndFix( + code, + { rules: { "capitalize-identifiers": "error" } }, + { + /* + * Apply a postprocessor that updates the locations of autofixes + * to make sure they correspond to locations in the original text. + */ + preprocess: input => input.split(" "), + postprocess(problemLists) { + return problemLists.reduce( + (combinedProblems, problemList, blockIndex) => + combinedProblems.concat( + problemList.map(problem => + Object.assign(problem, { + fix: { + text: problem.fix.text, + range: problem.fix.range.map( + rangeIndex => + rangeIndex + + blockIndex * 4, + ), + }, + }), + ), + ), + [], + ); + }, + }, + ); + + assert.strictEqual(fixResult.fixed, true); + assert.strictEqual(fixResult.messages.length, 0); + assert.strictEqual(fixResult.output, "FOO BAR BAZ"); + }); + }); + }); + + describe("verifyAndFix", () => { + it("Fixes the code", () => { + const messages = linter.verifyAndFix( + "var a", + { + rules: { + semi: 2, + }, + }, + { filename: "test.js" }, + ); + + assert.strictEqual( + messages.output, + "var a;", + "Fixes were applied correctly", + ); + assert.isTrue(messages.fixed); + }); + + it("does not require a third argument", () => { + const fixResult = linter.verifyAndFix("var a", { + rules: { + semi: 2, + }, + }); + + assert.deepStrictEqual(fixResult, { + fixed: true, + messages: [], + output: "var a;", + }); + }); + + it("does not include suggestions in autofix results", () => { + const fixResult = linter.verifyAndFix("var foo = /\\#/", { + rules: { + semi: 2, + "no-useless-escape": 2, + }, + }); + + assert.strictEqual(fixResult.output, "var foo = /\\#/;"); + assert.strictEqual(fixResult.fixed, true); + assert.strictEqual( + fixResult.messages[0].suggestions.length > 0, + true, + ); + }); + + it("does not apply autofixes when fix argument is `false`", () => { + const fixResult = linter.verifyAndFix( + "var a", + { + rules: { + semi: 2, + }, + }, + { fix: false }, + ); + + assert.strictEqual(fixResult.fixed, false); + }); + + it("stops fixing after 10 passes", () => { + linter.defineRule("add-spaces", { + meta: { + fixable: "whitespace", + }, + create(context) { + return { + Program(node) { + context.report({ + node, + message: "Add a space before this node.", + fix: fixer => fixer.insertTextBefore(node, " "), + }); + }, + }; + }, + }); + + const fixResult = linter.verifyAndFix("a", { + rules: { "add-spaces": "error" }, + }); + + assert.strictEqual(fixResult.fixed, true); + assert.strictEqual(fixResult.output, `${" ".repeat(10)}a`); + assert.strictEqual(fixResult.messages.length, 1); + }); + + it("should throw an error if fix is passed but meta has no `fixable` property", () => { + linter.defineRule("test-rule", { + meta: { + docs: {}, + schema: [], + }, + create: context => ({ + Program(node) { + context.report(node, "hello world", {}, () => ({ + range: [1, 1], + text: "", + })); + }, + }), + }); + + assert.throws(() => { + linter.verify("0", { rules: { "test-rule": "error" } }); + }, /Fixable rules must set the `meta\.fixable` property to "code" or "whitespace".\nOccurred while linting :1\nRule: "test-rule"$/u); + }); + + it("should throw an error if fix is passed and there is no metadata", () => { + linter.defineRule("test-rule", { + create: context => ({ + Program(node) { + context.report(node, "hello world", {}, () => ({ + range: [1, 1], + text: "", + })); + }, + }), + }); + + assert.throws(() => { + linter.verify("0", { rules: { "test-rule": "error" } }); + }, /Fixable rules must set the `meta\.fixable` property/u); + }); + + it("should throw an error if fix is passed from a legacy-format rule", () => { + linter.defineRule("test-rule", { + create: context => ({ + Program(node) { + context.report(node, "hello world", {}, () => ({ + range: [1, 1], + text: "", + })); + }, + }), + }); + + assert.throws(() => { + linter.verify("0", { rules: { "test-rule": "error" } }); + }, /Fixable rules must set the `meta\.fixable` property/u); + }); + + describe("Circular autofixes", () => { + it("should stop fixing if a circular fix is detected", () => { + linter.defineRules({ + "add-leading-hyphen": { + meta: { + fixable: "whitespace", + }, + create(context) { + return { + Program(node) { + const sourceCode = context.sourceCode; + const hasLeadingHyphen = sourceCode + .getText(node) + .startsWith("-"); + + if (!hasLeadingHyphen) { + context.report({ + node, + message: "Add leading hyphen.", + fix(fixer) { + return fixer.insertTextBefore( + node, + "-", + ); + }, + }); + } + }, + }; + }, + }, + "remove-leading-hyphen": { + meta: { + fixable: "whitespace", + }, + create(context) { + return { + Program(node) { + const sourceCode = context.sourceCode; + const hasLeadingHyphen = sourceCode + .getText(node) + .startsWith("-"); + + if (hasLeadingHyphen) { + context.report({ + node, + message: "Remove leading hyphen.", + fix(fixer) { + return fixer.removeRange([ + 0, 1, + ]); + }, + }); + } + }, + }; + }, + }, + }); + + const initialCode = "-a"; + const fixResult = linter.verifyAndFix( + initialCode, + { + rules: { + "add-leading-hyphen": "error", + "remove-leading-hyphen": "error", + }, + }, + { + filename: "test.js", + }, + ); + + assert.strictEqual( + fixResult.fixed, + true, + "Fixing was applied.", + ); + assert.strictEqual( + fixResult.output, + "-a", + "Output should match the original input due to circular fixes.", + ); + assert.strictEqual( + fixResult.messages.length, + 1, + "There should be one remaining lint message after detecting circular fixes.", + ); + assert.strictEqual( + fixResult.messages[0].ruleId, + "remove-leading-hyphen", + ); + + // Verify the warning was emitted + assert( + warningService.emitCircularFixesWarning.calledOnceWithExactly( + "test.js", + ), + "calls `warningService.emitCircularFixesWarning()` once with the correct argument", + ); + + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + suppressedMessages.length, + 0, + "No suppressed messages should exist.", + ); + }); + }); + }); + + describe("Edge cases", () => { + it("should properly parse import statements when sourceType is module", () => { + const code = "import foo from 'foo';"; + const messages = linter.verify(code, { + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should properly parse import all statements when sourceType is module", () => { + const code = "import * as foo from 'foo';"; + const messages = linter.verify(code, { + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should properly parse default export statements when sourceType is module", () => { + const code = "export default function initialize() {}"; + const messages = linter.verify(code, { + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/9687 + it("should report an error when invalid parserOptions found", () => { + let messages = linter.verify("", { + parserOptions: { ecmaVersion: 222 }, + }); + let suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages.length, 1); + assert.ok(messages[0].message.includes("Invalid ecmaVersion")); + assert.strictEqual(suppressedMessages.length, 0); + + messages = linter.verify("", { + parserOptions: { sourceType: "foo" }, + }); + suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages.length, 1); + assert.ok(messages[0].message.includes("Invalid sourceType")); + assert.strictEqual(suppressedMessages.length, 0); + + messages = linter.verify("", { + parserOptions: { ecmaVersion: 5, sourceType: "module" }, + }); + suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages.length, 1); + assert.ok( + messages[0].message.includes( + "sourceType 'module' is not supported when ecmaVersion < 2015", + ), + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not crash when invalid parentheses syntax is encountered", () => { + linter.verify("left = (aSize.width/2) - ()"); + }); + + it("should not crash when let is used inside of switch case", () => { + linter.verify("switch(foo) { case 1: let bar=2; }", { + parserOptions: { ecmaVersion: 6 }, + }); + }); + + it("should not crash when parsing destructured assignment", () => { + linter.verify("var { a='a' } = {};", { + parserOptions: { ecmaVersion: 6 }, + }); + }); + + it("should report syntax error when a keyword exists in object property shorthand", () => { + const messages = linter.verify("let a = {this}", { + parserOptions: { ecmaVersion: 6 }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].fatal, true); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not rewrite env setting in core (https://github.com/eslint/eslint/issues/4814)", () => { + /* + * This test focuses on the instance of https://github.com/eslint/eslint/blob/v2.0.0-alpha-2/conf/environments.js#L26-L28 + * This `verify()` takes the instance and runs https://github.com/eslint/eslint/blob/v2.0.0-alpha-2/lib/eslint.js#L416 + */ + linter.defineRule("test", { + create: () => ({}), + }); + linter.verify("var a = 0;", { + env: { node: true }, + parserOptions: { ecmaVersion: 6, sourceType: "module" }, + rules: { test: 2 }, + }); + + // This `verify()` takes the instance and tests that the instance was not modified. + let ok = false; + + linter.defineRule("test", { + create(context) { + assert( + context.parserOptions.ecmaFeatures.globalReturn, + "`ecmaFeatures.globalReturn` of the node environment should not be modified.", + ); + ok = true; + return {}; + }, + }); + linter.verify("var a = 0;", { + env: { node: true }, + rules: { test: 2 }, + }); + + assert(ok); + }); + + it("should throw when rule's create() function does not return an object", () => { + const config = { rules: { checker: "error" } }; + + linter.defineRule("checker", { + create: () => null, + }); // returns null + + assert.throws(() => { + linter.verify("abc", config, filename); + }, "The create() function for rule 'checker' did not return an object."); + + linter.defineRule("checker", { + create() {}, + }); // returns undefined + + assert.throws(() => { + linter.verify("abc", config, filename); + }, "The create() function for rule 'checker' did not return an object."); + }); + }); + + describe("Custom parser", () => { + const errorPrefix = "Parsing error: "; + + it("should have file path passed to it", () => { + const code = "/* this is code */"; + const parseSpy = { parse: sinon.spy(espree.parse) }; + + linter.defineParser("stub-parser", parseSpy); + linter.verify(code, { parser: "stub-parser" }, filename); + + sinon.assert.calledWithMatch(parseSpy.parse, "", { + filePath: filename, + }); + }); + + it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => { + const code = "var myDivElement =
;"; + + linter.defineParser("esprima", esprima); + const messages = linter.verify( + code, + { parser: "esprima", parserOptions: { jsx: true } }, + "filename", + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should return an error when the custom parser can't be found", () => { + const code = "var myDivElement =
;"; + const messages = linter.verify( + code, + { parser: "esprima-xyz" }, + "filename", + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + "Configured parser 'esprima-xyz' was not found.", + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not throw or report errors when the custom parser returns unrecognized operators (https://github.com/eslint/eslint/issues/10475)", () => { + const code = "null %% 'foo'"; + + linter.defineParser( + "unknown-logical-operator", + testParsers.unknownLogicalOperator, + ); + + // This shouldn't throw + const messages = linter.verify( + code, + { parser: "unknown-logical-operator" }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not throw or report errors when the custom parser returns nested unrecognized operators (https://github.com/eslint/eslint/issues/10560)", () => { + const code = "foo && bar %% baz"; + + linter.defineParser( + "unknown-logical-operator-nested", + testParsers.unknownLogicalOperatorNested, + ); + + // This shouldn't throw + const messages = linter.verify( + code, + { parser: "unknown-logical-operator-nested" }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not throw or return errors when the custom parser returns unknown AST nodes", () => { + const code = "foo && bar %% baz"; + + const nodes = []; + + linter.defineRule("collect-node-types", { + create: () => ({ + "*"(node) { + nodes.push(node.type); + }, + }), + }); + + linter.defineParser("non-js-parser", testParsers.nonJSParser); + + const messages = linter.verify( + code, + { + parser: "non-js-parser", + rules: { + "collect-node-types": "error", + }, + }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.isTrue(nodes.length > 0); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should strip leading line: prefix from parser error", () => { + linter.defineParser("line-error", testParsers.lineError); + const messages = linter.verify( + ";", + { parser: "line-error" }, + "filename", + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + errorPrefix + testParsers.lineError.expectedError, + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not modify a parser error message without a leading line: prefix", () => { + linter.defineParser("no-line-error", testParsers.noLineError); + const messages = linter.verify( + ";", + { parser: "no-line-error" }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + errorPrefix + testParsers.noLineError.expectedError, + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + describe("if a parser provides 'visitorKeys'", () => { + let types = []; + let sourceCode; + let scopeManager; + let firstChildNodes = []; + + beforeEach(() => { + types = []; + firstChildNodes = []; + linter.defineRule("collect-node-types", { + create: () => ({ + "*"(node) { + types.push(node.type); + }, + }), + }); + linter.defineRule("save-scope-manager", { + create(context) { + scopeManager = context.sourceCode.scopeManager; + + return {}; + }, + }); + linter.defineRule("esquery-option", { + create: () => ({ + ":first-child"(node) { + firstChildNodes.push(node); + }, + }), + }); + linter.defineParser( + "enhanced-parser2", + testParsers.enhancedParser2, + ); + linter.verify("@foo class A {}", { + parser: "enhanced-parser2", + rules: { + "collect-node-types": "error", + "save-scope-manager": "error", + "esquery-option": "error", + }, + }); + + sourceCode = linter.getSourceCode(); + }); + + it("Traverser should use the visitorKeys (so 'types' includes 'Decorator')", () => { + assert.deepStrictEqual(types, [ + "Program", + "ClassDeclaration", + "Decorator", + "Identifier", + "Identifier", + "ClassBody", + ]); + }); + + it("eslint-scope should use the visitorKeys (so 'childVisitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => { + assert.deepStrictEqual( + scopeManager.__options.childVisitorKeys.ClassDeclaration, // eslint-disable-line no-underscore-dangle -- ScopeManager API + ["experimentalDecorators", "id", "superClass", "body"], + ); + }); + + it("should use the same visitorKeys if the source code object is reused", () => { + const types2 = []; + + linter.defineRule("collect-node-types", { + create: () => ({ + "*"(node) { + types2.push(node.type); + }, + }), + }); + linter.verify(sourceCode, { + rules: { + "collect-node-types": "error", + }, + }); + + assert.deepStrictEqual(types2, [ + "Program", + "ClassDeclaration", + "Decorator", + "Identifier", + "Identifier", + "ClassBody", + ]); + }); + + it("esquery should use the visitorKeys (so 'visitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => { + assert.deepStrictEqual(firstChildNodes, [ + sourceCode.ast.body[0], + sourceCode.ast.body[0].experimentalDecorators[0], + ]); + }); + }); + + describe("if a parser provides 'scope'", () => { + let scope = null; + let sourceCode = null; + + beforeEach(() => { + linter.defineParser( + "enhanced-parser3", + testParsers.enhancedParser3, + ); + linter.defineRule("save-scope1", { + create: context => ({ + Program(node) { + scope = context.sourceCode.getScope(node); + }, + }), + }); + linter.verify("@foo class A {}", { + parser: "enhanced-parser3", + rules: { "save-scope1": 2 }, + }); + + sourceCode = linter.getSourceCode(); + }); + + it("should use the scope (so the global scope has the reference of '@foo')", () => { + assert.strictEqual(scope.references.length, 1); + assert.deepStrictEqual( + scope.references[0].identifier.name, + "foo", + ); + }); + + it("should use the same scope if the source code object is reused", () => { + let scope2 = null; + + linter.defineRule("save-scope2", { + create: context => ({ + Program(node) { + scope2 = context.sourceCode.getScope(node); + }, + }), + }); + linter.verify( + sourceCode, + { rules: { "save-scope2": 2 } }, + "test.js", + ); + + assert(scope2 !== null); + assert(scope2 === scope); + }); + }); + + it("should not pass any default parserOptions to the parser", () => { + linter.defineParser( + "throws-with-options", + testParsers.throwsWithOptions, + ); + const messages = linter.verify( + ";", + { parser: "throws-with-options" }, + "filename", + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("merging 'parserOptions'", () => { + it("should deeply merge 'parserOptions' from an environment with 'parserOptions' from the provided config", () => { + const code = "return
"; + const config = { + env: { + node: true, // ecmaFeatures: { globalReturn: true } + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }; + + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + // no parsing errors + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); }); describe("Linter with FlatConfigArray", () => { - - let linter; - const filename = "filename.js"; - - /** - * Creates a config array with some default properties. - * @param {FlatConfig|FlatConfig[]} value The value to base the - * config array on. - * @returns {FlatConfigArray} The created config array. - */ - function createFlatConfigArray(value) { - return new FlatConfigArray(value, { basePath: "" }); - } - - beforeEach(() => { - linter = new Linter({ configType: "flat" }); - }); - - describe("Static Members", () => { - describe("version", () => { - it("should return same version as instance property", () => { - assert.strictEqual(Linter.version, linter.version); - }); - }); - }); - - describe("Config Options", () => { - - describe("languageOptions", () => { - - describe("ecmaVersion", () => { - - it("should error when accessing a global that isn't available in ecmaVersion 5", () => { - const messages = linter.verify("new Map()", { - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - }, - rules: { - "no-undef": "error" - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1, "There should be one linting error."); - assert.strictEqual(messages[0].ruleId, "no-undef", "The linting error should be no-undef."); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should error when accessing a global that isn't available in ecmaVersion 3", () => { - const messages = linter.verify("JSON.stringify({})", { - languageOptions: { - ecmaVersion: 3, - sourceType: "script" - }, - rules: { - "no-undef": "error" - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1, "There should be one linting error."); - assert.strictEqual(messages[0].ruleId, "no-undef", "The linting error should be no-undef."); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should add globals for ES6 when ecmaVersion is 6", () => { - const messages = linter.verify("new Map()", { - languageOptions: { - ecmaVersion: 6 - }, - rules: { - "no-undef": "error" - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0, "There should be no linting errors."); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should allow destructuring when ecmaVersion is 6", () => { - const messages = linter.verify("let {a} = b", { - languageOptions: { - ecmaVersion: 6 - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0, "There should be no linting errors."); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("ecmaVersion should be normalized to year name for ES 6", () => { - const config = { - plugins: { - test: { - rules: { - checker: { - create: context => ({ - Program() { - assert.strictEqual(context.languageOptions.ecmaVersion, 2015); - } - }) - } - } - } - }, - languageOptions: { - ecmaVersion: 6 - }, - rules: { "test/checker": "error" } - }; - - linter.verify("foo", config, filename); - }); - - it("ecmaVersion should be normalized to latest year by default", () => { - const config = { - plugins: { - test: { - rules: { - checker: { - create: context => ({ - Program() { - assert.strictEqual(context.languageOptions.ecmaVersion, espree.latestEcmaVersion + 2009); - } - }) - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify("foo", config, filename); - }); - - it("ecmaVersion should not be normalized to year name for ES 5", () => { - const config = { - plugins: { - test: { - rules: { - checker: { - create: context => ({ - Program() { - assert.strictEqual(context.languageOptions.ecmaVersion, 5); - } - }) - } - } - } - }, - languageOptions: { - ecmaVersion: 5 - }, - rules: { "test/checker": "error" } - }; - - linter.verify("foo", config, filename); - }); - - it("ecmaVersion should be normalized to year name for 'latest'", () => { - const config = { - plugins: { - test: { - rules: { - checker: { - create: context => ({ - Program() { - assert.strictEqual(context.languageOptions.ecmaVersion, espree.latestEcmaVersion + 2009); - } - }) - } - } - } - }, - languageOptions: { - ecmaVersion: "latest" - }, - rules: { "test/checker": "error" } - }; - - linter.verify("foo", config, filename); - }); - - - }); - - describe("sourceType", () => { - - it("should be module by default", () => { - const config = { - plugins: { - test: { - rules: { - checker: { - create: context => ({ - Program() { - assert.strictEqual(context.languageOptions.sourceType, "module"); - } - }) - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify("import foo from 'bar'", config, filename); - }); - - it("should default to commonjs when passed a .cjs filename", () => { - const config = { - plugins: { - test: { - rules: { - checker: { - create: context => ({ - Program() { - assert.strictEqual(context.languageOptions.sourceType, "commonjs"); - } - }) - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify("import foo from 'bar'", config, `${filename}.cjs`); - }); - - - it("should error when import is used in a script", () => { - const messages = linter.verify("import foo from 'bar';", { - languageOptions: { - ecmaVersion: 6, - sourceType: "script" - } - }); - - assert.strictEqual(messages.length, 1, "There should be one parsing error."); - assert.strictEqual(messages[0].message, "Parsing error: 'import' and 'export' may appear only with 'sourceType: module'"); - }); - - it("should not error when import is used in a module", () => { - const messages = linter.verify("import foo from 'bar';", { - languageOptions: { - ecmaVersion: 6, - sourceType: "module" - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0, "There should no linting errors."); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should error when return is used at the top-level outside of commonjs", () => { - const messages = linter.verify("return", { - languageOptions: { - ecmaVersion: 6, - sourceType: "script" - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1, "There should be one parsing error."); - assert.strictEqual(messages[0].message, "Parsing error: 'return' outside of function"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not error when top-level return is used in commonjs", () => { - const messages = linter.verify("return", { - languageOptions: { - ecmaVersion: 6, - sourceType: "commonjs" - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0, "There should no linting errors."); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should error when accessing a Node.js global outside of commonjs", () => { - const messages = linter.verify("require()", { - languageOptions: { - ecmaVersion: 6 - }, - rules: { - "no-undef": "error" - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1, "There should be one linting error."); - assert.strictEqual(messages[0].ruleId, "no-undef", "The linting error should be no-undef."); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should add globals for Node.js when sourceType is commonjs", () => { - const messages = linter.verify("require()", { - languageOptions: { - ecmaVersion: 6, - sourceType: "commonjs" - }, - rules: { - "no-undef": "error" - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0, "There should be no linting errors."); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should allow 'await' as a property name in modules", () => { - const result = linter.verify( - "obj.await", - { - languageOptions: { - ecmaVersion: 6, - sourceType: "module" - } - } - ); - const suppressedMessages = linter.getSuppressedMessages(); - - assert(result.length === 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - }); - - describe("parser", () => { - - it("should be able to define a custom parser", () => { - const parser = { - parseForESLint: function parse(code, options) { - return { - ast: esprima.parse(code, options), - services: { - test: { - getMessage() { - return "Hi!"; - } - } - } - }; - } - }; - - const config = { - languageOptions: { - parser - } - }; - - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should pass parser as context.languageOptions.parser to all rules when provided on config", () => { - - const config = { - plugins: { - test: { - rules: { - "test-rule": { - create: sinon.mock().withArgs( - sinon.match({ languageOptions: { parser: esprima } }) - ).returns({}) - } - } - } - }, - languageOptions: { - parser: esprima - }, - rules: { - "test/test-rule": 2 - } - }; - - linter.verify("0", config, filename); - }); - - it("should use parseForESLint() in custom parser when custom parser is specified", () => { - const config = { - languageOptions: { - parser: testParsers.enhancedParser - } - }; - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should expose parser services when using parseForESLint() and services are specified", () => { - - const config = { - plugins: { - test: { - rules: { - "test-service-rule": { - create: context => ({ - Literal(node) { - assert.strictEqual(context.parserServices, context.sourceCode.parserServices); - context.report({ - node, - message: context.sourceCode.parserServices.test.getMessage() - }); - } - }) - } - } - } - }, - languageOptions: { - parser: testParsers.enhancedParser - }, - rules: { - "test/test-service-rule": 2 - } - }; - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "Hi!"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should use the same parserServices if source code object is reused", () => { - - const config = { - plugins: { - test: { - rules: { - "test-service-rule": { - create: context => ({ - Literal(node) { - assert.strictEqual(context.parserServices, context.sourceCode.parserServices); - context.report({ - node, - message: context.sourceCode.parserServices.test.getMessage() - }); - } - }) - } - } - } - }, - languageOptions: { - parser: testParsers.enhancedParser - }, - rules: { - "test/test-service-rule": 2 - } - }; - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "Hi!"); - assert.strictEqual(suppressedMessages.length, 0); - - const messages2 = linter.verify(linter.getSourceCode(), config, filename); - const suppressedMessages2 = linter.getSuppressedMessages(); - - assert.strictEqual(messages2.length, 1); - assert.strictEqual(messages2[0].message, "Hi!"); - assert.strictEqual(suppressedMessages2.length, 0); - }); - - it("should pass parser as context.languageOptions.parser to all rules when default parser is used", () => { - - // references to Espree get messed up in a browser context, so wrap it - const fakeParser = { - parse: espree.parse - }; - - const spy = sinon.spy(context => { - assert.strictEqual(context.languageOptions.parser, fakeParser); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - "test-rule": { create: spy } - } - } - }, - languageOptions: { - parser: fakeParser - }, - rules: { - "test/test-rule": 2 - } - }; - - linter.verify("0", config, filename); - assert.isTrue(spy.calledOnce); - }); - - - describe("Custom Parsers", () => { - - const errorPrefix = "Parsing error: "; - - it("should have file path passed to it", () => { - const code = "/* this is code */"; - const parseSpy = { parse: sinon.spy() }; - const config = { - languageOptions: { - parser: parseSpy - } - }; - - linter.verify(code, config, filename, true); - - sinon.assert.calledWithMatch(parseSpy.parse, "", { filePath: filename }); - }); - - it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => { - const code = "var myDivElement =
;"; - const config = { - languageOptions: { - parser: esprima, - parserOptions: { - jsx: true - } - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not throw or report errors when the custom parser returns unrecognized operators (https://github.com/eslint/eslint/issues/10475)", () => { - const code = "null %% 'foo'"; - const config = { - languageOptions: { - parser: testParsers.unknownLogicalOperator - } - }; - - // This shouldn't throw - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not throw or report errors when the custom parser returns nested unrecognized operators (https://github.com/eslint/eslint/issues/10560)", () => { - const code = "foo && bar %% baz"; - const config = { - languageOptions: { - parser: testParsers.unknownLogicalOperatorNested - } - }; - - // This shouldn't throw - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not throw or return errors when the custom parser returns unknown AST nodes", () => { - const code = "foo && bar %% baz"; - const nodes = []; - const config = { - plugins: { - test: { - rules: { - "collect-node-types": { - create: () => ({ - "*"(node) { - nodes.push(node.type); - } - }) - } - } - } - }, - languageOptions: { - parser: testParsers.nonJSParser - }, - rules: { - "test/collect-node-types": "error" - } - }; - - const messages = linter.verify(code, config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.isTrue(nodes.length > 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should strip leading line: prefix from parser error", () => { - const messages = linter.verify(";", { - languageOptions: { - parser: testParsers.lineError - } - }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, errorPrefix + testParsers.lineError.expectedError); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not modify a parser error message without a leading line: prefix", () => { - const messages = linter.verify(";", { - languageOptions: { - parser: testParsers.noLineError - } - }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.strictEqual(messages[0].message, errorPrefix + testParsers.noLineError.expectedError); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - describe("if a parser provides 'visitorKeys'", () => { - let types = []; - let sourceCode; - let scopeManager; - let firstChildNodes = []; - - beforeEach(() => { - types = []; - firstChildNodes = []; - const config = { - plugins: { - test: { - rules: { - "collect-node-types": { - create: () => ({ - "*"(node) { - types.push(node.type); - } - }) - }, - "save-scope-manager": { - create(context) { - scopeManager = context.sourceCode.scopeManager; - - return {}; - } - }, - "esquery-option": { - create: () => ({ - ":first-child"(node) { - firstChildNodes.push(node); - } - }) - } - } - } - }, - languageOptions: { - parser: testParsers.enhancedParser2 - }, - rules: { - "test/collect-node-types": "error", - "test/save-scope-manager": "error", - "test/esquery-option": "error" - } - }; - - linter.verify("@foo class A {}", config); - - sourceCode = linter.getSourceCode(); - }); - - it("Traverser should use the visitorKeys (so 'types' includes 'Decorator')", () => { - assert.deepStrictEqual( - types, - ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"] - ); - }); - - it("eslint-scope should use the visitorKeys (so 'childVisitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => { - assert.deepStrictEqual( - scopeManager.__options.childVisitorKeys.ClassDeclaration, // eslint-disable-line no-underscore-dangle -- ScopeManager API - ["experimentalDecorators", "id", "superClass", "body"] - ); - }); - - it("should use the same visitorKeys if the source code object is reused", () => { - const types2 = []; - const config = { - plugins: { - test: { - rules: { - "collect-node-types": { - create: () => ({ - "*"(node) { - types2.push(node.type); - } - }) - } - } - } - }, - rules: { - "test/collect-node-types": "error" - } - }; - - linter.verify(sourceCode, config); - - assert.deepStrictEqual( - types2, - ["Program", "ClassDeclaration", "Decorator", "Identifier", "Identifier", "ClassBody"] - ); - }); - - it("esquery should use the visitorKeys (so 'visitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => { - assert.deepStrictEqual( - firstChildNodes, - [sourceCode.ast.body[0], sourceCode.ast.body[0].experimentalDecorators[0]] - ); - }); - }); - - describe("if a parser provides 'scope'", () => { - let scope = null; - let sourceCode = null; - - beforeEach(() => { - const config = { - plugins: { - test: { - rules: { - "save-scope1": { - create: context => ({ - Program() { - scope = context.getScope(); - } - }) - } - } - } - }, - languageOptions: { - parser: testParsers.enhancedParser3 - }, - rules: { - "test/save-scope1": "error" - } - }; - - linter.verify("@foo class A {}", config); - - sourceCode = linter.getSourceCode(); - }); - - it("should use the scope (so the global scope has the reference of '@foo')", () => { - assert.strictEqual(scope.references.length, 1); - assert.deepStrictEqual( - scope.references[0].identifier.name, - "foo" - ); - }); - - it("should use the same scope if the source code object is reused", () => { - let scope2 = null; - const config = { - plugins: { - test: { - rules: { - "save-scope2": { - create: context => ({ - Program() { - scope2 = context.getScope(); - } - }) - } - } - } - }, - rules: { - "test/save-scope2": "error" - } - }; - - linter.verify(sourceCode, config, "test.js"); - - assert(scope2 !== null); - assert(scope2 === scope); - }); - }); - - it("should pass default languageOptions to the parser", () => { - - const spy = sinon.spy((code, options) => espree.parse(code, options)); - - linter.verify(";", { - languageOptions: { - parser: { - parse: spy - } - } - }, "filename.js"); - - assert(spy.calledWithMatch(";", { - ecmaVersion: espree.latestEcmaVersion + 2009, - sourceType: "module" - })); - }); - }); - - - }); - - describe("parseOptions", () => { - - it("should pass ecmaFeatures to all rules when provided on config", () => { - - const parserOptions = { - ecmaFeatures: { - jsx: true - } - }; - - const config = { - plugins: { - test: { - rules: { - "test-rule": { - create: sinon.mock().withArgs( - sinon.match({ languageOptions: { parserOptions } }) - ).returns({}) - } - } - } - }, - languageOptions: { - parserOptions - }, - rules: { - "test/test-rule": 2 - } - }; - - linter.verify("0", config, filename); - }); - - it("should switch globalReturn to false if sourceType is module", () => { - - const config = { - plugins: { - test: { - rules: { - "test-rule": { - create: sinon.mock().withArgs( - sinon.match({ - languageOptions: { - parserOptions: { - ecmaFeatures: { - globalReturn: false - } - } - } - }) - ).returns({}) - } - } - } - }, - languageOptions: { - sourceType: "module", - parserOptions: { - ecmaFeatures: { - globalReturn: true - } - } - }, - rules: { - "test/test-rule": 2 - } - }; - - linter.verify("0", config, filename); - }); - - it("should not parse sloppy-mode code when impliedStrict is true", () => { - - const messages = linter.verify("var private;", { - languageOptions: { - parserOptions: { - ecmaFeatures: { - impliedStrict: true - } - } - } - }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "Parsing error: The keyword 'private' is reserved"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should properly parse valid code when impliedStrict is true", () => { - - const messages = linter.verify("var foo;", { - languageOptions: { - parserOptions: { - ecmaFeatures: { - impliedStrict: true - } - } - } - }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should properly parse JSX when passed ecmaFeatures", () => { - - const messages = linter.verify("var x =
;", { - languageOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true - } - } - } - }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report an error when JSX code is encountered and JSX is not enabled", () => { - const code = "var myDivElement =
;"; - const messages = linter.verify(code, {}, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].line, 1); - assert.strictEqual(messages[0].column, 20); - assert.strictEqual(messages[0].message, "Parsing error: Unexpected token <"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report an error when JSX code is encountered and JSX is enabled", () => { - const code = "var myDivElement =
;"; - const messages = linter.verify(code, { - languageOptions: { - parserOptions: { - ecmaFeatures: { - jsx: true - } - } - } - }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => { - const code = "var myDivElement =
;"; - const messages = linter.verify(code, { - languageOptions: { - ecmaVersion: 6, - parserOptions: { - ecmaFeatures: { - jsx: true - } - } - } - - }, "filename.js"); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not allow the use of reserved words as variable names in ES3", () => { - const code = "var char;"; - const messages = linter.verify(code, { - languageOptions: { - ecmaVersion: 3, - sourceType: "script" - } - }, filename); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.isTrue(messages[0].fatal); - assert.match(messages[0].message, /^Parsing error:.*'char'/u); - }); - - it("should not allow the use of reserved words as property names in member expressions in ES3", () => { - const code = "obj.char;"; - const messages = linter.verify(code, { - languageOptions: { - ecmaVersion: 3, - sourceType: "script" - } - }, filename); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.isTrue(messages[0].fatal); - assert.match(messages[0].message, /^Parsing error:.*'char'/u); - }); - - it("should not allow the use of reserved words as property names in object literals in ES3", () => { - const code = "var obj = { char: 1 };"; - const messages = linter.verify(code, { - languageOptions: { - ecmaVersion: 3, - sourceType: "script" - } - }, filename); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.isTrue(messages[0].fatal); - assert.match(messages[0].message, /^Parsing error:.*'char'/u); - }); - - it("should allow the use of reserved words as variable and property names in ES3 when allowReserved is true", () => { - const code = "var char; obj.char; var obj = { char: 1 };"; - const messages = linter.verify(code, { - languageOptions: { - ecmaVersion: 3, - sourceType: "script", - parserOptions: { - allowReserved: true - } - } - }, filename); - - assert.strictEqual(messages.length, 0); - }); - - it("should not allow the use of reserved words as variable names in ES > 3", () => { - const ecmaVersions = [void 0, ...espree.supportedEcmaVersions.filter(ecmaVersion => ecmaVersion > 3)]; - - ecmaVersions.forEach(ecmaVersion => { - const code = "var enum;"; - const messages = linter.verify(code, { - languageOptions: { - ...(ecmaVersion ? { ecmaVersion } : {}), - sourceType: "script" - } - }, filename); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.isTrue(messages[0].fatal); - assert.match(messages[0].message, /^Parsing error:.*'enum'/u); - }); - }); - - it("should allow the use of reserved words as property names in ES > 3", () => { - const ecmaVersions = [void 0, ...espree.supportedEcmaVersions.filter(ecmaVersion => ecmaVersion > 3)]; - - ecmaVersions.forEach(ecmaVersion => { - const code = "obj.enum; obj.function; var obj = { enum: 1, function: 2 };"; - const messages = linter.verify(code, { - languageOptions: { - ...(ecmaVersion ? { ecmaVersion } : {}), - sourceType: "script" - } - }, filename); - - assert.strictEqual(messages.length, 0); - }); - }); - - it("should not allow `allowReserved: true` in ES > 3", () => { - const ecmaVersions = [void 0, ...espree.supportedEcmaVersions.filter(ecmaVersion => ecmaVersion > 3)]; - - ecmaVersions.forEach(ecmaVersion => { - const code = ""; - const messages = linter.verify(code, { - languageOptions: { - ...(ecmaVersion ? { ecmaVersion } : {}), - sourceType: "script", - parserOptions: { - allowReserved: true - } - } - }, filename); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.isTrue(messages[0].fatal); - assert.match(messages[0].message, /^Parsing error:.*allowReserved/u); - }); - }); - }); - }); - - describe("settings", () => { - const ruleId = "test-rule"; - - it("should pass settings to all rules", () => { - - const config = { - plugins: { - test: { - rules: { - [ruleId]: { - create: context => ({ - Literal(node) { - context.report(node, context.settings.info); - } - }) - } - } - } - }, - settings: { - info: "Hello" - }, - rules: { - [`test/${ruleId}`]: 1 - } - }; - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].message, "Hello"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not have any settings if they were not passed in", () => { - - const config = { - plugins: { - test: { - rules: { - [ruleId]: { - create: context => ({ - Literal(node) { - if (Object.getOwnPropertyNames(context.settings).length !== 0) { - context.report(node, "Settings should be empty"); - } - } - }) - } - } - } - }, - settings: { - }, - rules: { - [`test/${ruleId}`]: 1 - } - }; - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("rules", () => { - const code = "var answer = 6 * 7"; - - it("should be configurable by only setting the integer value", () => { - const rule = "semi", - config = { rules: {} }; - - config.rules[rule] = 1; - - const messages = linter.verify(code, config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, rule); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should be configurable by only setting the string value", () => { - const rule = "semi", - config = { rules: {} }; - - config.rules[rule] = "warn"; - - const messages = linter.verify(code, config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); - assert.strictEqual(messages[0].ruleId, rule); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should be configurable by passing in values as an array", () => { - const rule = "semi", - config = { rules: {} }; - - config.rules[rule] = [1]; - - const messages = linter.verify(code, config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, rule); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should be configurable by passing in string value as an array", () => { - const rule = "semi", - config = { rules: {} }; - - config.rules[rule] = ["warn"]; - - const messages = linter.verify(code, config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 1); - assert.strictEqual(messages[0].ruleId, rule); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not be configurable by setting other value", () => { - const rule = "semi", - config = { rules: {} }; - - config.rules[rule] = "1"; - - assert.throws(() => { - linter.verify(code, config, filename, true); - }, /Key "rules": Key "semi": Expected severity/u); - }); - - it("should process empty config", () => { - const config = {}; - const messages = linter.verify(code, config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - }); - - describe("verify()", () => { - - it("should report warnings in order by line and column when called", () => { - - const code = "foo()\n alert('test')"; - const config = { rules: { "no-mixed-spaces-and-tabs": 1, "eol-last": 1, semi: [1, "always"] } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 3); - assert.strictEqual(messages[0].line, 1); - assert.strictEqual(messages[0].column, 6); - assert.strictEqual(messages[1].line, 2); - assert.strictEqual(messages[1].column, 18); - assert.strictEqual(messages[2].line, 2); - assert.strictEqual(messages[2].column, 18); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report ignored file when filename isn't matched in the config array", () => { - - const code = "foo()\n alert('test')"; - const config = { rules: { "no-mixed-spaces-and-tabs": 1, "eol-last": 1, semi: [1, "always"] } }; - - const messages = linter.verify(code, config, "filename.ts"); - - assert.strictEqual(messages.length, 1); - assert.deepStrictEqual(messages[0], { - ruleId: null, - severity: 1, - message: "No matching configuration found for filename.ts.", - line: 0, - column: 0, - nodeType: null - }); - }); - - // https://github.com/eslint/eslint/issues/17669 - it("should use `cwd` constructor option as config `basePath` when config is not an instance of FlatConfigArray", () => { - const rule = { - create(context) { - return { - Program(node) { - context.report({ node, message: "Bad program." }); - } - }; - } - }; - - const code = "foo"; - const config = [ - { - plugins: { - test: { - rules: { - "test-rule-1": rule, - "test-rule-2": rule, - "test-rule-3": rule - } - } - } - }, - { - rules: { - "test/test-rule-1": 2 - } - }, - { - files: ["**/*.ts"], - rules: { - "test/test-rule-2": 2 - } - }, - { - files: ["bar/file.ts"], - rules: { - "test/test-rule-3": 2 - } - } - ]; - - const linterWithOptions = new Linter({ - configType: "flat", - cwd: "/foo" - }); - - let messages; - - messages = linterWithOptions.verify(code, config, "/file.js"); - assert.strictEqual(messages.length, 1); - assert.deepStrictEqual(messages[0], { - ruleId: null, - severity: 1, - message: "No matching configuration found for /file.js.", - line: 0, - column: 0, - nodeType: null - }); - - messages = linterWithOptions.verify(code, config, "/file.ts"); - assert.strictEqual(messages.length, 1); - assert.deepStrictEqual(messages[0], { - ruleId: null, - severity: 1, - message: "No matching configuration found for /file.ts.", - line: 0, - column: 0, - nodeType: null - }); - - messages = linterWithOptions.verify(code, config, "/bar/foo/file.js"); - assert.strictEqual(messages.length, 1); - assert.deepStrictEqual(messages[0], { - ruleId: null, - severity: 1, - message: "No matching configuration found for /bar/foo/file.js.", - line: 0, - column: 0, - nodeType: null - }); - - messages = linterWithOptions.verify(code, config, "/bar/foo/file.ts"); - assert.strictEqual(messages.length, 1); - assert.deepStrictEqual(messages[0], { - ruleId: null, - severity: 1, - message: "No matching configuration found for /bar/foo/file.ts.", - line: 0, - column: 0, - nodeType: null - }); - - messages = linterWithOptions.verify(code, config, "/foo/file.js"); - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "test/test-rule-1"); - - messages = linterWithOptions.verify(code, config, "/foo/file.ts"); - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "test/test-rule-1"); - assert.strictEqual(messages[1].ruleId, "test/test-rule-2"); - - messages = linterWithOptions.verify(code, config, "/foo/bar/file.ts"); - assert.strictEqual(messages.length, 3); - assert.strictEqual(messages[0].ruleId, "test/test-rule-1"); - assert.strictEqual(messages[1].ruleId, "test/test-rule-2"); - assert.strictEqual(messages[2].ruleId, "test/test-rule-3"); - }); - - describe("Plugins", () => { - - it("should not load rule definition when rule isn't used", () => { - - const spy = sinon.spy(); - - const config = { - plugins: { - test: { - rules: { - checker: { create: spy } - } - } - } - }; - - linter.verify("code", config, filename); - assert.isTrue(spy.notCalled, "Rule should not have been called"); - }); - }); - - describe("Rule Internals", () => { - - const code = TEST_CODE; - - it("should throw an error when an error occurs inside of a rule visitor", () => { - const config = { - plugins: { - test: { - rules: { - checker: { - create: () => ({ - Program() { - throw new Error("Intentional error."); - } - - }) - } - } - } - }, - rules: { "test/checker": "error" } - }; - - assert.throws(() => { - linter.verify(code, config, filename); - }, `Intentional error.\nOccurred while linting ${filename}:1\nRule: "test/checker"`); - }); - - it("should not call rule visitor with a `this` value", () => { - const spy = sinon.spy(); - const config = { - plugins: { - test: { - rules: { - checker: { - create: () => ({ - Program: spy - }) - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify("foo", config); - assert(spy.calledOnce); - assert.strictEqual(spy.firstCall.thisValue, void 0); - }); - - it("should not call unrecognized rule visitor when present in a rule", () => { - const spy = sinon.spy(); - const config = { - plugins: { - test: { - rules: { - checker: { - create: () => ({ - newListener: spy - }) - } - } - } - }, - rules: { - "test/checker": "error", - "no-undef": "error" - } - }; - - linter.verify("foo", config); - assert(spy.notCalled); - }); - - it("should have all the `parent` properties on nodes when the rule visitors are created", () => { - const spy = sinon.spy(context => { - assert.strictEqual(context.getSourceCode(), context.sourceCode); - const ast = context.sourceCode.ast; - - assert.strictEqual(ast.body[0].parent, ast); - assert.strictEqual(ast.body[0].expression.parent, ast.body[0]); - assert.strictEqual(ast.body[0].expression.left.parent, ast.body[0].expression); - assert.strictEqual(ast.body[0].expression.right.parent, ast.body[0].expression); - - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: spy } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify("foo + bar", config); - assert(spy.calledOnce); - }); - - it("events for each node type should fire", () => { - - // spies for various AST node types - const spyLiteral = sinon.spy(), - spyVariableDeclarator = sinon.spy(), - spyVariableDeclaration = sinon.spy(), - spyIdentifier = sinon.spy(), - spyBinaryExpression = sinon.spy(); - - const config = { - plugins: { - test: { - rules: { - checker: { - create() { - return { - Literal: spyLiteral, - VariableDeclarator: spyVariableDeclarator, - VariableDeclaration: spyVariableDeclaration, - Identifier: spyIdentifier, - BinaryExpression: spyBinaryExpression - }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - const messages = linter.verify(code, config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - sinon.assert.calledOnce(spyVariableDeclaration); - sinon.assert.calledOnce(spyVariableDeclarator); - sinon.assert.calledOnce(spyIdentifier); - sinon.assert.calledTwice(spyLiteral); - sinon.assert.calledOnce(spyBinaryExpression); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should throw an error if a rule reports a problem without a message", () => { - - const config = { - plugins: { - test: { - rules: { - "invalid-report": { - create: context => ({ - Program(node) { - context.report({ node }); - } - }) - } - } - } - }, - rules: { "test/invalid-report": "error" } - }; - - assert.throws( - () => linter.verify("foo", config), - TypeError, - "Missing `message` property in report() call; add a message that describes the linting problem." - ); - }); - - - }); - - describe("Rule Context", () => { - - describe("context.getFilename()", () => { - const ruleId = "filename-rule"; - - it("has access to the filename", () => { - - const config = { - plugins: { - test: { - rules: { - [ruleId]: { - create: context => ({ - Literal(node) { - context.report(node, context.getFilename()); - } - }) - } - } - } - }, - rules: { - [`test/${ruleId}`]: 1 - } - }; - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages[0].message, filename); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("defaults filename to ''", () => { - - const config = { - plugins: { - test: { - rules: { - [ruleId]: { - create: context => ({ - Literal(node) { - context.report(node, context.getFilename()); - } - }) - } - } - } - }, - rules: { - [`test/${ruleId}`]: 1 - } - }; - - - const messages = linter.verify("0", config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages[0].message, ""); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("context.filename", () => { - const ruleId = "filename-rule"; - - it("has access to the filename", () => { - - const config = { - plugins: { - test: { - rules: { - [ruleId]: { - create: context => ({ - Literal(node) { - assert.strictEqual(context.getFilename(), context.filename); - context.report(node, context.filename); - } - }) - } - } - } - }, - rules: { - [`test/${ruleId}`]: 1 - } - }; - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages[0].message, filename); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("defaults filename to ''", () => { - - const config = { - plugins: { - test: { - rules: { - [ruleId]: { - create: context => ({ - Literal(node) { - assert.strictEqual(context.getFilename(), context.filename); - context.report(node, context.filename); - } - }) - } - } - } - }, - rules: { - [`test/${ruleId}`]: 1 - } - }; - - - const messages = linter.verify("0", config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages[0].message, ""); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("context.getPhysicalFilename()", () => { - - const ruleId = "filename-rule"; - - it("has access to the physicalFilename", () => { - - const config = { - plugins: { - test: { - rules: { - [ruleId]: { - create: context => ({ - Literal(node) { - context.report(node, context.getPhysicalFilename()); - } - }) - } - } - } - }, - rules: { - [`test/${ruleId}`]: 1 - } - }; - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages[0].message, filename); - assert.strictEqual(suppressedMessages.length, 0); - }); - - }); - - describe("context.physicalFilename", () => { - - const ruleId = "filename-rule"; - - it("has access to the physicalFilename", () => { - - const config = { - plugins: { - test: { - rules: { - [ruleId]: { - create: context => ({ - Literal(node) { - assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); - context.report(node, context.physicalFilename); - } - }) - } - } - } - }, - rules: { - [`test/${ruleId}`]: 1 - } - }; - - const messages = linter.verify("0", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages[0].message, filename); - assert.strictEqual(suppressedMessages.length, 0); - }); - - }); - - describe("context.getSourceLines()", () => { - - it("should get proper lines when using \\n as a line break", () => { - const code = "a;\nb;"; - const spy = sinon.spy(context => { - assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: spy } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should get proper lines when using \\r\\n as a line break", () => { - const code = "a;\r\nb;"; - const spy = sinon.spy(context => { - assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: spy } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should get proper lines when using \\r as a line break", () => { - const code = "a;\rb;"; - const spy = sinon.spy(context => { - assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: spy } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should get proper lines when using \\u2028 as a line break", () => { - const code = "a;\u2028b;"; - const spy = sinon.spy(context => { - assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: spy } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should get proper lines when using \\u2029 as a line break", () => { - const code = "a;\u2029b;"; - const spy = sinon.spy(context => { - assert.deepStrictEqual(context.getSourceLines(), ["a;", "b;"]); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: spy } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy.calledOnce); - }); - - }); - - describe("context.getSource()", () => { - const code = TEST_CODE; - - it("should retrieve all text when used without parameters", () => { - - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - assert.strictEqual(context.getSource(), TEST_CODE); - }); - return { Program: spy }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve all text for root node", () => { - - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), TEST_CODE); - }); - return { Program: spy }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should clamp to valid range when retrieving characters before start of source", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2, 0), TEST_CODE); - }); - return { Program: spy }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve all text for binary expression", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node), "6 * 7"); - }); - return { BinaryExpression: spy }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve all text plus two characters before for binary expression", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2), "= 6 * 7"); - }); - return { BinaryExpression: spy }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve all text plus one character after for binary expression", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 0, 1), "6 * 7;"); - }); - return { BinaryExpression: spy }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve all text plus two characters before and one character after for binary expression", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(node => { - assert.strictEqual(context.getSource(node, 2, 1), "= 6 * 7;"); - }); - return { BinaryExpression: spy }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - }); - - describe("context.getAncestors()", () => { - const code = TEST_CODE; - - it("should retrieve all ancestors when used", () => { - - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const ancestors = context.getAncestors(); - - assert.strictEqual(ancestors.length, 3); - }); - return { BinaryExpression: spy }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config, filename, true); - assert(spy && spy.calledOnce); - }); - - it("should retrieve empty ancestors for root node", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const ancestors = context.getAncestors(); - - assert.strictEqual(ancestors.length, 0); - }); - - return { Program: spy }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("context.getNodeByRangeIndex()", () => { - const code = TEST_CODE; - - it("should retrieve a node starting at the given index", () => { - const spy = sinon.spy(context => { - assert.strictEqual(context.getNodeByRangeIndex(4).type, "Identifier"); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: spy } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should retrieve a node containing the given index", () => { - const spy = sinon.spy(context => { - assert.strictEqual(context.getNodeByRangeIndex(6).type, "Identifier"); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: spy } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should retrieve a node that is exactly the given index", () => { - const spy = sinon.spy(context => { - const node = context.getNodeByRangeIndex(13); - - assert.strictEqual(node.type, "Literal"); - assert.strictEqual(node.value, 6); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: spy } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should retrieve a node ending with the given index", () => { - const spy = sinon.spy(context => { - assert.strictEqual(context.getNodeByRangeIndex(9).type, "Identifier"); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: spy } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should retrieve the deepest node containing the given index", () => { - const spy = sinon.spy(context => { - const node1 = context.getNodeByRangeIndex(14); - - assert.strictEqual(node1.type, "BinaryExpression"); - - const node2 = context.getNodeByRangeIndex(3); - - assert.strictEqual(node2.type, "VariableDeclaration"); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: spy } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy.calledOnce); - }); - - it("should return null if the index is outside the range of any node", () => { - const spy = sinon.spy(context => { - const node1 = context.getNodeByRangeIndex(-1); - - assert.isNull(node1); - - const node2 = context.getNodeByRangeIndex(-99); - - assert.isNull(node2); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: spy } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy.calledOnce); - }); - }); - - describe("context.getScope()", () => { - const codeToTestScope = "function foo() { q: for(;;) { break q; } } function bar () { var q = t; } var baz = (() => { return 1; });"; - - it("should retrieve the global scope correctly from a Program", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "global"); - }); - return { Program: spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 6 - }, - rules: { "test/checker": "error" } - }; - - linter.verify(codeToTestScope, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the function scope correctly from a FunctionDeclaration", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "function"); - }); - return { FunctionDeclaration: spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 6 - }, - rules: { "test/checker": "error" } - }; - - linter.verify(codeToTestScope, config); - assert(spy && spy.calledTwice); - }); - - it("should retrieve the function scope correctly from a LabeledStatement", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.id.name, "foo"); - }); - return { LabeledStatement: spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 6 - }, - rules: { "test/checker": "error" } - }; - - - linter.verify(codeToTestScope, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the function scope correctly from within an ArrowFunctionExpression", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "ArrowFunctionExpression"); - }); - - return { ReturnStatement: spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 6 - }, - rules: { "test/checker": "error" } - }; - - - linter.verify(codeToTestScope, config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the function scope correctly from within an SwitchStatement", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "switch"); - assert.strictEqual(scope.block.type, "SwitchStatement"); - }); - - return { SwitchStatement: spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 6 - }, - rules: { "test/checker": "error" } - }; - - linter.verify("switch(foo){ case 'a': var b = 'foo'; }", config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the function scope correctly from within a BlockStatement", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.block.type, "BlockStatement"); - }); - - return { BlockStatement: spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 6 - }, - rules: { "test/checker": "error" } - }; - - - linter.verify("var x; {let y = 1}", config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the function scope correctly from within a nested block statement", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.block.type, "BlockStatement"); - }); - - return { BlockStatement: spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 6 - }, - rules: { "test/checker": "error" } - }; - - - linter.verify("if (true) { let x = 1 }", config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the function scope correctly from within a FunctionDeclaration", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "FunctionDeclaration"); - }); - - return { FunctionDeclaration: spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 6 - }, - rules: { "test/checker": "error" } - }; - - - linter.verify("function foo() {}", config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the function scope correctly from within a FunctionExpression", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block.type, "FunctionExpression"); - }); - - return { FunctionExpression: spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 6 - }, - rules: { "test/checker": "error" } - }; - - - linter.verify("(function foo() {})();", config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve the catch scope correctly from within a CatchClause", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "catch"); - assert.strictEqual(scope.block.type, "CatchClause"); - }); - - return { CatchClause: spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 6 - }, - rules: { "test/checker": "error" } - }; - - linter.verify("try {} catch (err) {}", config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve module scope correctly from an ES6 module", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "module"); - }); - - return { AssignmentExpression: spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 6, - sourceType: "module" - }, - rules: { "test/checker": "error" } - }; - - - linter.verify("var foo = {}; foo.bar = 1;", config); - assert(spy && spy.calledOnce); - }); - - it("should retrieve function scope correctly when sourceType is commonjs", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(scope.type, "function"); - }); - - return { AssignmentExpression: spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 6, - sourceType: "commonjs" - }, - rules: { "test/checker": "error" } - }; - - linter.verify("var foo = {}; foo.bar = 1;", config); - assert(spy && spy.calledOnce); - }); - - describe("Scope Internals", () => { - - /** - * Get the scope on the node `astSelector` specified. - * @param {string} codeToEvaluate The source code to verify. - * @param {string} astSelector The AST selector to get scope. - * @param {number} [ecmaVersion=5] The ECMAScript version. - * @returns {{node: ASTNode, scope: escope.Scope}} Gotten scope. - */ - function getScope(codeToEvaluate, astSelector, ecmaVersion = 5) { - let node, scope; - - const config = { - plugins: { - test: { - rules: { - "get-scope": { - create: context => ({ - [astSelector](node0) { - node = node0; - scope = context.getScope(); - } - }) - } - } - } - }, - languageOptions: { - ecmaVersion, - sourceType: "script" - }, - rules: { "test/get-scope": "error" } - }; - - linter.verify( - codeToEvaluate, - config - ); - - return { node, scope }; - } - - it("should return 'function' scope on FunctionDeclaration (ES5)", () => { - const { node, scope } = getScope("function f() {}", "FunctionDeclaration"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node); - }); - - it("should return 'function' scope on FunctionExpression (ES5)", () => { - const { node, scope } = getScope("!function f() {}", "FunctionExpression"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node); - }); - - it("should return 'function' scope on the body of FunctionDeclaration (ES5)", () => { - const { node, scope } = getScope("function f() {}", "BlockStatement"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent); - }); - - it("should return 'function' scope on the body of FunctionDeclaration (ES2015)", () => { - const { node, scope } = getScope("function f() {}", "BlockStatement", 2015); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent); - }); - - it("should return 'function' scope on BlockStatement in functions (ES5)", () => { - const { node, scope } = getScope("function f() { { var b; } }", "BlockStatement > BlockStatement"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); - }); - - it("should return 'block' scope on BlockStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { { let a; var b; } }", "BlockStatement > BlockStatement", 2015); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.upper.type, "function"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]); - assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "b"]); - }); - - it("should return 'block' scope on nested BlockStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { { let a; { let b; var c; } } }", "BlockStatement > BlockStatement > BlockStatement", 2015); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.upper.type, "block"); - assert.strictEqual(scope.upper.upper.type, "function"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); - assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["a"]); - assert.deepStrictEqual(scope.variableScope.variables.map(v => v.name), ["arguments", "c"]); - }); - - it("should return 'function' scope on SwitchStatement in functions (ES5)", () => { - const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchStatement"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); - }); - - it("should return 'switch' scope on SwitchStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchStatement", 2015); - - assert.strictEqual(scope.type, "switch"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); - }); - - it("should return 'function' scope on SwitchCase in functions (ES5)", () => { - const { node, scope } = getScope("function f() { switch (a) { case 0: var b; } }", "SwitchCase"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent.parent.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "b"]); - }); - - it("should return 'switch' scope on SwitchCase in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { switch (a) { case 0: let b; } }", "SwitchCase", 2015); - - assert.strictEqual(scope.type, "switch"); - assert.strictEqual(scope.block, node.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["b"]); - }); - - it("should return 'catch' scope on CatchClause in functions (ES5)", () => { - const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause"); - - assert.strictEqual(scope.type, "catch"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); - }); - - it("should return 'catch' scope on CatchClause in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause", 2015); - - assert.strictEqual(scope.type, "catch"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); - }); - - it("should return 'catch' scope on the block of CatchClause in functions (ES5)", () => { - const { node, scope } = getScope("function f() { try {} catch (e) { var a; } }", "CatchClause > BlockStatement"); - - assert.strictEqual(scope.type, "catch"); - assert.strictEqual(scope.block, node.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["e"]); - }); - - it("should return 'block' scope on the block of CatchClause in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { try {} catch (e) { let a; } }", "CatchClause > BlockStatement", 2015); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["a"]); - }); - - it("should return 'function' scope on ForStatement in functions (ES5)", () => { - const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]); - }); - - it("should return 'for' scope on ForStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement", 2015); - - assert.strictEqual(scope.type, "for"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["i"]); - }); - - it("should return 'function' scope on the block body of ForStatement in functions (ES5)", () => { - const { node, scope } = getScope("function f() { for (var i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent.parent.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "i"]); - }); - - it("should return 'block' scope on the block body of ForStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { for (let i = 0; i < 10; ++i) {} }", "ForStatement > BlockStatement", 2015); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.upper.type, "for"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), []); - assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["i"]); - }); - - it("should return 'function' scope on ForInStatement in functions (ES5)", () => { - const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]); - }); - - it("should return 'for' scope on ForInStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement", 2015); - - assert.strictEqual(scope.type, "for"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["key"]); - }); - - it("should return 'function' scope on the block body of ForInStatement in functions (ES5)", () => { - const { node, scope } = getScope("function f() { for (var key in obj) {} }", "ForInStatement > BlockStatement"); - - assert.strictEqual(scope.type, "function"); - assert.strictEqual(scope.block, node.parent.parent.parent); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["arguments", "key"]); - }); - - it("should return 'block' scope on the block body of ForInStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { for (let key in obj) {} }", "ForInStatement > BlockStatement", 2015); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.upper.type, "for"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), []); - assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["key"]); - }); - - it("should return 'for' scope on ForOfStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement", 2015); - - assert.strictEqual(scope.type, "for"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), ["x"]); - }); - - it("should return 'block' scope on the block body of ForOfStatement in functions (ES2015)", () => { - const { node, scope } = getScope("function f() { for (let x of xs) {} }", "ForOfStatement > BlockStatement", 2015); - - assert.strictEqual(scope.type, "block"); - assert.strictEqual(scope.upper.type, "for"); - assert.strictEqual(scope.block, node); - assert.deepStrictEqual(scope.variables.map(v => v.name), []); - assert.deepStrictEqual(scope.upper.variables.map(v => v.name), ["x"]); - }); - - it("should shadow the same name variable by the iteration variable.", () => { - const { node, scope } = getScope("let x; for (let x of x) {}", "ForOfStatement", 2015); - - assert.strictEqual(scope.type, "for"); - assert.strictEqual(scope.upper.type, "global"); - assert.strictEqual(scope.block, node); - assert.strictEqual(scope.upper.variables[0].references.length, 0); - assert.strictEqual(scope.references[0].identifier, node.left.declarations[0].id); - assert.strictEqual(scope.references[1].identifier, node.right); - assert.strictEqual(scope.references[1].resolved, scope.variables[0]); - }); - }); - - describe("Variables and references", () => { - const code = [ - "a;", - "function foo() { b; }", - "Object;", - "foo;", - "var c;", - "c;", - "/* global d */", - "d;", - "e;", - "f;" - ].join("\n"); - let scope = null; - - beforeEach(() => { - let ok = false; - - const config = { - plugins: { - test: { - rules: { - test: { - create: context => ({ - Program() { - scope = context.getScope(); - ok = true; - } - }) - } - } - } - }, - languageOptions: { - globals: { e: true, f: false }, - sourceType: "script", - ecmaVersion: 5 - }, - rules: { - "test/test": 2 - } - }; - - linter.verify(code, config); - assert(ok); - }); - - afterEach(() => { - scope = null; - }); - - it("Scope#through should contain references of undefined variables", () => { - assert.strictEqual(scope.through.length, 2); - assert.strictEqual(scope.through[0].identifier.name, "a"); - assert.strictEqual(scope.through[0].identifier.loc.start.line, 1); - assert.strictEqual(scope.through[0].resolved, null); - assert.strictEqual(scope.through[1].identifier.name, "b"); - assert.strictEqual(scope.through[1].identifier.loc.start.line, 2); - assert.strictEqual(scope.through[1].resolved, null); - }); - - it("Scope#variables should contain global variables", () => { - assert(scope.variables.some(v => v.name === "Object")); - assert(scope.variables.some(v => v.name === "foo")); - assert(scope.variables.some(v => v.name === "c")); - assert(scope.variables.some(v => v.name === "d")); - assert(scope.variables.some(v => v.name === "e")); - assert(scope.variables.some(v => v.name === "f")); - }); - - it("Scope#set should contain global variables", () => { - assert(scope.set.get("Object")); - assert(scope.set.get("foo")); - assert(scope.set.get("c")); - assert(scope.set.get("d")); - assert(scope.set.get("e")); - assert(scope.set.get("f")); - }); - - it("Variables#references should contain their references", () => { - assert.strictEqual(scope.set.get("Object").references.length, 1); - assert.strictEqual(scope.set.get("Object").references[0].identifier.name, "Object"); - assert.strictEqual(scope.set.get("Object").references[0].identifier.loc.start.line, 3); - assert.strictEqual(scope.set.get("Object").references[0].resolved, scope.set.get("Object")); - assert.strictEqual(scope.set.get("foo").references.length, 1); - assert.strictEqual(scope.set.get("foo").references[0].identifier.name, "foo"); - assert.strictEqual(scope.set.get("foo").references[0].identifier.loc.start.line, 4); - assert.strictEqual(scope.set.get("foo").references[0].resolved, scope.set.get("foo")); - assert.strictEqual(scope.set.get("c").references.length, 1); - assert.strictEqual(scope.set.get("c").references[0].identifier.name, "c"); - assert.strictEqual(scope.set.get("c").references[0].identifier.loc.start.line, 6); - assert.strictEqual(scope.set.get("c").references[0].resolved, scope.set.get("c")); - assert.strictEqual(scope.set.get("d").references.length, 1); - assert.strictEqual(scope.set.get("d").references[0].identifier.name, "d"); - assert.strictEqual(scope.set.get("d").references[0].identifier.loc.start.line, 8); - assert.strictEqual(scope.set.get("d").references[0].resolved, scope.set.get("d")); - assert.strictEqual(scope.set.get("e").references.length, 1); - assert.strictEqual(scope.set.get("e").references[0].identifier.name, "e"); - assert.strictEqual(scope.set.get("e").references[0].identifier.loc.start.line, 9); - assert.strictEqual(scope.set.get("e").references[0].resolved, scope.set.get("e")); - assert.strictEqual(scope.set.get("f").references.length, 1); - assert.strictEqual(scope.set.get("f").references[0].identifier.name, "f"); - assert.strictEqual(scope.set.get("f").references[0].identifier.loc.start.line, 10); - assert.strictEqual(scope.set.get("f").references[0].resolved, scope.set.get("f")); - }); - - it("Reference#resolved should be their variable", () => { - assert.strictEqual(scope.set.get("Object").references[0].resolved, scope.set.get("Object")); - assert.strictEqual(scope.set.get("foo").references[0].resolved, scope.set.get("foo")); - assert.strictEqual(scope.set.get("c").references[0].resolved, scope.set.get("c")); - assert.strictEqual(scope.set.get("d").references[0].resolved, scope.set.get("d")); - assert.strictEqual(scope.set.get("e").references[0].resolved, scope.set.get("e")); - assert.strictEqual(scope.set.get("f").references[0].resolved, scope.set.get("f")); - }); - }); - }); - - describe("context.getDeclaredVariables(node)", () => { - - /** - * Assert `context.getDeclaredVariables(node)` is valid. - * @param {string} code A code to check. - * @param {string} type A type string of ASTNode. This method checks variables on the node of the type. - * @param {Array>} expectedNamesList An array of expected variable names. The expected variable names is an array of string. - * @returns {void} - */ - function verify(code, type, expectedNamesList) { - const config = { - plugins: { - test: { - - rules: { - test: { - create(context) { - - /** - * Assert `context.getDeclaredVariables(node)` is empty. - * @param {ASTNode} node A node to check. - * @returns {void} - */ - function checkEmpty(node) { - assert.strictEqual(0, context.getDeclaredVariables(node).length); - } - const rule = { - Program: checkEmpty, - EmptyStatement: checkEmpty, - BlockStatement: checkEmpty, - ExpressionStatement: checkEmpty, - LabeledStatement: checkEmpty, - BreakStatement: checkEmpty, - ContinueStatement: checkEmpty, - WithStatement: checkEmpty, - SwitchStatement: checkEmpty, - ReturnStatement: checkEmpty, - ThrowStatement: checkEmpty, - TryStatement: checkEmpty, - WhileStatement: checkEmpty, - DoWhileStatement: checkEmpty, - ForStatement: checkEmpty, - ForInStatement: checkEmpty, - DebuggerStatement: checkEmpty, - ThisExpression: checkEmpty, - ArrayExpression: checkEmpty, - ObjectExpression: checkEmpty, - Property: checkEmpty, - SequenceExpression: checkEmpty, - UnaryExpression: checkEmpty, - BinaryExpression: checkEmpty, - AssignmentExpression: checkEmpty, - UpdateExpression: checkEmpty, - LogicalExpression: checkEmpty, - ConditionalExpression: checkEmpty, - CallExpression: checkEmpty, - NewExpression: checkEmpty, - MemberExpression: checkEmpty, - SwitchCase: checkEmpty, - Identifier: checkEmpty, - Literal: checkEmpty, - ForOfStatement: checkEmpty, - ArrowFunctionExpression: checkEmpty, - YieldExpression: checkEmpty, - TemplateLiteral: checkEmpty, - TaggedTemplateExpression: checkEmpty, - TemplateElement: checkEmpty, - ObjectPattern: checkEmpty, - ArrayPattern: checkEmpty, - RestElement: checkEmpty, - AssignmentPattern: checkEmpty, - ClassBody: checkEmpty, - MethodDefinition: checkEmpty, - MetaProperty: checkEmpty - }; - - rule[type] = function(node) { - const expectedNames = expectedNamesList.shift(); - const variables = context.getDeclaredVariables(node); - - assert(Array.isArray(expectedNames)); - assert(Array.isArray(variables)); - assert.strictEqual(expectedNames.length, variables.length); - for (let i = variables.length - 1; i >= 0; i--) { - assert.strictEqual(expectedNames[i], variables[i].name); - } - }; - return rule; - } - - } - } - - } - }, - languageOptions: { - ecmaVersion: 6, - sourceType: "module" - }, - rules: { - "test/test": 2 - } - }; - - linter.verify(code, config); - - // Check all expected names are asserted. - assert.strictEqual(0, expectedNamesList.length); - } - - it("VariableDeclaration", () => { - const code = "\n var {a, x: [b], y: {c = 0}} = foo;\n let {d, x: [e], y: {f = 0}} = foo;\n const {g, x: [h], y: {i = 0}} = foo, {j, k = function(z) { let l; }} = bar;\n "; - const namesList = [ - ["a", "b", "c"], - ["d", "e", "f"], - ["g", "h", "i", "j", "k"], - ["l"] - ]; - - verify(code, "VariableDeclaration", namesList); - }); - - it("VariableDeclaration (on for-in/of loop)", () => { - - // TDZ scope is created here, so tests to exclude those. - const code = "\n for (var {a, x: [b], y: {c = 0}} in foo) {\n let g;\n }\n for (let {d, x: [e], y: {f = 0}} of foo) {\n let h;\n }\n "; - const namesList = [ - ["a", "b", "c"], - ["g"], - ["d", "e", "f"], - ["h"] - ]; - - verify(code, "VariableDeclaration", namesList); - }); - - it("VariableDeclarator", () => { - - // TDZ scope is created here, so tests to exclude those. - const code = "\n var {a, x: [b], y: {c = 0}} = foo;\n let {d, x: [e], y: {f = 0}} = foo;\n const {g, x: [h], y: {i = 0}} = foo, {j, k = function(z) { let l; }} = bar;\n "; - const namesList = [ - ["a", "b", "c"], - ["d", "e", "f"], - ["g", "h", "i"], - ["j", "k"], - ["l"] - ]; - - verify(code, "VariableDeclarator", namesList); - }); - - it("FunctionDeclaration", () => { - const code = "\n function foo({a, x: [b], y: {c = 0}}, [d, e]) {\n let z;\n }\n function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) {\n let z;\n }\n "; - const namesList = [ - ["foo", "a", "b", "c", "d", "e"], - ["bar", "f", "g", "h", "i", "j"] - ]; - - verify(code, "FunctionDeclaration", namesList); - }); - - it("FunctionExpression", () => { - const code = "\n (function foo({a, x: [b], y: {c = 0}}, [d, e]) {\n let z;\n });\n (function bar({f, x: [g], y: {h = 0}}, [i, j = function(q) { let w; }]) {\n let z;\n });\n "; - const namesList = [ - ["foo", "a", "b", "c", "d", "e"], - ["bar", "f", "g", "h", "i", "j"], - ["q"] - ]; - - verify(code, "FunctionExpression", namesList); - }); - - it("ArrowFunctionExpression", () => { - const code = "\n (({a, x: [b], y: {c = 0}}, [d, e]) => {\n let z;\n });\n (({f, x: [g], y: {h = 0}}, [i, j]) => {\n let z;\n });\n "; - const namesList = [ - ["a", "b", "c", "d", "e"], - ["f", "g", "h", "i", "j"] - ]; - - verify(code, "ArrowFunctionExpression", namesList); - }); - - it("ClassDeclaration", () => { - const code = "\n class A { foo(x) { let y; } }\n class B { foo(x) { let y; } }\n "; - const namesList = [ - ["A", "A"], // outer scope's and inner scope's. - ["B", "B"] - ]; - - verify(code, "ClassDeclaration", namesList); - }); - - it("ClassExpression", () => { - const code = "\n (class A { foo(x) { let y; } });\n (class B { foo(x) { let y; } });\n "; - const namesList = [ - ["A"], - ["B"] - ]; - - verify(code, "ClassExpression", namesList); - }); - - it("CatchClause", () => { - const code = "\n try {} catch ({a, b}) {\n let x;\n try {} catch ({c, d}) {\n let y;\n }\n }\n "; - const namesList = [ - ["a", "b"], - ["c", "d"] - ]; - - verify(code, "CatchClause", namesList); - }); - - it("ImportDeclaration", () => { - const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; - const namesList = [ - [], - ["a"], - ["b", "c", "d"] - ]; - - verify(code, "ImportDeclaration", namesList); - }); - - it("ImportSpecifier", () => { - const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; - const namesList = [ - ["c"], - ["d"] - ]; - - verify(code, "ImportSpecifier", namesList); - }); - - it("ImportDefaultSpecifier", () => { - const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; - const namesList = [ - ["b"] - ]; - - verify(code, "ImportDefaultSpecifier", namesList); - }); - - it("ImportNamespaceSpecifier", () => { - const code = "\n import \"aaa\";\n import * as a from \"bbb\";\n import b, {c, x as d} from \"ccc\";\n "; - const namesList = [ - ["a"] - ]; - - verify(code, "ImportNamespaceSpecifier", namesList); - }); - }); - - describe("context.markVariableAsUsed()", () => { - - it("should mark variables in current scope as used", () => { - const code = "var a = 1, b = 2;"; - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); - - const scope = context.getScope(); - - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); - - return { "Program:exit": spy }; - } - } - } - } - }, - languageOptions: { - sourceType: "script" - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should mark variables in function args as used", () => { - const code = "function abc(a, b) { return 1; }"; - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); - - const scope = context.getScope(); - - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); - - return { ReturnStatement: spy }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should mark variables in higher scopes as used", () => { - const code = "var a, b; function abc() { return 1; }"; - let returnSpy, exitSpy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - returnSpy = sinon.spy(() => { - assert.isTrue(context.markVariableAsUsed("a")); - }); - exitSpy = sinon.spy(() => { - const scope = context.getScope(); - - assert.isTrue(getVariable(scope, "a").eslintUsed); - assert.notOk(getVariable(scope, "b").eslintUsed); - }); - - return { ReturnStatement: returnSpy, "Program:exit": exitSpy }; - } - } - } - } - }, - languageOptions: { - sourceType: "script" - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(returnSpy && returnSpy.calledOnce); - assert(exitSpy && exitSpy.calledOnce); - }); - - it("should mark variables as used when sourceType is commonjs", () => { - const code = "var a = 1, b = 2;"; - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const globalScope = context.getScope(), - childScope = globalScope.childScopes[0]; - - assert.isTrue(context.markVariableAsUsed("a"), "Call to markVariableAsUsed should return true"); - - assert.isTrue(getVariable(childScope, "a").eslintUsed, "'a' should be marked as used."); - assert.isUndefined(getVariable(childScope, "b").eslintUsed, "'b' should be marked as used."); - }); - - return { "Program:exit": spy }; - } - } - } - } - }, - languageOptions: { - sourceType: "commonjs" - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce, "Spy wasn't called."); - }); - - it("should mark variables in modules as used", () => { - const code = "var a = 1, b = 2;"; - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const globalScope = context.getScope(), - childScope = globalScope.childScopes[0]; - - assert.isTrue(context.markVariableAsUsed("a")); - - assert.isTrue(getVariable(childScope, "a").eslintUsed); - assert.isUndefined(getVariable(childScope, "b").eslintUsed); - }); - - return { "Program:exit": spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 6, - sourceType: "module" - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should return false if the given variable is not found", () => { - const code = "var a = 1, b = 2;"; - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - assert.isFalse(context.markVariableAsUsed("c")); - }); - - return { "Program:exit": spy }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("context.getCwd()", () => { - const code = "a;\nb;"; - const baseConfig = { rules: { "test/checker": "error" } }; - - it("should get cwd correctly in the context", () => { - const cwd = "cwd"; - const linterWithOption = new Linter({ cwd, configType: "flat" }); - let spy; - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), cwd); - }); - return { Program: spy }; - } - } - } - } - }, - ...baseConfig - }; - - linterWithOption.verify(code, config, `${cwd}/file.js`); - assert(spy && spy.calledOnce); - }); - - it("should assign process.cwd() to it if cwd is undefined", () => { - - const linterWithOption = new Linter({ configType: "flat" }); - let spy; - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), process.cwd()); - }); - return { Program: spy }; - } - } - } - } - }, - ...baseConfig - }; - - linterWithOption.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should assign process.cwd() to it if the option is undefined", () => { - let spy; - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), process.cwd()); - }); - return { Program: spy }; - } - } - } - } - }, - ...baseConfig - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("context.cwd", () => { - const code = "a;\nb;"; - const baseConfig = { rules: { "test/checker": "error" } }; - - it("should get cwd correctly in the context", () => { - const cwd = "cwd"; - const linterWithOption = new Linter({ cwd, configType: "flat" }); - let spy; - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - assert.strictEqual(context.cwd, cwd); - }); - return { Program: spy }; - } - } - } - } - }, - ...baseConfig - }; - - linterWithOption.verify(code, config, `${cwd}/file.js`); - assert(spy && spy.calledOnce); - }); - - it("should assign process.cwd() to it if cwd is undefined", () => { - - const linterWithOption = new Linter({ configType: "flat" }); - let spy; - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), context.cwd); - assert.strictEqual(context.cwd, process.cwd()); - }); - return { Program: spy }; - } - } - } - } - }, - ...baseConfig - }; - - linterWithOption.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("should assign process.cwd() to it if the option is undefined", () => { - let spy; - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - - spy = sinon.spy(() => { - assert.strictEqual(context.getCwd(), context.cwd); - assert.strictEqual(context.cwd, process.cwd()); - }); - return { Program: spy }; - } - } - } - } - }, - ...baseConfig - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - }); - - describe("Rule Severity", () => { - - it("rule should run as warning when set to 1 with a config array", () => { - const ruleId = "semi", - configs = createFlatConfigArray({ - files: ["**/*.js"], - rules: { - [ruleId]: 1 - } - }); - - configs.normalizeSync(); - const messages = linter.verify("foo", configs, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1, "Message length is wrong"); - assert.strictEqual(messages[0].ruleId, ruleId); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("rule should run as warning when set to 1 with a plain array", () => { - const ruleId = "semi", - configs = [{ - files: ["**/*.js"], - rules: { - [ruleId]: 1 - } - }]; - - const messages = linter.verify("foo", configs, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1, "Message length is wrong"); - assert.strictEqual(messages[0].ruleId, ruleId); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("rule should run as warning when set to 1 with an object", () => { - const ruleId = "semi", - config = { - files: ["**/*.js"], - rules: { - [ruleId]: 1 - } - }; - - const messages = linter.verify("foo", config, filename, true); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1, "Message length is wrong"); - assert.strictEqual(messages[0].ruleId, ruleId); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("Code with a hashbang comment", () => { - const code = "#!bin/program\n\nvar foo;;"; - - it("should preserve line numbers", () => { - const config = { rules: { "no-extra-semi": 1 } }; - const messages = linter.verify(code, config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-extra-semi"); - assert.strictEqual(messages[0].nodeType, "EmptyStatement"); - assert.strictEqual(messages[0].line, 3); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should have a comment with the hashbang in it", () => { - const spy = sinon.spy(context => { - const comments = context.getAllComments(); - - assert.strictEqual(comments.length, 1); - assert.strictEqual(comments[0].type, "Shebang"); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: spy } - } - } - }, - rules: { - "test/checker": "error" - } - }; - - linter.verify(code, config); - assert(spy.calledOnce); - }); - }); - - describe("Options", () => { - - describe("filename", () => { - it("should allow filename to be passed on options object", () => { - const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.filename, "foo.js"); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: filenameChecker } - } - } - }, - rules: { - "test/checker": "error" - } - }; - - linter.verify("foo;", config, { filename: "foo.js" }); - assert(filenameChecker.calledOnce); - }); - - it("should allow filename to be passed as third argument", () => { - const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.filename, "bar.js"); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: filenameChecker } - } - } - }, - rules: { - "test/checker": "error" - } - }; - - linter.verify("foo;", config, "bar.js"); - assert(filenameChecker.calledOnce); - }); - - it("should default filename to when options object doesn't have filename", () => { - const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.filename, ""); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: filenameChecker } - } - } - }, - rules: { - "test/checker": "error" - } - }; - - linter.verify("foo;", config, {}); - assert(filenameChecker.calledOnce); - }); - - it("should default filename to when only two arguments are passed", () => { - const filenameChecker = sinon.spy(context => { - assert.strictEqual(context.filename, ""); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: filenameChecker } - } - } - }, - rules: { - "test/checker": "error" - } - }; - - linter.verify("foo;", config); - assert(filenameChecker.calledOnce); - }); - }); - - describe("physicalFilename", () => { - it("should be same as `filename` passed on options object, if no processors are used", () => { - const physicalFilenameChecker = sinon.spy(context => { - assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); - assert.strictEqual(context.physicalFilename, "foo.js"); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: physicalFilenameChecker } - } - } - }, - rules: { - "test/checker": "error" - } - }; - - linter.verify("foo;", config, { filename: "foo.js" }); - assert(physicalFilenameChecker.calledOnce); - }); - - it("should default physicalFilename to when options object doesn't have filename", () => { - const physicalFilenameChecker = sinon.spy(context => { - assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); - assert.strictEqual(context.physicalFilename, ""); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: physicalFilenameChecker } - } - } - }, - rules: { - "test/checker": "error" - } - }; - - linter.verify("foo;", config, {}); - assert(physicalFilenameChecker.calledOnce); - }); - - it("should default physicalFilename to when only two arguments are passed", () => { - const physicalFilenameChecker = sinon.spy(context => { - assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); - assert.strictEqual(context.physicalFilename, ""); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - checker: { create: physicalFilenameChecker } - } - } - }, - rules: { - "test/checker": "error" - } - }; - - linter.verify("foo;", config); - assert(physicalFilenameChecker.calledOnce); - }); - }); - - }); - - describe("Inline Directives", () => { - - describe("/*global*/ Comments", () => { - - describe("when evaluating code containing /*global */ and /*globals */ blocks", () => { - - it("variables should be available in global scope", () => { - const code = ` + let linter; + let warningService; + const filename = "filename.js"; + + /** + * Creates a config array with some default properties. + * @param {FlatConfig|FlatConfig[]} value The value to base the + * config array on. + * @param {{basePath: string, shouldIgnore: boolean, baseConfig: FlatConfig}} [options] + * The options to use for the config array instance. + * @returns {FlatConfigArray} The created config array. + */ + function createFlatConfigArray(value, options) { + return new FlatConfigArray(value, options); + } + + beforeEach(() => { + warningService = sinon.stub(new WarningService()); + linter = new Linter({ configType: "flat", warningService }); + }); + + describe("Static Members", () => { + describe("version", () => { + it("should return same version as instance property", () => { + assert.strictEqual(Linter.version, linter.version); + }); + }); + }); + + describe("hasFlag()", () => { + it("should return true if an active flag is present", () => { + assert.strictEqual( + new Linter({ + configType: "flat", + flags: ["test_only"], + }).hasFlag("test_only"), + true, + ); + }); + + it("should return true for the replacement flag if an inactive flag that has been replaced is used", () => { + assert.strictEqual( + new Linter({ + configType: "flat", + flags: ["test_only_replaced"], + warningService, + }).hasFlag("test_only"), + true, + ); + + assert( + warningService.emitInactiveFlagWarning.calledOnceWithExactly( + "test_only_replaced", + "The flag 'test_only_replaced' is inactive: This flag has been renamed 'test_only' to reflect its stabilization. Please use 'test_only' instead.", + ), + "calls `warningService.emitInactiveFlagWarning()` once with the correct arguments", + ); + }); + + it("should return false if an inactive flag whose feature is enabled by default is used", () => { + assert.strictEqual( + new Linter({ + configType: "flat", + flags: ["test_only_enabled_by_default"], + warningService, + }).hasFlag("test_only_enabled_by_default"), + false, + ); + + assert( + warningService.emitInactiveFlagWarning.calledOnceWithExactly( + "test_only_enabled_by_default", + "The flag 'test_only_enabled_by_default' is inactive: This feature is now enabled by default.", + ), + "calls `warningService.emitInactiveFlagWarning()` once with the correct arguments", + ); + }); + + it("should throw an error if an inactive flag whose feature has been abandoned is used", () => { + assert.throws(() => { + // eslint-disable-next-line no-new -- needed for test + new Linter({ + configType: "flat", + flags: ["test_only_abandoned"], + }); + }, /The flag 'test_only_abandoned' is inactive: This feature has been abandoned/u); + }); + + it("should throw an error if an unknown flag is present", () => { + assert.throws(() => { + // eslint-disable-next-line no-new -- needed for test + new Linter({ configType: "flat", flags: ["x_unknown"] }); + }, /Unknown flag 'x_unknown'/u); + }); + + it("should return false if the flag is not present", () => { + assert.strictEqual( + new Linter({ configType: "flat" }).hasFlag("x_feature"), + false, + ); + }); + }); + + describe("Config Options", () => { + describe("languageOptions", () => { + describe("ecmaVersion", () => { + it("should error when accessing a global that isn't available in ecmaVersion 5", () => { + const messages = linter.verify("new Map()", { + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, + rules: { + "no-undef": "error", + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 1, + "There should be one linting error.", + ); + assert.strictEqual( + messages[0].ruleId, + "no-undef", + "The linting error should be no-undef.", + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should error when accessing a global that isn't available in ecmaVersion 3", () => { + const messages = linter.verify("JSON.stringify({})", { + languageOptions: { + ecmaVersion: 3, + sourceType: "script", + }, + rules: { + "no-undef": "error", + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 1, + "There should be one linting error.", + ); + assert.strictEqual( + messages[0].ruleId, + "no-undef", + "The linting error should be no-undef.", + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should add globals for ES6 when ecmaVersion is 6", () => { + const messages = linter.verify("new Map()", { + languageOptions: { + ecmaVersion: 6, + }, + rules: { + "no-undef": "error", + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 0, + "There should be no linting errors.", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should allow destructuring when ecmaVersion is 6", () => { + const messages = linter.verify("let {a} = b", { + languageOptions: { + ecmaVersion: 6, + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 0, + "There should be no linting errors.", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("ecmaVersion should be normalized to year name for ES 6", () => { + const config = { + plugins: { + test: { + rules: { + checker: { + create: context => ({ + Program() { + assert.strictEqual( + context.languageOptions + .ecmaVersion, + 2015, + ); + }, + }), + }, + }, + }, + }, + languageOptions: { + ecmaVersion: 6, + }, + rules: { "test/checker": "error" }, + }; + + linter.verify("foo", config, filename); + }); + + it("ecmaVersion should be 'latest' by default", () => { + const messages = linter.verify("{ using x = foo(); }"); // ECMAScript 2026 syntax + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); // No parsing errors + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("ecmaVersion should be normalized to latest year by default", () => { + const config = { + plugins: { + test: { + rules: { + checker: { + create: context => ({ + Program() { + assert.strictEqual( + context.languageOptions + .ecmaVersion, + LATEST_ECMA_VERSION, + ); + }, + }), + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + linter.verify("foo", config, filename); + }); + + it("ecmaVersion should not be normalized to year name for ES 5", () => { + const config = { + plugins: { + test: { + rules: { + checker: { + create: context => ({ + Program() { + assert.strictEqual( + context.languageOptions + .ecmaVersion, + 5, + ); + }, + }), + }, + }, + }, + }, + languageOptions: { + ecmaVersion: 5, + }, + rules: { "test/checker": "error" }, + }; + + linter.verify("foo", config, filename); + }); + + it("ecmaVersion should be normalized to year name for 'latest'", () => { + const config = { + plugins: { + test: { + rules: { + checker: { + create: context => ({ + Program() { + assert.strictEqual( + context.languageOptions + .ecmaVersion, + LATEST_ECMA_VERSION, + ); + }, + }), + }, + }, + }, + }, + languageOptions: { + ecmaVersion: "latest", + }, + rules: { "test/checker": "error" }, + }; + + linter.verify("foo", config, filename); + }); + }); + + describe("sourceType", () => { + it("should be module by default", () => { + const config = { + plugins: { + test: { + rules: { + checker: { + create: context => ({ + Program() { + assert.strictEqual( + context.languageOptions + .sourceType, + "module", + ); + }, + }), + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + linter.verify("import foo from 'bar'", config, filename); + }); + + it("should default to commonjs when passed a .cjs filename", () => { + const config = { + plugins: { + test: { + rules: { + checker: { + create: context => ({ + Program() { + assert.strictEqual( + context.languageOptions + .sourceType, + "commonjs", + ); + }, + }), + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + linter.verify( + "import foo from 'bar'", + config, + `${filename}.cjs`, + ); + }); + + it("should error when import is used in a script", () => { + const messages = linter.verify("import foo from 'bar';", { + languageOptions: { + ecmaVersion: 6, + sourceType: "script", + }, + }); + + assert.strictEqual( + messages.length, + 1, + "There should be one parsing error.", + ); + assert.strictEqual( + messages[0].message, + "Parsing error: 'import' and 'export' may appear only with 'sourceType: module'", + ); + }); + + it("should not error when import is used in a module", () => { + const messages = linter.verify("import foo from 'bar';", { + languageOptions: { + ecmaVersion: 6, + sourceType: "module", + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 0, + "There should no linting errors.", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should error when return is used at the top-level outside of commonjs", () => { + const messages = linter.verify("return", { + languageOptions: { + ecmaVersion: 6, + sourceType: "script", + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 1, + "There should be one parsing error.", + ); + assert.strictEqual( + messages[0].message, + "Parsing error: 'return' outside of function", + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not error when top-level return is used in commonjs", () => { + const messages = linter.verify("return", { + languageOptions: { + ecmaVersion: 6, + sourceType: "commonjs", + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 0, + "There should no linting errors.", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should error when accessing a Node.js global outside of commonjs", () => { + const messages = linter.verify("require()", { + languageOptions: { + ecmaVersion: 6, + }, + rules: { + "no-undef": "error", + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 1, + "There should be one linting error.", + ); + assert.strictEqual( + messages[0].ruleId, + "no-undef", + "The linting error should be no-undef.", + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should add globals for Node.js when sourceType is commonjs", () => { + const messages = linter.verify("require()", { + languageOptions: { + ecmaVersion: 6, + sourceType: "commonjs", + }, + rules: { + "no-undef": "error", + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 0, + "There should be no linting errors.", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should allow 'await' as a property name in modules", () => { + const result = linter.verify("obj.await", { + languageOptions: { + ecmaVersion: 6, + sourceType: "module", + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert(result.length === 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("parser", () => { + it("should be able to define a custom parser", () => { + const parser = { + parseForESLint: function parse(code, options) { + return { + ast: esprima.parse(code, options), + services: { + test: { + getMessage() { + return "Hi!"; + }, + }, + }, + }; + }, + }; + + const config = { + languageOptions: { + parser, + }, + }; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should pass parser as context.languageOptions.parser to all rules when provided on config", () => { + const config = { + plugins: { + test: { + rules: { + "test-rule": { + create: sinon + .mock() + .withArgs( + sinon.match({ + languageOptions: { + parser: esprima, + }, + }), + ) + .returns({}), + }, + }, + }, + }, + languageOptions: { + parser: esprima, + }, + rules: { + "test/test-rule": 2, + }, + }; + + linter.verify("0", config, filename); + }); + + it("should use parseForESLint() in custom parser when custom parser is specified", () => { + const config = { + languageOptions: { + parser: testParsers.enhancedParser, + }, + }; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should expose parser services when using parseForESLint() and services are specified", () => { + const config = { + plugins: { + test: { + rules: { + "test-service-rule": { + create: context => ({ + Literal(node) { + context.report({ + node, + message: + context.sourceCode.parserServices.test.getMessage(), + }); + }, + }), + }, + }, + }, + }, + languageOptions: { + parser: testParsers.enhancedParser, + }, + rules: { + "test/test-service-rule": 2, + }, + }; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].message, "Hi!"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should use the same parserServices if source code object is reused", () => { + const config = { + plugins: { + test: { + rules: { + "test-service-rule": { + create: context => ({ + Literal(node) { + context.report({ + node, + message: + context.sourceCode.parserServices.test.getMessage(), + }); + }, + }), + }, + }, + }, + }, + languageOptions: { + parser: testParsers.enhancedParser, + }, + rules: { + "test/test-service-rule": 2, + }, + }; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].message, "Hi!"); + assert.strictEqual(suppressedMessages.length, 0); + + const messages2 = linter.verify( + linter.getSourceCode(), + config, + filename, + ); + const suppressedMessages2 = linter.getSuppressedMessages(); + + assert.strictEqual(messages2.length, 1); + assert.strictEqual(messages2[0].message, "Hi!"); + assert.strictEqual(suppressedMessages2.length, 0); + }); + + it("should pass parser as context.languageOptions.parser to all rules when default parser is used", () => { + // references to Espree get messed up in a browser context, so wrap it + const fakeParser = { + parse: espree.parse, + }; + + const spy = sinon.spy(context => { + assert.strictEqual( + context.languageOptions.parser, + fakeParser, + ); + return {}; + }); + + const config = { + plugins: { + test: { + rules: { + "test-rule": { create: spy }, + }, + }, + }, + languageOptions: { + parser: fakeParser, + }, + rules: { + "test/test-rule": 2, + }, + }; + + linter.verify("0", config, filename); + assert.isTrue(spy.calledOnce); + }); + + describe("Custom Parsers", () => { + const errorPrefix = "Parsing error: "; + + it("should have file path passed to it", () => { + const code = "/* this is code */"; + const parseSpy = { parse: sinon.spy(espree.parse) }; + const config = { + languageOptions: { + parser: parseSpy, + }, + }; + + linter.verify(code, config, filename); + + sinon.assert.calledWithMatch(parseSpy.parse, "", { + filePath: filename, + }); + }); + + it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => { + const code = + "var myDivElement =
;"; + const config = { + languageOptions: { + parser: esprima, + parserOptions: { + jsx: true, + }, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not throw or report errors when the custom parser returns unrecognized operators (https://github.com/eslint/eslint/issues/10475)", () => { + const code = "null %% 'foo'"; + const config = { + languageOptions: { + parser: testParsers.unknownLogicalOperator, + }, + }; + + // This shouldn't throw + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not throw or report errors when the custom parser returns nested unrecognized operators (https://github.com/eslint/eslint/issues/10560)", () => { + const code = "foo && bar %% baz"; + const config = { + languageOptions: { + parser: testParsers.unknownLogicalOperatorNested, + }, + }; + + // This shouldn't throw + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not throw or return errors when the custom parser returns unknown AST nodes", () => { + const code = "foo && bar %% baz"; + const nodes = []; + const config = { + plugins: { + test: { + rules: { + "collect-node-types": { + create: () => ({ + "*"(node) { + nodes.push(node.type); + }, + }), + }, + }, + }, + }, + languageOptions: { + parser: testParsers.nonJSParser, + }, + rules: { + "test/collect-node-types": "error", + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.isTrue(nodes.length > 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should strip leading line: prefix from parser error", () => { + const messages = linter.verify( + ";", + { + languageOptions: { + parser: testParsers.lineError, + }, + }, + filename, + ); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + errorPrefix + testParsers.lineError.expectedError, + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not modify a parser error message without a leading line: prefix", () => { + const messages = linter.verify( + ";", + { + languageOptions: { + parser: testParsers.noLineError, + }, + }, + filename, + ); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + errorPrefix + testParsers.noLineError.expectedError, + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + describe("if a parser provides 'visitorKeys'", () => { + let types = []; + let sourceCode; + let scopeManager; + let firstChildNodes = []; + + beforeEach(() => { + types = []; + firstChildNodes = []; + const config = { + plugins: { + test: { + rules: { + "collect-node-types": { + create: () => ({ + "*"(node) { + types.push(node.type); + }, + }), + }, + "save-scope-manager": { + create(context) { + scopeManager = + context.sourceCode + .scopeManager; + + return {}; + }, + }, + "esquery-option": { + create: () => ({ + ":first-child"(node) { + firstChildNodes.push( + node, + ); + }, + }), + }, + }, + }, + }, + languageOptions: { + parser: testParsers.enhancedParser2, + }, + rules: { + "test/collect-node-types": "error", + "test/save-scope-manager": "error", + "test/esquery-option": "error", + }, + }; + + linter.verify("@foo class A {}", config); + + sourceCode = linter.getSourceCode(); + }); + + it("Traverser should use the visitorKeys (so 'types' includes 'Decorator')", () => { + assert.deepStrictEqual(types, [ + "Program", + "ClassDeclaration", + "Decorator", + "Identifier", + "Identifier", + "ClassBody", + ]); + }); + + it("eslint-scope should use the visitorKeys (so 'childVisitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => { + assert.deepStrictEqual( + // eslint-disable-next-line no-underscore-dangle -- ScopeManager API + scopeManager.__options.childVisitorKeys + .ClassDeclaration, + [ + "experimentalDecorators", + "id", + "superClass", + "body", + ], + ); + }); + + it("should use the same visitorKeys if the source code object is reused", () => { + const types2 = []; + const config = { + plugins: { + test: { + rules: { + "collect-node-types": { + create: () => ({ + "*"(node) { + types2.push(node.type); + }, + }), + }, + }, + }, + }, + rules: { + "test/collect-node-types": "error", + }, + }; + + linter.verify(sourceCode, config); + + assert.deepStrictEqual(types2, [ + "Program", + "ClassDeclaration", + "Decorator", + "Identifier", + "Identifier", + "ClassBody", + ]); + }); + + it("esquery should use the visitorKeys (so 'visitorKeys.ClassDeclaration' includes 'experimentalDecorators')", () => { + assert.deepStrictEqual(firstChildNodes, [ + sourceCode.ast.body[0], + sourceCode.ast.body[0] + .experimentalDecorators[0], + ]); + }); + }); + + describe("if a parser provides 'scope'", () => { + let scope = null; + let sourceCode = null; + + beforeEach(() => { + const config = { + plugins: { + test: { + rules: { + "save-scope1": { + create: context => ({ + Program(node) { + scope = + context.sourceCode.getScope( + node, + ); + }, + }), + }, + }, + }, + }, + languageOptions: { + parser: testParsers.enhancedParser3, + }, + rules: { + "test/save-scope1": "error", + }, + }; + + linter.verify("@foo class A {}", config); + + sourceCode = linter.getSourceCode(); + }); + + it("should use the scope (so the global scope has the reference of '@foo')", () => { + assert.strictEqual(scope.references.length, 1); + assert.deepStrictEqual( + scope.references[0].identifier.name, + "foo", + ); + }); + + it("should use the same scope if the source code object is reused", () => { + let scope2 = null; + const config = { + plugins: { + test: { + rules: { + "save-scope2": { + create: context => ({ + Program(node) { + scope2 = + context.sourceCode.getScope( + node, + ); + }, + }), + }, + }, + }, + }, + rules: { + "test/save-scope2": "error", + }, + }; + + linter.verify(sourceCode, config, "test.js"); + + assert(scope2 !== null); + assert(scope2 === scope); + }); + }); + + it("should pass default languageOptions to the parser", () => { + const spy = sinon.spy((code, options) => + espree.parse(code, options), + ); + + linter.verify( + ";", + { + languageOptions: { + parser: { + parse: spy, + }, + }, + }, + "filename.js", + ); + + assert( + spy.calledWithMatch(";", { + ecmaVersion: LATEST_ECMA_VERSION, + sourceType: "module", + }), + ); + }); + }); + }); + + describe("parseOptions", () => { + it("should pass ecmaFeatures to all rules when provided on config", () => { + const parserOptions = { + ecmaFeatures: { + jsx: true, + }, + }; + + const config = { + plugins: { + test: { + rules: { + "test-rule": { + create: sinon + .mock() + .withArgs( + sinon.match({ + languageOptions: { + parserOptions, + }, + }), + ) + .returns({}), + }, + }, + }, + }, + languageOptions: { + parserOptions, + }, + rules: { + "test/test-rule": 2, + }, + }; + + linter.verify("0", config, filename); + }); + + it("should switch globalReturn to false if sourceType is module", () => { + const config = { + plugins: { + test: { + rules: { + "test-rule": { + create: sinon + .mock() + .withArgs( + sinon.match({ + languageOptions: { + parserOptions: { + ecmaFeatures: { + globalReturn: false, + }, + }, + }, + }), + ) + .returns({}), + }, + }, + }, + }, + languageOptions: { + sourceType: "module", + parserOptions: { + ecmaFeatures: { + globalReturn: true, + }, + }, + }, + rules: { + "test/test-rule": 2, + }, + }; + + linter.verify("0", config, filename); + }); + + it("should not parse sloppy-mode code when impliedStrict is true", () => { + const messages = linter.verify( + "var private;", + { + languageOptions: { + parserOptions: { + ecmaFeatures: { + impliedStrict: true, + }, + }, + }, + }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "Parsing error: The keyword 'private' is reserved", + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should properly parse valid code when impliedStrict is true", () => { + const messages = linter.verify( + "var foo;", + { + languageOptions: { + parserOptions: { + ecmaFeatures: { + impliedStrict: true, + }, + }, + }, + }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should properly parse JSX when passed ecmaFeatures", () => { + const messages = linter.verify( + "var x =
;", + { + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report an error when JSX code is encountered and JSX is not enabled", () => { + const code = 'var myDivElement =
;'; + const messages = linter.verify(code, {}, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages[0].column, 20); + assert.strictEqual( + messages[0].message, + "Parsing error: Unexpected token <", + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report an error when JSX code is encountered and JSX is enabled", () => { + const code = 'var myDivElement =
;'; + const messages = linter.verify( + code, + { + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, + filename, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report an error when JSX code contains a spread operator and JSX is enabled", () => { + const code = "var myDivElement =
;"; + const messages = linter.verify( + code, + { + languageOptions: { + ecmaVersion: 6, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, + "filename.js", + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not allow the use of reserved words as variable names in ES3", () => { + const code = "var char;"; + const messages = linter.verify( + code, + { + languageOptions: { + ecmaVersion: 3, + sourceType: "script", + }, + }, + filename, + ); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.match( + messages[0].message, + /^Parsing error:.*'char'/u, + ); + }); + + it("should not allow the use of reserved words as property names in member expressions in ES3", () => { + const code = "obj.char;"; + const messages = linter.verify( + code, + { + languageOptions: { + ecmaVersion: 3, + sourceType: "script", + }, + }, + filename, + ); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.match( + messages[0].message, + /^Parsing error:.*'char'/u, + ); + }); + + it("should not allow the use of reserved words as property names in object literals in ES3", () => { + const code = "var obj = { char: 1 };"; + const messages = linter.verify( + code, + { + languageOptions: { + ecmaVersion: 3, + sourceType: "script", + }, + }, + filename, + ); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.match( + messages[0].message, + /^Parsing error:.*'char'/u, + ); + }); + + it("should allow the use of reserved words as variable and property names in ES3 when allowReserved is true", () => { + const code = "var char; obj.char; var obj = { char: 1 };"; + const messages = linter.verify( + code, + { + languageOptions: { + ecmaVersion: 3, + sourceType: "script", + parserOptions: { + allowReserved: true, + }, + }, + }, + filename, + ); + + assert.strictEqual(messages.length, 0); + }); + + it("should not allow the use of reserved words as variable names in ES > 3", () => { + const ecmaVersions = [ + void 0, + ...espree.supportedEcmaVersions.filter( + ecmaVersion => ecmaVersion > 3, + ), + ]; + + ecmaVersions.forEach(ecmaVersion => { + const code = "var enum;"; + const messages = linter.verify( + code, + { + languageOptions: { + ...(ecmaVersion ? { ecmaVersion } : {}), + sourceType: "script", + }, + }, + filename, + ); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.match( + messages[0].message, + /^Parsing error:.*'enum'/u, + ); + }); + }); + + it("should allow the use of reserved words as property names in ES > 3", () => { + const ecmaVersions = [ + void 0, + ...espree.supportedEcmaVersions.filter( + ecmaVersion => ecmaVersion > 3, + ), + ]; + + ecmaVersions.forEach(ecmaVersion => { + const code = + "obj.enum; obj.function; var obj = { enum: 1, function: 2 };"; + const messages = linter.verify( + code, + { + languageOptions: { + ...(ecmaVersion ? { ecmaVersion } : {}), + sourceType: "script", + }, + }, + filename, + ); + + assert.strictEqual(messages.length, 0); + }); + }); + + it("should not allow `allowReserved: true` in ES > 3", () => { + const ecmaVersions = [ + void 0, + ...espree.supportedEcmaVersions.filter( + ecmaVersion => ecmaVersion > 3, + ), + ]; + + ecmaVersions.forEach(ecmaVersion => { + const code = ""; + const messages = linter.verify( + code, + { + languageOptions: { + ...(ecmaVersion ? { ecmaVersion } : {}), + sourceType: "script", + parserOptions: { + allowReserved: true, + }, + }, + }, + filename, + ); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.match( + messages[0].message, + /^Parsing error:.*allowReserved/u, + ); + }); + }); + }); + }); + + describe("settings", () => { + const ruleId = "test-rule"; + + it("should pass settings to all rules", () => { + const config = { + plugins: { + test: { + rules: { + [ruleId]: { + create: context => ({ + Literal(node) { + context.report( + node, + context.settings.info, + ); + }, + }), + }, + }, + }, + }, + settings: { + info: "Hello", + }, + rules: { + [`test/${ruleId}`]: 1, + }, + }; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].message, "Hello"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not have any settings if they were not passed in", () => { + const config = { + plugins: { + test: { + rules: { + [ruleId]: { + create: context => ({ + Literal(node) { + if ( + Object.getOwnPropertyNames( + context.settings, + ).length !== 0 + ) { + context.report( + node, + "Settings should be empty", + ); + } + }, + }), + }, + }, + }, + }, + settings: {}, + rules: { + [`test/${ruleId}`]: 1, + }, + }; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("rules", () => { + const code = "var answer = 6 * 7"; + + it("should be configurable by only setting the integer value", () => { + const rule = "semi", + config = { rules: {} }; + + config.rules[rule] = 1; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, rule); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should be configurable by only setting the string value", () => { + const rule = "semi", + config = { rules: {} }; + + config.rules[rule] = "warn"; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].ruleId, rule); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should be configurable by passing in values as an array", () => { + const rule = "semi", + config = { rules: {} }; + + config.rules[rule] = [1]; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, rule); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should be configurable by passing in string value as an array", () => { + const rule = "semi", + config = { rules: {} }; + + config.rules[rule] = ["warn"]; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].ruleId, rule); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not be configurable by setting other value", () => { + const rule = "semi", + config = { rules: {} }; + + config.rules[rule] = "1"; + + assert.throws(() => { + linter.verify(code, config, filename); + }, /Key "rules": Key "semi": Expected severity/u); + }); + + it("should process empty config", () => { + const config = {}; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + }); + + describe("verify()", () => { + it("should report warnings in order by line and column when called", () => { + const code = "foo()\n alert('test')"; + const config = { + rules: { + "no-mixed-spaces-and-tabs": 1, + "eol-last": 1, + semi: [1, "always"], + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 3); + assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages[0].column, 6); + assert.strictEqual(messages[1].line, 2); + assert.strictEqual(messages[1].column, 18); + assert.strictEqual(messages[2].line, 2); + assert.strictEqual(messages[2].column, 18); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report ignored file when filename isn't matched in the config array", () => { + const code = "foo()\n alert('test')"; + const config = { + rules: { + "no-mixed-spaces-and-tabs": 1, + "eol-last": 1, + semi: [1, "always"], + }, + }; + + const messages = linter.verify(code, config, "filename.ts"); + + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual(messages[0], { + ruleId: null, + severity: 1, + message: "No matching configuration found for filename.ts.", + line: 0, + column: 0, + nodeType: null, + }); + }); + + // https://github.com/eslint/eslint/issues/17669 + it("should use `cwd` constructor option as config `basePath` when config is not an instance of FlatConfigArray with Posix paths", () => { + const rule = { + create(context) { + return { + Program(node) { + context.report({ node, message: "Bad program." }); + }, + }; + }, + }; + + const code = "foo"; + const config = [ + { + plugins: { + test: { + rules: { + "test-rule-1": rule, + "test-rule-2": rule, + "test-rule-3": rule, + }, + }, + }, + }, + { + rules: { + "test/test-rule-1": 2, + }, + }, + { + files: ["**/*.ts"], + rules: { + "test/test-rule-2": 2, + }, + }, + { + files: ["bar/file.ts"], + rules: { + "test/test-rule-3": 2, + }, + }, + ]; + + const linterWithOptions = new Linter({ + configType: "flat", + cwd: "/foo", + }); + + let messages; + + messages = linterWithOptions.verify(code, config, "/file.js"); + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual(messages[0], { + ruleId: null, + severity: 1, + message: "No matching configuration found for /file.js.", + line: 0, + column: 0, + nodeType: null, + }); + + messages = linterWithOptions.verify(code, config, "/file.ts"); + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual(messages[0], { + ruleId: null, + severity: 1, + message: "No matching configuration found for /file.ts.", + line: 0, + column: 0, + nodeType: null, + }); + + messages = linterWithOptions.verify( + code, + config, + "/bar/foo/file.js", + ); + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual(messages[0], { + ruleId: null, + severity: 1, + message: + "No matching configuration found for /bar/foo/file.js.", + line: 0, + column: 0, + nodeType: null, + }); + + messages = linterWithOptions.verify( + code, + config, + "/bar/foo/file.ts", + ); + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual(messages[0], { + ruleId: null, + severity: 1, + message: + "No matching configuration found for /bar/foo/file.ts.", + line: 0, + column: 0, + nodeType: null, + }); + + messages = linterWithOptions.verify(code, config, "/foo/file.js"); + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "test/test-rule-1"); + + messages = linterWithOptions.verify(code, config, "/foo/file.ts"); + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "test/test-rule-1"); + assert.strictEqual(messages[1].ruleId, "test/test-rule-2"); + + messages = linterWithOptions.verify( + code, + config, + "/foo/bar/file.ts", + ); + assert.strictEqual(messages.length, 3); + assert.strictEqual(messages[0].ruleId, "test/test-rule-1"); + assert.strictEqual(messages[1].ruleId, "test/test-rule-2"); + assert.strictEqual(messages[2].ruleId, "test/test-rule-3"); + }); + + // https://github.com/eslint/eslint/issues/18575 + it("should use `cwd` constructor option as config `basePath` when config is not an instance of FlatConfigArray with Windows paths", () => { + const rule = { + create(context) { + return { + Program(node) { + context.report({ node, message: "Bad program." }); + }, + }; + }, + }; + + const code = "foo"; + const config = [ + { + plugins: { + test: { + rules: { + "test-rule-1": rule, + "test-rule-2": rule, + "test-rule-3": rule, + }, + }, + }, + }, + { + rules: { + "test/test-rule-1": 2, + }, + }, + { + files: ["**/*.ts"], + rules: { + "test/test-rule-2": 2, + }, + }, + { + files: ["bar/file.ts"], + rules: { + "test/test-rule-3": 2, + }, + }, + ]; + + const linterWithOptions = new Linter({ + configType: "flat", + cwd: "C:\\foo", + }); + + let messages; + + messages = linterWithOptions.verify(code, config, "C:\\file.js"); + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual(messages[0], { + ruleId: null, + severity: 1, + message: "No matching configuration found for C:\\file.js.", + line: 0, + column: 0, + nodeType: null, + }); + + messages = linterWithOptions.verify(code, config, "C:\\file.ts"); + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual(messages[0], { + ruleId: null, + severity: 1, + message: "No matching configuration found for C:\\file.ts.", + line: 0, + column: 0, + nodeType: null, + }); + + messages = linterWithOptions.verify( + code, + config, + "D:\\foo\\file.js", + ); + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual(messages[0], { + ruleId: null, + severity: 1, + message: + "No matching configuration found for D:\\foo\\file.js.", + line: 0, + column: 0, + nodeType: null, + }); + + messages = linterWithOptions.verify( + code, + config, + "D:\\foo\\file.ts", + ); + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual(messages[0], { + ruleId: null, + severity: 1, + message: + "No matching configuration found for D:\\foo\\file.ts.", + line: 0, + column: 0, + nodeType: null, + }); + + messages = linterWithOptions.verify( + code, + config, + "C:\\foo\\file.js", + ); + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "test/test-rule-1"); + + messages = linterWithOptions.verify( + code, + config, + "C:\\foo\\file.ts", + ); + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "test/test-rule-1"); + assert.strictEqual(messages[1].ruleId, "test/test-rule-2"); + + messages = linterWithOptions.verify( + code, + config, + "C:\\foo\\bar\\file.ts", + ); + assert.strictEqual(messages.length, 3); + assert.strictEqual(messages[0].ruleId, "test/test-rule-1"); + assert.strictEqual(messages[1].ruleId, "test/test-rule-2"); + assert.strictEqual(messages[2].ruleId, "test/test-rule-3"); + }); + + it("should ignore external files with Posix paths", () => { + const configs = createFlatConfigArray( + { files: ["**/*.js"] }, + { basePath: "/foo" }, + ); + + configs.normalizeSync(); + const messages1 = linter.verify("foo", configs, "/foo/bar.js"); + const messages2 = linter.verify("foo", configs, "/bar.js"); + + assert.strictEqual(messages1.length, 0); + assert.strictEqual(messages2.length, 1); + assert.strictEqual( + messages2[0].message, + "No matching configuration found for /bar.js.", + ); + }); + + it("should ignore external files with Windows paths", () => { + const configs = createFlatConfigArray( + { files: ["**/*.js"] }, + { basePath: "C:\\foo" }, + ); + + configs.normalizeSync(); + const messages1 = linter.verify("foo", configs, "C:\\foo\\bar.js"); + const messages2 = linter.verify("foo", configs, "D:\\foo\\bar.js"); + + assert.strictEqual(messages1.length, 0); + assert.strictEqual(messages2.length, 1); + assert.strictEqual( + messages2[0].message, + "No matching configuration found for D:\\foo\\bar.js.", + ); + }); + + describe("Plugins", () => { + it("should not load rule definition when rule isn't used", () => { + const spy = sinon.spy(); + + const config = { + plugins: { + test: { + rules: { + checker: { create: spy }, + }, + }, + }, + }; + + linter.verify("code", config, filename); + assert.isTrue( + spy.notCalled, + "Rule should not have been called", + ); + }); + }); + + describe("Rule Internals", () => { + const code = TEST_CODE; + + it("should throw an error when an error occurs inside of a rule visitor", () => { + const config = { + plugins: { + test: { + rules: { + checker: { + create: () => ({ + Program() { + throw new Error( + "Intentional error.", + ); + }, + }), + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + assert.throws(() => { + linter.verify(code, config, filename); + }, `Intentional error.\nOccurred while linting ${filename}:1\nRule: "test/checker"`); + }); + + it("should not call rule visitor with a `this` value", () => { + const spy = sinon.spy(); + const config = { + plugins: { + test: { + rules: { + checker: { + create: () => ({ + Program: spy, + }), + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + linter.verify("foo", config); + assert(spy.calledOnce); + assert.strictEqual(spy.firstCall.thisValue, void 0); + }); + + it("should not call unrecognized rule visitor when present in a rule", () => { + const spy = sinon.spy(); + const config = { + plugins: { + test: { + rules: { + checker: { + create: () => ({ + newListener: spy, + }), + }, + }, + }, + }, + rules: { + "test/checker": "error", + "no-undef": "error", + }, + }; + + linter.verify("foo", config); + assert(spy.notCalled); + }); + + it("should have all the `parent` properties on nodes when the rule visitors are created", () => { + const spy = sinon.spy(context => { + assert.strictEqual( + context.getSourceCode(), + context.sourceCode, + ); + const ast = context.sourceCode.ast; + + assert.strictEqual(ast.body[0].parent, ast); + assert.strictEqual( + ast.body[0].expression.parent, + ast.body[0], + ); + assert.strictEqual( + ast.body[0].expression.left.parent, + ast.body[0].expression, + ); + assert.strictEqual( + ast.body[0].expression.right.parent, + ast.body[0].expression, + ); + + return {}; + }); + + const config = { + plugins: { + test: { + rules: { + checker: { create: spy }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + linter.verify("foo + bar", config); + assert(spy.calledOnce); + }); + + it("events for each node type should fire", () => { + // spies for various AST node types + const spyLiteral = sinon.spy(), + spyVariableDeclarator = sinon.spy(), + spyVariableDeclaration = sinon.spy(), + spyIdentifier = sinon.spy(), + spyBinaryExpression = sinon.spy(); + + const config = { + plugins: { + test: { + rules: { + checker: { + create() { + return { + Literal: spyLiteral, + VariableDeclarator: + spyVariableDeclarator, + VariableDeclaration: + spyVariableDeclaration, + Identifier: spyIdentifier, + BinaryExpression: + spyBinaryExpression, + }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + sinon.assert.calledOnce(spyVariableDeclaration); + sinon.assert.calledOnce(spyVariableDeclarator); + sinon.assert.calledOnce(spyIdentifier); + sinon.assert.calledTwice(spyLiteral); + sinon.assert.calledOnce(spyBinaryExpression); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should throw an error if a rule is a function", () => { + /** + * Legacy-format rule (a function instead of an object with `create` method). + * @param {RuleContext} context The ESLint rule context object. + * @returns {Object} Listeners. + */ + function functionStyleRule(context) { + return { + Program(node) { + context.report({ node, message: "bad" }); + }, + }; + } + + const config = { + plugins: { + test: { + rules: { + "function-style-rule": functionStyleRule, + }, + }, + }, + rules: { "test/function-style-rule": "error" }, + }; + + assert.throws( + () => linter.verify("foo", config), + TypeError, + "Error while loading rule 'test/function-style-rule': Rule must be an object with a `create` method", + ); + }); + + it("should throw an error if a rule is an object without 'create' method", () => { + const rule = { + create_(context) { + return { + Program(node) { + context.report({ node, message: "bad" }); + }, + }; + }, + }; + + const config = { + plugins: { + test: { + rules: { + "object-rule-without-create": rule, + }, + }, + }, + rules: { "test/object-rule-without-create": "error" }, + }; + + assert.throws( + () => linter.verify("foo", config), + TypeError, + "Error while loading rule 'test/object-rule-without-create': Rule must be an object with a `create` method", + ); + }); + + it("should throw an error if a rule with invalid `meta.schema` is enabled in the configuration", () => { + const config = [ + { + plugins: { + test: { + rules: { + "rule-with-invalid-schema": { + meta: { + schema: true, + }, + create() { + return {}; + }, + }, + }, + }, + }, + }, + { + rules: { "test/rule-with-invalid-schema": "error" }, + }, + ]; + + assert.throws( + () => linter.verify("foo", config), + "Error while processing options validation schema of rule 'test/rule-with-invalid-schema': Rule's `meta.schema` must be an array or object", + ); + }); + + it("should throw an error if a rule with invalid `meta.schema` is enabled in a configuration comment", () => { + const config = [ + { + plugins: { + test: { + rules: { + "rule-with-invalid-schema": { + meta: { + schema: true, + }, + create() { + return {}; + }, + }, + }, + }, + }, + }, + ]; + + assert.throws( + () => + linter.verify( + "/* eslint test/rule-with-invalid-schema: 2 */", + config, + ), + "Error while processing options validation schema of rule 'test/rule-with-invalid-schema': Rule's `meta.schema` must be an array or object", + ); + }); + + it("should throw an error if a rule reports a problem without a message", () => { + const config = { + plugins: { + test: { + rules: { + "invalid-report": { + create: context => ({ + Program(node) { + context.report({ node }); + }, + }), + }, + }, + }, + }, + rules: { "test/invalid-report": "error" }, + }; + + assert.throws( + () => linter.verify("foo", config), + TypeError, + "Missing `message` property in report() call; add a message that describes the linting problem.", + ); + }); + }); + + describe("Rule Context", () => { + describe("context.getFilename()", () => { + const ruleId = "filename-rule"; + + it("has access to the filename", () => { + const config = { + plugins: { + test: { + rules: { + [ruleId]: { + create: context => ({ + Literal(node) { + context.report( + node, + context.getFilename(), + ); + }, + }), + }, + }, + }, + }, + rules: { + [`test/${ruleId}`]: 1, + }, + }; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages[0].message, filename); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("defaults filename to ''", () => { + const config = { + plugins: { + test: { + rules: { + [ruleId]: { + create: context => ({ + Literal(node) { + context.report( + node, + context.getFilename(), + ); + }, + }), + }, + }, + }, + }, + rules: { + [`test/${ruleId}`]: 1, + }, + }; + + const messages = linter.verify("0", config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages[0].message, ""); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("context.filename", () => { + const ruleId = "filename-rule"; + + it("has access to the filename", () => { + const config = { + plugins: { + test: { + rules: { + [ruleId]: { + create: context => ({ + Literal(node) { + assert.strictEqual( + context.getFilename(), + context.filename, + ); + context.report( + node, + context.filename, + ); + }, + }), + }, + }, + }, + }, + rules: { + [`test/${ruleId}`]: 1, + }, + }; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages[0].message, filename); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("defaults filename to ''", () => { + const config = { + plugins: { + test: { + rules: { + [ruleId]: { + create: context => ({ + Literal(node) { + assert.strictEqual( + context.getFilename(), + context.filename, + ); + context.report( + node, + context.filename, + ); + }, + }), + }, + }, + }, + }, + rules: { + [`test/${ruleId}`]: 1, + }, + }; + + const messages = linter.verify("0", config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages[0].message, ""); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("context.getPhysicalFilename()", () => { + const ruleId = "filename-rule"; + + it("has access to the physicalFilename", () => { + const config = { + plugins: { + test: { + rules: { + [ruleId]: { + create: context => ({ + Literal(node) { + context.report( + node, + context.getPhysicalFilename(), + ); + }, + }), + }, + }, + }, + }, + rules: { + [`test/${ruleId}`]: 1, + }, + }; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages[0].message, filename); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("context.physicalFilename", () => { + const ruleId = "filename-rule"; + + it("has access to the physicalFilename", () => { + const config = { + plugins: { + test: { + rules: { + [ruleId]: { + create: context => ({ + Literal(node) { + assert.strictEqual( + context.getPhysicalFilename(), + context.physicalFilename, + ); + context.report( + node, + context.physicalFilename, + ); + }, + }), + }, + }, + }, + }, + rules: { + [`test/${ruleId}`]: 1, + }, + }; + + const messages = linter.verify("0", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages[0].message, filename); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("context.getCwd()", () => { + const code = "a;\nb;"; + const baseConfig = { rules: { "test/checker": "error" } }; + + it("should get cwd correctly in the context", () => { + const cwd = "/cwd"; + const linterWithOption = new Linter({ + cwd, + configType: "flat", + }); + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual( + context.getCwd(), + cwd, + ); + }); + return { Program: spy }; + }, + }, + }, + }, + }, + ...baseConfig, + }; + + linterWithOption.verify(code, config, `${cwd}/file.js`); + assert(spy && spy.calledOnce); + }); + + it("should assign process.cwd() to it if cwd is undefined", () => { + const linterWithOption = new Linter({ configType: "flat" }); + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual( + context.getCwd(), + process.cwd(), + ); + }); + return { Program: spy }; + }, + }, + }, + }, + }, + ...baseConfig, + }; + + linterWithOption.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("should assign process.cwd() to it if the option is undefined", () => { + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual( + context.getCwd(), + process.cwd(), + ); + }); + return { Program: spy }; + }, + }, + }, + }, + }, + ...baseConfig, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("context.cwd", () => { + const code = "a;\nb;"; + const baseConfig = { rules: { "test/checker": "error" } }; + + it("should get cwd correctly in the context", () => { + const cwd = "/cwd"; + const linterWithOption = new Linter({ + cwd, + configType: "flat", + }); + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual( + context.cwd, + cwd, + ); + }); + return { Program: spy }; + }, + }, + }, + }, + }, + ...baseConfig, + }; + + linterWithOption.verify(code, config, `${cwd}/file.js`); + assert(spy && spy.calledOnce); + }); + + it("should assign process.cwd() to it if cwd is undefined", () => { + const linterWithOption = new Linter({ configType: "flat" }); + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual( + context.getCwd(), + context.cwd, + ); + assert.strictEqual( + context.cwd, + process.cwd(), + ); + }); + return { Program: spy }; + }, + }, + }, + }, + }, + ...baseConfig, + }; + + linterWithOption.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("should assign process.cwd() to it if the option is undefined", () => { + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(() => { + assert.strictEqual( + context.getCwd(), + context.cwd, + ); + assert.strictEqual( + context.cwd, + process.cwd(), + ); + }); + return { Program: spy }; + }, + }, + }, + }, + }, + ...baseConfig, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + }); + + describe("Rule Severity", () => { + it("rule should run as warning when set to 1 with a config array", () => { + const ruleId = "semi", + configs = createFlatConfigArray({ + files: ["**/*.js"], + rules: { + [ruleId]: 1, + }, + }); + + configs.normalizeSync(); + const messages = linter.verify("foo", configs, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 1, + "Message length is wrong", + ); + assert.strictEqual(messages[0].ruleId, ruleId); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("rule should run as warning when set to 1 with a plain array", () => { + const ruleId = "semi", + configs = [ + { + files: ["**/*.js"], + rules: { + [ruleId]: 1, + }, + }, + ]; + + const messages = linter.verify("foo", configs, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 1, + "Message length is wrong", + ); + assert.strictEqual(messages[0].ruleId, ruleId); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("rule should run as warning when set to 1 with an object", () => { + const ruleId = "semi", + config = { + files: ["**/*.js"], + rules: { + [ruleId]: 1, + }, + }; + + const messages = linter.verify("foo", config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 1, + "Message length is wrong", + ); + assert.strictEqual(messages[0].ruleId, ruleId); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("Code with a hashbang comment", () => { + const code = "#!bin/program\n\nvar foo;;"; + + it("should preserve line numbers", () => { + const config = { rules: { "no-extra-semi": 1 } }; + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-extra-semi"); + assert.strictEqual(messages[0].nodeType, "EmptyStatement"); + assert.strictEqual(messages[0].line, 3); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should have a comment with the hashbang in it", () => { + const spy = sinon.spy(context => { + const comments = context.sourceCode.getAllComments(); + + assert.strictEqual(comments.length, 1); + assert.strictEqual(comments[0].type, "Shebang"); + return {}; + }); + + const config = { + plugins: { + test: { + rules: { + checker: { create: spy }, + }, + }, + }, + rules: { + "test/checker": "error", + }, + }; + + linter.verify(code, config); + assert(spy.calledOnce); + }); + }); + + describe("Options", () => { + describe("filename", () => { + it("should allow filename to be passed on options object", () => { + const filenameChecker = sinon.spy(context => { + assert.strictEqual(context.filename, "foo.js"); + return {}; + }); + + const config = { + plugins: { + test: { + rules: { + checker: { create: filenameChecker }, + }, + }, + }, + rules: { + "test/checker": "error", + }, + }; + + linter.verify("foo;", config, { filename: "foo.js" }); + assert(filenameChecker.calledOnce); + }); + + it("should allow filename to be passed as third argument", () => { + const filenameChecker = sinon.spy(context => { + assert.strictEqual(context.filename, "bar.js"); + return {}; + }); + + const config = { + plugins: { + test: { + rules: { + checker: { create: filenameChecker }, + }, + }, + }, + rules: { + "test/checker": "error", + }, + }; + + linter.verify("foo;", config, "bar.js"); + assert(filenameChecker.calledOnce); + }); + + it("should default filename to when options object doesn't have filename", () => { + const filenameChecker = sinon.spy(context => { + assert.strictEqual(context.filename, ""); + return {}; + }); + + const config = { + plugins: { + test: { + rules: { + checker: { create: filenameChecker }, + }, + }, + }, + rules: { + "test/checker": "error", + }, + }; + + linter.verify("foo;", config, {}); + assert(filenameChecker.calledOnce); + }); + + it("should default filename to when only two arguments are passed", () => { + const filenameChecker = sinon.spy(context => { + assert.strictEqual(context.filename, ""); + return {}; + }); + + const config = { + plugins: { + test: { + rules: { + checker: { create: filenameChecker }, + }, + }, + }, + rules: { + "test/checker": "error", + }, + }; + + linter.verify("foo;", config); + assert(filenameChecker.calledOnce); + }); + }); + + describe("physicalFilename", () => { + it("should be same as `filename` passed on options object, if no processors are used", () => { + const physicalFilenameChecker = sinon.spy(context => { + assert.strictEqual( + context.getPhysicalFilename(), + context.physicalFilename, + ); + assert.strictEqual(context.physicalFilename, "foo.js"); + return {}; + }); + + const config = { + plugins: { + test: { + rules: { + checker: { + create: physicalFilenameChecker, + }, + }, + }, + }, + rules: { + "test/checker": "error", + }, + }; + + linter.verify("foo;", config, { filename: "foo.js" }); + assert(physicalFilenameChecker.calledOnce); + }); + + it("should default physicalFilename to when options object doesn't have filename", () => { + const physicalFilenameChecker = sinon.spy(context => { + assert.strictEqual( + context.getPhysicalFilename(), + context.physicalFilename, + ); + assert.strictEqual(context.physicalFilename, ""); + return {}; + }); + + const config = { + plugins: { + test: { + rules: { + checker: { + create: physicalFilenameChecker, + }, + }, + }, + }, + rules: { + "test/checker": "error", + }, + }; + + linter.verify("foo;", config, {}); + assert(physicalFilenameChecker.calledOnce); + }); + + it("should default physicalFilename to when only two arguments are passed", () => { + const physicalFilenameChecker = sinon.spy(context => { + assert.strictEqual( + context.getPhysicalFilename(), + context.physicalFilename, + ); + assert.strictEqual(context.physicalFilename, ""); + return {}; + }); + + const config = { + plugins: { + test: { + rules: { + checker: { + create: physicalFilenameChecker, + }, + }, + }, + }, + rules: { + "test/checker": "error", + }, + }; + + linter.verify("foo;", config); + assert(physicalFilenameChecker.calledOnce); + }); + }); + + describe("ruleFilter", () => { + it("should not run rules that are filtered out", () => { + const code = ['alert("test");'].join("\n"); + const config = { + rules: { "no-alert": 1 }, + }; + + const messages = linter.verify(code, config, { + ruleFilter: ({ ruleId }) => ruleId !== "no-alert", + }); + + assert.strictEqual(messages.length, 0); + }); + + it("should run rules that are not filtered out", () => { + const code = ['alert("test");'].join("\n"); + const config = { + rules: { "no-alert": 1 }, + }; + + const messages = linter.verify(code, config, { + ruleFilter: ({ ruleId }) => ruleId === "no-alert", + }); + + assert.strictEqual(messages.length, 1); + }); + + it("should run rules that are not filtered out but not run rules that are filtered out", () => { + const code = ['alert("test");', "fakeVar.run();"].join( + "\n", + ); + const config = { + rules: { "no-alert": 1, "no-undef": 1 }, + }; + + const messages = linter.verify(code, config, { + ruleFilter: ({ ruleId }) => ruleId === "no-alert", + }); + + assert.strictEqual(messages.length, 1); + }); + + it("should filter rules by severity", () => { + const code = ['alert("test")'].join("\n"); + const config = { + rules: { "no-alert": 1, semi: 2 }, + }; + + const messages = linter.verify(code, config, { + ruleFilter: ({ severity }) => severity === 2, + }); + + assert.strictEqual(messages.length, 1); + }); + + it("should ignore disable directives in filtered rules", () => { + const code = [ + "// eslint-disable-next-line no-alert", + 'alert("test")', + ].join("\n"); + const config = { + rules: { "no-alert": 1, semi: 2 }, + }; + + const messages = linter.verify(code, config, { + ruleFilter: ({ severity }) => severity === 2, + reportUnusedDisableDirectives: "error", + }); + + assert.strictEqual(messages.length, 1); + }); + + it("should ignore disable directives in filtered rules even when unused", () => { + const code = [ + "// eslint-disable-next-line no-alert", + 'notAnAlert("test")', + ].join("\n"); + const config = { + rules: { "no-alert": 1, semi: 2 }, + }; + + const messages = linter.verify(code, config, { + ruleFilter: ({ severity }) => severity === 2, + reportUnusedDisableDirectives: "error", + }); + + assert.strictEqual(messages.length, 1); + }); + + it("should not ignore disable directives in non-filtered rules", () => { + const code = [ + "// eslint-disable-next-line semi", + 'alert("test")', + ].join("\n"); + const config = { + rules: { "no-alert": 1, semi: 2 }, + }; + + const messages = linter.verify(code, config, { + ruleFilter: ({ severity }) => severity === 2, + reportUnusedDisableDirectives: "error", + }); + + assert.strictEqual(messages.length, 0); + }); + + it("should report disable directives in non-filtered rules when unused", () => { + const code = [ + "// eslint-disable-next-line semi", + 'alert("test");', + ].join("\n"); + const config = { + rules: { "no-alert": 1, semi: 2 }, + }; + + const messages = linter.verify(code, config, { + ruleFilter: ({ severity }) => severity === 2, + reportUnusedDisableDirectives: "error", + }); + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "Unused eslint-disable directive (no problems were reported from 'semi').", + ); + }); + }); + }); + + describe("Inline Directives", () => { + describe("/*global*/ Comments", () => { + describe("when evaluating code containing /*global */ and /*globals */ blocks", () => { + /** + * Asserts the global variables in the provided code using the specified language options and data. + * @param {string} code The code to verify. + * @param {Object} languageOptions The language options to use. + * @param {Object} [data={}] Additional data for the assertion. + * @returns {void} + */ + function assertGlobalVariable( + code, + languageOptions, + data = {}, + ) { + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ); + const g = getVariable( + scope, + data.name, + ); + + assert.strictEqual( + g.name, + data.name, + ); + assert.strictEqual( + g.writeable, + data.writeable, + ); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + if (languageOptions !== void 0) { + config.languageOptions = languageOptions; + } + + linter.verify(code, config); + assert(spy && spy.calledOnce); + } + + it("variables should be available in global scope", () => { + const code = ` /*global a b:true c:false d:readable e:writeable Math:off */ function foo() {} /*globals f:true*/ /* global ConfigGlobal : readable */ `; - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - const a = getVariable(scope, "a"), - b = getVariable(scope, "b"), - c = getVariable(scope, "c"), - d = getVariable(scope, "d"), - e = getVariable(scope, "e"), - f = getVariable(scope, "f"), - mathGlobal = getVariable(scope, "Math"), - arrayGlobal = getVariable(scope, "Array"), - configGlobal = getVariable(scope, "ConfigGlobal"); - - assert.strictEqual(a.name, "a"); - assert.strictEqual(a.writeable, false); - assert.strictEqual(b.name, "b"); - assert.strictEqual(b.writeable, true); - assert.strictEqual(c.name, "c"); - assert.strictEqual(c.writeable, false); - assert.strictEqual(d.name, "d"); - assert.strictEqual(d.writeable, false); - assert.strictEqual(e.name, "e"); - assert.strictEqual(e.writeable, true); - assert.strictEqual(f.name, "f"); - assert.strictEqual(f.writeable, true); - assert.strictEqual(mathGlobal, null); - assert.strictEqual(arrayGlobal, null); - assert.strictEqual(configGlobal.name, "ConfigGlobal"); - assert.strictEqual(configGlobal.writeable, false); - }); - - return { Program: spy }; - } - } - } - } - }, - rules: { "test/checker": "error" }, - languageOptions: { - globals: { Array: "off", ConfigGlobal: "writeable" } - } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("when evaluating code containing a /*global */ block with sloppy whitespace", () => { - const code = "/* global a b : true c: false*/"; - - it("variables should be available in global scope", () => { - - let spy; - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - a = getVariable(scope, "a"), - b = getVariable(scope, "b"), - c = getVariable(scope, "c"); - - assert.strictEqual(a.name, "a"); - assert.strictEqual(a.writeable, false); - assert.strictEqual(b.name, "b"); - assert.strictEqual(b.writeable, true); - assert.strictEqual(c.name, "c"); - assert.strictEqual(c.writeable, false); - }); - - return { Program: spy }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("when evaluating code containing a line comment", () => { - const code = "//global a \n function f() {}"; - - it("should not introduce a global variable", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(getVariable(scope, "a"), null); - }); - - return { Program: spy }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("when evaluating code containing normal block comments", () => { - const code = "/**/ /*a*/ /*b:true*/ /*foo c:false*/"; - - it("should not introduce a global variable", () => { - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.strictEqual(getVariable(scope, "a"), null); - assert.strictEqual(getVariable(scope, "b"), null); - assert.strictEqual(getVariable(scope, "foo"), null); - assert.strictEqual(getVariable(scope, "c"), null); - }); - - return { Program: spy }; - } - } - } - } - }, - rules: { "test/checker": "error" } - }; - - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - it("should attach a \"/*global\" comment node to declared variables", () => { - const code = "/* global foo */\n/* global bar, baz */"; - let ok = false; - const config = { - plugins: { - test: { - rules: { - test: { - create: context => ({ - Program() { - const scope = context.getScope(); - const sourceCode = context.sourceCode; - const comments = sourceCode.getAllComments(); - - assert.strictEqual(context.getSourceCode(), sourceCode); - assert.strictEqual(2, comments.length); - - const foo = getVariable(scope, "foo"); - - assert.strictEqual(foo.eslintExplicitGlobal, true); - assert.strictEqual(foo.eslintExplicitGlobalComments[0], comments[0]); - - const bar = getVariable(scope, "bar"); - - assert.strictEqual(bar.eslintExplicitGlobal, true); - assert.strictEqual(bar.eslintExplicitGlobalComments[0], comments[1]); - - const baz = getVariable(scope, "baz"); - - assert.strictEqual(baz.eslintExplicitGlobal, true); - assert.strictEqual(baz.eslintExplicitGlobalComments[0], comments[1]); - - ok = true; - } - }) - } - } - } - }, - rules: { "test/test": "error" } - }; - - - linter.verify(code, config); - assert(ok); - }); - - it("should report a linting error when a global is set to an invalid value", () => { - const results = linter.verify("/* global foo: AAAAA, bar: readonly */\nfoo;\nbar;", { rules: { "no-undef": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(results, [ - { - ruleId: null, - severity: 2, - message: "'AAAAA' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')", - line: 1, - column: 1, - endLine: 1, - endColumn: 39, - fatal: true, - nodeType: null - }, - { - ruleId: "no-undef", - messageId: "undef", - severity: 2, - message: "'foo' is not defined.", - line: 2, - column: 1, - endLine: 2, - endColumn: 4, - nodeType: "Identifier" - } - ]); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - }); - - describe("/*exported*/ Comments", () => { - - it("we should behave nicely when no matching variable is found", () => { - const code = "/* exported horse */"; - const config = { rules: {} }; - - linter.verify(code, config, filename, true); - }); - - it("variables should be exported", () => { - const code = "/* exported horse */\n\nvar horse = 'circus'"; - let spy; - - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); - - assert.strictEqual(horse.eslintUsed, true); - }); - - return { Program: spy }; - } - } - } - } - }, - languageOptions: { - sourceType: "script" - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("undefined variables should not be exported", () => { - const code = "/* exported horse */\n\nhorse = 'circus'"; - let spy; - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); - - assert.strictEqual(horse, null); - }); - - return { Program: spy }; - } - } - } - } - }, - languageOptions: { - sourceType: "script" - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("variables should be exported in strict mode", () => { - const code = "/* exported horse */\n'use strict';\nvar horse = 'circus'"; - let spy; - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); - - assert.strictEqual(horse.eslintUsed, true); - }); - - return { Program: spy }; - } - } - } - } - }, - languageOptions: { - sourceType: "script" - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("variables should not be exported in the es6 module environment", () => { - const code = "/* exported horse */\nvar horse = 'circus'"; - let spy; - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); - - assert.strictEqual(horse, null); // there is no global scope at all - }); - - return { Program: spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 6, - sourceType: "module" - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - it("variables should not be exported when in a commonjs file", () => { - const code = "/* exported horse */\nvar horse = 'circus'"; - let spy; - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(), - horse = getVariable(scope, "horse"); - - assert.strictEqual(horse, null); // there is no global scope at all - }); - - return { Program: spy }; - } - } - } - } - }, - languageOptions: { - sourceType: "commonjs" - }, - rules: { "test/checker": "error" } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - }); - - describe("/*eslint*/ Comments", () => { - describe("when evaluating code with comments to enable rules", () => { - - it("should report a violation", () => { - const code = "/*eslint no-alert:1*/ alert('test');"; - const config = { rules: {} }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1, "Incorrect message length"); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].message, "Unexpected alert."); - assert.include(messages[0].nodeType, "CallExpression"); - - assert.strictEqual(suppressedMessages.length, 0, "Incorrect suppressed message length"); - }); - - it("rules should not change initial config", () => { - const config = { - languageOptions: { - sourceType: "script" - }, - rules: { strict: 2 } - }; - const codeA = "/*eslint strict: 0*/ function bar() { return 2; }"; - const codeB = "function foo() { return 1; }"; - let messages = linter.verify(codeA, config, filename, false); - let suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - - messages = linter.verify(codeB, config, filename, false); - suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("rules should not change initial config", () => { - const config = { - languageOptions: { - sourceType: "script" - }, - rules: { quotes: [2, "double"] } - }; - const codeA = "/*eslint quotes: 0*/ function bar() { return '2'; }"; - const codeB = "function foo() { return '1'; }"; - let messages = linter.verify(codeA, config, filename, false); - let suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - - messages = linter.verify(codeB, config, filename, false); - suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("rules should not change initial config", () => { - const config = { rules: { quotes: [2, "double"] } }; - const codeA = "/*eslint quotes: [0, \"single\"]*/ function bar() { return '2'; }"; - const codeB = "function foo() { return '1'; }"; - let messages = linter.verify(codeA, config, filename, false); - let suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - - messages = linter.verify(codeB, config, filename, false); - suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("rules should not change initial config", () => { - const config = { - languageOptions: { - sourceType: "script" - }, - rules: { "no-unused-vars": [2, { vars: "all" }] } - }; - const codeA = "/*eslint no-unused-vars: [0, {\"vars\": \"local\"}]*/ var a = 44;"; - const codeB = "var b = 55;"; - let messages = linter.verify(codeA, config, filename, false); - let suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - - messages = linter.verify(codeB, config, filename, false); - suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with invalid comments to enable rules", () => { - it("should report a violation when the config is not a valid rule configuration", () => { - const messages = linter.verify("/*eslint no-alert:true*/ alert('test');", {}); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - severity: 2, - ruleId: "no-alert", - message: "Inline configuration for rule \"no-alert\" is invalid:\n\tExpected severity of \"off\", 0, \"warn\", 1, \"error\", or 2. You passed \"true\".\n", - line: 1, - column: 1, - endLine: 1, - endColumn: 25, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation when a rule is configured using a string severity that contains uppercase letters", () => { - const messages = linter.verify("/*eslint no-alert: \"Error\"*/ alert('test');", {}); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - severity: 2, - ruleId: "no-alert", - message: "Inline configuration for rule \"no-alert\" is invalid:\n\tExpected severity of \"off\", 0, \"warn\", 1, \"error\", or 2. You passed \"Error\".\n", - line: 1, - column: 1, - endLine: 1, - endColumn: 29, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation when the config violates a rule's schema", () => { - const messages = linter.verify("/* eslint no-alert: [error, {nonExistentPropertyName: true}]*/", {}); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - severity: 2, - ruleId: "no-alert", - message: "Inline configuration for rule \"no-alert\" is invalid:\n\tValue [{\"nonExistentPropertyName\":true}] should NOT have more than 0 items.\n", - line: 1, - column: 1, - endLine: 1, - endColumn: 63, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should apply valid configuration even if there is an invalid configuration present", () => { - const code = [ - "/* eslint no-unused-vars: [ */ // <-- this one is invalid JSON", - "/* eslint no-undef: [\"error\"] */ // <-- this one is fine, and thus should apply", - "foo(); // <-- expected no-undef error here" - ].join("\n"); - - const messages = linter.verify(code, {}); - const suppressedMessages = linter.getSuppressedMessages(); - - // different engines have different JSON parsing error messages - assert.match(messages[0].message, /Failed to parse JSON from ' "no-unused-vars": \['/u); - assert.strictEqual(messages[0].severity, 2); - assert.isTrue(messages[0].fatal); - assert.isNull(messages[0].ruleId); - assert.strictEqual(messages[0].line, 1); - assert.strictEqual(messages[0].column, 1); - assert.isNull(messages[0].nodeType); - - assert.deepStrictEqual( - messages[1], - { - severity: 2, - ruleId: "no-undef", - message: "'foo' is not defined.", - messageId: "undef", - line: 3, - column: 1, - endLine: 3, - endColumn: 4, - nodeType: "Identifier" - } - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - }); - - describe("when evaluating code with comments to disable rules", () => { - - it("should not report a violation", () => { - const config = { rules: { "no-alert": 1 } }; - const messages = linter.verify("/*eslint no-alert:0*/ alert('test');", config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report an error when disabling a non-existent rule in inline comment", () => { - let code = "/*eslint foo:0*/ ;"; - let messages = linter.verify(code, {}, filename); - let suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1, "/*eslint*/ comment should report problem."); - assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); - assert.strictEqual(suppressedMessages.length, 0); - - code = "/*eslint-disable foo*/ ;"; - messages = linter.verify(code, {}, filename); - suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1, "/*eslint-disable*/ comment should report problem."); - assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); - assert.strictEqual(suppressedMessages.length, 0); - - code = "/*eslint-disable-line foo*/ ;"; - messages = linter.verify(code, {}, filename); - suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1, "/*eslint-disable-line*/ comment should report problem."); - assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); - assert.strictEqual(suppressedMessages.length, 0); - - code = "/*eslint-disable-next-line foo*/ ;"; - messages = linter.verify(code, {}, filename); - suppressedMessages = linter.getSuppressedMessages(); - assert.strictEqual(messages.length, 1, "/*eslint-disable-next-line*/ comment should report problem."); - assert.strictEqual(messages[0].message, "Definition for rule 'foo' was not found."); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report an error, when disabling a non-existent rule in config", () => { - const messages = linter.verify("", { rules: { foo: 0 } }, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should throw an error when a non-existent rule in config", () => { - assert.throws(() => { - linter.verify("", { rules: { foo: 1 } }, filename); - }, /Key "rules": Key "foo":/u); - - assert.throws(() => { - linter.verify("", { rules: { foo: 2 } }, filename); - }, /Key "rules": Key "foo":/u); - - }); - }); - - describe("when evaluating code with comments to enable multiple rules", () => { - const code = "/*eslint no-alert:1 no-console:1*/ alert('test'); console.log('test');"; - - it("should report a violation", () => { - const config = { rules: {} }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].message, "Unexpected alert."); - assert.include(messages[0].nodeType, "CallExpression"); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to enable and disable multiple rules", () => { - const code = "/*eslint no-alert:1 no-console:0*/ alert('test'); console.log('test');"; - - it("should report a violation", () => { - const config = { rules: { "no-console": 1, "no-alert": 0 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].message, "Unexpected alert."); - assert.include(messages[0].nodeType, "CallExpression"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to disable and enable configurable rule as part of plugin", () => { - - let baseConfig; - - beforeEach(() => { - baseConfig = { - plugins: { - "test-plugin": { - rules: { - "test-rule": { - create: context => ({ - Literal(node) { - if (node.value === "trigger violation") { - context.report(node, "Reporting violation."); - } - } - }) - } - } - } - } - }; - - }); - - it("should not report a violation when inline comment enables plugin rule and there's no violation", () => { - const config = { ...baseConfig, rules: {} }; - const code = "/*eslint test-plugin/test-rule: 2*/ var a = \"no violation\";"; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report a violation when inline comment disables plugin rule", () => { - const code = "/*eslint test-plugin/test-rule:0*/ var a = \"trigger violation\""; - const config = { ...baseConfig, rules: { "test-plugin/test-rule": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation when the report is right before the comment", () => { - const code = " /* eslint-disable */ "; - - const config = { - plugins: { - test: { - rules: { - checker: { - create: context => ({ - Program() { - context.report({ loc: { line: 1, column: 0 }, message: "foo" }); - } - }) - } - } - } - }, - rules: { - "test/checker": "error" - } - }; - - const problems = linter.verify(code, config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(problems.length, 1); - assert.strictEqual(problems[0].message, "foo"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report a violation when the report is right at the start of the comment", () => { - const code = " /* eslint-disable */ "; - - const config = { - plugins: { - test: { - rules: { - checker: { - create: context => ({ - Program() { - context.report({ loc: { line: 1, column: 1 }, message: "foo" }); - } - }) - } - } - } - }, - rules: { - "test/checker": "error" - } - }; - - const problems = linter.verify(code, config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(problems.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].message, "foo"); - assert.deepStrictEqual(suppressedMessages[0].suppressions, [{ kind: "directive", justification: "" }]); - }); - - it("rules should not change initial config", () => { - const config = { ...baseConfig, rules: { "test-plugin/test-rule": 2 } }; - const codeA = "/*eslint test-plugin/test-rule: 0*/ var a = \"trigger violation\";"; - const codeB = "var a = \"trigger violation\";"; - let messages = linter.verify(codeA, config, filename, false); - let suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - - messages = linter.verify(codeB, config, filename, false); - suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to enable and disable all reporting", () => { - it("should report a violation", () => { - - const code = [ - "/*eslint-disable */", - "alert('test');", - "/*eslint-enable */", - "alert('test');" - ].join("\n"); - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].message, "Unexpected alert."); - assert.include(messages[0].nodeType, "CallExpression"); - assert.strictEqual(messages[0].line, 4); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 2); - }); - - it("should not report a violation", () => { - const code = [ - "/*eslint-disable */", - "alert('test');", - "alert('test');" - ].join("\n"); - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 2); - }); - - it("should not report a violation", () => { - const code = [ - " alert('test1');/*eslint-disable */\n", - "alert('test');", - " alert('test');\n", - "/*eslint-enable */alert('test2');" - ].join(""); - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].column, 21); - assert.strictEqual(messages[1].column, 19); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].column, 1); - assert.strictEqual(suppressedMessages[1].column, 56); - }); - - it("should report a violation", () => { - - const code = [ - "/*eslint-disable */", - "alert('test');", - "/*eslint-disable */", - "alert('test');", - "/*eslint-enable*/", - "alert('test');", - "/*eslint-enable*/" - ].join("\n"); - - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(suppressedMessages.length, 2); - }); - - - it("should not report a violation", () => { - const code = [ - "/*eslint-disable */", - "(function(){ var b = 44;})()", - "/*eslint-enable */;any();" - ].join("\n"); - - const config = { rules: { "no-unused-vars": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-unused-vars"); - }); - - it("should not report a violation", () => { - const code = [ - "(function(){ /*eslint-disable */ var b = 44;})()", - "/*eslint-enable */;any();" - ].join("\n"); - - const config = { rules: { "no-unused-vars": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-unused-vars"); - }); - }); - - describe("when evaluating code with comments to enable and disable multiple comma separated rules", () => { - const code = "/*eslint no-alert:1, no-console:0*/ alert('test'); console.log('test');"; - - it("should report a violation", () => { - const config = { rules: { "no-console": 1, "no-alert": 0 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].message, "Unexpected alert."); - assert.include(messages[0].nodeType, "CallExpression"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to enable configurable rule", () => { - const code = "/*eslint quotes:[2, \"double\"]*/ alert('test');"; - - it("should report a violation", () => { - const config = { rules: { quotes: [2, "single"] } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "quotes"); - assert.strictEqual(messages[0].message, "Strings must use doublequote."); - assert.include(messages[0].nodeType, "Literal"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to enable configurable rule using string severity", () => { - const code = "/*eslint quotes:[\"error\", \"double\"]*/ alert('test');"; - - it("should report a violation", () => { - const config = { rules: { quotes: [2, "single"] } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "quotes"); - assert.strictEqual(messages[0].message, "Strings must use doublequote."); - assert.include(messages[0].nodeType, "Literal"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with incorrectly formatted comments to disable rule", () => { - it("should report a violation", () => { - const code = "/*eslint no-alert:'1'*/ alert('test');"; - - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - - /* - * Incorrectly formatted comment threw error; - * message from caught exception - * may differ amongst UAs, so verifying - * first part only as defined in the - * parseJsonConfig function in lib/eslint.js - */ - assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":'1'':/u); - assert.strictEqual(messages[0].line, 1); - assert.strictEqual(messages[0].column, 1); - - assert.strictEqual(messages[1].ruleId, "no-alert"); - assert.strictEqual(messages[1].message, "Unexpected alert."); - assert.include(messages[1].nodeType, "CallExpression"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation", () => { - const code = "/*eslint no-alert:abc*/ alert('test');"; - - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - - /* - * Incorrectly formatted comment threw error; - * message from caught exception - * may differ amongst UAs, so verifying - * first part only as defined in the - * parseJsonConfig function in lib/eslint.js - */ - assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":abc':/u); - assert.strictEqual(messages[0].line, 1); - assert.strictEqual(messages[0].column, 1); - - assert.strictEqual(messages[1].ruleId, "no-alert"); - assert.strictEqual(messages[1].message, "Unexpected alert."); - assert.include(messages[1].nodeType, "CallExpression"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation", () => { - const code = "/*eslint no-alert:0 2*/ alert('test');"; - - const config = { rules: { "no-alert": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - - /* - * Incorrectly formatted comment threw error; - * message from caught exception - * may differ amongst UAs, so verifying - * first part only as defined in the - * parseJsonConfig function in lib/eslint.js - */ - assert.match(messages[0].message, /^Failed to parse JSON from ' "no-alert":0 2':/u); - assert.strictEqual(messages[0].line, 1); - assert.strictEqual(messages[0].column, 1); - - assert.strictEqual(messages[1].ruleId, "no-alert"); - assert.strictEqual(messages[1].message, "Unexpected alert."); - assert.include(messages[1].nodeType, "CallExpression"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments which have colon in its value", () => { - const code = String.raw` + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ); + const a = getVariable( + scope, + "a", + ), + b = getVariable( + scope, + "b", + ), + c = getVariable( + scope, + "c", + ), + d = getVariable( + scope, + "d", + ), + e = getVariable( + scope, + "e", + ), + f = getVariable( + scope, + "f", + ), + mathGlobal = + getVariable( + scope, + "Math", + ), + arrayGlobal = + getVariable( + scope, + "Array", + ), + configGlobal = + getVariable( + scope, + "ConfigGlobal", + ); + + assert.strictEqual( + a.name, + "a", + ); + assert.strictEqual( + a.writeable, + false, + ); + assert.strictEqual( + b.name, + "b", + ); + assert.strictEqual( + b.writeable, + true, + ); + assert.strictEqual( + c.name, + "c", + ); + assert.strictEqual( + c.writeable, + false, + ); + assert.strictEqual( + d.name, + "d", + ); + assert.strictEqual( + d.writeable, + false, + ); + assert.strictEqual( + e.name, + "e", + ); + assert.strictEqual( + e.writeable, + true, + ); + assert.strictEqual( + f.name, + "f", + ); + assert.strictEqual( + f.writeable, + true, + ); + assert.strictEqual( + mathGlobal, + null, + ); + assert.strictEqual( + arrayGlobal, + null, + ); + assert.strictEqual( + configGlobal.name, + "ConfigGlobal", + ); + assert.strictEqual( + configGlobal.writeable, + false, + ); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + languageOptions: { + globals: { + Array: "off", + ConfigGlobal: "writeable", + }, + }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + // https://github.com/eslint/eslint/issues/18363 + it("not throw when defining a global named __defineSetter__", () => { + assertGlobalVariable( + "/*global __defineSetter__ */", + {}, + { name: "__defineSetter__", writeable: false }, + ); + assertGlobalVariable( + "/*global __defineSetter__ */", + void 0, + { name: "__defineSetter__", writeable: false }, + ); + assertGlobalVariable( + "/*global __defineSetter__ */", + { globals: { __defineSetter__: "off" } }, + { name: "__defineSetter__", writeable: false }, + ); + assertGlobalVariable( + "/*global __defineSetter__ */", + { globals: { __defineSetter__: "writeable" } }, + { name: "__defineSetter__", writeable: false }, + ); + assertGlobalVariable( + "/*global __defineSetter__:writeable */", + {}, + { name: "__defineSetter__", writeable: true }, + ); + }); + }); + + describe("when evaluating code containing a /*global */ block with sloppy whitespace", () => { + const code = "/* global a b : true c: false*/"; + + it("variables should be available in global scope", () => { + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ), + a = getVariable( + scope, + "a", + ), + b = getVariable( + scope, + "b", + ), + c = getVariable( + scope, + "c", + ); + + assert.strictEqual( + a.name, + "a", + ); + assert.strictEqual( + a.writeable, + false, + ); + assert.strictEqual( + b.name, + "b", + ); + assert.strictEqual( + b.writeable, + true, + ); + assert.strictEqual( + c.name, + "c", + ); + assert.strictEqual( + c.writeable, + false, + ); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("when evaluating code containing a line comment", () => { + const code = "//global a \n function f() {}"; + + it("should not introduce a global variable", () => { + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ); + + assert.strictEqual( + getVariable(scope, "a"), + null, + ); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("when evaluating code containing normal block comments", () => { + const code = "/**/ /*a*/ /*b:true*/ /*foo c:false*/"; + + it("should not introduce a global variable", () => { + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ); + + assert.strictEqual( + getVariable(scope, "a"), + null, + ); + assert.strictEqual( + getVariable(scope, "b"), + null, + ); + assert.strictEqual( + getVariable( + scope, + "foo", + ), + null, + ); + assert.strictEqual( + getVariable(scope, "c"), + null, + ); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + rules: { "test/checker": "error" }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + it('should attach a "/*global" comment node to declared variables', () => { + const code = "/* global foo */\n/* global bar, baz */"; + let ok = false; + const config = { + plugins: { + test: { + rules: { + test: { + create: context => ({ + Program(node) { + const scope = + context.sourceCode.getScope( + node, + ); + const sourceCode = + context.sourceCode; + const comments = + sourceCode.getAllComments(); + + assert.strictEqual( + context.getSourceCode(), + sourceCode, + ); + assert.strictEqual( + 2, + comments.length, + ); + + const foo = getVariable( + scope, + "foo", + ); + + assert.strictEqual( + foo.eslintExplicitGlobal, + true, + ); + assert.strictEqual( + foo + .eslintExplicitGlobalComments[0], + comments[0], + ); + + const bar = getVariable( + scope, + "bar", + ); + + assert.strictEqual( + bar.eslintExplicitGlobal, + true, + ); + assert.strictEqual( + bar + .eslintExplicitGlobalComments[0], + comments[1], + ); + + const baz = getVariable( + scope, + "baz", + ); + + assert.strictEqual( + baz.eslintExplicitGlobal, + true, + ); + assert.strictEqual( + baz + .eslintExplicitGlobalComments[0], + comments[1], + ); + + ok = true; + }, + }), + }, + }, + }, + }, + rules: { "test/test": "error" }, + }; + + linter.verify(code, config); + assert(ok); + }); + + it("should report a linting error when a global is set to an invalid value", () => { + const results = linter.verify( + "/* global foo: AAAAA, bar: readonly */\nfoo;\nbar;", + { rules: { "no-undef": "error" } }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(results, [ + { + ruleId: null, + severity: 2, + message: + "'AAAAA' is not a valid configuration for a global (use 'readonly', 'writable', or 'off')", + line: 1, + column: 1, + endLine: 1, + endColumn: 39, + fatal: true, + nodeType: null, + }, + { + ruleId: "no-undef", + messageId: "undef", + severity: 2, + message: "'foo' is not defined.", + line: 2, + column: 1, + endLine: 2, + endColumn: 4, + nodeType: "Identifier", + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("/*exported*/ Comments", () => { + it("we should behave nicely when no matching variable is found", () => { + const code = "/* exported horse */"; + const config = { rules: {} }; + + linter.verify(code, config, filename); + }); + + it("variable should be exported", () => { + const code = "/* exported horse */\n\nvar horse;"; + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ), + horse = getVariable( + scope, + "horse", + ); + + assert.isTrue(horse.eslintUsed); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + languageOptions: { + sourceType: "script", + }, + rules: { "test/checker": "error" }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("`key: value` pair variable should not be exported", () => { + const code = "/* exported horse: true */\n\nvar horse;"; + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ), + horse = getVariable( + scope, + "horse", + ); + + assert.notOk(horse.eslintUsed); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + languageOptions: { + sourceType: "script", + }, + rules: { "test/checker": "error" }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("variables with comma should be exported", () => { + const code = "/* exported horse, dog */\n\nvar horse, dog;"; + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ); + + ["horse", "dog"].forEach( + name => { + assert.isTrue( + getVariable( + scope, + name, + ).eslintUsed, + ); + }, + ); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + languageOptions: { + sourceType: "script", + }, + rules: { "test/checker": "error" }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("variables without comma should not be exported", () => { + const code = "/* exported horse dog */\n\nvar horse, dog;"; + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ); + + ["horse", "dog"].forEach( + name => { + assert.notOk( + getVariable( + scope, + name, + ).eslintUsed, + ); + }, + ); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + languageOptions: { + sourceType: "script", + }, + rules: { "test/checker": "error" }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("variables should be exported", () => { + const code = "/* exported horse */\n\nvar horse = 'circus'"; + let spy; + + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ), + horse = getVariable( + scope, + "horse", + ); + + assert.strictEqual( + horse.eslintUsed, + true, + ); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + languageOptions: { + sourceType: "script", + }, + rules: { "test/checker": "error" }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("undefined variables should not be exported", () => { + const code = "/* exported horse */\n\nhorse = 'circus'"; + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ), + horse = getVariable( + scope, + "horse", + ); + + assert.strictEqual(horse, null); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + languageOptions: { + sourceType: "script", + }, + rules: { "test/checker": "error" }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("variables should be exported in strict mode", () => { + const code = + "/* exported horse */\n'use strict';\nvar horse = 'circus'"; + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ), + horse = getVariable( + scope, + "horse", + ); + + assert.strictEqual( + horse.eslintUsed, + true, + ); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + languageOptions: { + sourceType: "script", + }, + rules: { "test/checker": "error" }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("variables should not be exported in the es6 module environment", () => { + const code = "/* exported horse */\nvar horse = 'circus'"; + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ), + horse = getVariable( + scope, + "horse", + ); + + assert.strictEqual(horse, null); // there is no global scope at all + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + languageOptions: { + ecmaVersion: 6, + sourceType: "module", + }, + rules: { "test/checker": "error" }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + + it("variables should not be exported when in a commonjs file", () => { + const code = "/* exported horse */\nvar horse = 'circus'"; + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ), + horse = getVariable( + scope, + "horse", + ); + + assert.strictEqual(horse, null); // there is no global scope at all + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + languageOptions: { + sourceType: "commonjs", + }, + rules: { "test/checker": "error" }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("/*eslint*/ Comments", () => { + describe("when evaluating code with comments to enable rules", () => { + it("should report a violation", () => { + const code = "/*eslint no-alert:1*/ alert('test');"; + const config = { rules: {} }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 1, + "Incorrect message length", + ); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual( + messages[0].message, + "Unexpected alert.", + ); + assert.include(messages[0].nodeType, "CallExpression"); + + assert.strictEqual( + suppressedMessages.length, + 0, + "Incorrect suppressed message length", + ); + }); + + it("rules should not change initial config", () => { + const config = { + languageOptions: { + sourceType: "script", + }, + rules: { strict: 2 }, + }; + const codeA = + "/*eslint strict: 0*/ function bar() { return 2; }"; + const codeB = "function foo() { return 1; }"; + let messages = linter.verify(codeA, config, filename); + let suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + + messages = linter.verify(codeB, config, filename); + suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("rules should not change initial config", () => { + const config = { + languageOptions: { + sourceType: "script", + }, + rules: { quotes: [2, "double"] }, + }; + const codeA = + "/*eslint quotes: 0*/ function bar() { return '2'; }"; + const codeB = "function foo() { return '1'; }"; + let messages = linter.verify(codeA, config, filename); + let suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + + messages = linter.verify(codeB, config, filename); + suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("rules should not change initial config", () => { + const config = { rules: { quotes: [2, "double"] } }; + const codeA = + "/*eslint quotes: [0, \"single\"]*/ function bar() { return '2'; }"; + const codeB = "function foo() { return '1'; }"; + let messages = linter.verify(codeA, config, filename); + let suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + + messages = linter.verify(codeB, config, filename); + suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("rules should not change initial config", () => { + const config = { + languageOptions: { + sourceType: "script", + }, + rules: { "no-unused-vars": [2, { vars: "all" }] }, + }; + const codeA = + '/*eslint no-unused-vars: [0, {"vars": "local"}]*/ var a = 44;'; + const codeB = "var b = 55;"; + let messages = linter.verify(codeA, config, filename); + let suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + + messages = linter.verify(codeB, config, filename); + suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 0); + }); + + describe("when the rule was already configured", () => { + const plugin = { + rules: { + "has-default-options": { + meta: { + schema: [ + { + type: "string", + }, + ], + defaultOptions: ["option not provided"], + }, + create(context) { + const message = context.options[0]; + + return { + Identifier(node) { + context.report({ + node, + message, + }); + }, + }; + }, + }, + "my-rule": { + meta: { + schema: [ + { + type: "string", + }, + ], + }, + create(context) { + const message = + context.options[0] ?? + "option not provided"; + + return { + Program(node) { + context.report({ + node, + message, + }); + }, + }; + }, + }, + "requires-option": { + meta: { + schema: { + type: "array", + items: [ + { + type: "string", + }, + ], + minItems: 1, + }, + }, + create(context) { + const message = context.options[0]; + + return { + Identifier(node) { + context.report({ + node, + message, + }); + }, + }; + }, + }, + }, + }; + + [ + "off", + "error", + ["off"], + ["error"], + ["off", "bar"], + ["error", "bar"], + ].forEach(ruleConfig => { + const config = { + plugins: { + test: plugin, + }, + rules: { + "test/has-default-options": ruleConfig, + "test/my-rule": ruleConfig, + }, + }; + + it(`severity from the /*eslint*/ comment and options from the config should apply when the comment has only severity (original config: ${JSON.stringify(ruleConfig)})`, () => { + const code = + "/*eslint test/my-rule: 'warn', test/has-default-options: 'warn' */ id"; + const messages = linter.verify(code, config); + const suppressedMessages = + linter.getSuppressedMessages(); + + const expectedMessage = + Array.isArray(ruleConfig) && + ruleConfig.length > 1 + ? ruleConfig[1] + : "option not provided"; + + assert.strictEqual(messages.length, 2); + assert.strictEqual( + messages[0].ruleId, + "test/my-rule", + ); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual( + messages[0].message, + expectedMessage, + ); + assert.strictEqual( + messages[1].ruleId, + "test/has-default-options", + ); + assert.strictEqual(messages[1].severity, 1); + assert.strictEqual( + messages[1].message, + expectedMessage, + ); + assert.strictEqual( + suppressedMessages.length, + 0, + ); + }); + + it(`severity from the /*eslint*/ comment and options from the config should apply when the comment has array with only severity (original config: ${JSON.stringify(ruleConfig)})`, () => { + const code = + "/*eslint test/my-rule: ['warn'], test/has-default-options: ['warn'] */ id"; + const messages = linter.verify(code, config); + const suppressedMessages = + linter.getSuppressedMessages(); + + const expectedMessage = + Array.isArray(ruleConfig) && + ruleConfig.length > 1 + ? ruleConfig[1] + : "option not provided"; + + assert.strictEqual(messages.length, 2); + assert.strictEqual( + messages[0].ruleId, + "test/my-rule", + ); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual( + messages[0].message, + expectedMessage, + ); + assert.strictEqual( + messages[1].ruleId, + "test/has-default-options", + ); + assert.strictEqual(messages[1].severity, 1); + assert.strictEqual( + messages[1].message, + expectedMessage, + ); + assert.strictEqual( + suppressedMessages.length, + 0, + ); + }); + + it(`severity and options from the /*eslint*/ comment should apply when the comment includes options (original config: ${JSON.stringify(ruleConfig)})`, () => { + const code = + "/*eslint test/my-rule: ['warn', 'foo'], test/has-default-options: ['warn', 'foo'] */ id"; + const messages = linter.verify(code, config); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual( + messages[0].ruleId, + "test/my-rule", + ); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual(messages[0].message, "foo"); + assert.strictEqual( + messages[1].ruleId, + "test/has-default-options", + ); + assert.strictEqual(messages[1].severity, 1); + assert.strictEqual(messages[1].message, "foo"); + assert.strictEqual( + suppressedMessages.length, + 0, + ); + }); + }); + + it("reports an unnecessary inline config from the /*eslint*/ comment when the comment disables a rule that is not enabled so can't be turned off", () => { + const code = "/*eslint test/my-rule: 'off' */"; + const config = { + linterOptions: { + reportUnusedInlineConfigs: "error", + }, + plugins: { + test: plugin, + }, + }; + const messages = linter.verify(code, config); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, null); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + "Unused inline config ('test/my-rule' is not enabled so can't be turned off).", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("doesn't report an inline config from the /*eslint*/ comment when the comment enables a rule that is not enabled so can't be turned off", () => { + const code = "/*eslint test/my-rule: 'error' */"; + const config = { + linterOptions: { + reportUnusedInlineConfigs: "error", + }, + plugins: { + test: plugin, + }, + }; + const messages = linter.verify(code, config); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].ruleId, + "test/my-rule", + ); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + "option not provided", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + ["warn", ["warn"], ["warn", "bar"]].forEach( + ruleConfig => { + const config = { + plugins: { + test: plugin, + }, + rules: { + "test/my-rule": ruleConfig, + }, + }; + const configWithLinterOptions = { + ...config, + linterOptions: { + reportUnusedInlineConfigs: "error", + }, + }; + + it(`does not report an unnecessary inline config from the /*eslint*/ comment when the comment has only severity and reportUnusedInlineConfigs is disabled (original config: ${JSON.stringify(ruleConfig)})`, () => { + const code = + "/*eslint test/my-rule: 'warn' */"; + const messages = linter.verify( + code, + config, + ); + const suppressedMessages = + linter.getSuppressedMessages(); + + const expectedMessage = + Array.isArray(ruleConfig) && + ruleConfig.length > 1 + ? ruleConfig[1] + : "option not provided"; + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].ruleId, + "test/my-rule", + ); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual( + messages[0].message, + expectedMessage, + ); + assert.strictEqual( + suppressedMessages.length, + 0, + ); + }); + + it(`reports an unnecessary inline config from the /*eslint*/ comment when the comment has array with only severity and reportUnusedInlineConfigs is enabled (original config: ${JSON.stringify(ruleConfig)})`, () => { + const code = + "/*eslint test/my-rule: ['warn'] */"; + const messages = linter.verify( + code, + configWithLinterOptions, + ); + const suppressedMessages = + linter.getSuppressedMessages(); + + const expectedRuleMessage = + Array.isArray(ruleConfig) && + ruleConfig.length > 1 + ? ruleConfig[1] + : "option not provided"; + + assert.strictEqual(messages.length, 2); + assert.strictEqual( + messages[0].ruleId, + "test/my-rule", + ); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual( + messages[0].message, + expectedRuleMessage, + ); + assert.strictEqual( + messages[1].ruleId, + null, + ); + assert.strictEqual(messages[1].severity, 2); + assert.strictEqual( + messages[1].message, + "Unused inline config ('test/my-rule' is already configured to 'warn').", + ); + assert.strictEqual( + suppressedMessages.length, + 0, + ); + }); + + it(`does not report options or severity from the /*eslint*/ comment when the comment includes new options and reportUnusedInlineConfigs is enabled (original config: ${JSON.stringify(ruleConfig)})`, () => { + const code = + "/*eslint test/my-rule: ['warn', 'foo'] */"; + const messages = linter.verify( + code, + configWithLinterOptions, + ); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].ruleId, + "test/my-rule", + ); + assert.strictEqual(messages[0].severity, 1); + assert.strictEqual( + messages[0].message, + "foo", + ); + assert.strictEqual( + suppressedMessages.length, + 0, + ); + }); + }, + ); + + [ + ["error", 2], + ["warn", 1], + ].forEach(([severity, severityCode]) => { + it(`reports an unnecessary inline config from the /*eslint*/ comment and options from the config when the comment has array with options and reportUnusedInlineConfigs is enabled (severity: ${severity})`, () => { + const code = `/*eslint test/my-rule: ['${severity}', 'bar'] */`; + const config = { + plugins: { + test: plugin, + }, + rules: { + "test/my-rule": [`${severity}`, "bar"], + }, + }; + const messages = linter.verify(code, { + ...config, + linterOptions: { + reportUnusedInlineConfigs: "error", + }, + }); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual( + messages[0].ruleId, + "test/my-rule", + ); + assert.strictEqual( + messages[0].severity, + severityCode, + ); + assert.strictEqual(messages[0].message, "bar"); + assert.strictEqual(messages[1].ruleId, null); + assert.strictEqual(messages[1].severity, 2); + assert.strictEqual( + messages[1].message, + `Unused inline config ('test/my-rule' is already configured to '${severity}' with the same options).`, + ); + assert.strictEqual( + suppressedMessages.length, + 0, + ); + }); + }); + + it("should validate and use originally configured options when /*eslint*/ comment enables rule that was set to 'off' in the configuration", () => { + const code = + "/*eslint test/my-rule: ['warn'], test/requires-option: 'warn' */ foo;"; + const config = { + plugins: { + test: plugin, + }, + rules: { + "test/my-rule": ["off", true], // invalid options for this rule + "test/requires-option": [ + "off", + "Don't use identifier", + ], // valid options for this rule + }, + }; + const messages = linter.verify(code, config); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual( + messages[0].ruleId, + "test/my-rule", + ); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual( + messages[0].message, + 'Inline configuration for rule "test/my-rule" is invalid:\n\tValue true should be string.\n', + ); + assert.strictEqual( + messages[1].ruleId, + "test/requires-option", + ); + assert.strictEqual(messages[1].severity, 1); + assert.strictEqual( + messages[1].message, + "Don't use identifier", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + }); + + describe("when evaluating code with invalid comments to enable rules", () => { + it("should report a violation when the config is not a valid rule configuration", () => { + const messages = linter.verify( + "/*eslint no-alert:true*/ alert('test');", + ); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + severity: 2, + ruleId: "no-alert", + message: + 'Inline configuration for rule "no-alert" is invalid:\n\tExpected severity of "off", 0, "warn", 1, "error", or 2. You passed "true".\n', + line: 1, + column: 1, + endLine: 1, + endColumn: 25, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation when a rule is configured using a string severity that contains uppercase letters", () => { + const messages = linter.verify( + "/*eslint no-alert: \"Error\"*/ alert('test');", + ); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + severity: 2, + ruleId: "no-alert", + message: + 'Inline configuration for rule "no-alert" is invalid:\n\tExpected severity of "off", 0, "warn", 1, "error", or 2. You passed "Error".\n', + line: 1, + column: 1, + endLine: 1, + endColumn: 29, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation when the config violates a rule's schema", () => { + const messages = linter.verify( + "/* eslint no-alert: [error, {nonExistentPropertyName: true}]*/", + ); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + severity: 2, + ruleId: "no-alert", + message: + 'Inline configuration for rule "no-alert" is invalid:\n\tValue [{"nonExistentPropertyName":true}] should NOT have more than 0 items.\n', + line: 1, + column: 1, + endLine: 1, + endColumn: 63, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply valid configuration even if there is an invalid configuration present", () => { + const code = [ + "/* eslint no-unused-vars: [ */ // <-- this one is invalid JSON", + '/* eslint no-undef: ["error"] */ // <-- this one is fine, and thus should apply', + "foo(); // <-- expected no-undef error here", + ].join("\n"); + + const messages = linter.verify(code); + const suppressedMessages = + linter.getSuppressedMessages(); + + // different engines have different JSON parsing error messages + assert.match( + messages[0].message, + /Failed to parse JSON from '"no-unused-vars": \['/u, + ); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.isNull(messages[0].ruleId); + assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages[0].column, 1); + assert.isNull(messages[0].nodeType); + + assert.deepStrictEqual(messages[1], { + severity: 2, + ruleId: "no-undef", + message: "'foo' is not defined.", + messageId: "undef", + line: 3, + column: 1, + endLine: 3, + endColumn: 4, + nodeType: "Identifier", + }); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to disable rules", () => { + it("should not report a violation", () => { + const config = { rules: { "no-alert": 1 } }; + const messages = linter.verify( + "/*eslint no-alert:0*/ alert('test');", + config, + filename, + ); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report an error when disabling a non-existent rule in inline comment", () => { + let code = "/*eslint foo:0*/ ;"; + let messages = linter.verify(code, {}, filename); + let suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 1, + "/*eslint*/ comment should report problem.", + ); + assert.strictEqual( + messages[0].message, + "Definition for rule 'foo' was not found.", + ); + assert.strictEqual(suppressedMessages.length, 0); + + code = "/*eslint-disable foo*/ ;"; + messages = linter.verify(code, {}, filename); + suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual( + messages.length, + 1, + "/*eslint-disable*/ comment should report problem.", + ); + assert.strictEqual( + messages[0].message, + "Definition for rule 'foo' was not found.", + ); + assert.strictEqual(suppressedMessages.length, 0); + + code = "/*eslint-disable-line foo*/ ;"; + messages = linter.verify(code, {}, filename); + suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual( + messages.length, + 1, + "/*eslint-disable-line*/ comment should report problem.", + ); + assert.strictEqual( + messages[0].message, + "Definition for rule 'foo' was not found.", + ); + assert.strictEqual(suppressedMessages.length, 0); + + code = "/*eslint-disable-next-line foo*/ ;"; + messages = linter.verify(code, {}, filename); + suppressedMessages = linter.getSuppressedMessages(); + assert.strictEqual( + messages.length, + 1, + "/*eslint-disable-next-line*/ comment should report problem.", + ); + assert.strictEqual( + messages[0].message, + "Definition for rule 'foo' was not found.", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report an error, when disabling a non-existent rule in config", () => { + const messages = linter.verify( + "", + { rules: { foo: 0 } }, + filename, + ); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should throw an error when a non-existent rule in config", () => { + assert.throws(() => { + linter.verify("", { rules: { foo: 1 } }, filename); + }, /Key "rules": Key "foo":/u); + + assert.throws(() => { + linter.verify("", { rules: { foo: 2 } }, filename); + }, /Key "rules": Key "foo":/u); + }); + }); + + describe("when evaluating code with comments to enable multiple rules", () => { + const code = + "/*eslint no-alert:1 no-console:1*/ alert('test'); console.log('test');"; + + it("should report a violation", () => { + const config = { rules: {} }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual( + messages[0].message, + "Unexpected alert.", + ); + assert.include(messages[0].nodeType, "CallExpression"); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to enable and disable multiple rules", () => { + const code = + "/*eslint no-alert:1 no-console:0*/ alert('test'); console.log('test');"; + + it("should report a violation", () => { + const config = { + rules: { "no-console": 1, "no-alert": 0 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual( + messages[0].message, + "Unexpected alert.", + ); + assert.include(messages[0].nodeType, "CallExpression"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to disable and enable configurable rule as part of plugin", () => { + let baseConfig; + + beforeEach(() => { + baseConfig = { + plugins: { + "test-plugin": { + rules: { + "test-rule": { + create: context => ({ + Literal(node) { + if ( + node.value === + "trigger violation" + ) { + context.report( + node, + "Reporting violation.", + ); + } + }, + }), + }, + }, + }, + }, + }; + }); + + it("should not report a violation when inline comment enables plugin rule and there's no violation", () => { + const config = { ...baseConfig, rules: {} }; + const code = + '/*eslint test-plugin/test-rule: 2*/ var a = "no violation";'; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report a violation when inline comment disables plugin rule", () => { + const code = + '/*eslint test-plugin/test-rule:0*/ var a = "trigger violation"'; + const config = { + ...baseConfig, + rules: { "test-plugin/test-rule": 1 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation when the report is right before the comment", () => { + const code = " /* eslint-disable */ "; + + const config = { + plugins: { + test: { + rules: { + checker: { + create: context => ({ + Program() { + context.report({ + loc: { + line: 1, + column: 0, + }, + message: "foo", + }); + }, + }), + }, + }, + }, + }, + rules: { + "test/checker": "error", + }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }; + + const problems = linter.verify(code, config); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(problems.length, 1); + assert.strictEqual(problems[0].message, "foo"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report a violation when the report is right at the start of the comment", () => { + const code = " /* eslint-disable */ "; + + const config = { + plugins: { + test: { + rules: { + checker: { + create: context => ({ + Program() { + context.report({ + loc: { + line: 1, + column: 1, + }, + message: "foo", + }); + }, + }), + }, + }, + }, + }, + rules: { + "test/checker": "error", + }, + }; + + const problems = linter.verify(code, config); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(problems.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].message, + "foo", + ); + assert.deepStrictEqual( + suppressedMessages[0].suppressions, + [{ kind: "directive", justification: "" }], + ); + }); + + it("rules should not change initial config", () => { + const config = { + ...baseConfig, + rules: { "test-plugin/test-rule": 2 }, + }; + const codeA = + '/*eslint test-plugin/test-rule: 0*/ var a = "trigger violation";'; + const codeB = 'var a = "trigger violation";'; + let messages = linter.verify(codeA, config, filename); + let suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + + messages = linter.verify(codeB, config, filename); + suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with multiple configuration comments for same rule", () => { + let baseConfig; + + beforeEach(() => { + baseConfig = { + plugins: { + "test-plugin": { + rules: { + "no-foo": { + meta: { + schema: [ + { + enum: [ + "bar", + "baz", + "qux", + ], + }, + ], + }, + create(context) { + const replacement = + context.options[0] ?? + "default"; + + return { + "Identifier[name='foo']"( + node, + ) { + context.report( + node, + `Replace 'foo' with '${replacement}'.`, + ); + }, + }; + }, + }, + }, + }, + }, + }; + }); + + it("should apply the first and report an error for the second when there are two", () => { + const code = + "/*eslint test-plugin/no-foo: ['error', 'bar']*/ /*eslint test-plugin/no-foo: ['error', 'baz']*/ foo;"; + + const messages = linter.verify(code, baseConfig); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + severity: 2, + message: + 'Rule "test-plugin/no-foo" is already configured by another configuration comment in the preceding code. This configuration is ignored.', + line: 1, + column: 49, + endLine: 1, + endColumn: 96, + nodeType: null, + }, + { + ruleId: "test-plugin/no-foo", + severity: 2, + message: "Replace 'foo' with 'bar'.", + line: 1, + column: 97, + endLine: 1, + endColumn: 100, + nodeType: "Identifier", + }, + ]); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply the first and report an error for each other when there are more than two", () => { + const code = + "/*eslint test-plugin/no-foo: ['error', 'bar']*/ /*eslint test-plugin/no-foo: ['error', 'baz']*/ /*eslint test-plugin/no-foo: ['error', 'qux']*/ foo;"; + + const messages = linter.verify(code, baseConfig); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + severity: 2, + message: + 'Rule "test-plugin/no-foo" is already configured by another configuration comment in the preceding code. This configuration is ignored.', + line: 1, + column: 49, + endLine: 1, + endColumn: 96, + nodeType: null, + }, + { + ruleId: null, + severity: 2, + message: + 'Rule "test-plugin/no-foo" is already configured by another configuration comment in the preceding code. This configuration is ignored.', + line: 1, + column: 97, + endLine: 1, + endColumn: 144, + nodeType: null, + }, + { + ruleId: "test-plugin/no-foo", + severity: 2, + message: "Replace 'foo' with 'bar'.", + line: 1, + column: 145, + endLine: 1, + endColumn: 148, + nodeType: "Identifier", + }, + ]); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply the first and report an error for the second when both just override severity", () => { + const code = + "/*eslint test-plugin/no-foo: 'warn'*/ /*eslint test-plugin/no-foo: 'error'*/ foo;"; + + const messages = linter.verify(code, { + ...baseConfig, + rules: { "test-plugin/no-foo": ["error", "bar"] }, + }); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + severity: 2, + message: + 'Rule "test-plugin/no-foo" is already configured by another configuration comment in the preceding code. This configuration is ignored.', + line: 1, + column: 39, + endLine: 1, + endColumn: 77, + nodeType: null, + }, + { + ruleId: "test-plugin/no-foo", + severity: 1, + message: "Replace 'foo' with 'bar'.", + line: 1, + column: 78, + endLine: 1, + endColumn: 81, + nodeType: "Identifier", + }, + ]); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply the second if the first has an invalid configuration", () => { + const code = + "/*eslint test-plugin/no-foo: ['error', 'quux']*/ /*eslint test-plugin/no-foo: ['error', 'bar']*/ foo;"; + + const messages = linter.verify(code, baseConfig); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.include( + messages[0].message, + 'Inline configuration for rule "test-plugin/no-foo" is invalid', + ); + assert.strictEqual( + messages[1].message, + "Replace 'foo' with 'bar'.", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply configurations for other rules that are in the same comment as the duplicate", () => { + const code = + "/*eslint test-plugin/no-foo: ['error', 'bar']*/ /*eslint test-plugin/no-foo: ['error', 'baz'], no-alert: ['error']*/ foo; alert();"; + + const messages = linter.verify(code, baseConfig); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 3); + assert.strictEqual( + messages[0].message, + 'Rule "test-plugin/no-foo" is already configured by another configuration comment in the preceding code. This configuration is ignored.', + ); + assert.strictEqual( + messages[1].message, + "Replace 'foo' with 'bar'.", + ); + assert.strictEqual(messages[2].ruleId, "no-alert"); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to enable and disable all reporting", () => { + it("should report a violation", () => { + const code = [ + "/*eslint-disable */", + "alert('test');", + "/*eslint-enable */", + "alert('test');", + ].join("\n"); + const config = { rules: { "no-alert": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual( + messages[0].message, + "Unexpected alert.", + ); + assert.include(messages[0].nodeType, "CallExpression"); + assert.strictEqual(messages[0].line, 4); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[0].line, 2); + }); + + it("should not report a violation", () => { + const code = [ + "/*eslint-disable */", + "alert('test');", + "alert('test');", + ].join("\n"); + const config = { rules: { "no-alert": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 2); + }); + + it("should not report a violation", () => { + const code = [ + " alert('test1');/*eslint-disable */\n", + "alert('test');", + " alert('test');\n", + "/*eslint-enable */alert('test2');", + ].join(""); + const config = { rules: { "no-alert": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].column, 21); + assert.strictEqual(messages[1].column, 19); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual(suppressedMessages[0].column, 1); + assert.strictEqual(suppressedMessages[1].column, 56); + }); + + it("should report a violation", () => { + const code = [ + "/*eslint-disable */", + "alert('test');", + "/*eslint-disable */", + "alert('test');", + "/*eslint-enable*/", + "alert('test');", + "/*eslint-enable*/", + ].join("\n"); + + const config = { + rules: { "no-alert": 1 }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(suppressedMessages.length, 2); + }); + + it("should not report a violation", () => { + const code = [ + "/*eslint-disable */", + "(function(){ var b = 44;})()", + "/*eslint-enable */;any();", + ].join("\n"); + + const config = { rules: { "no-unused-vars": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-unused-vars", + ); + }); + + it("should not report a violation", () => { + const code = [ + "(function(){ /*eslint-disable */ var b = 44;})()", + "/*eslint-enable */;any();", + ].join("\n"); + + const config = { rules: { "no-unused-vars": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-unused-vars", + ); + }); + }); + + describe("when evaluating code with comments to enable and disable multiple comma separated rules", () => { + const code = + "/*eslint no-alert:1, no-console:0*/ alert('test'); console.log('test');"; + + it("should report a violation", () => { + const config = { + rules: { "no-console": 1, "no-alert": 0 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual( + messages[0].message, + "Unexpected alert.", + ); + assert.include(messages[0].nodeType, "CallExpression"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to enable configurable rule", () => { + const code = + "/*eslint quotes:[2, \"double\"]*/ alert('test');"; + + it("should report a violation", () => { + const config = { rules: { quotes: [2, "single"] } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "quotes"); + assert.strictEqual( + messages[0].message, + "Strings must use doublequote.", + ); + assert.include(messages[0].nodeType, "Literal"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to enable configurable rule using string severity", () => { + const code = + '/*eslint quotes:["error", "double"]*/ alert(\'test\');'; + + it("should report a violation", () => { + const config = { rules: { quotes: [2, "single"] } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "quotes"); + assert.strictEqual( + messages[0].message, + "Strings must use doublequote.", + ); + assert.include(messages[0].nodeType, "Literal"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with incorrectly formatted comments to disable rule", () => { + it("should report a violation", () => { + const code = "/*eslint no-alert:'1'*/ alert('test');"; + + const config = { rules: { "no-alert": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + + /* + * Incorrectly formatted comment threw error; + * message from caught exception + * may differ amongst UAs, so verifying + * first part only as defined in the + * parseJsonConfig function in lib/eslint.js + */ + assert.match( + messages[0].message, + /^Failed to parse JSON from '"no-alert":'1'':/u, + ); + assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages[0].column, 1); + assert.strictEqual(messages[0].endLine, 1); + assert.strictEqual(messages[0].endColumn, 24); + assert.strictEqual(messages[0].ruleId, null); + assert.strictEqual(messages[0].fatal, true); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual(messages[0].nodeType, null); + + assert.strictEqual(messages[1].ruleId, "no-alert"); + assert.strictEqual( + messages[1].message, + "Unexpected alert.", + ); + assert.include(messages[1].nodeType, "CallExpression"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation", () => { + const code = "/*eslint no-alert:abc*/ alert('test');"; + + const config = { rules: { "no-alert": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + + /* + * Incorrectly formatted comment threw error; + * message from caught exception + * may differ amongst UAs, so verifying + * first part only as defined in the + * parseJsonConfig function in lib/eslint.js + */ + assert.match( + messages[0].message, + /^Failed to parse JSON from '"no-alert":abc':/u, + ); + assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages[0].column, 1); + assert.strictEqual(messages[0].endLine, 1); + assert.strictEqual(messages[0].endColumn, 24); + assert.strictEqual(messages[0].ruleId, null); + assert.strictEqual(messages[0].fatal, true); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual(messages[0].nodeType, null); + + assert.strictEqual(messages[1].ruleId, "no-alert"); + assert.strictEqual( + messages[1].message, + "Unexpected alert.", + ); + assert.include(messages[1].nodeType, "CallExpression"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation", () => { + const code = + "\n\n\n /*eslint no-alert:0 2*/ alert('test');"; + + const config = { rules: { "no-alert": 1 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + + /* + * Incorrectly formatted comment threw error; + * message from caught exception + * may differ amongst UAs, so verifying + * first part only as defined in the + * parseJsonConfig function in lib/eslint.js + */ + assert.match( + messages[0].message, + /^Failed to parse JSON from '"no-alert":0 2':/u, + ); + assert.strictEqual(messages[0].line, 4); + assert.strictEqual(messages[0].column, 5); + assert.strictEqual(messages[0].endLine, 4); + assert.strictEqual(messages[0].endColumn, 28); + assert.strictEqual(messages[0].ruleId, null); + assert.strictEqual(messages[0].fatal, true); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual(messages[0].nodeType, null); + + assert.strictEqual(messages[1].ruleId, "no-alert"); + assert.strictEqual( + messages[1].message, + "Unexpected alert.", + ); + assert.include(messages[1].nodeType, "CallExpression"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments which have colon in its value", () => { + const code = String.raw` /* eslint max-len: [2, 100, 2, {ignoreUrls: true, ignorePattern: "data:image\\/|\\s*require\\s*\\(|^\\s*loader\\.lazy|-\\*-"}] */ alert('test'); `; - it("should not parse errors, should report a violation", () => { - const messages = linter.verify(code, {}, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "max-len"); - assert.strictEqual(messages[0].message, "This line has a length of 129. Maximum allowed is 100."); - assert.include(messages[0].nodeType, "Program"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments that contain escape sequences", () => { - const code = String.raw` + it("should not parse errors, should report a violation", () => { + const messages = linter.verify(code, {}, filename); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "max-len"); + assert.strictEqual( + messages[0].message, + "This line has a length of 129. Maximum allowed is 100.", + ); + assert.include(messages[0].nodeType, "Program"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments that contain escape sequences", () => { + const code = String.raw` /* eslint max-len: ["error", 1, { ignoreComments: true, ignorePattern: "console\\.log\\(" }] */ console.log("test"); consolexlog("test2"); var a = "test2"; `; - it("should validate correctly", () => { - const config = { rules: {} }; - const messages = linter.verify(code, config, filename); - const [message1, message2] = messages; - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(message1.ruleId, "max-len"); - assert.strictEqual(message1.message, "This line has a length of 21. Maximum allowed is 1."); - assert.strictEqual(message1.line, 4); - assert.strictEqual(message1.column, 1); - assert.include(message1.nodeType, "Program"); - assert.strictEqual(message2.ruleId, "max-len"); - assert.strictEqual(message2.message, "This line has a length of 16. Maximum allowed is 1."); - assert.strictEqual(message2.line, 5); - assert.strictEqual(message2.column, 1); - assert.include(message2.nodeType, "Program"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - }); - - describe("/*eslint-disable*/ and /*eslint-enable*/", () => { - it("should report a violation", () => { - const code = [ - "/*eslint-disable no-alert */", - "alert('test');", - "console.log('test');" // here - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("should report no violation", () => { - const code = [ - "/* eslint-disable quotes */", - "console.log(\"foo\");", - "/* eslint-enable quotes */" - ].join("\n"); - const config = { rules: { quotes: 2 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation", () => { - const code = [ - "/*eslint-disable no-alert, no-console */", - "alert('test');", - "console.log('test');", - "/*eslint-enable*/", - - "alert('test');", // here - "console.log('test');" // here - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].line, 5); - assert.strictEqual(messages[1].ruleId, "no-console"); - assert.strictEqual(messages[1].line, 6); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[1].line, 3); - }); - - it("should report a violation", () => { - const code = [ - "/*eslint-disable no-alert */", - "alert('test');", - "console.log('test');", // here - "/*eslint-enable no-console */", - "alert('test');" - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].line, 5); - }); - - - it("should report a violation", () => { - const code = [ - "/*eslint-disable no-alert, no-console */", - "alert('test');", - "console.log('test');", - "/*eslint-enable no-alert*/", - - "alert('test');", // here - "console.log('test');" - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].line, 5); - - assert.strictEqual(suppressedMessages.length, 3); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[1].line, 3); - assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[2].line, 6); - }); - - - it("should report a violation", () => { - const code = [ - "/*eslint-disable no-alert */", - - "/*eslint-disable no-console */", - "alert('test');", - "console.log('test');", - "/*eslint-enable */", - - "alert('test');", // here - "console.log('test');", // here - - "/*eslint-enable */", - - "alert('test');", // here - "console.log('test');", // here - - "/*eslint-enable*/" - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 4); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].line, 6); - assert.strictEqual(messages[1].ruleId, "no-console"); - assert.strictEqual(messages[1].line, 7); - assert.strictEqual(messages[2].ruleId, "no-alert"); - assert.strictEqual(messages[2].line, 9); - assert.strictEqual(messages[3].ruleId, "no-console"); - assert.strictEqual(messages[3].line, 10); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 3); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[1].line, 4); - }); - - it("should report a violation", () => { - const code = [ - "/*eslint-disable no-alert, no-console */", - "alert('test');", - "console.log('test');", - - "/*eslint-enable no-alert */", - - "alert('test');", // here - "console.log('test');", - - "/*eslint-enable no-console */", - - "alert('test');", // here - "console.log('test');", // here - "/*eslint-enable no-console */" - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 3); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].line, 5); - assert.strictEqual(messages[1].ruleId, "no-alert"); - assert.strictEqual(messages[1].line, 8); - assert.strictEqual(messages[2].ruleId, "no-console"); - assert.strictEqual(messages[2].line, 9); - - assert.strictEqual(suppressedMessages.length, 3); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[1].line, 3); - assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[2].line, 6); - }); - - it("should report a violation when severity is warn", () => { - const code = [ - "/*eslint-disable no-alert, no-console */", - "alert('test');", - "console.log('test');", - - "/*eslint-enable no-alert */", - - "alert('test');", // here - "console.log('test');", - - "/*eslint-enable no-console */", - - "alert('test');", // here - "console.log('test');", // here - "/*eslint-enable no-console */" - ].join("\n"); - const config = { rules: { "no-alert": "warn", "no-console": "warn" } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 3); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].line, 5); - assert.strictEqual(messages[1].ruleId, "no-alert"); - assert.strictEqual(messages[1].line, 8); - assert.strictEqual(messages[2].ruleId, "no-console"); - assert.strictEqual(messages[2].line, 9); - - assert.strictEqual(suppressedMessages.length, 3); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[1].line, 3); - assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[2].line, 6); - }); - - it("should report no violation", () => { - const code = [ - "/*eslint-disable no-unused-vars */", - "var foo; // eslint-disable-line no-unused-vars", - "var bar;", - "/* eslint-enable no-unused-vars */" // here - ].join("\n"); - const config = { rules: { "no-unused-vars": 2 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-unused-vars"); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[1].ruleId, "no-unused-vars"); - assert.strictEqual(suppressedMessages[1].line, 3); - }); - - it("should report a violation with quoted rule names in eslint-disable", () => { - const code = [ - "/*eslint-disable 'no-alert' */", - "alert('test');", - "console.log('test');", // here - "/*eslint-enable */", - "/*eslint-disable \"no-console\" */", - "alert('test');", // here - "console.log('test');" - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-console"); - assert.strictEqual(messages[1].ruleId, "no-alert"); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - }); - - it("should report a violation with quoted rule names in eslint-enable", () => { - const code = [ - "/*eslint-disable no-alert, no-console */", - "alert('test');", - "console.log('test');", - "/*eslint-enable 'no-alert'*/", - "alert('test');", // here - "console.log('test');", - "/*eslint-enable \"no-console\"*/", - "console.log('test');" // here - ].join("\n"); - const config = { rules: { "no-alert": 1, "no-console": 1 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].line, 5); - assert.strictEqual(messages[1].ruleId, "no-console"); - assert.strictEqual(messages[1].line, 8); - - assert.strictEqual(suppressedMessages.length, 3); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 2); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[1].line, 3); - assert.strictEqual(suppressedMessages[2].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[2].line, 6); - }); - }); - - describe("/*eslint-disable-line*/", () => { - - it("should report a violation", () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert", - "console.log('test');" // here - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - assert.strictEqual(messages[0].line, 2); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 1); - }); - - it("should report a violation", () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert", - "console.log('test'); // eslint-disable-line no-console", - "alert('test');" // here - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[0].line, 3); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 1); - assert.strictEqual(suppressedMessages[1].ruleId, "no-console"); - assert.strictEqual(suppressedMessages[1].line, 2); - }); - - it("should report a violation if eslint-disable-line in a block comment is not on a single line", () => { - const code = [ - "/* eslint-disable-line", - "*", - "*/ console.log('test');" // here - ].join("\n"); - const config = { - rules: { - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not disable rule and add an extra report if eslint-disable-line in a block comment is not on a single line", () => { - const code = [ - "alert('test'); /* eslint-disable-line ", - "no-alert */" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - - const messages = linter.verify(code, config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages, [ - { - ruleId: "no-alert", - severity: 1, - line: 1, - column: 1, - endLine: 1, - endColumn: 14, - message: "Unexpected alert.", - messageId: "unexpected", - nodeType: "CallExpression" - }, - { - ruleId: null, - severity: 2, - message: "eslint-disable-line comment should not span multiple lines.", - line: 1, - column: 16, - endLine: 2, - endColumn: 12, - nodeType: null - } - ]); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report a violation for eslint-disable-line in block comment", () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert", - "alert('test'); /*eslint-disable-line no-alert*/" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 2); - }); - - it("should not report a violation", () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert", - "console.log('test'); // eslint-disable-line no-console" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 2); - }); - - it("should not report a violation", () => { - const code = [ - "alert('test') // eslint-disable-line no-alert, quotes, semi", - "console.log('test'); // eslint-disable-line" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "double"], - semi: [1, "always"], - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 5); - }); - - it("should not report a violation", () => { - const code = [ - "alert('test') /* eslint-disable-line no-alert, quotes, semi */", - "console.log('test'); /* eslint-disable-line */" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "double"], - semi: [1, "always"], - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 5); - }); - - it("should ignore violations of multiple rules when specified in mixed comments", () => { - const code = [ - " alert(\"test\"); /* eslint-disable-line no-alert */ // eslint-disable-line quotes" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "single"] - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].ruleId, "quotes"); - }); - - it("should report no violation", () => { - const code = [ - "var foo1; // eslint-disable-line no-unused-vars", - "var foo2; // eslint-disable-line no-unused-vars", - "var foo3; // eslint-disable-line no-unused-vars", - "var foo4; // eslint-disable-line no-unused-vars", - "var foo5; // eslint-disable-line no-unused-vars" - ].join("\n"); - const config = { rules: { "no-unused-vars": 2 } }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 5); - }); - - it("should report a violation with quoted rule names in eslint-disable-line", () => { - const code = [ - "alert('test'); // eslint-disable-line 'no-alert'", - "console.log('test');", // here - "alert('test'); // eslint-disable-line \"no-alert\"" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - assert.strictEqual(messages[0].line, 2); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[0].line, 1); - assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].line, 3); - }); - }); - - describe("/*eslint-disable-next-line*/", () => { - it("should ignore violation of specified rule on next line", () => { - const code = [ - "// eslint-disable-next-line no-alert", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => { - const code = [ - "/* eslint-disable-next-line no-alert */", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => { - const code = [ - "/* eslint-disable-next-line no-alert */", - "alert('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("should not ignore violation if code is not on next line", () => { - const code = [ - "/* eslint-disable-next-line", - "no-alert */alert('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should ignore violation if block comment span multiple lines", () => { - const code = [ - "/* eslint-disable-next-line", - "no-alert */", - "alert('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - // For https://github.com/eslint/eslint/issues/14284 - it("should ignore violation if block comment span multiple lines with description", () => { - const code = ` + it("should validate correctly", () => { + const config = { rules: {} }; + const messages = linter.verify(code, config, filename); + const [message1, message2] = messages; + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(message1.ruleId, "max-len"); + assert.strictEqual( + message1.message, + "This line has a length of 21. Maximum allowed is 1.", + ); + assert.strictEqual(message1.line, 4); + assert.strictEqual(message1.column, 1); + assert.include(message1.nodeType, "Program"); + assert.strictEqual(message2.ruleId, "max-len"); + assert.strictEqual( + message2.message, + "This line has a length of 16. Maximum allowed is 1.", + ); + assert.strictEqual(message2.line, 5); + assert.strictEqual(message2.column, 1); + assert.include(message2.nodeType, "Program"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + }); + + describe("/*eslint-disable*/ and /*eslint-enable*/", () => { + it("should report a violation", () => { + const code = [ + "/*eslint-disable no-alert */", + "alert('test');", + "console.log('test');", // here + ].join("\n"); + const config = { + rules: { "no-alert": 1, "no-console": 1 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + }); + + it("should report no violation", () => { + const code = [ + "/* eslint-disable quotes */", + 'console.log("foo");', + "/* eslint-enable quotes */", + ].join("\n"); + const config = { + rules: { quotes: 2 }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation", () => { + const code = [ + "/*eslint-disable no-alert, no-console */", + "alert('test');", + "console.log('test');", + "/*eslint-enable*/", + + "alert('test');", // here + "console.log('test');", // here + ].join("\n"); + const config = { + rules: { "no-alert": 1, "no-console": 1 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 5); + assert.strictEqual(messages[1].ruleId, "no-console"); + assert.strictEqual(messages[1].line, 6); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual( + suppressedMessages[1].ruleId, + "no-console", + ); + assert.strictEqual(suppressedMessages[1].line, 3); + }); + + it("should report a violation", () => { + const code = [ + "/*eslint-disable no-alert */", + "alert('test');", + "console.log('test');", // here + "/*eslint-enable no-console */", + "alert('test');", + ].join("\n"); + const config = { + rules: { "no-alert": 1, "no-console": 1 }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual( + suppressedMessages[1].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[1].line, 5); + }); + + it("should report a violation", () => { + const code = [ + "/*eslint-disable no-alert, no-console */", + "alert('test');", + "console.log('test');", + "/*eslint-enable no-alert*/", + + "alert('test');", // here + "console.log('test');", + ].join("\n"); + const config = { + rules: { "no-alert": 1, "no-console": 1 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 5); + + assert.strictEqual(suppressedMessages.length, 3); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual( + suppressedMessages[1].ruleId, + "no-console", + ); + assert.strictEqual(suppressedMessages[1].line, 3); + assert.strictEqual( + suppressedMessages[2].ruleId, + "no-console", + ); + assert.strictEqual(suppressedMessages[2].line, 6); + }); + + it("should report a violation", () => { + const code = [ + "/*eslint-disable no-alert */", + + "/*eslint-disable no-console */", + "alert('test');", + "console.log('test');", + "/*eslint-enable */", + + "alert('test');", // here + "console.log('test');", // here + + "/*eslint-enable */", + + "alert('test');", // here + "console.log('test');", // here + + "/*eslint-enable*/", + ].join("\n"); + const config = { + rules: { "no-alert": 1, "no-console": 1 }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 4); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 6); + assert.strictEqual(messages[1].ruleId, "no-console"); + assert.strictEqual(messages[1].line, 7); + assert.strictEqual(messages[2].ruleId, "no-alert"); + assert.strictEqual(messages[2].line, 9); + assert.strictEqual(messages[3].ruleId, "no-console"); + assert.strictEqual(messages[3].line, 10); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[0].line, 3); + assert.strictEqual( + suppressedMessages[1].ruleId, + "no-console", + ); + assert.strictEqual(suppressedMessages[1].line, 4); + }); + + it("should report a violation", () => { + const code = [ + "/*eslint-disable no-alert, no-console */", + "alert('test');", + "console.log('test');", + + "/*eslint-enable no-alert */", + + "alert('test');", // here + "console.log('test');", + + "/*eslint-enable no-console */", + + "alert('test');", // here + "console.log('test');", // here + "/*eslint-enable no-console */", + ].join("\n"); + const config = { + rules: { "no-alert": 1, "no-console": 1 }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 3); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 5); + assert.strictEqual(messages[1].ruleId, "no-alert"); + assert.strictEqual(messages[1].line, 8); + assert.strictEqual(messages[2].ruleId, "no-console"); + assert.strictEqual(messages[2].line, 9); + + assert.strictEqual(suppressedMessages.length, 3); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual( + suppressedMessages[1].ruleId, + "no-console", + ); + assert.strictEqual(suppressedMessages[1].line, 3); + assert.strictEqual( + suppressedMessages[2].ruleId, + "no-console", + ); + assert.strictEqual(suppressedMessages[2].line, 6); + }); + + it("should report a violation when severity is warn", () => { + const code = [ + "/*eslint-disable no-alert, no-console */", + "alert('test');", + "console.log('test');", + + "/*eslint-enable no-alert */", + + "alert('test');", // here + "console.log('test');", + + "/*eslint-enable no-console */", + + "alert('test');", // here + "console.log('test');", // here + "/*eslint-enable no-console */", + ].join("\n"); + const config = { + rules: { "no-alert": "warn", "no-console": "warn" }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 3); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 5); + assert.strictEqual(messages[1].ruleId, "no-alert"); + assert.strictEqual(messages[1].line, 8); + assert.strictEqual(messages[2].ruleId, "no-console"); + assert.strictEqual(messages[2].line, 9); + + assert.strictEqual(suppressedMessages.length, 3); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual( + suppressedMessages[1].ruleId, + "no-console", + ); + assert.strictEqual(suppressedMessages[1].line, 3); + assert.strictEqual( + suppressedMessages[2].ruleId, + "no-console", + ); + assert.strictEqual(suppressedMessages[2].line, 6); + }); + + it("should report no violation", () => { + const code = [ + "/*eslint-disable no-unused-vars */", + "var foo; // eslint-disable-line no-unused-vars", + "var bar;", + "/* eslint-enable no-unused-vars */", // here + ].join("\n"); + const config = { + rules: { "no-unused-vars": 2 }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-unused-vars", + ); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual( + suppressedMessages[1].ruleId, + "no-unused-vars", + ); + assert.strictEqual(suppressedMessages[1].line, 3); + }); + + it("should report a violation with quoted rule names in eslint-disable", () => { + const code = [ + "/*eslint-disable 'no-alert' */", + "alert('test');", + "console.log('test');", // here + "/*eslint-enable */", + '/*eslint-disable "no-console" */', + "alert('test');", // here + "console.log('test');", + ].join("\n"); + const config = { + rules: { "no-alert": 1, "no-console": 1 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-console"); + assert.strictEqual(messages[1].ruleId, "no-alert"); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual( + suppressedMessages[1].ruleId, + "no-console", + ); + }); + + it("should report a violation with quoted rule names in eslint-enable", () => { + const code = [ + "/*eslint-disable no-alert, no-console */", + "alert('test');", + "console.log('test');", + "/*eslint-enable 'no-alert'*/", + "alert('test');", // here + "console.log('test');", + '/*eslint-enable "no-console"*/', + "console.log('test');", // here + ].join("\n"); + const config = { + rules: { "no-alert": 1, "no-console": 1 }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 5); + assert.strictEqual(messages[1].ruleId, "no-console"); + assert.strictEqual(messages[1].line, 8); + + assert.strictEqual(suppressedMessages.length, 3); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[0].line, 2); + assert.strictEqual( + suppressedMessages[1].ruleId, + "no-console", + ); + assert.strictEqual(suppressedMessages[1].line, 3); + assert.strictEqual( + suppressedMessages[2].ruleId, + "no-console", + ); + assert.strictEqual(suppressedMessages[2].line, 6); + }); + }); + + describe("/*eslint-disable-line*/", () => { + it("should report a violation", () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + "console.log('test');", // here + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + assert.strictEqual(messages[0].line, 2); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[0].line, 1); + }); + + it("should report a violation", () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + "console.log('test'); // eslint-disable-line no-console", + "alert('test');", // here + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[0].line, 3); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[0].line, 1); + assert.strictEqual( + suppressedMessages[1].ruleId, + "no-console", + ); + assert.strictEqual(suppressedMessages[1].line, 2); + }); + + it("should report a violation if eslint-disable-line in a block comment is not on a single line", () => { + const code = [ + "/* eslint-disable-line", + "*", + "*/ console.log('test');", // here + ].join("\n"); + const config = { + rules: { + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not disable rule and add an extra report if eslint-disable-line in a block comment is not on a single line", () => { + const code = [ + "alert('test'); /* eslint-disable-line ", + "no-alert */", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: "no-alert", + severity: 1, + line: 1, + column: 1, + endLine: 1, + endColumn: 14, + message: "Unexpected alert.", + messageId: "unexpected", + nodeType: "CallExpression", + }, + { + ruleId: null, + severity: 2, + message: + "eslint-disable-line comment should not span multiple lines.", + line: 1, + column: 16, + endLine: 2, + endColumn: 12, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report a violation for eslint-disable-line in block comment", () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + "alert('test'); /*eslint-disable-line no-alert*/", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 2); + }); + + it("should not report a violation", () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + "console.log('test'); // eslint-disable-line no-console", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 2); + }); + + it("should not report a violation", () => { + const code = [ + "alert('test') // eslint-disable-line no-alert, quotes, semi", + "console.log('test'); // eslint-disable-line", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "double"], + semi: [1, "always"], + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 5); + }); + + it("should not report a violation", () => { + const code = [ + "alert('test') /* eslint-disable-line no-alert, quotes, semi */", + "console.log('test'); /* eslint-disable-line */", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "double"], + semi: [1, "always"], + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 5); + }); + + it("should ignore violations of multiple rules when specified in mixed comments", () => { + const code = [ + ' alert("test"); /* eslint-disable-line no-alert */ // eslint-disable-line quotes', + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "single"], + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[1].ruleId, "quotes"); + }); + + it("should report no violation", () => { + const code = [ + "var foo1; // eslint-disable-line no-unused-vars", + "var foo2; // eslint-disable-line no-unused-vars", + "var foo3; // eslint-disable-line no-unused-vars", + "var foo4; // eslint-disable-line no-unused-vars", + "var foo5; // eslint-disable-line no-unused-vars", + ].join("\n"); + const config = { rules: { "no-unused-vars": 2 } }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 5); + }); + + it("should report a violation with quoted rule names in eslint-disable-line", () => { + const code = [ + "alert('test'); // eslint-disable-line 'no-alert'", + "console.log('test');", // here + "alert('test'); // eslint-disable-line \"no-alert\"", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + assert.strictEqual(messages[0].line, 2); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[0].line, 1); + assert.strictEqual( + suppressedMessages[1].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[1].line, 3); + }); + }); + + describe("/*eslint-disable-next-line*/", () => { + it("should ignore violation of specified rule on next line", () => { + const code = [ + "// eslint-disable-next-line no-alert", + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + }); + + it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => { + const code = [ + "/* eslint-disable-next-line no-alert */", + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + }); + it("should ignore violation of specified rule if eslint-disable-next-line is a block comment", () => { + const code = [ + "/* eslint-disable-next-line no-alert */", + "alert('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + }); + + it("should not ignore violation if code is not on next line", () => { + const code = [ + "/* eslint-disable-next-line", + "no-alert */alert('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ignore violation if block comment span multiple lines", () => { + const code = [ + "/* eslint-disable-next-line", + "no-alert */", + "alert('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + }); + + // For https://github.com/eslint/eslint/issues/14284 + it("should ignore violation if block comment span multiple lines with description", () => { + const code = ` /* eslint-disable-next-line no-alert -- description on why this exception is seen as appropriate but past a comfortable reading line length */ alert("buzz"); `; - const config = { - rules: { - "no-alert": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("should ignore violations only of specified rule", () => { - const code = [ - "// eslint-disable-next-line no-console", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should ignore violations only of specified rule when block comment span multiple lines", () => { - const code = [ - "/* eslint-disable-next-line", - "no-console */", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[1].ruleId, "no-console"); - }); - - it("should ignore violations of multiple rules when specified", () => { - const code = [ - "// eslint-disable-next-line no-alert, quotes", - "alert(\"test\");", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "single"], - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].ruleId, "quotes"); - }); - - it("should ignore violations of multiple rules when specified in multiple lines", () => { - const code = [ - "/* eslint-disable-next-line", - "no-alert,", - "quotes", - "*/", - "alert(\"test\");", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "single"], - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - }); - - it("should ignore violations of multiple rules when specified in mixed comments", () => { - const code = [ - "/* eslint-disable-next-line no-alert */ // eslint-disable-next-line quotes", - "alert(\"test\");" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "single"] - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].ruleId, "quotes"); - }); - - it("should ignore violations of multiple rules when specified in mixed single line and multi line comments", () => { - const code = [ - "/* eslint-disable-next-line", - "no-alert", - "*/ // eslint-disable-next-line quotes", - "alert(\"test\");" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "single"] - } - }; - const messages = linter.verify(code, config, filename); - - assert.strictEqual(messages.length, 0); - }); - - it("should ignore violations of only the specified rule on next line", () => { - const code = [ - "// eslint-disable-next-line quotes", - "alert(\"test\");", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - quotes: [1, "single"], - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "quotes"); - }); - - it("should ignore violations of specified rule on next line only", () => { - const code = [ - "alert('test');", - "// eslint-disable-next-line no-alert", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("should ignore all rule violations on next line if none specified", () => { - const code = [ - "// eslint-disable-next-line", - "alert(\"test\");", - "console.log('test')" - ].join("\n"); - const config = { - rules: { - semi: [1, "never"], - quotes: [1, "single"], - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 3); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].ruleId, "quotes"); - assert.strictEqual(suppressedMessages[2].ruleId, "semi"); - }); - - it("should ignore violations if eslint-disable-next-line is a block comment", () => { - const code = [ - "alert('test');", - "/* eslint-disable-next-line no-alert */", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("should report a violation", () => { - const code = [ - "/* eslint-disable-next-line", - "*", - "*/", - "console.log('test');" // here - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not ignore violations if comment is of the type hashbang", () => { - const code = [ - "#! eslint-disable-next-line no-alert", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].ruleId, "no-alert"); - assert.strictEqual(messages[1].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should ignore violation of specified rule on next line with quoted rule names", () => { - const code = [ - "// eslint-disable-next-line 'no-alert'", - "alert('test');", - "// eslint-disable-next-line \"no-alert\"", - "alert('test');", - "console.log('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1, - "no-console": 1 - } - }; - const messages = linter.verify(code, config, filename); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-console"); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.strictEqual(suppressedMessages[1].ruleId, "no-alert"); - }); - }); - - describe("descriptions in directive comments", () => { - it("should ignore the part preceded by '--' in '/*eslint*/'.", () => { - const aaa = sinon.stub().returns({}); - const bbb = sinon.stub().returns({}); - const config = { - plugins: { - test: { - rules: { - aaa: { create: aaa }, - bbb: { create: bbb } - } - } - } - }; - - const messages = linter.verify(` + const config = { + rules: { + "no-alert": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + }); + + it("should ignore violations only of specified rule", () => { + const code = [ + "// eslint-disable-next-line no-console", + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ignore violations only of specified rule when block comment span multiple lines", () => { + const code = [ + "/* eslint-disable-next-line", + "no-console */", + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }; + const messages = linter.verify(code, config, filename); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[1].ruleId, "no-console"); + }); + + it("should ignore violations of multiple rules when specified", () => { + const code = [ + "// eslint-disable-next-line no-alert, quotes", + 'alert("test");', + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "single"], + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[1].ruleId, "quotes"); + }); + + it("should ignore violations of multiple rules when specified in multiple lines", () => { + const code = [ + "/* eslint-disable-next-line", + "no-alert,", + "quotes", + "*/", + 'alert("test");', + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "single"], + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + }); + + it("should ignore violations of multiple rules when specified in mixed comments", () => { + const code = [ + "/* eslint-disable-next-line no-alert */ // eslint-disable-next-line quotes", + 'alert("test");', + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "single"], + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[1].ruleId, "quotes"); + }); + + it("should ignore violations of multiple rules when specified in mixed single line and multi line comments", () => { + const code = [ + "/* eslint-disable-next-line", + "no-alert", + "*/ // eslint-disable-next-line quotes", + 'alert("test");', + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "single"], + }, + }; + const messages = linter.verify(code, config, filename); + + assert.strictEqual(messages.length, 0); + }); + + it("should ignore violations of only the specified rule on next line", () => { + const code = [ + "// eslint-disable-next-line quotes", + 'alert("test");', + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + quotes: [1, "single"], + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "quotes"); + }); + + it("should ignore violations of specified rule on next line only", () => { + const code = [ + "alert('test');", + "// eslint-disable-next-line no-alert", + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + }); + + it("should ignore all rule violations on next line if none specified", () => { + const code = [ + "// eslint-disable-next-line", + 'alert("test");', + "console.log('test')", + ].join("\n"); + const config = { + rules: { + semi: [1, "never"], + quotes: [1, "single"], + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 3); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual(suppressedMessages[1].ruleId, "quotes"); + assert.strictEqual(suppressedMessages[2].ruleId, "semi"); + }); + + it("should ignore violations if eslint-disable-next-line is a block comment", () => { + const code = [ + "alert('test');", + "/* eslint-disable-next-line no-alert */", + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + }); + + it("should report a violation", () => { + const code = [ + "/* eslint-disable-next-line", + "*", + "*/", + "console.log('test');", // here + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not ignore violations if comment is of the type hashbang", () => { + const code = [ + "#! eslint-disable-next-line no-alert", + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].ruleId, "no-alert"); + assert.strictEqual(messages[1].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ignore violation of specified rule on next line with quoted rule names", () => { + const code = [ + "// eslint-disable-next-line 'no-alert'", + "alert('test');", + '// eslint-disable-next-line "no-alert"', + "alert('test');", + "console.log('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + "no-console": 1, + }, + }; + const messages = linter.verify(code, config, filename); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-console"); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + assert.strictEqual( + suppressedMessages[1].ruleId, + "no-alert", + ); + }); + }); + + describe("descriptions in directive comments", () => { + it("should ignore the part preceded by '--' in '/*eslint*/'.", () => { + const aaa = sinon.stub().returns({}); + const bbb = sinon.stub().returns({}); + const config = { + plugins: { + test: { + rules: { + aaa: { create: aaa }, + bbb: { create: bbb }, + }, + }, + }, + }; + + const messages = linter.verify( + ` /*eslint test/aaa:error -- test/bbb:error */ console.log("hello") - `, config); - const suppressedMessages = linter.getSuppressedMessages(); + `, + config, + ); + const suppressedMessages = linter.getSuppressedMessages(); - // Don't include syntax error of the comment. - assert.deepStrictEqual(messages, []); + // Don't include syntax error of the comment. + assert.deepStrictEqual(messages, []); - // Use only `aaa`. - assert.strictEqual(aaa.callCount, 1); - assert.strictEqual(bbb.callCount, 0); + // Use only `aaa`. + assert.strictEqual(aaa.callCount, 1); + assert.strictEqual(bbb.callCount, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); + assert.strictEqual(suppressedMessages.length, 0); + }); - it("should ignore the part preceded by '--' in '/*globals*/'.", () => { - const messages = linter.verify(` + it("should ignore the part preceded by '--' in '/*globals*/'.", () => { + const messages = linter.verify( + ` /*globals aaa -- bbb */ var aaa = {} var bbb = {} - `, { - languageOptions: { - sourceType: "script" - }, - rules: { "no-redeclare": "error" } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Don't include `bbb` - assert.deepStrictEqual( - messages, - [{ - column: 31, - endColumn: 34, - line: 2, - endLine: 2, - message: "'aaa' is already defined by a variable declaration.", - messageId: "redeclaredBySyntax", - nodeType: "Block", - ruleId: "no-redeclare", - severity: 2 - }] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should ignore the part preceded by '--' in '/*exported*/'.", () => { - const messages = linter.verify(` + `, + { + languageOptions: { + sourceType: "script", + }, + rules: { "no-redeclare": "error" }, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Don't include `bbb` + assert.deepStrictEqual(messages, [ + { + column: 31, + endColumn: 34, + line: 2, + endLine: 2, + message: + "'aaa' is already defined by a variable declaration.", + messageId: "redeclaredBySyntax", + nodeType: "Block", + ruleId: "no-redeclare", + severity: 2, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ignore the part preceded by '--' in '/*exported*/'.", () => { + const messages = linter.verify( + ` /*exported aaa -- bbb */ var aaa = {} var bbb = {} - `, { - languageOptions: { - sourceType: "script" - }, - rules: { "no-unused-vars": "error" } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Don't include `aaa` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endColumn: 28, - endLine: 4, - line: 4, - message: "'bbb' is assigned a value but never used.", - messageId: "unusedVar", - nodeType: "Identifier", - ruleId: "no-unused-vars", - severity: 2 - }] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should ignore the part preceded by '--' in '/*eslint-disable*/'.", () => { - const messages = linter.verify(` + `, + { + languageOptions: { + sourceType: "script", + }, + rules: { "no-unused-vars": "error" }, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Don't include `aaa` + assert.deepStrictEqual(messages, [ + { + column: 25, + endColumn: 28, + endLine: 4, + line: 4, + message: + "'bbb' is assigned a value but never used.", + messageId: "unusedVar", + nodeType: "Identifier", + ruleId: "no-unused-vars", + severity: 2, + suggestions: [ + { + data: { + varName: "bbb", + }, + desc: "Remove unused variable 'bbb'.", + fix: { + range: [99, 111], + text: "", + }, + messageId: "removeVar", + }, + ], + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ignore the part preceded by '--' in '/*eslint-disable*/'.", () => { + const messages = linter.verify( + ` /*eslint-disable no-redeclare -- no-unused-vars */ var aaa = {} var aaa = {} - `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Do include `no-unused-vars` but not `no-redeclare` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endLine: 4, - endColumn: 28, - line: 4, - message: "'aaa' is assigned a value but never used.", - messageId: "unusedVar", - nodeType: "Identifier", - ruleId: "no-unused-vars", - severity: 2 - }] - ); - - assert.deepStrictEqual( - suppressedMessages, - [{ - column: 25, - endColumn: 28, - endLine: 4, - line: 4, - message: "'aaa' is already defined.", - messageId: "redeclared", - nodeType: "Identifier", - ruleId: "no-redeclare", - severity: 2, - suppressions: [{ kind: "directive", justification: "no-unused-vars" }] - }] - ); - }); - - it("should ignore the part preceded by '--' in '/*eslint-enable*/'.", () => { - const messages = linter.verify(` + `, + { + rules: { + "no-redeclare": "error", + "no-unused-vars": "error", + }, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Do include `no-unused-vars` but not `no-redeclare` + assert.deepStrictEqual(messages, [ + { + column: 25, + endLine: 4, + endColumn: 28, + line: 4, + message: + "'aaa' is assigned a value but never used.", + messageId: "unusedVar", + nodeType: "Identifier", + ruleId: "no-unused-vars", + severity: 2, + }, + ]); + + assert.deepStrictEqual(suppressedMessages, [ + { + column: 25, + endColumn: 28, + endLine: 4, + line: 4, + message: "'aaa' is already defined.", + messageId: "redeclared", + nodeType: "Identifier", + ruleId: "no-redeclare", + severity: 2, + suppressions: [ + { + kind: "directive", + justification: "no-unused-vars", + }, + ], + }, + ]); + }); + + it("should ignore the part preceded by '--' in '/*eslint-enable*/'.", () => { + const messages = linter.verify( + ` /*eslint-disable no-redeclare, no-unused-vars */ /*eslint-enable no-redeclare -- no-unused-vars */ var aaa = {} var aaa = {} - `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Do include `no-redeclare` but not `no-unused-vars` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endLine: 5, - endColumn: 28, - line: 5, - message: "'aaa' is already defined.", - messageId: "redeclared", - nodeType: "Identifier", - ruleId: "no-redeclare", - severity: 2 - }] - ); - - assert.deepStrictEqual( - suppressedMessages, - [{ - column: 25, - endLine: 5, - endColumn: 28, - line: 5, - message: "'aaa' is assigned a value but never used.", - messageId: "unusedVar", - nodeType: "Identifier", - ruleId: "no-unused-vars", - severity: 2, - suppressions: [{ kind: "directive", justification: "" }] - }] - ); - }); - - it("should ignore the part preceded by '--' in '//eslint-disable-line'.", () => { - const messages = linter.verify(` + `, + { + rules: { + "no-redeclare": "error", + "no-unused-vars": "error", + }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Do include `no-redeclare` but not `no-unused-vars` + assert.deepStrictEqual(messages, [ + { + column: 25, + endLine: 5, + endColumn: 28, + line: 5, + message: "'aaa' is already defined.", + messageId: "redeclared", + nodeType: "Identifier", + ruleId: "no-redeclare", + severity: 2, + }, + ]); + + assert.deepStrictEqual(suppressedMessages, [ + { + column: 25, + endLine: 5, + endColumn: 28, + line: 5, + message: + "'aaa' is assigned a value but never used.", + messageId: "unusedVar", + nodeType: "Identifier", + ruleId: "no-unused-vars", + severity: 2, + suppressions: [ + { kind: "directive", justification: "" }, + ], + }, + ]); + }); + + it("should ignore the part preceded by '--' in '//eslint-disable-line'.", () => { + const messages = linter.verify( + ` var aaa = {} //eslint-disable-line no-redeclare -- no-unused-vars var aaa = {} //eslint-disable-line no-redeclare -- no-unused-vars - `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Do include `no-unused-vars` but not `no-redeclare` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endLine: 3, - endColumn: 28, - line: 3, - message: "'aaa' is assigned a value but never used.", - messageId: "unusedVar", - nodeType: "Identifier", - ruleId: "no-unused-vars", - severity: 2 - }] - ); - - assert.deepStrictEqual( - suppressedMessages, - [{ - column: 25, - endLine: 3, - endColumn: 28, - line: 3, - message: "'aaa' is already defined.", - messageId: "redeclared", - nodeType: "Identifier", - ruleId: "no-redeclare", - severity: 2, - suppressions: [{ kind: "directive", justification: "no-unused-vars" }] - }] - ); - }); - - it("should ignore the part preceded by '--' in '/*eslint-disable-line*/'.", () => { - const messages = linter.verify(` + `, + { + rules: { + "no-redeclare": "error", + "no-unused-vars": "error", + }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Do include `no-unused-vars` but not `no-redeclare` + assert.deepStrictEqual(messages, [ + { + column: 25, + endLine: 3, + endColumn: 28, + line: 3, + message: + "'aaa' is assigned a value but never used.", + messageId: "unusedVar", + nodeType: "Identifier", + ruleId: "no-unused-vars", + severity: 2, + }, + ]); + + assert.deepStrictEqual(suppressedMessages, [ + { + column: 25, + endLine: 3, + endColumn: 28, + line: 3, + message: "'aaa' is already defined.", + messageId: "redeclared", + nodeType: "Identifier", + ruleId: "no-redeclare", + severity: 2, + suppressions: [ + { + kind: "directive", + justification: "no-unused-vars", + }, + ], + }, + ]); + }); + + it("should ignore the part preceded by '--' in '/*eslint-disable-line*/'.", () => { + const messages = linter.verify( + ` var aaa = {} /*eslint-disable-line no-redeclare -- no-unused-vars */ var aaa = {} /*eslint-disable-line no-redeclare -- no-unused-vars */ - `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Do include `no-unused-vars` but not `no-redeclare` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endLine: 3, - endColumn: 28, - line: 3, - message: "'aaa' is assigned a value but never used.", - messageId: "unusedVar", - nodeType: "Identifier", - ruleId: "no-unused-vars", - severity: 2 - }] - ); - - assert.deepStrictEqual( - suppressedMessages, - [{ - column: 25, - endLine: 3, - endColumn: 28, - line: 3, - message: "'aaa' is already defined.", - messageId: "redeclared", - nodeType: "Identifier", - ruleId: "no-redeclare", - severity: 2, - suppressions: [{ kind: "directive", justification: "no-unused-vars" }] - }] - ); - }); - - it("should ignore the part preceded by '--' in '//eslint-disable-next-line'.", () => { - const messages = linter.verify(` + `, + { + rules: { + "no-redeclare": "error", + "no-unused-vars": "error", + }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Do include `no-unused-vars` but not `no-redeclare` + assert.deepStrictEqual(messages, [ + { + column: 25, + endLine: 3, + endColumn: 28, + line: 3, + message: + "'aaa' is assigned a value but never used.", + messageId: "unusedVar", + nodeType: "Identifier", + ruleId: "no-unused-vars", + severity: 2, + }, + ]); + + assert.deepStrictEqual(suppressedMessages, [ + { + column: 25, + endLine: 3, + endColumn: 28, + line: 3, + message: "'aaa' is already defined.", + messageId: "redeclared", + nodeType: "Identifier", + ruleId: "no-redeclare", + severity: 2, + suppressions: [ + { + kind: "directive", + justification: "no-unused-vars", + }, + ], + }, + ]); + }); + + it("should ignore the part preceded by '--' in '//eslint-disable-next-line'.", () => { + const messages = linter.verify( + ` //eslint-disable-next-line no-redeclare -- no-unused-vars var aaa = {} //eslint-disable-next-line no-redeclare -- no-unused-vars var aaa = {} - `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Do include `no-unused-vars` but not `no-redeclare` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endLine: 5, - endColumn: 28, - line: 5, - message: "'aaa' is assigned a value but never used.", - messageId: "unusedVar", - nodeType: "Identifier", - ruleId: "no-unused-vars", - severity: 2 - }] - ); - - assert.deepStrictEqual( - suppressedMessages, - [{ - column: 25, - endLine: 5, - endColumn: 28, - line: 5, - message: "'aaa' is already defined.", - messageId: "redeclared", - nodeType: "Identifier", - ruleId: "no-redeclare", - severity: 2, - suppressions: [{ kind: "directive", justification: "no-unused-vars" }] - }] - ); - }); - - it("should ignore the part preceded by '--' in '/*eslint-disable-next-line*/'.", () => { - const messages = linter.verify(` + `, + { + rules: { + "no-redeclare": "error", + "no-unused-vars": "error", + }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Do include `no-unused-vars` but not `no-redeclare` + assert.deepStrictEqual(messages, [ + { + column: 25, + endLine: 5, + endColumn: 28, + line: 5, + message: + "'aaa' is assigned a value but never used.", + messageId: "unusedVar", + nodeType: "Identifier", + ruleId: "no-unused-vars", + severity: 2, + }, + ]); + + assert.deepStrictEqual(suppressedMessages, [ + { + column: 25, + endLine: 5, + endColumn: 28, + line: 5, + message: "'aaa' is already defined.", + messageId: "redeclared", + nodeType: "Identifier", + ruleId: "no-redeclare", + severity: 2, + suppressions: [ + { + kind: "directive", + justification: "no-unused-vars", + }, + ], + }, + ]); + }); + + it("should ignore the part preceded by '--' in '/*eslint-disable-next-line*/'.", () => { + const messages = linter.verify( + ` /*eslint-disable-next-line no-redeclare -- no-unused-vars */ var aaa = {} /*eslint-disable-next-line no-redeclare -- no-unused-vars */ var aaa = {} - `, { rules: { "no-redeclare": "error", "no-unused-vars": "error" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - // Do include `no-unused-vars` but not `no-redeclare` - assert.deepStrictEqual( - messages, - [{ - column: 25, - endLine: 5, - endColumn: 28, - line: 5, - message: "'aaa' is assigned a value but never used.", - messageId: "unusedVar", - nodeType: "Identifier", - ruleId: "no-unused-vars", - severity: 2 - }] - ); - - assert.deepStrictEqual( - suppressedMessages, - [{ - column: 25, - endLine: 5, - endColumn: 28, - line: 5, - message: "'aaa' is already defined.", - messageId: "redeclared", - nodeType: "Identifier", - ruleId: "no-redeclare", - severity: 2, - suppressions: [{ kind: "directive", justification: "no-unused-vars" }] - }] - ); - }); - - it("should not ignore the part preceded by '--' if the '--' is not surrounded by whitespaces.", () => { - const rule = sinon.stub().returns({}); - const config = { - plugins: { - test: { - rules: { - "a--rule": { create: rule } - } - } - } - }; - - const messages = linter.verify(` + `, + { + rules: { + "no-redeclare": "error", + "no-unused-vars": "error", + }, + linterOptions: { reportUnusedDisableDirectives: 0 }, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Do include `no-unused-vars` but not `no-redeclare` + assert.deepStrictEqual(messages, [ + { + column: 25, + endLine: 5, + endColumn: 28, + line: 5, + message: + "'aaa' is assigned a value but never used.", + messageId: "unusedVar", + nodeType: "Identifier", + ruleId: "no-unused-vars", + severity: 2, + }, + ]); + + assert.deepStrictEqual(suppressedMessages, [ + { + column: 25, + endLine: 5, + endColumn: 28, + line: 5, + message: "'aaa' is already defined.", + messageId: "redeclared", + nodeType: "Identifier", + ruleId: "no-redeclare", + severity: 2, + suppressions: [ + { + kind: "directive", + justification: "no-unused-vars", + }, + ], + }, + ]); + }); + + it("should not ignore the part preceded by '--' if the '--' is not surrounded by whitespaces.", () => { + const rule = sinon.stub().returns({}); + const config = { + plugins: { + test: { + rules: { + "a--rule": { create: rule }, + }, + }, + }, + }; + + const messages = linter.verify( + ` /*eslint test/a--rule:error */ console.log("hello") - `, config); - const suppressedMessages = linter.getSuppressedMessages(); - - // Don't include syntax error of the comment. - assert.deepStrictEqual(messages, []); - - // Use `a--rule`. - assert.strictEqual(rule.callCount, 1); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should ignore the part preceded by '--' even if the '--' is longer than 2.", () => { - const aaa = sinon.stub().returns({}); - const bbb = sinon.stub().returns({}); - const config = { - plugins: { - test: { - rules: { - aaa: { create: aaa }, - bbb: { create: bbb } - } - } - } - }; - - const messages = linter.verify(` + `, + config, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Don't include syntax error of the comment. + assert.deepStrictEqual(messages, []); + + // Use `a--rule`. + assert.strictEqual(rule.callCount, 1); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ignore the part preceded by '--' even if the '--' is longer than 2.", () => { + const aaa = sinon.stub().returns({}); + const bbb = sinon.stub().returns({}); + const config = { + plugins: { + test: { + rules: { + aaa: { create: aaa }, + bbb: { create: bbb }, + }, + }, + }, + }; + + const messages = linter.verify( + ` /*eslint test/aaa:error -------- test/bbb:error */ console.log("hello") - `, config); - const suppressedMessages = linter.getSuppressedMessages(); - - // Don't include syntax error of the comment. - assert.deepStrictEqual(messages, []); - - // Use only `aaa`. - assert.strictEqual(aaa.callCount, 1); - assert.strictEqual(bbb.callCount, 0); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should ignore the part preceded by '--' with line breaks.", () => { - const aaa = sinon.stub().returns({}); - const bbb = sinon.stub().returns({}); - const config = { - plugins: { - test: { - rules: { - aaa: { create: aaa }, - bbb: { create: bbb } - } - } - } - }; - - const messages = linter.verify(` + `, + config, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Don't include syntax error of the comment. + assert.deepStrictEqual(messages, []); + + // Use only `aaa`. + assert.strictEqual(aaa.callCount, 1); + assert.strictEqual(bbb.callCount, 0); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ignore the part preceded by '--' with line breaks.", () => { + const aaa = sinon.stub().returns({}); + const bbb = sinon.stub().returns({}); + const config = { + plugins: { + test: { + rules: { + aaa: { create: aaa }, + bbb: { create: bbb }, + }, + }, + }, + }; + + const messages = linter.verify( + ` /*eslint test/aaa:error -------- test/bbb:error */ console.log("hello") - `, config); - const suppressedMessages = linter.getSuppressedMessages(); - - // Don't include syntax error of the comment. - assert.deepStrictEqual(messages, []); - - // Use only `aaa`. - assert.strictEqual(aaa.callCount, 1); - assert.strictEqual(bbb.callCount, 0); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("allowInlineConfig option", () => { - describe("when evaluating code with comments to change config when allowInlineConfig is enabled", () => { - it("should report a violation for disabling rules", () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: false - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation for global variable declarations", () => { - let ok = false; - const code = [ - "/* global foo */" - ].join("\n"); - const config = { - plugins: { - test: { - rules: { - test: { - create: context => ({ - Program() { - const scope = context.getScope(); - const sourceCode = context.sourceCode; - const comments = sourceCode.getAllComments(); - - assert.strictEqual(context.getSourceCode(), sourceCode); - assert.strictEqual(1, comments.length); - - const foo = getVariable(scope, "foo"); - - assert.notOk(foo); - - ok = true; - } - }) - } - } - } - }, - rules: { - "test/test": 2 - } - }; - - linter.verify(code, config, { allowInlineConfig: false }); - assert(ok); - }); - - it("should report a violation for eslint-disable", () => { - const code = [ - "/* eslint-disable */", - "alert('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: false - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not report a violation for rule changes", () => { - const code = [ - "/*eslint no-alert:2*/", - "alert('test');" - ].join("\n"); - const config = { - rules: { - "no-alert": 0 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: false - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report a violation for disable-line", () => { - const code = [ - "alert('test'); // eslint-disable-line" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: false - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].ruleId, "no-alert"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - }); - - describe("when evaluating code with 'noInlineConfig'", () => { - for (const directive of [ - "globals foo", - "global foo", - "exported foo", - "eslint eqeqeq: error", - "eslint-disable eqeqeq", - "eslint-disable-line eqeqeq", - "eslint-disable-next-line eqeqeq", - "eslint-enable eqeqeq" - ]) { - // eslint-disable-next-line no-loop-func -- No closures - it(`should warn '/* ${directive} */' if 'noInlineConfig' was given.`, () => { - const messages = linter.verify(`/* ${directive} */`, { - linterOptions: { - noInlineConfig: true - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages.length, 1); - assert.deepStrictEqual(messages[0].fatal, void 0); - assert.deepStrictEqual(messages[0].ruleId, null); - assert.deepStrictEqual(messages[0].severity, 1); - assert.deepStrictEqual(messages[0].message, `'/* ${directive} */' has no effect because you have 'noInlineConfig' setting in your config.`); - - assert.strictEqual(suppressedMessages.length, 0); - }); - } - - for (const directive of [ - "eslint-disable-line eqeqeq", - "eslint-disable-next-line eqeqeq" - ]) { - // eslint-disable-next-line no-loop-func -- No closures - it(`should warn '// ${directive}' if 'noInlineConfig' was given.`, () => { - const messages = linter.verify(`// ${directive}`, { - linterOptions: { - noInlineConfig: true - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages.length, 1); - assert.deepStrictEqual(messages[0].fatal, void 0); - assert.deepStrictEqual(messages[0].ruleId, null); - assert.deepStrictEqual(messages[0].severity, 1); - assert.deepStrictEqual(messages[0].message, `'// ${directive}' has no effect because you have 'noInlineConfig' setting in your config.`); - - assert.strictEqual(suppressedMessages.length, 0); - }); - } - - it("should not warn if 'noInlineConfig' and '--no-inline-config' were given.", () => { - const messages = linter.verify("/* globals foo */", { - linterOptions: { - noInlineConfig: true - } - }, { allowInlineConfig: false }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { - it("should not report a violation", () => { - const code = [ - "alert('test'); // eslint-disable-line no-alert" - ].join("\n"); - const config = { - rules: { - "no-alert": 1 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: true - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - }); - - }); - - - describe("config.noInlineConfig + options.allowInlineConfig", () => { - - it("should report both a rule violation and a warning about inline config", () => { - const code = [ - "/* eslint-disable */ // <-- this should be inline config warning", - "foo(); // <-- this should be no-undef error" - ].join("\n"); - const config = { - rules: { - "no-undef": 2 - }, - linterOptions: { - noInlineConfig: true - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: true - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "'/* eslint-disable */' has no effect because you have 'noInlineConfig' setting in your config.", - line: 1, - column: 1, - endLine: 1, - endColumn: 21, - severity: 1, - nodeType: null - }, - { - ruleId: "no-undef", - messageId: "undef", - message: "'foo' is not defined.", - line: 2, - endLine: 2, - column: 1, - endColumn: 4, - severity: 2, - nodeType: "Identifier" - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - - it("should report both a rule violation without warning about inline config when noInlineConfig is true and allowInlineConfig is false", () => { - const code = [ - "/* eslint-disable */ // <-- this should be inline config warning", - "foo(); // <-- this should be no-undef error" - ].join("\n"); - const config = { - rules: { - "no-undef": 2 - }, - linterOptions: { - noInlineConfig: true - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: false - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.deepStrictEqual( - messages, - [ - { - ruleId: "no-undef", - messageId: "undef", - message: "'foo' is not defined.", - line: 2, - endLine: 2, - column: 1, - endColumn: 4, - severity: 2, - nodeType: "Identifier" - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report both a rule violation without warning about inline config when both are false", () => { - const code = [ - "/* eslint-disable */ // <-- this should be inline config warning", - "foo(); // <-- this should be no-undef error" - ].join("\n"); - const config = { - rules: { - "no-undef": 2 - }, - linterOptions: { - noInlineConfig: false - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: false - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.deepStrictEqual( - messages, - [ - { - ruleId: "no-undef", - messageId: "undef", - message: "'foo' is not defined.", - line: 2, - endLine: 2, - column: 1, - endColumn: 4, - severity: 2, - nodeType: "Identifier" - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report one suppresed problem when noInlineConfig is false and allowInlineConfig is true", () => { - const code = [ - "/* eslint-disable */ // <-- this should be inline config warning", - "foo(); // <-- this should be no-undef error" - ].join("\n"); - const config = { - rules: { - "no-undef": 2 - }, - linterOptions: { - noInlineConfig: false - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: true - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - assert.strictEqual(suppressedMessages.length, 1); - assert.deepStrictEqual( - suppressedMessages, - [ - { - ruleId: "no-undef", - messageId: "undef", - message: "'foo' is not defined.", - line: 2, - endLine: 2, - column: 1, - endColumn: 4, - severity: 2, - nodeType: "Identifier", - suppressions: [ - { - justification: "", - kind: "directive" - } - ] - } - ] - ); - - }); - }); - - describe("reportUnusedDisableDirectives option", () => { - it("reports problems for unused eslint-disable comments", () => { - const messages = linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("reports problems for unused eslint-disable comments (error)", () => { - const messages = linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: "error" }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("reports problems for unused eslint-disable comments (warn)", () => { - const messages = linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: "warn" }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 1, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("reports problems for unused eslint-disable comments (in config) (boolean value, true)", () => { - const messages = linter.verify("/* eslint-disable */", { - linterOptions: { - reportUnusedDisableDirectives: true - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 1, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("does not report problems for unused eslint-disable comments (in config) (boolean value, false)", () => { - const messages = linter.verify("/* eslint-disable */", { - linterOptions: { - reportUnusedDisableDirectives: false - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages, []); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("does not report problems for unused eslint-disable comments (in config) (string value, off)", () => { - const messages = linter.verify("/* eslint-disable */", { - linterOptions: { - reportUnusedDisableDirectives: "off" - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages, []); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("reports problems for unused eslint-disable comments (in config) (string value, error)", () => { - const messages = linter.verify("/* eslint-disable */", { - linterOptions: { - reportUnusedDisableDirectives: "error" - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("throws with invalid string for reportUnusedDisableDirectives in config", () => { - assert.throws(() => linter.verify("/* eslint-disable */", { - linterOptions: { - reportUnusedDisableDirectives: "foo" - } - }), 'Key "linterOptions": Key "reportUnusedDisableDirectives": Expected one of: "error", "warn", "off", 0, 1, 2, or a boolean.'); - }); - - it("throws with invalid type for reportUnusedDisableDirectives in config", () => { - assert.throws(() => linter.verify("/* eslint-disable */", { - linterOptions: { - reportUnusedDisableDirectives: {} - } - }), 'Key "linterOptions": Key "reportUnusedDisableDirectives": Expected one of: "error", "warn", "off", 0, 1, 2, or a boolean.'); - }); - - it("reports problems for partially unused eslint-disable comments (in config)", () => { - const code = "alert('test'); // eslint-disable-line no-alert, no-redeclare"; - const config = { - linterOptions: { - reportUnusedDisableDirectives: "warn" - }, - rules: { - "no-alert": 1, - "no-redeclare": 1 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: true - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'no-redeclare').", - line: 1, - column: 16, - fix: { - range: [46, 60], - text: "" - }, - severity: 1, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - }); - - it("reports problems for unused eslint-disable-next-line comments (in config)", () => { - assert.deepStrictEqual( - linter.verify("// eslint-disable-next-line", { - linterOptions: { - reportUnusedDisableDirectives: "warn" - } - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 27], - text: " " - }, - severity: 1, - nodeType: null - } - ] - ); - }); - - it("reports problems for unused multiline eslint-disable-next-line comments (in config)", () => { - assert.deepStrictEqual( - linter.verify("/* \neslint-disable-next-line\n */", { - linterOptions: { - reportUnusedDisableDirectives: "warn" - } - }), - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 32], - text: " " - }, - severity: 1, - nodeType: null - } - ] - ); - }); - - it("reports problems for partially unused eslint-disable-next-line comments (in config)", () => { - const code = "// eslint-disable-next-line no-alert, no-redeclare \nalert('test');"; - const config = { - linterOptions: { - reportUnusedDisableDirectives: "warn" - }, - rules: { - "no-alert": 1, - "no-redeclare": 1 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: true - }); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'no-redeclare').", - line: 1, - column: 1, - fix: { - range: [36, 50], - text: "" - }, - severity: 1, - nodeType: null - } - ] - ); - }); - - it("reports problems for partially unused multiline eslint-disable-next-line comments (in config)", () => { - const code = ` + `, + config, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + // Don't include syntax error of the comment. + assert.deepStrictEqual(messages, []); + + // Use only `aaa`. + assert.strictEqual(aaa.callCount, 1); + assert.strictEqual(bbb.callCount, 0); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("allowInlineConfig option", () => { + describe("when evaluating code with comments to change config when allowInlineConfig is enabled", () => { + it("should report a violation for disabling rules", () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false, + }); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation for global variable declarations", () => { + let ok = false; + const code = ["/* global foo */"].join("\n"); + const config = { + plugins: { + test: { + rules: { + test: { + create: context => ({ + Program(node) { + const scope = + context.sourceCode.getScope( + node, + ); + const sourceCode = + context.sourceCode; + const comments = + sourceCode.getAllComments(); + + assert.strictEqual( + context.getSourceCode(), + sourceCode, + ); + assert.strictEqual( + 1, + comments.length, + ); + + const foo = getVariable( + scope, + "foo", + ); + + assert.notOk(foo); + + ok = true; + }, + }), + }, + }, + }, + }, + rules: { + "test/test": 2, + }, + }; + + linter.verify(code, config, { + allowInlineConfig: false, + }); + assert(ok); + }); + + it("should report a violation for eslint-disable", () => { + const code = [ + "/* eslint-disable */", + "alert('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false, + }); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not report a violation for rule changes", () => { + const code = [ + "/*eslint no-alert:2*/", + "alert('test');", + ].join("\n"); + const config = { + rules: { + "no-alert": 0, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false, + }); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report a violation for disable-line", () => { + const code = [ + "alert('test'); // eslint-disable-line", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false, + }); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-alert"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with 'noInlineConfig'", () => { + for (const directive of [ + "globals foo", + "global foo", + "exported foo", + "eslint eqeqeq: error", + "eslint-disable eqeqeq", + "eslint-disable-line eqeqeq", + "eslint-disable-next-line eqeqeq", + "eslint-enable eqeqeq", + ]) { + // eslint-disable-next-line no-loop-func -- No closures + it(`should warn '/* ${directive} */' if 'noInlineConfig' was given.`, () => { + const messages = linter.verify( + `/* ${directive} */`, + { + linterOptions: { + noInlineConfig: true, + }, + }, + ); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages.length, 1); + assert.deepStrictEqual(messages[0].fatal, void 0); + assert.deepStrictEqual(messages[0].ruleId, null); + assert.deepStrictEqual(messages[0].severity, 1); + assert.deepStrictEqual( + messages[0].message, + `'/* ${directive} */' has no effect because you have 'noInlineConfig' setting in your config.`, + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + } + + for (const directive of [ + "eslint-disable-line eqeqeq", + "eslint-disable-next-line eqeqeq", + ]) { + // eslint-disable-next-line no-loop-func -- No closures + it(`should warn '// ${directive}' if 'noInlineConfig' was given.`, () => { + const messages = linter.verify(`// ${directive}`, { + linterOptions: { + noInlineConfig: true, + }, + }); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages.length, 1); + assert.deepStrictEqual(messages[0].fatal, void 0); + assert.deepStrictEqual(messages[0].ruleId, null); + assert.deepStrictEqual(messages[0].severity, 1); + assert.deepStrictEqual( + messages[0].message, + `'// ${directive}' has no effect because you have 'noInlineConfig' setting in your config.`, + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + } + + it("should not warn if 'noInlineConfig' and '--no-inline-config' were given.", () => { + const messages = linter.verify( + "/* globals foo */", + { + linterOptions: { + noInlineConfig: true, + }, + }, + { allowInlineConfig: false }, + ); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => { + it("should not report a violation", () => { + const code = [ + "alert('test'); // eslint-disable-line no-alert", + ].join("\n"); + const config = { + rules: { + "no-alert": 1, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true, + }); + const suppressedMessages = + linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + }); + }); + }); + + describe("config.noInlineConfig + options.allowInlineConfig", () => { + it("should report both a rule violation and a warning about inline config", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error", + ].join("\n"); + const config = { + rules: { + "no-undef": 2, + }, + linterOptions: { + noInlineConfig: true, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "'/* eslint-disable */' has no effect because you have 'noInlineConfig' setting in your config.", + line: 1, + column: 1, + endLine: 1, + endColumn: 21, + severity: 1, + nodeType: null, + }, + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier", + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report both a rule violation without warning about inline config when noInlineConfig is true and allowInlineConfig is false", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error", + ].join("\n"); + const config = { + rules: { + "no-undef": 2, + }, + linterOptions: { + noInlineConfig: true, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual(messages, [ + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier", + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report both a rule violation without warning about inline config when both are false", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error", + ].join("\n"); + const config = { + rules: { + "no-undef": 2, + }, + linterOptions: { + noInlineConfig: false, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: false, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.deepStrictEqual(messages, [ + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier", + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report one suppressed problem when noInlineConfig is false and allowInlineConfig is true", () => { + const code = [ + "/* eslint-disable */ // <-- this should be inline config warning", + "foo(); // <-- this should be no-undef error", + ].join("\n"); + const config = { + rules: { + "no-undef": 2, + }, + linterOptions: { + noInlineConfig: false, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + assert.strictEqual(suppressedMessages.length, 1); + assert.deepStrictEqual(suppressedMessages, [ + { + ruleId: "no-undef", + messageId: "undef", + message: "'foo' is not defined.", + line: 2, + endLine: 2, + column: 1, + endColumn: 4, + severity: 2, + nodeType: "Identifier", + suppressions: [ + { + justification: "", + kind: "directive", + }, + ], + }, + ]); + }); + }); + + describe("reportUnusedDisableDirectives option", () => { + it("reports problems for unused eslint-disable comments", () => { + const messages = linter.verify( + "/* eslint-disable */", + {}, + { reportUnusedDisableDirectives: true }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-disable comments (error)", () => { + const messages = linter.verify( + "/* eslint-disable */", + {}, + { reportUnusedDisableDirectives: "error" }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-disable comments (warn)", () => { + const messages = linter.verify( + "/* eslint-disable */", + {}, + { reportUnusedDisableDirectives: "warn" }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 1, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-disable comments (in config) (boolean value, true)", () => { + const messages = linter.verify("/* eslint-disable */", { + linterOptions: { + reportUnusedDisableDirectives: true, + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 1, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("does not report problems for unused eslint-disable comments (in config) (boolean value, false)", () => { + const messages = linter.verify("/* eslint-disable */", { + linterOptions: { + reportUnusedDisableDirectives: false, + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, []); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("does not report problems for unused eslint-disable comments (in config) (string value, off)", () => { + const messages = linter.verify("/* eslint-disable */", { + linterOptions: { + reportUnusedDisableDirectives: "off", + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, []); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-disable comments (in config) (string value, error)", () => { + const messages = linter.verify("/* eslint-disable */", { + linterOptions: { + reportUnusedDisableDirectives: "error", + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("throws with invalid string for reportUnusedDisableDirectives in config", () => { + assert.throws( + () => + linter.verify("/* eslint-disable */", { + linterOptions: { + reportUnusedDisableDirectives: "foo", + }, + }), + 'Key "linterOptions": Key "reportUnusedDisableDirectives": Expected one of: "error", "warn", "off", 0, 1, 2, or a boolean.', + ); + }); + + it("throws with invalid type for reportUnusedDisableDirectives in config", () => { + assert.throws( + () => + linter.verify("/* eslint-disable */", { + linterOptions: { + reportUnusedDisableDirectives: {}, + }, + }), + 'Key "linterOptions": Key "reportUnusedDisableDirectives": Expected one of: "error", "warn", "off", 0, 1, 2, or a boolean.', + ); + }); + + it("reports problems for partially unused eslint-disable comments (in config)", () => { + const code = + "alert('test'); // eslint-disable-line no-alert, no-redeclare"; + const config = { + linterOptions: { + reportUnusedDisableDirectives: "warn", + }, + rules: { + "no-alert": 1, + "no-redeclare": 1, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'no-redeclare').", + line: 1, + column: 16, + fix: { + range: [46, 60], + text: "", + }, + severity: 1, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-alert", + ); + }); + + it("reports problems for unused eslint-disable-next-line comments (in config)", () => { + assert.deepStrictEqual( + linter.verify("// eslint-disable-next-line", { + linterOptions: { + reportUnusedDisableDirectives: "warn", + }, + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 27], + text: " ", + }, + severity: 1, + nodeType: null, + }, + ], + ); + }); + + it("reports problems for unused multiline eslint-disable-next-line comments (in config)", () => { + assert.deepStrictEqual( + linter.verify("/* \neslint-disable-next-line\n */", { + linterOptions: { + reportUnusedDisableDirectives: "warn", + }, + }), + [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 32], + text: " ", + }, + severity: 1, + nodeType: null, + }, + ], + ); + }); + + it("reports problems for partially unused eslint-disable-next-line comments (in config)", () => { + const code = + "// eslint-disable-next-line no-alert, no-redeclare \nalert('test');"; + const config = { + linterOptions: { + reportUnusedDisableDirectives: "warn", + }, + rules: { + "no-alert": 1, + "no-redeclare": 1, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true, + }); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'no-redeclare').", + line: 1, + column: 1, + fix: { + range: [36, 50], + text: "", + }, + severity: 1, + nodeType: null, + }, + ]); + }); + + it("reports problems for partially unused multiline eslint-disable-next-line comments (in config)", () => { + const code = ` /* eslint-disable-next-line no-alert, no-redeclare -- * Here's a very long description about why this configuration is necessary * along with some additional information **/ alert('test'); `; - const config = { - linterOptions: { - reportUnusedDisableDirectives: "warn" - }, - rules: { - "no-alert": 1, - "no-redeclare": 1 - } - }; - - const messages = linter.verify(code, config, { - filename, - allowInlineConfig: true - }); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported from 'no-redeclare').", - line: 2, - column: 21, - fix: { - range: [57, 71], - text: "" - }, - severity: 1, - nodeType: null - } - ] - ); - }); - - it("reports problems for unused eslint-enable comments", () => { - const messages = linter.verify("/* eslint-enable */", {}, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", - line: 1, - column: 1, - fix: { - range: [0, 19], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("reports problems for unused eslint-enable comments with ruleId", () => { - const messages = linter.verify("/* eslint-enable no-alert */", {}, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'no-alert').", - line: 1, - column: 1, - fix: { - range: [0, 28], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("reports problems for unused eslint-enable comments with mismatch ruleId", () => { - const code = [ - "/* eslint-disable no-alert */", - "alert(\"test\");", - "/* eslint-enable no-console */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found for 'no-console').", - line: 3, - column: 1, - fix: { - range: [45, 75], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 1); - }); - - it("reports problems for unused eslint-enable comments with used eslint-enable comments", () => { - const code = [ - "/* eslint-disable no-alert -- j1 */", - "alert(\"test\");", - "/* eslint-disable no-alert -- j2 */", - "alert(\"test\");", - "/* eslint-enable no-alert -- j3 */", - "/* eslint-enable -- j4 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-enable directive (no matching eslint-disable directives were found).", - line: 6, - column: 1, - fix: { - range: [137, 162], - text: " " - }, - severity: 2, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - assert.strictEqual(suppressedMessages[1].suppressions.length, 2); - }); - - it("reports problems for multiple eslint-enable comments with same ruleId", () => { - const code = [ - "/* eslint-disable no-alert -- j1 */", - "alert(\"test\"); //", - "/* eslint-enable no-alert -- j2 */", - "/* eslint-enable no-alert -- j3 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].line, 4); - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - }); - - it("reports problems for multiple eslint-enable comments without ruleId (Rule is already enabled)", () => { - const code = [ - "/* eslint-disable no-alert -- j1 */", - "alert(\"test\"); //", - "/* eslint-enable no-alert -- j2 */", - "/* eslint-enable -- j3 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].line, 4); - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - }); - - it("reports problems for multiple eslint-enable comments with ruleId (Rule is already enabled by eslint-enable comments without ruleId)", () => { - const code = [ - "/* eslint-disable no-alert -- j1 */", - "alert(\"test\"); //", - "/* eslint-enable -- j3 */", - "/* eslint-enable no-alert -- j2 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].line, 4); - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - }); - - it("reports problems for eslint-enable comments without ruleId (Two rules are already enabled)", () => { - const code = [ - "/* eslint-disable no-alert, no-console -- j1 */", - "alert(\"test\"); //", - "console.log(\"test\"); //", - "/* eslint-enable no-alert -- j2 */", - "/* eslint-enable no-console -- j3 */", - "/* eslint-enable -- j4 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2, - "no-console": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].line, 6); - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - assert.strictEqual(suppressedMessages[1].suppressions.length, 1); - }); - - it("reports problems for multiple eslint-enable comments with ruleId (Two rules are already enabled by eslint-enable comments without ruleId)", () => { - const code = [ - "/* eslint-disable no-alert, no-console -- j1 */", - "alert(\"test\"); //", - "console.log(\"test\"); //", - "/* eslint-enable -- j2 */", - "/* eslint-enable no-console -- j3 */", - "/* eslint-enable no-alert -- j4 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2, - "no-console": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].line, 5); - assert.strictEqual(messages[1].line, 6); - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - assert.strictEqual(suppressedMessages[1].suppressions.length, 1); - }); - - it("reports problems for multiple eslint-enable comments", () => { - const code = [ - "/* eslint-disable no-alert, no-console -- j1 */", - "alert(\"test\"); //", - "console.log(\"test\"); //", - "/* eslint-enable no-console -- j2 */", - "/* eslint-enable -- j3 */", - "/* eslint-enable no-alert -- j4 */", - "/* eslint-enable -- j5 */" - ].join("\n"); - const config = { - rules: { - "no-alert": 2, - "no-console": 2 - } - }; - const messages = linter.verify(code, config, { reportUnusedDisableDirectives: true }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 2); - assert.strictEqual(messages[0].line, 6); - assert.strictEqual(messages[1].line, 7); - assert.strictEqual(suppressedMessages.length, 2); - assert.strictEqual(suppressedMessages[0].suppressions.length, 1); - assert.strictEqual(suppressedMessages[1].suppressions.length, 1); - }); - - it("reports problems for unused eslint-disable comments (warn)", () => { - const messages = linter.verify("/* eslint-disable */", {}, { reportUnusedDisableDirectives: "warn" }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual( - messages, - [ - { - ruleId: null, - message: "Unused eslint-disable directive (no problems were reported).", - line: 1, - column: 1, - fix: { - range: [0, 20], - text: " " - }, - severity: 1, - nodeType: null - } - ] - ); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - describe("autofix", () => { - const alwaysReportsRule = { - create(context) { - return { - Program(node) { - context.report({ message: "bad code", loc: node.loc.end }); - }, - "Identifier[name=bad]"(node) { - context.report({ message: "bad id", loc: node.loc }); - } - }; - } - }; - - const neverReportsRule = { - create() { - return {}; - } - }; - - const ruleCount = 3; - const usedRules = Array.from( - { length: ruleCount }, - (_, index) => `used${index ? `-${index}` : ""}` // "used", "used-1", "used-2" - ); - const unusedRules = usedRules.map(name => `un${name}`); // "unused", "unused-1", "unused-2" - - const config = { - plugins: { - test: { - rules: {} - } - }, - linterOptions: { - reportUnusedDisableDirectives: "warn" - }, - rules: { - ...Object.fromEntries(usedRules.map(name => [`test/${name}`, "error"])), - ...Object.fromEntries(unusedRules.map(name => [`test/${name}`, "error"])) - } - }; - - beforeEach(() => { - config.plugins.test.rules = { - ...Object.fromEntries(usedRules.map(name => [name, alwaysReportsRule])), - ...Object.fromEntries(unusedRules.map(name => [name, neverReportsRule])) - }; - }); - - const tests = [ - - //----------------------------------------------- - // Removing the entire comment - //----------------------------------------------- - - { - code: "// eslint-disable-line test/unused", - output: " " - }, - { - code: "foo// eslint-disable-line test/unused", - output: "foo " - }, - { - code: "// eslint-disable-line ,test/unused,", - output: " " - }, - { - code: "// eslint-disable-line test/unused-1, test/unused-2", - output: " " - }, - { - code: "// eslint-disable-line ,test/unused-1,, test/unused-2,, -- comment", - output: " " - }, - { - code: "// eslint-disable-next-line test/unused\n", - output: " \n" - }, - { - code: "// eslint-disable-next-line test/unused\nfoo", - output: " \nfoo" - }, - { - code: "/* eslint-disable \ntest/unused\n*/", - output: " " - }, - { - code: "/* eslint-enable \ntest/unused\n*/", - output: " " - }, - - //----------------------------------------------- - // Removing only individual rules - //----------------------------------------------- - - // content before the first rule should not be changed - { - code: "//eslint-disable-line test/unused, test/used", - output: "//eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/unused, test/used", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/unused, test/used", - output: "// eslint-disable-line test/used" - }, - { - code: "/*\neslint-disable test/unused, test/used*/", - output: "/*\neslint-disable test/used*/" - }, - { - code: "/*\n eslint-disable test/unused, test/used*/", - output: "/*\n eslint-disable test/used*/" - }, - { - code: "/*\r\neslint-disable test/unused, test/used*/", - output: "/*\r\neslint-disable test/used*/" - }, - { - code: "/*\u2028eslint-disable test/unused, test/used*/", - output: "/*\u2028eslint-disable test/used*/" - }, - { - code: "/*\u00A0eslint-disable test/unused, test/used*/", - output: "/*\u00A0eslint-disable test/used*/" - }, - { - code: "/* eslint-disable test/used */ bad /*\neslint-enable test/unused, test/used*/", - output: "/* eslint-disable test/used */ bad /*\neslint-enable test/used*/" - }, - { - code: "/* eslint-disable test/used */ bad /*\n eslint-enable test/unused, test/used*/", - output: "/* eslint-disable test/used */ bad /*\n eslint-enable test/used*/" - }, - { - code: "/* eslint-disable test/used */ bad /*\r\neslint-enable test/unused, test/used*/", - output: "/* eslint-disable test/used */ bad /*\r\neslint-enable test/used*/" - }, - { - code: "/* eslint-disable test/used */ bad /*\u2028eslint-enable test/unused, test/used*/", - output: "/* eslint-disable test/used */ bad /*\u2028eslint-enable test/used*/" - }, - { - code: "/* eslint-disable test/used */ bad /*\u00A0eslint-enable test/unused, test/used*/", - output: "/* eslint-disable test/used */ bad /*\u00A0eslint-enable test/used*/" - }, - { - code: "// eslint-disable-line test/unused, test/used", - output: "// eslint-disable-line test/used" - }, - { - code: "/* eslint-disable\ntest/unused, test/used*/", - output: "/* eslint-disable\ntest/used*/" - }, - { - code: "/* eslint-disable\n test/unused, test/used*/", - output: "/* eslint-disable\n test/used*/" - }, - { - code: "/* eslint-disable\r\ntest/unused, test/used*/", - output: "/* eslint-disable\r\ntest/used*/" - }, - { - code: "/* eslint-disable\u2028test/unused, test/used*/", - output: "/* eslint-disable\u2028test/used*/" - }, - { - code: "/* eslint-disable\u00A0test/unused, test/used*/", - output: "/* eslint-disable\u00A0test/used*/" - }, - - // when removing the first rule, the comma and all whitespace up to the next rule (or next lone comma) should also be removed - { - code: "// eslint-disable-line test/unused,test/used", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/unused, test/used", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/unused , test/used", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/unused, test/used", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/unused ,test/used", - output: "// eslint-disable-line test/used" - }, - { - code: "/* eslint-disable test/unused\n,\ntest/used */", - output: "/* eslint-disable test/used */" - }, - { - code: "/* eslint-disable test/unused \n \n,\n\n test/used */", - output: "/* eslint-disable test/used */" - }, - { - code: "/* eslint-disable test/unused\u2028,\u2028test/used */", - output: "/* eslint-disable test/used */" - }, - { - code: "/* eslint-disable test/used */ bad /* eslint-enable test/unused\n,\ntest/used */", - output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */" - }, - { - code: "/* eslint-disable test/used */ bad /* eslint-enable test/unused \n \n,\n\n test/used */", - output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */" - }, - { - code: "/* eslint-disable test/used */ bad /* eslint-enable test/unused\u2028,\u2028test/used */", - output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */" - }, - { - code: "// eslint-disable-line test/unused\u00A0,\u00A0test/used", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/unused,,test/used", - output: "// eslint-disable-line ,test/used" - }, - { - code: "// eslint-disable-line test/unused, ,test/used", - output: "// eslint-disable-line ,test/used" - }, - { - code: "// eslint-disable-line test/unused,, test/used", - output: "// eslint-disable-line , test/used" - }, - { - code: "// eslint-disable-line test/unused,test/used ", - output: "// eslint-disable-line test/used " - }, - { - code: "// eslint-disable-next-line test/unused,test/used\n", - output: "// eslint-disable-next-line test/used\n" - }, - - // when removing a rule in the middle, one comma and all whitespace between commas should also be removed - { - code: "// eslint-disable-line test/used-1,test/unused,test/used-2", - output: "// eslint-disable-line test/used-1,test/used-2" - }, - { - code: "// eslint-disable-line test/used-1, test/unused,test/used-2", - output: "// eslint-disable-line test/used-1,test/used-2" - }, - { - code: "// eslint-disable-line test/used-1,test/unused ,test/used-2", - output: "// eslint-disable-line test/used-1,test/used-2" - }, - { - code: "// eslint-disable-line test/used-1, test/unused ,test/used-2", - output: "// eslint-disable-line test/used-1,test/used-2" - }, - { - code: "/* eslint-disable test/used-1,\ntest/unused\n,test/used-2 */", - output: "/* eslint-disable test/used-1,test/used-2 */" - }, - { - code: "/* eslint-disable test/used-1,\n\n test/unused \n \n ,test/used-2 */", - output: "/* eslint-disable test/used-1,test/used-2 */" - }, - { - code: "/* eslint-disable test/used-1,\u2028test/unused\u2028,test/used-2 */", - output: "/* eslint-disable test/used-1,test/used-2 */" - }, - { - code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\ntest/unused\n,test/used-2 */", - output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,test/used-2 */" - }, - { - code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n\n test/unused \n \n ,test/used-2 */", - output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,test/used-2 */" - }, - { - code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\u2028test/unused\u2028,test/used-2 */", - output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,test/used-2 */" - }, - { - code: "// eslint-disable-line test/used-1,\u00A0test/unused\u00A0,test/used-2", - output: "// eslint-disable-line test/used-1,test/used-2" - }, - - // when removing a rule in the middle, content around commas should not be changed - { - code: "// eslint-disable-line test/used-1, test/unused ,test/used-2", - output: "// eslint-disable-line test/used-1,test/used-2" - }, - { - code: "// eslint-disable-line test/used-1,test/unused, test/used-2", - output: "// eslint-disable-line test/used-1, test/used-2" - }, - { - code: "// eslint-disable-line test/used-1 ,test/unused,test/used-2", - output: "// eslint-disable-line test/used-1 ,test/used-2" - }, - { - code: "// eslint-disable-line test/used-1 ,test/unused, test/used-2", - output: "// eslint-disable-line test/used-1 , test/used-2" - }, - { - code: "// eslint-disable-line test/used-1 , test/unused , test/used-2", - output: "// eslint-disable-line test/used-1 , test/used-2" - }, - { - code: "/* eslint-disable test/used-1\n,test/unused,\ntest/used-2 */", - output: "/* eslint-disable test/used-1\n,\ntest/used-2 */" - }, - { - code: "/* eslint-disable test/used-1\u2028,test/unused,\u2028test/used-2 */", - output: "/* eslint-disable test/used-1\u2028,\u2028test/used-2 */" - }, - { - code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1\n,test/unused,\ntest/used-2 */", - output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1\n,\ntest/used-2 */" - }, - { - code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1\u2028,test/unused,\u2028test/used-2 */", - output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1\u2028,\u2028test/used-2 */" - }, - { - code: "// eslint-disable-line test/used-1\u00A0,test/unused,\u00A0test/used-2", - output: "// eslint-disable-line test/used-1\u00A0,\u00A0test/used-2" - }, - { - code: "// eslint-disable-line , test/unused ,test/used", - output: "// eslint-disable-line ,test/used" - }, - { - code: "/* eslint-disable\n, test/unused ,test/used */", - output: "/* eslint-disable\n,test/used */" - }, - { - code: "/* eslint-disable test/used-1,\n,test/unused,test/used-2 */", - output: "/* eslint-disable test/used-1,\n,test/used-2 */" - }, - { - code: "/* eslint-disable test/used-1,test/unused,\n,test/used-2 */", - output: "/* eslint-disable test/used-1,\n,test/used-2 */" - }, - { - code: "/* eslint-disable test/used-1,\n,test/unused,\n,test/used-2 */", - output: "/* eslint-disable test/used-1,\n,\n,test/used-2 */" - }, - { - code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,test/unused,test/used-2 */", - output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,test/used-2 */" - }, - { - code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,test/unused,\n,test/used-2 */", - output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,test/used-2 */" - }, - { - code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,test/unused,\n,test/used-2 */", - output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,\n,test/used-2 */" - }, - { - code: "// eslint-disable-line test/used, test/unused,", - output: "// eslint-disable-line test/used," - }, - { - code: "// eslint-disable-next-line test/used, test/unused,\n", - output: "// eslint-disable-next-line test/used,\n" - }, - { - code: "// eslint-disable-line test/used, test/unused, ", - output: "// eslint-disable-line test/used, " - }, - { - code: "// eslint-disable-line test/used, test/unused, -- comment", - output: "// eslint-disable-line test/used, -- comment" - }, - { - code: "/* eslint-disable test/used, test/unused,\n*/", - output: "/* eslint-disable test/used,\n*/" - }, - { - code: "/* eslint-disable test/used */ bad /* eslint-enable test/used, test/unused,\n*/", - output: "/* eslint-disable test/used */ bad /* eslint-enable test/used,\n*/" - }, - - // when removing the last rule, the comma and all whitespace up to the previous rule (or previous lone comma) should also be removed - { - code: "// eslint-disable-line test/used,test/unused", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/used, test/unused", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/used ,test/unused", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/used , test/unused", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/used, test/unused", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/used ,test/unused", - output: "// eslint-disable-line test/used" - }, - { - code: "/* eslint-disable test/used\n,\ntest/unused */", - output: "/* eslint-disable test/used */" - }, - { - code: "/* eslint-disable test/used \n \n,\n\n test/unused */", - output: "/* eslint-disable test/used */" - }, - { - code: "/* eslint-disable test/used\u2028,\u2028test/unused */", - output: "/* eslint-disable test/used */" - }, - { - code: "/* eslint-disable test/used */ bad /* eslint-enable test/used\n,\ntest/unused */", - output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */" - }, - { - code: "/* eslint-disable test/used */ bad /* eslint-enable test/used \n \n,\n\n test/unused */", - output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */" - }, - { - code: "/* eslint-disable test/used */ bad /* eslint-enable test/used\u2028,\u2028test/unused */", - output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */" - }, - { - code: "// eslint-disable-line test/used\u00A0,\u00A0test/unused", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/used,,test/unused", - output: "// eslint-disable-line test/used," - }, - { - code: "// eslint-disable-line test/used, ,test/unused", - output: "// eslint-disable-line test/used," - }, - { - code: "/* eslint-disable test/used,\n,test/unused */", - output: "/* eslint-disable test/used, */" - }, - { - code: "/* eslint-disable test/used\n, ,test/unused */", - output: "/* eslint-disable test/used\n, */" - }, - { - code: "/* eslint-disable test/used */ bad /* eslint-enable test/used,\n,test/unused */", - output: "/* eslint-disable test/used */ bad /* eslint-enable test/used, */" - }, - { - code: "/* eslint-disable test/used */ bad /* eslint-enable test/used\n, ,test/unused */", - output: "/* eslint-disable test/used */ bad /* eslint-enable test/used\n, */" - }, - - // content after the last rule should not be changed - { - code: "// eslint-disable-line test/used,test/unused", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/used,test/unused ", - output: "// eslint-disable-line test/used " - }, - { - code: "// eslint-disable-line test/used,test/unused ", - output: "// eslint-disable-line test/used " - }, - { - code: "// eslint-disable-line test/used,test/unused -- comment", - output: "// eslint-disable-line test/used -- comment" - }, - { - code: "// eslint-disable-next-line test/used,test/unused\n", - output: "// eslint-disable-next-line test/used\n" - }, - { - code: "// eslint-disable-next-line test/used,test/unused \n", - output: "// eslint-disable-next-line test/used \n" - }, - { - code: "/* eslint-disable test/used,test/unused\u2028*/", - output: "/* eslint-disable test/used\u2028*/" - }, - { - code: "/* eslint-disable test/used */ bad /* eslint-enable test/used,test/unused\u2028*/", - output: "/* eslint-disable test/used */ bad /* eslint-enable test/used\u2028*/" - }, - { - code: "// eslint-disable-line test/used,test/unused\u00A0", - output: "// eslint-disable-line test/used\u00A0" - }, - - // multiply rules to remove - { - code: "// eslint-disable-line test/used, test/unused-1, test/unused-2", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/unused-1, test/used, test/unused-2", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/unused-1, test/unused-2, test/used", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/used-1, test/unused-1, test/used-2, test/unused-2", - output: "// eslint-disable-line test/used-1, test/used-2" - }, - { - code: "// eslint-disable-line test/unused-1, test/used-1, test/unused-2, test/used-2", - output: "// eslint-disable-line test/used-1, test/used-2" - }, - { - code: ` + const config = { + linterOptions: { + reportUnusedDisableDirectives: "warn", + }, + rules: { + "no-alert": 1, + "no-redeclare": 1, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true, + }); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported from 'no-redeclare').", + line: 2, + column: 21, + fix: { + range: [57, 71], + text: "", + }, + severity: 1, + nodeType: null, + }, + ]); + }); + + it("reports problems for unused eslint-enable comments", () => { + const messages = linter.verify( + "/* eslint-enable */", + {}, + { reportUnusedDisableDirectives: true }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 1, + column: 1, + fix: { + range: [0, 19], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-enable comments with ruleId", () => { + const messages = linter.verify( + "/* eslint-enable no-alert */", + {}, + { reportUnusedDisableDirectives: true }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'no-alert').", + line: 1, + column: 1, + fix: { + range: [0, 28], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-enable comments with mismatch ruleId", () => { + const code = [ + "/* eslint-disable no-alert */", + 'alert("test");', + "/* eslint-enable no-console */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found for 'no-console').", + line: 3, + column: 1, + fix: { + range: [45, 75], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 1); + }); + + it("reports problems for unused eslint-enable comments with used eslint-enable comments", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + 'alert("test");', + "/* eslint-disable no-alert -- j2 */", + 'alert("test");', + "/* eslint-enable no-alert -- j3 */", + "/* eslint-enable -- j4 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-enable directive (no matching eslint-disable directives were found).", + line: 6, + column: 1, + fix: { + range: [137, 162], + text: " ", + }, + severity: 2, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].suppressions.length, + 1, + ); + assert.strictEqual( + suppressedMessages[1].suppressions.length, + 2, + ); + }); + + it("reports problems for multiple eslint-enable comments with same ruleId", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + 'alert("test"); //', + "/* eslint-enable no-alert -- j2 */", + "/* eslint-enable no-alert -- j3 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 4); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].suppressions.length, + 1, + ); + }); + + it("reports problems for multiple eslint-enable comments without ruleId (Rule is already enabled)", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + 'alert("test"); //', + "/* eslint-enable no-alert -- j2 */", + "/* eslint-enable -- j3 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 4); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].suppressions.length, + 1, + ); + }); + + it("reports problems for multiple eslint-enable comments with ruleId (Rule is already enabled by eslint-enable comments without ruleId)", () => { + const code = [ + "/* eslint-disable no-alert -- j1 */", + 'alert("test"); //', + "/* eslint-enable -- j3 */", + "/* eslint-enable no-alert -- j2 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 4); + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].suppressions.length, + 1, + ); + }); + + it("reports problems for eslint-enable comments without ruleId (Two rules are already enabled)", () => { + const code = [ + "/* eslint-disable no-alert, no-console -- j1 */", + 'alert("test"); //', + 'console.log("test"); //', + "/* eslint-enable no-alert -- j2 */", + "/* eslint-enable no-console -- j3 */", + "/* eslint-enable -- j4 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + "no-console": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].line, 6); + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].suppressions.length, + 1, + ); + assert.strictEqual( + suppressedMessages[1].suppressions.length, + 1, + ); + }); + + it("reports problems for multiple eslint-enable comments with ruleId (Two rules are already enabled by eslint-enable comments without ruleId)", () => { + const code = [ + "/* eslint-disable no-alert, no-console -- j1 */", + 'alert("test"); //', + 'console.log("test"); //', + "/* eslint-enable -- j2 */", + "/* eslint-enable no-console -- j3 */", + "/* eslint-enable no-alert -- j4 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + "no-console": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].line, 5); + assert.strictEqual(messages[1].line, 6); + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].suppressions.length, + 1, + ); + assert.strictEqual( + suppressedMessages[1].suppressions.length, + 1, + ); + }); + + it("reports problems for multiple eslint-enable comments", () => { + const code = [ + "/* eslint-disable no-alert, no-console -- j1 */", + 'alert("test"); //', + 'console.log("test"); //', + "/* eslint-enable no-console -- j2 */", + "/* eslint-enable -- j3 */", + "/* eslint-enable no-alert -- j4 */", + "/* eslint-enable -- j5 */", + ].join("\n"); + const config = { + rules: { + "no-alert": 2, + "no-console": 2, + }, + }; + const messages = linter.verify(code, config, { + reportUnusedDisableDirectives: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 2); + assert.strictEqual(messages[0].line, 6); + assert.strictEqual(messages[1].line, 7); + assert.strictEqual(suppressedMessages.length, 2); + assert.strictEqual( + suppressedMessages[0].suppressions.length, + 1, + ); + assert.strictEqual( + suppressedMessages[1].suppressions.length, + 1, + ); + }); + + it("reports problems for unused eslint-disable comments (warn, explicitly set)", () => { + const messages = linter.verify( + "/* eslint-disable */", + {}, + { reportUnusedDisableDirectives: "warn" }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 1, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports problems for unused eslint-disable comments (warn by default)", () => { + const messages = linter.verify("/* eslint-disable */", {}); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages, [ + { + ruleId: null, + message: + "Unused eslint-disable directive (no problems were reported).", + line: 1, + column: 1, + fix: { + range: [0, 20], + text: " ", + }, + severity: 1, + nodeType: null, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("reports no problems for no-fallthrough despite comment pattern match", () => { + const code = + "switch (foo) { case 0: a(); \n// eslint-disable-next-line no-fallthrough\n case 1: }"; + const config = { + linterOptions: { + reportUnusedDisableDirectives: true, + }, + rules: { + "no-fallthrough": 2, + }, + }; + + const messages = linter.verify(code, config, { + filename, + allowInlineConfig: true, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual( + suppressedMessages[0].ruleId, + "no-fallthrough", + ); + }); + + describe("autofix", () => { + const alwaysReportsRule = { + create(context) { + return { + Program(node) { + context.report({ + message: "bad code", + loc: node.loc.end, + }); + }, + "Identifier[name=bad]"(node) { + context.report({ + message: "bad id", + loc: node.loc, + }); + }, + }; + }, + }; + + const neverReportsRule = { + create() { + return {}; + }, + }; + + const ruleCount = 3; + const usedRules = Array.from( + { length: ruleCount }, + (_, index) => `used${index ? `-${index}` : ""}`, // "used", "used-1", "used-2" + ); + const unusedRules = usedRules.map(name => `un${name}`); // "unused", "unused-1", "unused-2" + + const config = { + plugins: { + test: { + rules: {}, + }, + }, + linterOptions: { + reportUnusedDisableDirectives: "warn", + }, + rules: { + ...Object.fromEntries( + usedRules.map(name => [ + `test/${name}`, + "error", + ]), + ), + ...Object.fromEntries( + unusedRules.map(name => [ + `test/${name}`, + "error", + ]), + ), + }, + }; + + beforeEach(() => { + config.plugins.test.rules = { + ...Object.fromEntries( + usedRules.map(name => [ + name, + alwaysReportsRule, + ]), + ), + ...Object.fromEntries( + unusedRules.map(name => [ + name, + neverReportsRule, + ]), + ), + }; + }); + + const tests = [ + //----------------------------------------------- + // Removing the entire comment + //----------------------------------------------- + + { + code: "// eslint-disable-line test/unused", + output: " ", + }, + { + code: "foo// eslint-disable-line test/unused", + output: "foo ", + }, + { + code: "// eslint-disable-line ,test/unused,", + output: " ", + }, + { + code: "// eslint-disable-line test/unused-1, test/unused-2", + output: " ", + }, + { + code: "// eslint-disable-line ,test/unused-1,, test/unused-2,, -- comment", + output: " ", + }, + { + code: "// eslint-disable-next-line test/unused\n", + output: " \n", + }, + { + code: "// eslint-disable-next-line test/unused\nfoo", + output: " \nfoo", + }, + { + code: "/* eslint-disable \ntest/unused\n*/", + output: " ", + }, + { + code: "/* eslint-enable \ntest/unused\n*/", + output: " ", + }, + + //----------------------------------------------- + // Removing only individual rules + //----------------------------------------------- + + // content before the first rule should not be changed + { + code: "//eslint-disable-line test/unused, test/used", + output: "//eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/unused, test/used", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/unused, test/used", + output: "// eslint-disable-line test/used", + }, + { + code: "/*\neslint-disable test/unused, test/used*/", + output: "/*\neslint-disable test/used*/", + }, + { + code: "/*\n eslint-disable test/unused, test/used*/", + output: "/*\n eslint-disable test/used*/", + }, + { + code: "/*\r\neslint-disable test/unused, test/used*/", + output: "/*\r\neslint-disable test/used*/", + }, + { + code: "/*\u2028eslint-disable test/unused, test/used*/", + output: "/*\u2028eslint-disable test/used*/", + }, + { + code: "/*\u00A0eslint-disable test/unused, test/used*/", + output: "/*\u00A0eslint-disable test/used*/", + }, + { + code: "/* eslint-disable test/used */ bad /*\neslint-enable test/unused, test/used*/", + output: "/* eslint-disable test/used */ bad /*\neslint-enable test/used*/", + }, + { + code: "/* eslint-disable test/used */ bad /*\n eslint-enable test/unused, test/used*/", + output: "/* eslint-disable test/used */ bad /*\n eslint-enable test/used*/", + }, + { + code: "/* eslint-disable test/used */ bad /*\r\neslint-enable test/unused, test/used*/", + output: "/* eslint-disable test/used */ bad /*\r\neslint-enable test/used*/", + }, + { + code: "/* eslint-disable test/used */ bad /*\u2028eslint-enable test/unused, test/used*/", + output: "/* eslint-disable test/used */ bad /*\u2028eslint-enable test/used*/", + }, + { + code: "/* eslint-disable test/used */ bad /*\u00A0eslint-enable test/unused, test/used*/", + output: "/* eslint-disable test/used */ bad /*\u00A0eslint-enable test/used*/", + }, + { + code: "// eslint-disable-line test/unused, test/used", + output: "// eslint-disable-line test/used", + }, + { + code: "/* eslint-disable\ntest/unused, test/used*/", + output: "/* eslint-disable\ntest/used*/", + }, + { + code: "/* eslint-disable\n test/unused, test/used*/", + output: "/* eslint-disable\n test/used*/", + }, + { + code: "/* eslint-disable\r\ntest/unused, test/used*/", + output: "/* eslint-disable\r\ntest/used*/", + }, + { + code: "/* eslint-disable\u2028test/unused, test/used*/", + output: "/* eslint-disable\u2028test/used*/", + }, + { + code: "/* eslint-disable\u00A0test/unused, test/used*/", + output: "/* eslint-disable\u00A0test/used*/", + }, + + // when removing the first rule, the comma and all whitespace up to the next rule (or next lone comma) should also be removed + { + code: "// eslint-disable-line test/unused,test/used", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/unused, test/used", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/unused , test/used", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/unused, test/used", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/unused ,test/used", + output: "// eslint-disable-line test/used", + }, + { + code: "/* eslint-disable test/unused\n,\ntest/used */", + output: "/* eslint-disable test/used */", + }, + { + code: "/* eslint-disable test/unused \n \n,\n\n test/used */", + output: "/* eslint-disable test/used */", + }, + { + code: "/* eslint-disable test/unused\u2028,\u2028test/used */", + output: "/* eslint-disable test/used */", + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/unused\n,\ntest/used */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */", + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/unused \n \n,\n\n test/used */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */", + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/unused\u2028,\u2028test/used */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */", + }, + { + code: "// eslint-disable-line test/unused\u00A0,\u00A0test/used", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/unused,,test/used", + output: "// eslint-disable-line ,test/used", + }, + { + code: "// eslint-disable-line test/unused, ,test/used", + output: "// eslint-disable-line ,test/used", + }, + { + code: "// eslint-disable-line test/unused,, test/used", + output: "// eslint-disable-line , test/used", + }, + { + code: "// eslint-disable-line test/unused,test/used ", + output: "// eslint-disable-line test/used ", + }, + { + code: "// eslint-disable-next-line test/unused,test/used\n", + output: "// eslint-disable-next-line test/used\n", + }, + + // when removing a rule in the middle, one comma and all whitespace between commas should also be removed + { + code: "// eslint-disable-line test/used-1,test/unused,test/used-2", + output: "// eslint-disable-line test/used-1,test/used-2", + }, + { + code: "// eslint-disable-line test/used-1, test/unused,test/used-2", + output: "// eslint-disable-line test/used-1,test/used-2", + }, + { + code: "// eslint-disable-line test/used-1,test/unused ,test/used-2", + output: "// eslint-disable-line test/used-1,test/used-2", + }, + { + code: "// eslint-disable-line test/used-1, test/unused ,test/used-2", + output: "// eslint-disable-line test/used-1,test/used-2", + }, + { + code: "/* eslint-disable test/used-1,\ntest/unused\n,test/used-2 */", + output: "/* eslint-disable test/used-1,test/used-2 */", + }, + { + code: "/* eslint-disable test/used-1,\n\n test/unused \n \n ,test/used-2 */", + output: "/* eslint-disable test/used-1,test/used-2 */", + }, + { + code: "/* eslint-disable test/used-1,\u2028test/unused\u2028,test/used-2 */", + output: "/* eslint-disable test/used-1,test/used-2 */", + }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\ntest/unused\n,test/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,test/used-2 */", + }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n\n test/unused \n \n ,test/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,test/used-2 */", + }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\u2028test/unused\u2028,test/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,test/used-2 */", + }, + { + code: "// eslint-disable-line test/used-1,\u00A0test/unused\u00A0,test/used-2", + output: "// eslint-disable-line test/used-1,test/used-2", + }, + + // when removing a rule in the middle, content around commas should not be changed + { + code: "// eslint-disable-line test/used-1, test/unused ,test/used-2", + output: "// eslint-disable-line test/used-1,test/used-2", + }, + { + code: "// eslint-disable-line test/used-1,test/unused, test/used-2", + output: "// eslint-disable-line test/used-1, test/used-2", + }, + { + code: "// eslint-disable-line test/used-1 ,test/unused,test/used-2", + output: "// eslint-disable-line test/used-1 ,test/used-2", + }, + { + code: "// eslint-disable-line test/used-1 ,test/unused, test/used-2", + output: "// eslint-disable-line test/used-1 , test/used-2", + }, + { + code: "// eslint-disable-line test/used-1 , test/unused , test/used-2", + output: "// eslint-disable-line test/used-1 , test/used-2", + }, + { + code: "/* eslint-disable test/used-1\n,test/unused,\ntest/used-2 */", + output: "/* eslint-disable test/used-1\n,\ntest/used-2 */", + }, + { + code: "/* eslint-disable test/used-1\u2028,test/unused,\u2028test/used-2 */", + output: "/* eslint-disable test/used-1\u2028,\u2028test/used-2 */", + }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1\n,test/unused,\ntest/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1\n,\ntest/used-2 */", + }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1\u2028,test/unused,\u2028test/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1\u2028,\u2028test/used-2 */", + }, + { + code: "// eslint-disable-line test/used-1\u00A0,test/unused,\u00A0test/used-2", + output: "// eslint-disable-line test/used-1\u00A0,\u00A0test/used-2", + }, + { + code: "// eslint-disable-line , test/unused ,test/used", + output: "// eslint-disable-line ,test/used", + }, + { + code: "/* eslint-disable\n, test/unused ,test/used */", + output: "/* eslint-disable\n,test/used */", + }, + { + code: "/* eslint-disable test/used-1,\n,test/unused,test/used-2 */", + output: "/* eslint-disable test/used-1,\n,test/used-2 */", + }, + { + code: "/* eslint-disable test/used-1,test/unused,\n,test/used-2 */", + output: "/* eslint-disable test/used-1,\n,test/used-2 */", + }, + { + code: "/* eslint-disable test/used-1,\n,test/unused,\n,test/used-2 */", + output: "/* eslint-disable test/used-1,\n,\n,test/used-2 */", + }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,test/unused,test/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,test/used-2 */", + }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,test/unused,\n,test/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,test/used-2 */", + }, + { + code: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,test/unused,\n,test/used-2 */", + output: "/* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1,\n,\n,test/used-2 */", + }, + { + code: "// eslint-disable-line test/used, test/unused,", + output: "// eslint-disable-line test/used,", + }, + { + code: "// eslint-disable-next-line test/used, test/unused,\n", + output: "// eslint-disable-next-line test/used,\n", + }, + { + code: "// eslint-disable-line test/used, test/unused, ", + output: "// eslint-disable-line test/used, ", + }, + { + code: "// eslint-disable-line test/used, test/unused, -- comment", + output: "// eslint-disable-line test/used, -- comment", + }, + { + code: "/* eslint-disable test/used, test/unused,\n*/", + output: "/* eslint-disable test/used,\n*/", + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/used, test/unused,\n*/", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used,\n*/", + }, + + // when removing the last rule, the comma and all whitespace up to the previous rule (or previous lone comma) should also be removed + { + code: "// eslint-disable-line test/used,test/unused", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/used, test/unused", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/used ,test/unused", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/used , test/unused", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/used, test/unused", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/used ,test/unused", + output: "// eslint-disable-line test/used", + }, + { + code: "/* eslint-disable test/used\n,\ntest/unused */", + output: "/* eslint-disable test/used */", + }, + { + code: "/* eslint-disable test/used \n \n,\n\n test/unused */", + output: "/* eslint-disable test/used */", + }, + { + code: "/* eslint-disable test/used\u2028,\u2028test/unused */", + output: "/* eslint-disable test/used */", + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/used\n,\ntest/unused */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */", + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/used \n \n,\n\n test/unused */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */", + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/used\u2028,\u2028test/unused */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used */", + }, + { + code: "// eslint-disable-line test/used\u00A0,\u00A0test/unused", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/used,,test/unused", + output: "// eslint-disable-line test/used,", + }, + { + code: "// eslint-disable-line test/used, ,test/unused", + output: "// eslint-disable-line test/used,", + }, + { + code: "/* eslint-disable test/used,\n,test/unused */", + output: "/* eslint-disable test/used, */", + }, + { + code: "/* eslint-disable test/used\n, ,test/unused */", + output: "/* eslint-disable test/used\n, */", + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/used,\n,test/unused */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used, */", + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/used\n, ,test/unused */", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used\n, */", + }, + + // content after the last rule should not be changed + { + code: "// eslint-disable-line test/used,test/unused", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/used,test/unused ", + output: "// eslint-disable-line test/used ", + }, + { + code: "// eslint-disable-line test/used,test/unused ", + output: "// eslint-disable-line test/used ", + }, + { + code: "// eslint-disable-line test/used,test/unused -- comment", + output: "// eslint-disable-line test/used -- comment", + }, + { + code: "// eslint-disable-next-line test/used,test/unused\n", + output: "// eslint-disable-next-line test/used\n", + }, + { + code: "// eslint-disable-next-line test/used,test/unused \n", + output: "// eslint-disable-next-line test/used \n", + }, + { + code: "/* eslint-disable test/used,test/unused\u2028*/", + output: "/* eslint-disable test/used\u2028*/", + }, + { + code: "/* eslint-disable test/used */ bad /* eslint-enable test/used,test/unused\u2028*/", + output: "/* eslint-disable test/used */ bad /* eslint-enable test/used\u2028*/", + }, + { + code: "// eslint-disable-line test/used,test/unused\u00A0", + output: "// eslint-disable-line test/used\u00A0", + }, + + // multiply rules to remove + { + code: "// eslint-disable-line test/used, test/unused-1, test/unused-2", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/unused-1, test/used, test/unused-2", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/unused-1, test/unused-2, test/used", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/used-1, test/unused-1, test/used-2, test/unused-2", + output: "// eslint-disable-line test/used-1, test/used-2", + }, + { + code: "// eslint-disable-line test/unused-1, test/used-1, test/unused-2, test/used-2", + output: "// eslint-disable-line test/used-1, test/used-2", + }, + { + code: ` /* eslint-disable test/unused-1, test/used-1, test/unused-2, test/used-2 */ `, - output: ` + output: ` /* eslint-disable test/used-1, test/used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable test/unused-1, test/used-1, @@ -16694,15 +17966,15 @@ var a = "test2"; test/used-2 */ `, - output: ` + output: ` /* eslint-disable test/used-1, test/used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable test/used-1, test/unused-1, @@ -16710,15 +17982,15 @@ var a = "test2"; test/unused-2 */ `, - output: ` + output: ` /* eslint-disable test/used-1, test/used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable test/used-1, test/unused-1, @@ -16726,15 +17998,15 @@ var a = "test2"; test/unused-2, */ `, - output: ` + output: ` /* eslint-disable test/used-1, test/used-2, */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable ,test/unused-1 ,test/used-1 @@ -16742,15 +18014,15 @@ var a = "test2"; ,test/used-2 */ `, - output: ` + output: ` /* eslint-disable ,test/used-1 ,test/used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable ,test/used-1 ,test/unused-1 @@ -16758,15 +18030,15 @@ var a = "test2"; ,test/unused-2 */ `, - output: ` + output: ` /* eslint-disable ,test/used-1 ,test/used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable test/used-1, test/unused-1, @@ -16776,17 +18048,17 @@ var a = "test2"; -- comment */ `, - output: ` + output: ` /* eslint-disable test/used-1, test/used-2 -- comment */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/unused-1, @@ -16795,16 +18067,16 @@ var a = "test2"; test/used-2 */ `, - output: ` + output: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1, test/used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable @@ -16814,17 +18086,17 @@ var a = "test2"; test/used-2 */ `, - output: ` + output: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1, test/used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable @@ -16834,17 +18106,17 @@ var a = "test2"; test/unused-2 */ `, - output: ` + output: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1, test/used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable @@ -16854,17 +18126,17 @@ var a = "test2"; test/unused-2, */ `, - output: ` + output: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable test/used-1, test/used-2, */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable @@ -16874,17 +18146,17 @@ var a = "test2"; ,test/used-2 */ `, - output: ` + output: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable ,test/used-1 ,test/used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable @@ -16894,17 +18166,17 @@ var a = "test2"; ,test/unused-2 */ `, - output: ` + output: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable ,test/used-1 ,test/used-2 */ - ` - }, - { - code: ` + `, + }, + { + code: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable @@ -16916,7 +18188,7 @@ var a = "test2"; -- comment */ `, - output: ` + output: ` /* eslint-disable test/used-1, test/used-2 */ bad /* eslint-enable @@ -16925,1406 +18197,2296 @@ var a = "test2"; -- comment */ - ` - }, - - // duplicates in the list - { - code: "// eslint-disable-line test/unused, test/unused, test/used", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/unused, test/used, test/unused", - output: "// eslint-disable-line test/used" - }, - { - code: "// eslint-disable-line test/used, test/unused, test/unused, test/used", - output: "// eslint-disable-line test/used, test/used" - } - ]; - - for (const { code, output } of tests) { - // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach() - it(code, () => { - assert.strictEqual( - linter.verifyAndFix(code, config).output, - output - ); - }); - - // Test for quoted rule names - for (const testcaseForLiteral of [ - { code: code.replace(/(test\/[\w-]+)/gu, '"$1"'), output: output.replace(/(test\/[\w-]+)/gu, '"$1"') }, - { code: code.replace(/(test\/[\w-]+)/gu, "'$1'"), output: output.replace(/(test\/[\w-]+)/gu, "'$1'") } - ]) { - // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach() - it(testcaseForLiteral.code, () => { - assert.strictEqual( - linter.verifyAndFix(testcaseForLiteral.code, config).output, - testcaseForLiteral.output - ); - }); - } - } - }); - }); - - }); - - describe("Default Global Variables", () => { - const code = "x"; - - it("builtin global variables should be available in the global scope", () => { - let spy; - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.notStrictEqual(getVariable(scope, "Object"), null); - assert.notStrictEqual(getVariable(scope, "Array"), null); - assert.notStrictEqual(getVariable(scope, "undefined"), null); - }); - - return { Program: spy }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - }, - rules: { - "test/checker": "error" - } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce, "Rule should have been called."); - }); - - it("ES6 global variables should be available by default", () => { - let spy; - const config = { - plugins: { - test: { - rules: { - checker: { - create(context) { - spy = sinon.spy(() => { - const scope = context.getScope(); - - assert.notStrictEqual(getVariable(scope, "Promise"), null); - assert.notStrictEqual(getVariable(scope, "Symbol"), null); - assert.notStrictEqual(getVariable(scope, "WeakMap"), null); - }); - - return { Program: spy }; - } - } - } - } - }, - languageOptions: { - sourceType: "script" - }, - rules: { - "test/checker": "error" - } - }; - - linter.verify(code, config); - assert(spy && spy.calledOnce); - }); - - }); - - describe("Suggestions", () => { - it("provides suggestion information for tools to use", () => { - - const config = { - plugins: { - test: { - rules: { - "rule-with-suggestions": { - meta: { hasSuggestions: true }, - create: context => ({ - Program(node) { - context.report({ - node, - message: "Incorrect spacing", - suggest: [{ - desc: "Insert space at the beginning", - fix: fixer => fixer.insertTextBefore(node, " ") - }, { - desc: "Insert space at the end", - fix: fixer => fixer.insertTextAfter(node, " ") - }] - }); - } - }) - } - } - } - }, - rules: { - "test/rule-with-suggestions": "error" - } - }; - - const messages = linter.verify("var a = 1;", config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages[0].suggestions, [{ - desc: "Insert space at the beginning", - fix: { - range: [0, 0], - text: " " - } - }, { - desc: "Insert space at the end", - fix: { - range: [10, 10], - text: " " - } - }]); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("supports messageIds for suggestions", () => { - - const config = { - plugins: { - test: { - rules: { - "rule-with-suggestions": { - meta: { - messages: { - suggestion1: "Insert space at the beginning", - suggestion2: "Insert space at the end" - }, - hasSuggestions: true - }, - create: context => ({ - Program(node) { - context.report({ - node, - message: "Incorrect spacing", - suggest: [{ - messageId: "suggestion1", - fix: fixer => fixer.insertTextBefore(node, " ") - }, { - messageId: "suggestion2", - fix: fixer => fixer.insertTextAfter(node, " ") - }] - }); - } - }) - } - } - } - }, - rules: { - "test/rule-with-suggestions": "error" - } - }; - - const messages = linter.verify("var a = 1;", config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages[0].suggestions, [{ - messageId: "suggestion1", - desc: "Insert space at the beginning", - fix: { - range: [0, 0], - text: " " - } - }, { - messageId: "suggestion2", - desc: "Insert space at the end", - fix: { - range: [10, 10], - text: " " - } - }]); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled", () => { - - const config = { - plugins: { - test: { - rules: { - "rule-with-suggestions": { - meta: { docs: {}, schema: [] }, - create: context => ({ - Program(node) { - context.report({ - node, - message: "hello world", - suggest: [{ desc: "convert to foo", fix: fixer => fixer.insertTextBefore(node, " ") }] - }); - } - }) - } - } - } - }, - rules: { - "test/rule-with-suggestions": "error" - } - }; - - assert.throws(() => { - linter.verify("0", config); - }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`."); - }); - - it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled and the rule has the obsolete `meta.docs.suggestion` property", () => { - - const config = { - plugins: { - test: { - rules: { - "rule-with-meta-docs-suggestion": { - meta: { docs: { suggestion: true }, schema: [] }, - create: context => ({ - Program(node) { - context.report({ - node, - message: "hello world", - suggest: [{ desc: "convert to foo", fix: fixer => fixer.insertTextBefore(node, " ") }] - }); - } - }) - } - } - } - }, - rules: { - "test/rule-with-meta-docs-suggestion": "error" - } - }; - - assert.throws(() => { - linter.verify("0", config); - }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint."); - }); - }); - - describe("Error Conditions", () => { - describe("when evaluating broken code", () => { - const code = BROKEN_TEST_CODE; - - it("should report a violation with a useful parse error prefix", () => { - const messages = linter.verify(code, {}); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.isNull(messages[0].ruleId); - assert.strictEqual(messages[0].line, 1); - assert.strictEqual(messages[0].column, 4); - assert.isTrue(messages[0].fatal); - assert.match(messages[0].message, /^Parsing error:/u); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should report source code where the issue is present", () => { - const inValidCode = [ - "var x = 20;", - "if (x ==4 {", - " x++;", - "}" - ]; - const messages = linter.verify(inValidCode.join("\n"), {}); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].severity, 2); - assert.isTrue(messages[0].fatal); - assert.match(messages[0].message, /^Parsing error:/u); - - assert.strictEqual(suppressedMessages.length, 0); - }); - }); - - describe("when using a rule which has been replaced", () => { - const code = TEST_CODE; - - it("should report the new rule", () => { - - assert.throws(() => { - linter.verify(code, { rules: { "no-comma-dangle": 2 } }); - }, /Key "rules": Key "no-comma-dangle": Rule "no-comma-dangle" was removed and replaced by "comma-dangle"/u); - - }); - }); - - }); - }); - - describe("getSourceCode()", () => { - const code = TEST_CODE; - - it("should retrieve SourceCode object after reset", () => { - linter.verify(code, {}, filename, true); - - const sourceCode = linter.getSourceCode(); - - assert.isObject(sourceCode); - assert.strictEqual(sourceCode.text, code); - assert.isObject(sourceCode.ast); - }); - - it("should retrieve SourceCode object without reset", () => { - linter.verify(code, {}, filename); - - const sourceCode = linter.getSourceCode(); - - assert.isObject(sourceCode); - assert.strictEqual(sourceCode.text, code); - assert.isObject(sourceCode.ast); - }); - - }); - - describe("getSuppressedMessages()", () => { - it("should have no suppressed messages", () => { - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should have a suppressed message", () => { - const code = "/* eslint-disable no-alert -- justification */\nalert(\"test\");"; - const config = { - rules: { "no-alert": 1 } - }; - const messages = linter.verify(code, config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.deepStrictEqual( - suppressedMessages[0].suppressions, - [{ kind: "directive", justification: "justification" }] - ); - }); - - it("should have a suppressed message", () => { - const code = [ - "/* eslint-disable no-alert -- j1", - " * j2", - " */", - "alert(\"test\");" - ].join("\n"); - const config = { - rules: { "no-alert": 1 } - }; - const messages = linter.verify(code, config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0); - - assert.strictEqual(suppressedMessages.length, 1); - assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); - assert.deepStrictEqual( - suppressedMessages[0].suppressions, - [{ kind: "directive", justification: "j1\n * j2" }] - ); - }); - }); - - describe("defineRule()", () => { - it("should throw an error when called in flat config mode", () => { - assert.throws(() => { - linter.defineRule("foo", { - create() {} - }); - }, /This method cannot be used with flat config/u); - }); - }); - - describe("defineRules()", () => { - it("should throw an error when called in flat config mode", () => { - assert.throws(() => { - linter.defineRules({}); - }, /This method cannot be used with flat config/u); - }); - }); - - describe("defineParser()", () => { - it("should throw an error when called in flat config mode", () => { - assert.throws(() => { - linter.defineParser("foo", {}); - }, /This method cannot be used with flat config/u); - }); - }); - - describe("getRules()", () => { - it("should throw an error when called in flat config mode", () => { - assert.throws(() => { - linter.getRules(); - }, /This method cannot be used with flat config/u); - }); - }); - - describe("version", () => { - it("should return current version number", () => { - const version = linter.version; - - assert.isString(version); - assert.isTrue(parseInt(version[0], 10) >= 3); - }); - }); - - describe("verifyAndFix()", () => { - it("Fixes the code", () => { - const messages = linter.verifyAndFix("var a", { - rules: { - semi: 2 - } - }, { filename: "test.js" }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.output, "var a;", "Fixes were applied correctly"); - assert.isTrue(messages.fixed); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("does not require a third argument", () => { - const fixResult = linter.verifyAndFix("var a", { - rules: { - semi: 2 - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(fixResult, { - fixed: true, - messages: [], - output: "var a;" - }); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("does not include suggestions in autofix results", () => { - const fixResult = linter.verifyAndFix("var foo = /\\#/", { - rules: { - semi: 2, - "no-useless-escape": 2 - } - }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(fixResult.output, "var foo = /\\#/;"); - assert.strictEqual(fixResult.fixed, true); - assert.strictEqual(fixResult.messages[0].suggestions.length > 0, true); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("does not apply autofixes when fix argument is `false`", () => { - const fixResult = linter.verifyAndFix("var a", { - rules: { - semi: 2 - } - }, { fix: false }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(fixResult.fixed, false); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("stops fixing after 10 passes", () => { - - const config = { - plugins: { - test: { - rules: { - "add-spaces": { - meta: { - fixable: "whitespace" - }, - create(context) { - return { - Program(node) { - context.report({ - node, - message: "Add a space before this node.", - fix: fixer => fixer.insertTextBefore(node, " ") - }); - } - }; - } - } - } - } - }, - rules: { - "test/add-spaces": "error" - } - }; - - const fixResult = linter.verifyAndFix("a", config); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(fixResult.fixed, true); - assert.strictEqual(fixResult.output, `${" ".repeat(10)}a`); - assert.strictEqual(fixResult.messages.length, 1); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should throw an error if fix is passed but meta has no `fixable` property", () => { - - const config = { - plugins: { - test: { - rules: { - "test-rule": { - meta: { - docs: {}, - schema: [] - }, - create: context => ({ - Program(node) { - context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); - } - }) - } - } - } - }, - rules: { - "test/test-rule": "error" - } - }; - - - assert.throws(() => { - linter.verify("0", config); - }, /Fixable rules must set the `meta\.fixable` property to "code" or "whitespace".\nOccurred while linting :1\nRule: "test\/test-rule"$/u); - }); - - it("should throw an error if fix is passed and there is no metadata", () => { - - const config = { - plugins: { - test: { - rules: { - "test-rule": { - create: context => ({ - Program(node) { - context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); - } - }) - } - } - } - }, - rules: { - "test/test-rule": "error" - } - }; - - assert.throws(() => { - linter.verify("0", config); - }, /Fixable rules must set the `meta\.fixable` property/u); - }); - - it("should throw an error if fix is passed from a legacy-format rule", () => { - - const config = { - plugins: { - test: { - rules: { - "test-rule": { - create: context => ({ - Program(node) { - context.report(node, "hello world", {}, () => ({ range: [1, 1], text: "" })); - } - }) - } - } - } - }, - rules: { - "test/test-rule": "error" - } - }; - - - assert.throws(() => { - linter.verify("0", config); - }, /Fixable rules must set the `meta\.fixable` property/u); - }); - }); - - describe("Mutability", () => { - let linter1 = null; - let linter2 = null; - - beforeEach(() => { - linter1 = new Linter(); - linter2 = new Linter(); - }); - - describe("rules", () => { - it("with no changes, same rules are loaded", () => { - assert.sameDeepMembers(Array.from(linter1.getRules().keys()), Array.from(linter2.getRules().keys())); - }); - - it("loading rule in one doesn't change the other", () => { - linter1.defineRule("mock-rule", { - create: () => ({}) - }); - - assert.isTrue(linter1.getRules().has("mock-rule"), "mock rule is present"); - assert.isFalse(linter2.getRules().has("mock-rule"), "mock rule is not present"); - }); - }); - }); - - - describe("processors", () => { - let receivedFilenames = []; - let receivedPhysicalFilenames = []; - const extraConfig = { - plugins: { - test: { - rules: { - "report-original-text": { - meta: { - - }, - create(context) { - return { - Program(ast) { - assert.strictEqual(context.getFilename(), context.filename); - assert.strictEqual(context.getPhysicalFilename(), context.physicalFilename); - - receivedFilenames.push(context.filename); - receivedPhysicalFilenames.push(context.physicalFilename); - - context.report({ node: ast, message: context.sourceCode.text }); - } - }; - } - } - } - } - } - }; - - beforeEach(() => { - receivedFilenames = []; - receivedPhysicalFilenames = []; - }); - - describe("preprocessors", () => { - it("should receive text and filename.", () => { - const code = "foo bar baz"; - const preprocess = sinon.spy(text => text.split(" ")); - const configs = createFlatConfigArray({}); - - configs.normalizeSync(); - - linter.verify(code, configs, { filename, preprocess }); - - assert.strictEqual(preprocess.calledOnce, true, "preprocess wasn't called"); - assert.deepStrictEqual(preprocess.args[0], [code, filename], "preprocess was called with the wrong arguments"); - }); - - it("should run preprocess only once", () => { - const logs = []; - const config = { - files: ["*.md"], - processor: { - preprocess(text, filenameForText) { - logs.push({ - text, - filename: filenameForText - }); - - return [{ text: "bar", filename: "0.js" }]; - }, - postprocess() { - return []; - } - } - }; - - linter.verify("foo", config, "a.md"); - assert.strictEqual(logs.length, 1, "preprocess() should only be called once."); - }); - - it("should apply a preprocessor to the code, and lint each code sample separately", () => { - const code = "foo bar baz"; - const configs = createFlatConfigArray([ - extraConfig, - { rules: { "test/report-original-text": "error" } } - ]); - - configs.normalizeSync(); - - const problems = linter.verify( - code, - configs, - { - filename, - - // Apply a preprocessor that splits the source text into spaces and lints each word individually - preprocess(input) { - return input.split(" "); - } - } - ); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(problems.length, 3); - assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should apply a preprocessor to the code even if the preprocessor returned code block objects.", () => { - const code = "foo bar baz"; - const configs = createFlatConfigArray([ - extraConfig, - { rules: { "test/report-original-text": "error" } } - ]); - - configs.normalizeSync(); - - const problems = linter.verify( - code, - configs, - { - filename, - - // Apply a preprocessor that splits the source text into spaces and lints each word individually - preprocess(input) { - return input.split(" ").map(text => ({ - filename: "block.js", - text - })); - } - } - ); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(problems.length, 3); - assert.deepStrictEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]); - - assert.strictEqual(suppressedMessages.length, 0); - - // filename - assert.strictEqual(receivedFilenames.length, 3); - assert(/^filename\.js[/\\]0_block\.js/u.test(receivedFilenames[0])); - assert(/^filename\.js[/\\]1_block\.js/u.test(receivedFilenames[1])); - assert(/^filename\.js[/\\]2_block\.js/u.test(receivedFilenames[2])); - - // physical filename - assert.strictEqual(receivedPhysicalFilenames.length, 3); - assert.strictEqual(receivedPhysicalFilenames.every(name => name === filename), true); - }); - - it("should receive text even if a SourceCode object was given.", () => { - const code = "foo"; - const preprocess = sinon.spy(text => text.split(" ")); - const configs = createFlatConfigArray([ - extraConfig - ]); - - configs.normalizeSync(); - - linter.verify(code, configs); - const sourceCode = linter.getSourceCode(); - - linter.verify(sourceCode, configs, { filename, preprocess }); - - assert.strictEqual(preprocess.calledOnce, true); - assert.deepStrictEqual(preprocess.args[0], [code, filename]); - }); - - it("should receive text even if a SourceCode object was given (with BOM).", () => { - const code = "\uFEFFfoo"; - const preprocess = sinon.spy(text => text.split(" ")); - const configs = createFlatConfigArray([ - extraConfig - ]); - - configs.normalizeSync(); - - linter.verify(code, configs); - const sourceCode = linter.getSourceCode(); - - linter.verify(sourceCode, configs, { filename, preprocess }); - - assert.strictEqual(preprocess.calledOnce, true); - assert.deepStrictEqual(preprocess.args[0], [code, filename]); - }); - - it("should catch preprocess error.", () => { - const code = "foo"; - const preprocess = sinon.spy(() => { - throw Object.assign(new SyntaxError("Invalid syntax"), { - lineNumber: 1, - column: 1 - }); - }); - - const configs = createFlatConfigArray([ - extraConfig - ]); - - configs.normalizeSync(); - - const messages = linter.verify(code, configs, { filename, preprocess }); - - assert.strictEqual(preprocess.calledOnce, true); - assert.deepStrictEqual(preprocess.args[0], [code, filename]); - assert.deepStrictEqual(messages, [ - { - ruleId: null, - fatal: true, - severity: 2, - message: "Preprocessing error: Invalid syntax", - line: 1, - column: 1, - nodeType: null - } - ]); - }); - }); - - describe("postprocessors", () => { - it("should receive result and filename.", () => { - const code = "foo bar baz"; - const preprocess = sinon.spy(text => text.split(" ")); - const postprocess = sinon.spy(text => [text]); - const configs = createFlatConfigArray([ - extraConfig - ]); - - configs.normalizeSync(); - - linter.verify(code, configs, { filename, postprocess, preprocess }); - - assert.strictEqual(postprocess.calledOnce, true); - assert.deepStrictEqual(postprocess.args[0], [[[], [], []], filename]); - }); - - it("should apply a postprocessor to the reported messages", () => { - const code = "foo bar baz"; - const configs = createFlatConfigArray([ - extraConfig, - { rules: { "test/report-original-text": "error" } } - ]); - - configs.normalizeSync(); - - const problems = linter.verify( - code, - configs, - { - preprocess: input => input.split(" "), - - /* - * Apply a postprocessor that updates the locations of the reported problems - * to make sure they correspond to the locations in the original text. - */ - postprocess(problemLists) { - problemLists.forEach(problemList => assert.strictEqual(problemList.length, 1)); - return problemLists.reduce( - (combinedList, problemList, index) => - combinedList.concat( - problemList.map( - problem => - Object.assign( - {}, - problem, - { - message: problem.message.toUpperCase(), - column: problem.column + index * 4 - } - ) - ) - ), - [] - ); - } - } - ); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(problems.length, 3); - assert.deepStrictEqual(problems.map(problem => problem.message), ["FOO", "BAR", "BAZ"]); - assert.deepStrictEqual(problems.map(problem => problem.column), [1, 5, 9]); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should use postprocessed problem ranges when applying autofixes", () => { - const code = "foo bar baz"; - const configs = createFlatConfigArray([ - extraConfig, - { - plugins: { - test2: { - rules: { - "capitalize-identifiers": { - meta: { - fixable: "code" - }, - create(context) { - return { - Identifier(node) { - if (node.name !== node.name.toUpperCase()) { - context.report({ - node, - message: "Capitalize this identifier", - fix: fixer => fixer.replaceText(node, node.name.toUpperCase()) - }); - } - } - }; - } - } - } - } - } - }, - { rules: { "test2/capitalize-identifiers": "error" } } - ]); - - configs.normalizeSync(); - - const fixResult = linter.verifyAndFix( - code, - configs, - { - - /* - * Apply a postprocessor that updates the locations of autofixes - * to make sure they correspond to locations in the original text. - */ - preprocess: input => input.split(" "), - postprocess(problemLists) { - return problemLists.reduce( - (combinedProblems, problemList, blockIndex) => - combinedProblems.concat( - problemList.map(problem => - Object.assign(problem, { - fix: { - text: problem.fix.text, - range: problem.fix.range.map( - rangeIndex => rangeIndex + blockIndex * 4 - ) - } - })) - ), - [] - ); - } - } - ); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(fixResult.fixed, true); - assert.strictEqual(fixResult.messages.length, 0); - assert.strictEqual(fixResult.output, "FOO BAR BAZ"); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - // https://github.com/eslint/eslint/issues/16716 - it("should receive unique range arrays in suggestions", () => { - const configs = [ - { - plugins: { - "test-processors": { - processors: { - "line-processor": (() => { - const blocksMap = new Map(); - - return { - preprocess(text, fileName) { - const lines = text.split("\n"); - - blocksMap.set(fileName, lines); - - return lines.map((line, index) => ({ - text: line, - filename: `${index}.js` - })); - }, - - postprocess(messageLists, fileName) { - const lines = blocksMap.get(fileName); - let rangeOffset = 0; - - // intentionaly mutates objects and arrays - messageLists.forEach((messages, index) => { - messages.forEach(message => { - message.line += index; - if (typeof message.endLine === "number") { - message.endLine += index; - } - if (message.fix) { - message.fix.range[0] += rangeOffset; - message.fix.range[1] += rangeOffset; - } - if (message.suggestions) { - message.suggestions.forEach(suggestion => { - suggestion.fix.range[0] += rangeOffset; - suggestion.fix.range[1] += rangeOffset; - }); - } - }); - rangeOffset += lines[index].length + 1; - }); - - return messageLists.flat(); - }, - - supportsAutofix: true - }; - })() - } - }, - - "test-rules": { - rules: { - "no-foo": { - meta: { - hasSuggestions: true, - messages: { - unexpected: "Don't use 'foo'.", - replaceWithBar: "Replace with 'bar'", - replaceWithBaz: "Replace with 'baz'" - } - - }, - create(context) { - return { - Identifier(node) { - const { range } = node; - - if (node.name === "foo") { - context.report({ - node, - messageId: "unexpected", - suggest: [ - { - messageId: "replaceWithBar", - fix: () => ({ range, text: "bar" }) - }, - { - messageId: "replaceWithBaz", - fix: () => ({ range, text: "baz" }) - } - ] - }); - } - } - }; - } - } - } - } - } - }, - { - files: ["**/*.txt"], - processor: "test-processors/line-processor" - }, - { - files: ["**/*.js"], - rules: { - "test-rules/no-foo": 2 - } - } - ]; - - const result = linter.verifyAndFix( - "var a = 5;\nvar foo;\nfoo = a;", - configs, - { filename: "a.txt" } - ); - - assert.deepStrictEqual(result.messages, [ - { - ruleId: "test-rules/no-foo", - severity: 2, - message: "Don't use 'foo'.", - line: 2, - column: 5, - nodeType: "Identifier", - messageId: "unexpected", - endLine: 2, - endColumn: 8, - suggestions: [ - { - messageId: "replaceWithBar", - fix: { range: [15, 18], text: "bar" }, - desc: "Replace with 'bar'" - }, - { - messageId: "replaceWithBaz", - fix: { range: [15, 18], text: "baz" }, - desc: "Replace with 'baz'" - } - ] - }, - { - ruleId: "test-rules/no-foo", - severity: 2, - message: "Don't use 'foo'.", - line: 3, - column: 1, - nodeType: "Identifier", - messageId: "unexpected", - endLine: 3, - endColumn: 4, - suggestions: [ - { - messageId: "replaceWithBar", - fix: { range: [20, 23], text: "bar" }, - desc: "Replace with 'bar'" - }, - { - messageId: "replaceWithBaz", - fix: { range: [20, 23], text: "baz" }, - desc: "Replace with 'baz'" - } - ] - } - ]); - }); - }); - }); - - describe("Edge cases", () => { - - describe("Modules", () => { - const moduleConfig = { - languageOptions: { - sourceType: "module", - ecmaVersion: 6 - } - }; - - it("should properly parse import statements when sourceType is module", () => { - const code = "import foo from 'foo';"; - const messages = linter.verify(code, moduleConfig); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0, "Unexpected linting error."); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should properly parse import all statements when sourceType is module", () => { - const code = "import * as foo from 'foo';"; - const messages = linter.verify(code, moduleConfig); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0, "Unexpected linting error."); - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should properly parse default export statements when sourceType is module", () => { - const code = "export default function initialize() {}"; - const messages = linter.verify(code, moduleConfig); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 0, "Unexpected linting error."); - assert.strictEqual(suppressedMessages.length, 0); - }); - - }); - - - // https://github.com/eslint/eslint/issues/9687 - it("should report an error when invalid languageOptions found", () => { - let messages = linter.verify("", { languageOptions: { ecmaVersion: 222 } }); - - assert.deepStrictEqual(messages.length, 1); - assert.ok(messages[0].message.includes("Invalid ecmaVersion")); - - assert.throws(() => { - linter.verify("", { languageOptions: { sourceType: "foo" } }); - }, /Expected "script", "module", or "commonjs"./u); - - - messages = linter.verify("", { languageOptions: { ecmaVersion: 5, sourceType: "module" } }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.deepStrictEqual(messages.length, 1); - assert.ok(messages[0].message.includes("sourceType 'module' is not supported when ecmaVersion < 2015")); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not crash when invalid parentheses syntax is encountered", () => { - linter.verify("left = (aSize.width/2) - ()", {}); - }); - - it("should not crash when let is used inside of switch case", () => { - linter.verify("switch(foo) { case 1: let bar=2; }", { languageOptions: { ecmaVersion: 6 } }); - }); - - it("should not crash when parsing destructured assignment", () => { - linter.verify("var { a='a' } = {};", { languageOptions: { ecmaVersion: 6 } }); - }); - - it("should report syntax error when a keyword exists in object property shorthand", () => { - const messages = linter.verify("let a = {this}", { languageOptions: { ecmaVersion: 6 } }); - const suppressedMessages = linter.getSuppressedMessages(); - - assert.strictEqual(messages.length, 1); - assert.strictEqual(messages[0].fatal, true); - - assert.strictEqual(suppressedMessages.length, 0); - }); - - it("should not crash when we reuse the SourceCode object", () => { - const config = { - languageOptions: { - ecmaVersion: 6, - parserOptions: { - ecmaFeatures: { jsx: true } - } - } - }; - - linter.verify("function render() { return
{hello}
}", config); - linter.verify(linter.getSourceCode(), config); - }); - - it("should reuse the SourceCode object", () => { - let ast1 = null, - ast2 = null; - - const config = { - plugins: { - test: { - rules: { - "save-ast1": { - create: () => ({ - Program(node) { - ast1 = node; - } - }) - }, - - "save-ast2": { - create: () => ({ - Program(node) { - ast2 = node; - } - }) - } - - } - } - }, - languageOptions: { - ecmaVersion: 6, - parserOptions: { - ecmaFeatures: { jsx: true } - } - } - }; - - - linter.verify("function render() { return
{hello}
}", { ...config, rules: { "test/save-ast1": "error" } }); - linter.verify(linter.getSourceCode(), { ...config, rules: { "test/save-ast2": "error" } }); - - assert(ast1 !== null); - assert(ast2 !== null); - assert(ast1 === ast2); - }); - - it("should not modify config object passed as argument", () => { - const config = {}; - - Object.freeze(config); - linter.verify("var", config); - }); - - it("should pass 'id' to rule contexts with the rule id", () => { - - const spy = sinon.spy(context => { - assert.strictEqual(context.id, "test/foo-bar-baz"); - return {}; - }); - - const config = { - plugins: { - test: { - rules: { - "foo-bar-baz": { create: spy } - } - } - }, - rules: { - "test/foo-bar-baz": "error" - } - }; - - - linter.verify("x", config); - assert(spy.calledOnce); - }); - - describe("when evaluating an empty string", () => { - it("runs rules", () => { - - const config = { - plugins: { - test: { - rules: { - "no-programs": { - create(context) { - return { - Program(node) { - context.report({ node, message: "No programs allowed." }); - } - }; - } - } - } - } - }, - rules: { - "test/no-programs": "error" - } - }; - - assert.strictEqual( - linter.verify("", config).length, - 1 - ); - }); - }); - - }); - + `, + }, + + // duplicates in the list + { + code: "// eslint-disable-line test/unused, test/unused, test/used", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/unused, test/used, test/unused", + output: "// eslint-disable-line test/used", + }, + { + code: "// eslint-disable-line test/used, test/unused, test/unused, test/used", + output: "// eslint-disable-line test/used, test/used", + }, + ]; + + for (const { code, output } of tests) { + // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach() + it(code, () => { + assert.strictEqual( + linter.verifyAndFix(code, config).output, + output, + ); + }); + + // Test for quoted rule names + for (const testcaseForLiteral of [ + { + code: code.replace(/(test\/[\w-]+)/gu, '"$1"'), + output: output.replace( + /(test\/[\w-]+)/gu, + '"$1"', + ), + }, + { + code: code.replace(/(test\/[\w-]+)/gu, "'$1'"), + output: output.replace( + /(test\/[\w-]+)/gu, + "'$1'", + ), + }, + ]) { + // eslint-disable-next-line no-loop-func -- `linter` is getting updated in beforeEach() + it(testcaseForLiteral.code, () => { + assert.strictEqual( + linter.verifyAndFix( + testcaseForLiteral.code, + config, + ).output, + testcaseForLiteral.output, + ); + }); + } + } + }); + }); + }); + + describe("Default Global Variables", () => { + const code = "x"; + + it("builtin global variables should be available in the global scope", () => { + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ); + + assert.notStrictEqual( + getVariable(scope, "Object"), + null, + ); + assert.notStrictEqual( + getVariable(scope, "Array"), + null, + ); + assert.notStrictEqual( + getVariable(scope, "undefined"), + null, + ); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, + rules: { + "test/checker": "error", + }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce, "Rule should have been called."); + }); + + it("ES6 global variables should be available by default", () => { + let spy; + const config = { + plugins: { + test: { + rules: { + checker: { + create(context) { + spy = sinon.spy(node => { + const scope = + context.sourceCode.getScope( + node, + ); + + assert.notStrictEqual( + getVariable(scope, "Promise"), + null, + ); + assert.notStrictEqual( + getVariable(scope, "Symbol"), + null, + ); + assert.notStrictEqual( + getVariable(scope, "WeakMap"), + null, + ); + }); + + return { Program: spy }; + }, + }, + }, + }, + }, + languageOptions: { + sourceType: "script", + }, + rules: { + "test/checker": "error", + }, + }; + + linter.verify(code, config); + assert(spy && spy.calledOnce); + }); + }); + + describe("Suggestions", () => { + it("provides suggestion information for tools to use", () => { + const config = { + plugins: { + test: { + rules: { + "rule-with-suggestions": { + meta: { hasSuggestions: true }, + create: context => ({ + Program(node) { + context.report({ + node, + message: "Incorrect spacing", + suggest: [ + { + desc: "Insert space at the beginning", + fix: fixer => + fixer.insertTextBefore( + node, + " ", + ), + }, + { + desc: "Insert space at the end", + fix: fixer => + fixer.insertTextAfter( + node, + " ", + ), + }, + ], + }); + }, + }), + }, + }, + }, + }, + rules: { + "test/rule-with-suggestions": "error", + }, + }; + + const messages = linter.verify("var a = 1;", config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages[0].suggestions, [ + { + desc: "Insert space at the beginning", + fix: { + range: [0, 0], + text: " ", + }, + }, + { + desc: "Insert space at the end", + fix: { + range: [10, 10], + text: " ", + }, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("supports messageIds for suggestions", () => { + const config = { + plugins: { + test: { + rules: { + "rule-with-suggestions": { + meta: { + messages: { + suggestion1: + "Insert space at the beginning", + suggestion2: + "Insert space at the end", + }, + hasSuggestions: true, + }, + create: context => ({ + Program(node) { + context.report({ + node, + message: "Incorrect spacing", + suggest: [ + { + messageId: + "suggestion1", + fix: fixer => + fixer.insertTextBefore( + node, + " ", + ), + }, + { + messageId: + "suggestion2", + fix: fixer => + fixer.insertTextAfter( + node, + " ", + ), + }, + ], + }); + }, + }), + }, + }, + }, + }, + rules: { + "test/rule-with-suggestions": "error", + }, + }; + + const messages = linter.verify("var a = 1;", config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages[0].suggestions, [ + { + messageId: "suggestion1", + desc: "Insert space at the beginning", + fix: { + range: [0, 0], + text: " ", + }, + }, + { + messageId: "suggestion2", + desc: "Insert space at the end", + fix: { + range: [10, 10], + text: " ", + }, + }, + ]); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled", () => { + const config = { + plugins: { + test: { + rules: { + "rule-with-suggestions": { + meta: { docs: {}, schema: [] }, + create: context => ({ + Program(node) { + context.report({ + node, + message: "hello world", + suggest: [ + { + desc: "convert to foo", + fix: fixer => + fixer.insertTextBefore( + node, + " ", + ), + }, + ], + }); + }, + }), + }, + }, + }, + }, + rules: { + "test/rule-with-suggestions": "error", + }, + }; + + assert.throws(() => { + linter.verify("0", config); + }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`."); + }); + + it("should throw an error if suggestion is passed but `meta.hasSuggestions` property is not enabled and the rule has the obsolete `meta.docs.suggestion` property", () => { + const config = { + plugins: { + test: { + rules: { + "rule-with-meta-docs-suggestion": { + meta: { + docs: { suggestion: true }, + schema: [], + }, + create: context => ({ + Program(node) { + context.report({ + node, + message: "hello world", + suggest: [ + { + desc: "convert to foo", + fix: fixer => + fixer.insertTextBefore( + node, + " ", + ), + }, + ], + }); + }, + }), + }, + }, + }, + }, + rules: { + "test/rule-with-meta-docs-suggestion": "error", + }, + }; + + assert.throws(() => { + linter.verify("0", config); + }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`. `meta.docs.suggestion` is ignored by ESLint."); + }); + }); + + describe("Error Conditions", () => { + describe("when evaluating broken code", () => { + const code = BROKEN_TEST_CODE; + + it("should report a violation with a useful parse error prefix", () => { + const messages = linter.verify(code); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.isNull(messages[0].ruleId); + assert.strictEqual(messages[0].line, 1); + assert.strictEqual(messages[0].column, 4); + assert.isTrue(messages[0].fatal); + assert.match(messages[0].message, /^Parsing error:/u); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should report source code where the issue is present", () => { + const inValidCode = [ + "var x = 20;", + "if (x ==4 {", + " x++;", + "}", + ]; + const messages = linter.verify(inValidCode.join("\n"), {}); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].severity, 2); + assert.isTrue(messages[0].fatal); + assert.match(messages[0].message, /^Parsing error:/u); + + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + describe("when using a rule which has been replaced", () => { + const code = TEST_CODE; + + it("should report the new rule", () => { + assert.throws(() => { + linter.verify(code, { + rules: { "no-comma-dangle": 2 }, + }); + }, /Key "rules": Key "no-comma-dangle": Rule "no-comma-dangle" was removed and replaced by "comma-dangle"/u); + }); + }); + }); + + it("should default to flat config mode when a config isn't passed", () => { + // eslint-env should not be honored + const messages = linter.verify( + "/*eslint no-undef:error*//*eslint-env browser*/\nwindow;", + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].ruleId, "no-undef"); + assert.strictEqual(messages[0].severity, 2); + assert.strictEqual(messages[0].line, 2); + assert.strictEqual(messages[0].column, 1); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + describe("Passing SourceCode", () => { + it("should verify a SourceCode object created with the constructor", () => { + const text = "var foo = bar;"; + const sourceCode = new SourceCode({ + text, + ast: espree.parse(text, { + loc: true, + range: true, + tokens: true, + comment: true, + }), + }); + const messages = linter.verify(sourceCode, { + rules: { "no-undef": "error" }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual( + messages[0].message, + "'bar' is not defined.", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should ensure that SourceCode properties are copied over during linting", () => { + const text = "var foo = bar;"; + const sourceCode = new SourceCode({ + text, + ast: espree.parse(text, { + loc: true, + range: true, + tokens: true, + comment: true, + }), + hasBOM: true, + }); + + linter.verify(sourceCode, { rules: { "no-undef": "error" } }); + const resultSourceCode = linter.getSourceCode(); + + assert.strictEqual(resultSourceCode.text, text); + assert.strictEqual(resultSourceCode.ast, sourceCode.ast); + assert.strictEqual(resultSourceCode.hasBOM, true); + }); + }); + }); + + describe("getSourceCode()", () => { + const code = TEST_CODE; + + it("should retrieve SourceCode object after reset", () => { + linter.verify(code, {}, filename); + + const sourceCode = linter.getSourceCode(); + + assert.isObject(sourceCode); + assert.strictEqual(sourceCode.text, code); + assert.isObject(sourceCode.ast); + }); + + it("should retrieve SourceCode object without reset", () => { + linter.verify(code, {}, filename); + + const sourceCode = linter.getSourceCode(); + + assert.isObject(sourceCode); + assert.strictEqual(sourceCode.text, code); + assert.isObject(sourceCode.ast); + }); + }); + + describe("getSuppressedMessages()", () => { + it("should have no suppressed messages", () => { + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should have a suppressed message", () => { + const code = + '/* eslint-disable no-alert -- justification */\nalert("test");'; + const config = { + rules: { "no-alert": 1 }, + }; + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.deepStrictEqual(suppressedMessages[0].suppressions, [ + { kind: "directive", justification: "justification" }, + ]); + }); + + it("should have a suppressed message", () => { + const code = [ + "/* eslint-disable no-alert -- j1", + " * j2", + " */", + 'alert("test");', + ].join("\n"); + const config = { + rules: { "no-alert": 1 }, + }; + const messages = linter.verify(code, config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 0); + + assert.strictEqual(suppressedMessages.length, 1); + assert.strictEqual(suppressedMessages[0].ruleId, "no-alert"); + assert.deepStrictEqual(suppressedMessages[0].suppressions, [ + { kind: "directive", justification: "j1\n * j2" }, + ]); + }); + }); + + describe("defineRule()", () => { + it("should throw an error when called in flat config mode", () => { + assert.throws(() => { + linter.defineRule("foo", { + create() {}, + }); + }, /This method cannot be used with flat config/u); + }); + }); + + describe("defineRules()", () => { + it("should throw an error when called in flat config mode", () => { + assert.throws(() => { + linter.defineRules({}); + }, /This method cannot be used with flat config/u); + }); + }); + + describe("defineParser()", () => { + it("should throw an error when called in flat config mode", () => { + assert.throws(() => { + linter.defineParser("foo", {}); + }, /This method cannot be used with flat config/u); + }); + }); + + describe("getRules()", () => { + it("should throw an error when called in flat config mode", () => { + assert.throws(() => { + linter.getRules(); + }, /This method cannot be used with flat config/u); + }); + }); + + describe("version", () => { + it("should return current version number", () => { + const version = linter.version; + + assert.isString(version); + assert.isTrue(parseInt(version[0], 10) >= 3); + }); + }); + + describe("verifyAndFix()", () => { + it("Fixes the code", () => { + const messages = linter.verifyAndFix( + "var a", + { + rules: { + semi: 2, + }, + }, + { filename: "test.js" }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.output, + "var a;", + "Fixes were applied correctly", + ); + assert.isTrue(messages.fixed); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("does not require a third argument", () => { + const fixResult = linter.verifyAndFix("var a", { + rules: { + semi: 2, + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(fixResult, { + fixed: true, + messages: [], + output: "var a;", + }); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("does not include suggestions in autofix results", () => { + const fixResult = linter.verifyAndFix("var foo = /\\#/", { + rules: { + semi: 2, + "no-useless-escape": 2, + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(fixResult.output, "var foo = /\\#/;"); + assert.strictEqual(fixResult.fixed, true); + assert.strictEqual( + fixResult.messages[0].suggestions.length > 0, + true, + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("does not apply autofixes when fix argument is `false`", () => { + const fixResult = linter.verifyAndFix( + "var a", + { + rules: { + semi: 2, + }, + }, + { fix: false }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(fixResult.fixed, false); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("stops fixing after 10 passes", () => { + const config = { + plugins: { + test: { + rules: { + "add-spaces": { + meta: { + fixable: "whitespace", + }, + create(context) { + return { + Program(node) { + context.report({ + node, + message: + "Add a space before this node.", + fix: fixer => + fixer.insertTextBefore( + node, + " ", + ), + }); + }, + }; + }, + }, + }, + }, + }, + rules: { + "test/add-spaces": "error", + }, + }; + + const fixResult = linter.verifyAndFix("a", config); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(fixResult.fixed, true); + assert.strictEqual(fixResult.output, `${" ".repeat(10)}a`); + assert.strictEqual(fixResult.messages.length, 1); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should throw an error if fix is passed but meta has no `fixable` property", () => { + const config = { + plugins: { + test: { + rules: { + "test-rule": { + meta: { + docs: {}, + schema: [], + }, + create: context => ({ + Program(node) { + context.report( + node, + "hello world", + {}, + () => ({ range: [1, 1], text: "" }), + ); + }, + }), + }, + }, + }, + }, + rules: { + "test/test-rule": "error", + }, + }; + + assert.throws(() => { + linter.verify("0", config); + }, /Fixable rules must set the `meta\.fixable` property to "code" or "whitespace".\nOccurred while linting :1\nRule: "test\/test-rule"$/u); + }); + + it("should throw an error if fix is passed and there is no metadata", () => { + const config = { + plugins: { + test: { + rules: { + "test-rule": { + create: context => ({ + Program(node) { + context.report( + node, + "hello world", + {}, + () => ({ range: [1, 1], text: "" }), + ); + }, + }), + }, + }, + }, + }, + rules: { + "test/test-rule": "error", + }, + }; + + assert.throws(() => { + linter.verify("0", config); + }, /Fixable rules must set the `meta\.fixable` property/u); + }); + + it("should throw an error if fix is passed from a legacy-format rule", () => { + const config = { + plugins: { + test: { + rules: { + "test-rule": { + create: context => ({ + Program(node) { + context.report( + node, + "hello world", + {}, + () => ({ range: [1, 1], text: "" }), + ); + }, + }), + }, + }, + }, + }, + rules: { + "test/test-rule": "error", + }, + }; + + assert.throws(() => { + linter.verify("0", config); + }, /Fixable rules must set the `meta\.fixable` property/u); + }); + + describe("Circular autofixes", () => { + it("should stop fixing if a circular fix is detected", () => { + const config = { + plugins: { + test: { + rules: { + "add-leading-hyphen": { + meta: { + fixable: "whitespace", + }, + create(context) { + return { + Program(node) { + const sourceCode = + context.sourceCode; + const hasLeadingHyphen = + sourceCode + .getText(node) + .startsWith("-"); + + if (!hasLeadingHyphen) { + context.report({ + node, + message: + "Add leading hyphen.", + fix(fixer) { + return fixer.insertTextBefore( + node, + "-", + ); + }, + }); + } + }, + }; + }, + }, + "remove-leading-hyphen": { + meta: { + fixable: "whitespace", + }, + create(context) { + return { + Program(node) { + const sourceCode = + context.sourceCode; + const hasLeadingHyphen = + sourceCode + .getText(node) + .startsWith("-"); + + if (hasLeadingHyphen) { + context.report({ + node, + message: + "Remove leading hyphen.", + fix(fixer) { + return fixer.removeRange( + [0, 1], + ); + }, + }); + } + }, + }; + }, + }, + }, + }, + }, + rules: { + "test/add-leading-hyphen": "error", + "test/remove-leading-hyphen": "error", + }, + }; + + const initialCode = "-a"; + const fixResult = linter.verifyAndFix(initialCode, config, { + filename: "test.js", + }); + + assert.strictEqual( + fixResult.fixed, + true, + "Fixing was applied.", + ); + assert.strictEqual( + fixResult.output, + "-a", + "Output should match the original input due to circular fixes.", + ); + assert.strictEqual( + fixResult.messages.length, + 1, + "There should be one remaining lint message after detecting circular fixes.", + ); + assert.strictEqual( + fixResult.messages[0].ruleId, + "test/remove-leading-hyphen", + ); + + // Verify the warning was emitted + assert( + warningService.emitCircularFixesWarning.calledOnceWithExactly( + "test.js", + ), + "calls `warningService.emitCircularFixesWarning()` once with the correct argument", + ); + + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + suppressedMessages.length, + 0, + "No suppressed messages should exist.", + ); + }); + }); + }); + + describe("options", () => { + it("rules should apply meta.defaultOptions on top of schema defaults", () => { + const config = { + plugins: { + test: { + rules: { + checker: { + meta: { + defaultOptions: [ + { + inBoth: "from-default-options", + inDefaultOptions: + "from-default-options", + }, + ], + schema: [ + { + type: "object", + properties: { + inBoth: { + default: "from-schema", + type: "string", + }, + inDefaultOptions: { + type: "string", + }, + inSchema: { + default: "from-schema", + type: "string", + }, + }, + additionalProperties: false, + }, + ], + }, + create(context) { + return { + Program(node) { + context.report({ + message: JSON.stringify( + context.options[0], + ), + node, + }); + }, + }; + }, + }, + }, + }, + }, + rules: { + "test/checker": "error", + }, + }; + + const messages = linter.verify("foo", config, filename); + + assert.deepStrictEqual(JSON.parse(messages[0].message), { + inBoth: "from-default-options", + inDefaultOptions: "from-default-options", + inSchema: "from-schema", + }); + }); + + it("meta.defaultOptions should be applied even if rule has schema:false", () => { + const config = { + plugins: { + test: { + rules: { + checker: { + meta: { + defaultOptions: ["foo"], + schema: false, + }, + create(context) { + return { + Program(node) { + context.report({ + message: context.options[0], + node, + }); + }, + }; + }, + }, + }, + }, + }, + rules: { + "test/checker": "error", + }, + }; + const messages = linter.verify("", config, filename); + + assert.strictEqual(messages[0].message, "foo"); + }); + }); + + describe("processors", () => { + let receivedFilenames = []; + let receivedPhysicalFilenames = []; + const extraConfig = { + plugins: { + test: { + rules: { + "report-original-text": { + meta: {}, + create(context) { + return { + Program(ast) { + assert.strictEqual( + context.getFilename(), + context.filename, + ); + assert.strictEqual( + context.getPhysicalFilename(), + context.physicalFilename, + ); + + receivedFilenames.push( + context.filename, + ); + receivedPhysicalFilenames.push( + context.physicalFilename, + ); + + context.report({ + node: ast, + message: context.sourceCode.text, + }); + }, + }; + }, + }, + }, + }, + }, + }; + + beforeEach(() => { + receivedFilenames = []; + receivedPhysicalFilenames = []; + }); + + describe("preprocessors", () => { + it("should receive text and filename.", () => { + const code = "foo bar baz"; + const preprocess = sinon.spy(text => text.split(" ")); + const configs = createFlatConfigArray({}); + + configs.normalizeSync(); + + linter.verify(code, configs, { filename, preprocess }); + + assert.strictEqual( + preprocess.calledOnce, + true, + "preprocess wasn't called", + ); + assert.deepStrictEqual( + preprocess.args[0], + [code, filename], + "preprocess was called with the wrong arguments", + ); + }); + + it("should run preprocess only once", () => { + const logs = []; + const config = { + files: ["*.md"], + processor: { + preprocess(text, filenameForText) { + logs.push({ + text, + filename: filenameForText, + }); + + return [{ text: "bar", filename: "0.js" }]; + }, + postprocess() { + return []; + }, + }, + }; + + linter.verify("foo", config, "a.md"); + assert.strictEqual( + logs.length, + 1, + "preprocess() should only be called once.", + ); + }); + + it("should pass the BOM to preprocess", () => { + const logs = []; + const code = "\uFEFFfoo"; + const config = { + files: ["**/*.myjs"], + processor: { + preprocess(text, filenameForText) { + logs.push({ + text, + filename: filenameForText, + }); + + return [{ text, filename: filenameForText }]; + }, + postprocess(messages) { + return messages.flat(); + }, + }, + rules: { + "unicode-bom": ["error", "never"], + }, + }; + + const results = linter.verify(code, config, { + filename: "a.myjs", + filterCodeBlock() { + return true; + }, + }); + + assert.deepStrictEqual(logs, [ + { + text: code, + filename: "a.myjs", + }, + ]); + + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].ruleId, "unicode-bom"); + }); + + it("should apply a preprocessor to the code, and lint each code sample separately", () => { + const code = "foo bar baz"; + const configs = createFlatConfigArray([ + extraConfig, + { rules: { "test/report-original-text": "error" } }, + ]); + + configs.normalizeSync(); + + const problems = linter.verify(code, configs, { + filename, + + // Apply a preprocessor that splits the source text into spaces and lints each word individually + preprocess(input) { + return input.split(" "); + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(problems.length, 3); + assert.deepStrictEqual( + problems.map(problem => problem.message), + ["foo", "bar", "baz"], + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should apply a preprocessor to the code even if the preprocessor returned code block objects.", () => { + const code = "foo bar baz"; + const configs = createFlatConfigArray([ + extraConfig, + { rules: { "test/report-original-text": "error" } }, + ]); + + configs.normalizeSync(); + + const problems = linter.verify(code, configs, { + filename, + + // Apply a preprocessor that splits the source text into spaces and lints each word individually + preprocess(input) { + return input.split(" ").map(text => ({ + filename: "block.js", + text, + })); + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(problems.length, 3); + assert.deepStrictEqual( + problems.map(problem => problem.message), + ["foo", "bar", "baz"], + ); + + assert.strictEqual(suppressedMessages.length, 0); + + // filename + assert.strictEqual(receivedFilenames.length, 3); + assert( + /^filename\.js[/\\]0_block\.js/u.test(receivedFilenames[0]), + ); + assert( + /^filename\.js[/\\]1_block\.js/u.test(receivedFilenames[1]), + ); + assert( + /^filename\.js[/\\]2_block\.js/u.test(receivedFilenames[2]), + ); + + // physical filename + assert.strictEqual(receivedPhysicalFilenames.length, 3); + assert.strictEqual( + receivedPhysicalFilenames.every(name => name === filename), + true, + ); + }); + + it("should receive text even if a SourceCode object was given.", () => { + const code = "foo"; + const preprocess = sinon.spy(text => text.split(" ")); + const configs = createFlatConfigArray([extraConfig]); + + configs.normalizeSync(); + + linter.verify(code, configs); + const sourceCode = linter.getSourceCode(); + + linter.verify(sourceCode, configs, { filename, preprocess }); + + assert.strictEqual(preprocess.calledOnce, true); + assert.deepStrictEqual(preprocess.args[0], [code, filename]); + }); + + it("should receive text even if a SourceCode object was given (with BOM).", () => { + const code = "\uFEFFfoo"; + const preprocess = sinon.spy(text => text.split(" ")); + const configs = createFlatConfigArray([extraConfig]); + + configs.normalizeSync(); + + linter.verify(code, configs); + const sourceCode = linter.getSourceCode(); + + linter.verify(sourceCode, configs, { filename, preprocess }); + + assert.strictEqual(preprocess.calledOnce, true); + assert.deepStrictEqual(preprocess.args[0], [code, filename]); + }); + + it("should catch preprocess error.", () => { + const code = "foo"; + const preprocess = sinon.spy(() => { + throw Object.assign(new SyntaxError("Invalid syntax"), { + lineNumber: 1, + column: 1, + }); + }); + + const configs = createFlatConfigArray([extraConfig]); + + configs.normalizeSync(); + + const messages = linter.verify(code, configs, { + filename, + preprocess, + }); + + assert.strictEqual(preprocess.calledOnce, true); + assert.deepStrictEqual(preprocess.args[0], [code, filename]); + assert.deepStrictEqual(messages, [ + { + ruleId: null, + fatal: true, + severity: 2, + message: "Preprocessing error: Invalid syntax", + line: 1, + column: 1, + nodeType: null, + }, + ]); + }); + + // https://github.com/eslint/markdown/blob/main/rfcs/configure-file-name-from-block-meta.md#name-uniqueness + it("should allow preprocessor to return filenames with a slash and treat them as subpaths.", () => { + const problems = linter.verify( + "foo bar baz", + [ + { + files: [filename], + processor: { + preprocess(input) { + return input.split(" ").map(text => ({ + filename: "example/block.js", + text, + })); + }, + postprocess(messagesList) { + return messagesList.flat(); + }, + }, + }, + extraConfig, + { + files: ["**/block.js"], + rules: { + "test/report-original-text": "error", + }, + }, + ], + { + filename, + }, + ); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(problems.length, 3); + assert.deepStrictEqual( + problems.map(problem => problem.message), + ["foo", "bar", "baz"], + ); + + assert.strictEqual(suppressedMessages.length, 0); + + // filename + assert.strictEqual(receivedFilenames.length, 3); + assert.match( + receivedFilenames[0], + /^filename\.js[/\\]0_example[/\\]block\.js/u, + ); + assert.match( + receivedFilenames[1], + /^filename\.js[/\\]1_example[/\\]block\.js/u, + ); + assert.match( + receivedFilenames[2], + /^filename\.js[/\\]2_example[/\\]block\.js/u, + ); + + // physical filename + assert.strictEqual(receivedPhysicalFilenames.length, 3); + assert.strictEqual( + receivedPhysicalFilenames.every(name => name === filename), + true, + ); + }); + }); + + describe("postprocessors", () => { + it("should receive result and filename.", () => { + const code = "foo bar baz"; + const preprocess = sinon.spy(text => text.split(" ")); + const postprocess = sinon.spy(text => [text]); + const configs = createFlatConfigArray([extraConfig]); + + configs.normalizeSync(); + + linter.verify(code, configs, { + filename, + postprocess, + preprocess, + }); + + assert.strictEqual(postprocess.calledOnce, true); + assert.deepStrictEqual(postprocess.args[0], [ + [[], [], []], + filename, + ]); + }); + + it("should apply a postprocessor to the reported messages", () => { + const code = "foo bar baz"; + const configs = createFlatConfigArray([ + extraConfig, + { rules: { "test/report-original-text": "error" } }, + ]); + + configs.normalizeSync(); + + const problems = linter.verify(code, configs, { + preprocess: input => input.split(" "), + + /* + * Apply a postprocessor that updates the locations of the reported problems + * to make sure they correspond to the locations in the original text. + */ + postprocess(problemLists) { + problemLists.forEach(problemList => + assert.strictEqual(problemList.length, 1), + ); + return problemLists.reduce( + (combinedList, problemList, index) => + combinedList.concat( + problemList.map(problem => + Object.assign({}, problem, { + message: + problem.message.toUpperCase(), + column: problem.column + index * 4, + }), + ), + ), + [], + ); + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(problems.length, 3); + assert.deepStrictEqual( + problems.map(problem => problem.message), + ["FOO", "BAR", "BAZ"], + ); + assert.deepStrictEqual( + problems.map(problem => problem.column), + [1, 5, 9], + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should use postprocessed problem ranges when applying autofixes", () => { + const code = "foo bar baz"; + const configs = createFlatConfigArray([ + extraConfig, + { + plugins: { + test2: { + rules: { + "capitalize-identifiers": { + meta: { + fixable: "code", + }, + create(context) { + return { + Identifier(node) { + if ( + node.name !== + node.name.toUpperCase() + ) { + context.report({ + node, + message: + "Capitalize this identifier", + fix: fixer => + fixer.replaceText( + node, + node.name.toUpperCase(), + ), + }); + } + }, + }; + }, + }, + }, + }, + }, + }, + { rules: { "test2/capitalize-identifiers": "error" } }, + ]); + + configs.normalizeSync(); + + const fixResult = linter.verifyAndFix(code, configs, { + /* + * Apply a postprocessor that updates the locations of autofixes + * to make sure they correspond to locations in the original text. + */ + preprocess: input => input.split(" "), + postprocess(problemLists) { + return problemLists.reduce( + (combinedProblems, problemList, blockIndex) => + combinedProblems.concat( + problemList.map(problem => + Object.assign(problem, { + fix: { + text: problem.fix.text, + range: problem.fix.range.map( + rangeIndex => + rangeIndex + + blockIndex * 4, + ), + }, + }), + ), + ), + [], + ); + }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(fixResult.fixed, true); + assert.strictEqual(fixResult.messages.length, 0); + assert.strictEqual(fixResult.output, "FOO BAR BAZ"); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + // https://github.com/eslint/eslint/issues/16716 + it("should receive unique range arrays in suggestions", () => { + const configs = [ + { + plugins: { + "test-processors": { + processors: { + "line-processor": (() => { + const blocksMap = new Map(); + + return { + preprocess(text, fileName) { + const lines = text.split("\n"); + + blocksMap.set(fileName, lines); + + return lines.map( + (line, index) => ({ + text: line, + filename: `${index}.js`, + }), + ); + }, + + postprocess( + messageLists, + fileName, + ) { + const lines = + blocksMap.get(fileName); + let rangeOffset = 0; + + // intentionally mutates objects and arrays + messageLists.forEach( + (messages, index) => { + messages.forEach( + message => { + message.line += + index; + if ( + typeof message.endLine === + "number" + ) { + message.endLine += + index; + } + if ( + message.fix + ) { + message.fix.range[0] += + rangeOffset; + message.fix.range[1] += + rangeOffset; + } + if ( + message.suggestions + ) { + message.suggestions.forEach( + suggestion => { + suggestion.fix.range[0] += + rangeOffset; + suggestion.fix.range[1] += + rangeOffset; + }, + ); + } + }, + ); + rangeOffset += + lines[index] + .length + 1; + }, + ); + + return messageLists.flat(); + }, + + supportsAutofix: true, + }; + })(), + }, + }, + + "test-rules": { + rules: { + "no-foo": { + meta: { + hasSuggestions: true, + messages: { + unexpected: "Don't use 'foo'.", + replaceWithBar: + "Replace with 'bar'", + replaceWithBaz: + "Replace with 'baz'", + }, + }, + create(context) { + return { + Identifier(node) { + const { range } = node; + + if (node.name === "foo") { + context.report({ + node, + messageId: + "unexpected", + suggest: [ + { + messageId: + "replaceWithBar", + fix: () => ({ + range, + text: "bar", + }), + }, + { + messageId: + "replaceWithBaz", + fix: () => ({ + range, + text: "baz", + }), + }, + ], + }); + } + }, + }; + }, + }, + }, + }, + }, + }, + { + files: ["**/*.txt"], + processor: "test-processors/line-processor", + }, + { + files: ["**/*.js"], + rules: { + "test-rules/no-foo": 2, + }, + }, + ]; + + const result = linter.verifyAndFix( + "var a = 5;\nvar foo;\nfoo = a;", + configs, + { filename: "a.txt" }, + ); + + assert.deepStrictEqual(result.messages, [ + { + ruleId: "test-rules/no-foo", + severity: 2, + message: "Don't use 'foo'.", + line: 2, + column: 5, + nodeType: "Identifier", + messageId: "unexpected", + endLine: 2, + endColumn: 8, + suggestions: [ + { + messageId: "replaceWithBar", + fix: { range: [15, 18], text: "bar" }, + desc: "Replace with 'bar'", + }, + { + messageId: "replaceWithBaz", + fix: { range: [15, 18], text: "baz" }, + desc: "Replace with 'baz'", + }, + ], + }, + { + ruleId: "test-rules/no-foo", + severity: 2, + message: "Don't use 'foo'.", + line: 3, + column: 1, + nodeType: "Identifier", + messageId: "unexpected", + endLine: 3, + endColumn: 4, + suggestions: [ + { + messageId: "replaceWithBar", + fix: { range: [20, 23], text: "bar" }, + desc: "Replace with 'bar'", + }, + { + messageId: "replaceWithBaz", + fix: { range: [20, 23], text: "baz" }, + desc: "Replace with 'baz'", + }, + ], + }, + ]); + }); + }); + }); + + describe("Edge cases", () => { + describe("Modules", () => { + const moduleConfig = { + languageOptions: { + sourceType: "module", + ecmaVersion: 6, + }, + }; + + it("should properly parse import statements when sourceType is module", () => { + const code = "import foo from 'foo';"; + const messages = linter.verify(code, moduleConfig); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 0, + "Unexpected linting error.", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should properly parse import all statements when sourceType is module", () => { + const code = "import * as foo from 'foo';"; + const messages = linter.verify(code, moduleConfig); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 0, + "Unexpected linting error.", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should properly parse default export statements when sourceType is module", () => { + const code = "export default function initialize() {}"; + const messages = linter.verify(code, moduleConfig); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual( + messages.length, + 0, + "Unexpected linting error.", + ); + assert.strictEqual(suppressedMessages.length, 0); + }); + }); + + // https://github.com/eslint/eslint/issues/9687 + it("should report an error when invalid languageOptions found", () => { + let messages = linter.verify("", { + languageOptions: { ecmaVersion: 222 }, + }); + + assert.deepStrictEqual(messages.length, 1); + assert.ok(messages[0].message.includes("Invalid ecmaVersion")); + + assert.throws(() => { + linter.verify("", { languageOptions: { sourceType: "foo" } }); + }, /Expected "script", "module", or "commonjs"./u); + + messages = linter.verify("", { + languageOptions: { ecmaVersion: 5, sourceType: "module" }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.deepStrictEqual(messages.length, 1); + assert.ok( + messages[0].message.includes( + "sourceType 'module' is not supported when ecmaVersion < 2015", + ), + ); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not crash when invalid parentheses syntax is encountered", () => { + linter.verify("left = (aSize.width/2) - ()"); + }); + + it("should not crash when let is used inside of switch case", () => { + linter.verify("switch(foo) { case 1: let bar=2; }", { + languageOptions: { ecmaVersion: 6 }, + }); + }); + + it("should not crash when parsing destructured assignment", () => { + linter.verify("var { a='a' } = {};", { + languageOptions: { ecmaVersion: 6 }, + }); + }); + + it("should report syntax error when a keyword exists in object property shorthand", () => { + const messages = linter.verify("let a = {this}", { + languageOptions: { ecmaVersion: 6 }, + }); + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(messages.length, 1); + assert.strictEqual(messages[0].fatal, true); + + assert.strictEqual(suppressedMessages.length, 0); + }); + + it("should not crash when we reuse the SourceCode object", () => { + const config = { + languageOptions: { + ecmaVersion: 6, + parserOptions: { + ecmaFeatures: { jsx: true }, + }, + }, + }; + + linter.verify( + "function render() { return
{hello}
}", + config, + ); + linter.verify(linter.getSourceCode(), config); + }); + + it("should reuse the SourceCode object", () => { + let ast1 = null, + ast2 = null; + + const config = { + plugins: { + test: { + rules: { + "save-ast1": { + create: () => ({ + Program(node) { + ast1 = node; + }, + }), + }, + + "save-ast2": { + create: () => ({ + Program(node) { + ast2 = node; + }, + }), + }, + }, + }, + }, + languageOptions: { + ecmaVersion: 6, + parserOptions: { + ecmaFeatures: { jsx: true }, + }, + }, + }; + + linter.verify( + "function render() { return
{hello}
}", + { ...config, rules: { "test/save-ast1": "error" } }, + ); + linter.verify(linter.getSourceCode(), { + ...config, + rules: { "test/save-ast2": "error" }, + }); + + assert(ast1 !== null); + assert(ast2 !== null); + assert(ast1 === ast2); + }); + + it("should not modify config object passed as argument", () => { + const config = {}; + + Object.freeze(config); + linter.verify("var", config); + }); + + it("should pass 'id' to rule contexts with the rule id", () => { + const spy = sinon.spy(context => { + assert.strictEqual(context.id, "test/foo-bar-baz"); + return {}; + }); + + const config = { + plugins: { + test: { + rules: { + "foo-bar-baz": { create: spy }, + }, + }, + }, + rules: { + "test/foo-bar-baz": "error", + }, + }; + + linter.verify("x", config); + assert(spy.calledOnce); + }); + + describe("when evaluating an empty string", () => { + it("runs rules", () => { + const config = { + plugins: { + test: { + rules: { + "no-programs": { + create(context) { + return { + Program(node) { + context.report({ + node, + message: + "No programs allowed.", + }); + }, + }; + }, + }, + }, + }, + }, + rules: { + "test/no-programs": "error", + }, + }; + + assert.strictEqual(linter.verify("", config).length, 1); + }); + }); + }); + + describe("Languages", () => { + describe("With a language that doesn't have language options", () => { + const config = { + files: ["**/*.json"], + plugins: { + json: jsonPlugin, + }, + language: "json/json", + rules: { + "json/no-empty-keys": 1, + }, + }; + + it("linter.verify() should work", () => { + const messages = linter.verify('{ "": 42 }', config, { + filename: "foo.json", + }); + + assert.strictEqual(messages.length, 1); + + const [message] = messages; + + assert.strictEqual(message.ruleId, "json/no-empty-keys"); + assert.strictEqual(message.severity, 1); + assert.strictEqual(message.messageId, "emptyKey"); + }); + }); + + describe("With a language that has 0-based lines and 1-based columns", () => { + /** + * Changes a 1-based line & 0-based column location to be a 0-based line & 1-based column location + * @param {Object} nodeOrToken An object with a `loc` property. + * @returns {void} + */ + function adjustLoc(nodeOrToken) { + nodeOrToken.loc = { + start: { + line: nodeOrToken.loc.start.line - 1, + column: nodeOrToken.loc.start.column + 1, + }, + end: { + line: nodeOrToken.loc.end.line - 1, + column: nodeOrToken.loc.end.column + 1, + }, + }; + } + + const config = { + plugins: { + test: { + languages: { + js: { + ...jslang, + lineStart: 0, + columnStart: 1, + parse(...args) { + const result = jslang.parse(...args); + + Traverser.traverse(result.ast, { + enter(node) { + adjustLoc(node); + }, + }); + + result.ast.tokens.forEach(adjustLoc); + result.ast.comments.forEach(adjustLoc); + + return result; + }, + }, + }, + rules: { + "no-classes": { + create(context) { + return { + ClassDeclaration(node) { + context.report({ + node, + message: "No classes allowed.", + }); + }, + }; + }, + }, + }, + }, + }, + language: "test/js", + rules: { + "test/no-classes": "error", + }, + }; + + it("should report 1-based location of a lint problem", () => { + const messages = linter.verify( + `${"\n".repeat(4)}${" ".repeat(7)}class A {${"\n".repeat(2)}${" ".repeat(12)}} \n`, + config, + ); + + assert.strictEqual(messages.length, 1); + + const [message] = messages; + + assert.strictEqual(message.ruleId, "test/no-classes"); + assert.strictEqual(message.message, "No classes allowed."); + assert.strictEqual(message.line, 5); + assert.strictEqual(message.column, 8); + assert.strictEqual(message.endLine, 7); + assert.strictEqual(message.endColumn, 14); + }); + + it("should correctly apply eslint-disable-line directive", () => { + const messages = linter.verify( + `${"\n".repeat(4)}class A {} /* eslint-disable-line */\n`, + config, + ); + + assert.strictEqual(messages.length, 0); + + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(suppressedMessages.length, 1); + + const [message] = suppressedMessages; + + assert.strictEqual(message.ruleId, "test/no-classes"); + assert.strictEqual(message.message, "No classes allowed."); + assert.strictEqual(message.line, 5); + assert.strictEqual(message.column, 1); + assert.strictEqual(message.endLine, 5); + assert.strictEqual(message.endColumn, 11); + }); + + it("should correctly apply single-line eslint-disable-next-line directive", () => { + const messages = linter.verify( + `${"\n".repeat(4)} /* eslint-disable-next-line */\nclass A {} \n`, + config, + ); + + assert.strictEqual(messages.length, 0); + + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(suppressedMessages.length, 1); + + const [message] = suppressedMessages; + + assert.strictEqual(message.ruleId, "test/no-classes"); + assert.strictEqual(message.message, "No classes allowed."); + assert.strictEqual(message.line, 6); + assert.strictEqual(message.column, 1); + assert.strictEqual(message.endLine, 6); + assert.strictEqual(message.endColumn, 11); + }); + + it("should correctly apply multiline eslint-disable-next-line directive", () => { + const messages = linter.verify( + `${"\n".repeat(4)}/* eslint-disable-next-line\n test/no-classes*/\nclass A {} \n`, + config, + ); + + assert.strictEqual(messages.length, 0); + + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(suppressedMessages.length, 1); + + const [message] = suppressedMessages; + + assert.strictEqual(message.ruleId, "test/no-classes"); + assert.strictEqual(message.message, "No classes allowed."); + assert.strictEqual(message.line, 7); + assert.strictEqual(message.column, 1); + assert.strictEqual(message.endLine, 7); + assert.strictEqual(message.endColumn, 11); + }); + + it("should correctly apply eslint-disable directive", () => { + const messages = linter.verify( + `${"\n".repeat(4)}/* eslint-disable test/no-classes */class A {} \n`, + config, + ); + + assert.strictEqual(messages.length, 0); + + const suppressedMessages = linter.getSuppressedMessages(); + + assert.strictEqual(suppressedMessages.length, 1); + + const [message] = suppressedMessages; + + assert.strictEqual(message.ruleId, "test/no-classes"); + assert.strictEqual(message.message, "No classes allowed."); + assert.strictEqual(message.line, 5); + assert.strictEqual(message.column, 37); + assert.strictEqual(message.endLine, 5); + assert.strictEqual(message.endColumn, 47); + }); + + it("should correctly report unused disable directives", () => { + const messages = linter.verify( + [ + "", + "", + " /* eslint-enable test/no-classes */", + " /* eslint-disable test/no-classes */", + " /* eslint-enable test/no-classes */", + " // eslint-disable-line test/no-classes", + " class A {}", + " // eslint-disable-line test/no-classes", + " class B {}", + " // eslint-disable-next-line test/no-classes", + "", + " class C {}", + " /* eslint-disable-next-line", + " test/no-classes */ ", + "", + " class D {}", + ].join("\n"), + config, + ); + + assert.strictEqual(messages.length, 10); + + assert.strictEqual(messages[0].ruleId, null); + assert.match( + messages[0].message, + /Unused eslint-enable directive/u, + ); + assert.strictEqual(messages[0].line, 3); + assert.strictEqual(messages[0].column, 3); + + assert.strictEqual(messages[1].ruleId, null); + assert.match( + messages[1].message, + /Unused eslint-disable directive/u, + ); + assert.strictEqual(messages[1].line, 4); + assert.strictEqual(messages[1].column, 3); + + assert.strictEqual(messages[2].ruleId, null); + assert.match( + messages[2].message, + /Unused eslint-disable directive/u, + ); + assert.strictEqual(messages[2].line, 6); + assert.strictEqual(messages[2].column, 3); + + assert.strictEqual(messages[3].ruleId, "test/no-classes"); + assert.strictEqual(messages[3].message, "No classes allowed."); + assert.strictEqual(messages[3].line, 7); + assert.strictEqual(messages[3].column, 3); + + assert.strictEqual(messages[4].ruleId, null); + assert.match( + messages[4].message, + /Unused eslint-disable directive/u, + ); + assert.strictEqual(messages[4].line, 8); + assert.strictEqual(messages[4].column, 3); + + assert.strictEqual(messages[5].ruleId, "test/no-classes"); + assert.strictEqual(messages[5].message, "No classes allowed."); + assert.strictEqual(messages[5].line, 9); + assert.strictEqual(messages[5].column, 3); + + assert.strictEqual(messages[6].ruleId, null); + assert.match( + messages[6].message, + /Unused eslint-disable directive/u, + ); + assert.strictEqual(messages[6].line, 10); + assert.strictEqual(messages[6].column, 3); + + assert.strictEqual(messages[7].ruleId, "test/no-classes"); + assert.strictEqual(messages[7].message, "No classes allowed."); + assert.strictEqual(messages[7].line, 12); + assert.strictEqual(messages[7].column, 3); + + assert.strictEqual(messages[8].ruleId, null); + assert.match( + messages[8].message, + /Unused eslint-disable directive/u, + ); + assert.strictEqual(messages[8].line, 13); + assert.strictEqual(messages[8].column, 3); + + assert.strictEqual(messages[9].ruleId, "test/no-classes"); + assert.strictEqual(messages[9].message, "No classes allowed."); + assert.strictEqual(messages[9].line, 16); + assert.strictEqual(messages[9].column, 3); + + assert.strictEqual(linter.getSuppressedMessages().length, 0); + }); + + it("should correctly report problem for a non-existent rule in disable directive", () => { + const messages = linter.verify( + `${"\n".repeat(4)}${" ".repeat(7)}/* eslint-disable \n${" ".repeat(8)}test/foo */ \n`, + config, + ); + + assert.strictEqual(messages.length, 1); + + const [message] = messages; + + assert.strictEqual(message.ruleId, "test/foo"); + assert.strictEqual( + message.message, + "Definition for rule 'test/foo' was not found.", + ); + assert.strictEqual(message.line, 5); + assert.strictEqual(message.column, 8); + assert.strictEqual(message.endLine, 6); + assert.strictEqual(message.endColumn, 20); + }); + }); + }); }); diff --git a/tests/lib/linter/node-event-generator.js b/tests/lib/linter/node-event-generator.js deleted file mode 100644 index 2719bf30e300..000000000000 --- a/tests/lib/linter/node-event-generator.js +++ /dev/null @@ -1,471 +0,0 @@ -/** - * @fileoverview Tests for NodeEventGenerator. - * @author Toru Nagashima - */ -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("assert"), - sinon = require("sinon"), - espree = require("espree"), - vk = require("eslint-visitor-keys"), - Traverser = require("../../../lib/shared/traverser"), - EventGeneratorTester = require("../../../tools/internal-testers/event-generator-tester"), - createEmitter = require("../../../lib/linter/safe-emitter"), - NodeEventGenerator = require("../../../lib/linter/node-event-generator"); - - -//------------------------------------------------------------------------------ -// Constants -//------------------------------------------------------------------------------ - -const ESPREE_CONFIG = { - ecmaVersion: 6, - comment: true, - tokens: true, - range: true, - loc: true -}; - -const STANDARD_ESQUERY_OPTION = { visitorKeys: vk.KEYS, fallback: Traverser.getKeys }; - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("NodeEventGenerator", () => { - EventGeneratorTester.testEventGeneratorInterface( - new NodeEventGenerator(createEmitter(), STANDARD_ESQUERY_OPTION) - ); - - describe("entering a single AST node", () => { - let emitter, generator; - - beforeEach(() => { - emitter = Object.create(createEmitter(), { emit: { value: sinon.spy() } }); - - ["Foo", "Bar", "Foo > Bar", "Foo:exit"].forEach(selector => emitter.on(selector, () => {})); - generator = new NodeEventGenerator(emitter, STANDARD_ESQUERY_OPTION); - }); - - it("should generate events for entering AST node.", () => { - const dummyNode = { type: "Foo", value: 1 }; - - generator.enterNode(dummyNode); - - assert(emitter.emit.calledOnce); - assert(emitter.emit.calledWith("Foo", dummyNode)); - }); - - it("should generate events for exiting AST node.", () => { - const dummyNode = { type: "Foo", value: 1 }; - - generator.leaveNode(dummyNode); - - assert(emitter.emit.calledOnce); - assert(emitter.emit.calledWith("Foo:exit", dummyNode)); - }); - - it("should generate events for AST queries", () => { - const dummyNode = { type: "Bar", parent: { type: "Foo" } }; - - generator.enterNode(dummyNode); - - assert(emitter.emit.calledTwice); - assert(emitter.emit.calledWith("Foo > Bar", dummyNode)); - }); - }); - - describe("traversing the entire AST", () => { - - /** - * Gets a list of emitted types/selectors from the generator, in emission order - * @param {ASTNode} ast The AST to traverse - * @param {Array|Set} possibleQueries Selectors to detect - * @returns {Array[]} A list of emissions, in the order that they were emitted. Each emission is a two-element - * array where the first element is a string, and the second element is the emitted AST node. - */ - function getEmissions(ast, possibleQueries) { - const emissions = []; - const emitter = Object.create(createEmitter(), { - emit: { - value: (selector, node) => emissions.push([selector, node]) - } - }); - - possibleQueries.forEach(query => emitter.on(query, () => {})); - const generator = new NodeEventGenerator(emitter, STANDARD_ESQUERY_OPTION); - - Traverser.traverse(ast, { - enter(node, parent) { - node.parent = parent; - generator.enterNode(node); - }, - leave(node) { - generator.leaveNode(node); - } - }); - - return emissions; - } - - /** - * Creates a test case that asserts a particular sequence of generator emissions - * @param {string} sourceText The source text that should be parsed and traversed - * @param {string[]} possibleQueries A collection of selectors that rules are listening for - * @param {Array[]} expectedEmissions A function that accepts the AST and returns a list of the emissions that the - * generator is expected to produce, in order. - * Each element of this list is an array where the first element is a selector (string), and the second is an AST node - * This should only include emissions that appear in possibleQueries. - * @returns {void} - */ - function assertEmissions(sourceText, possibleQueries, expectedEmissions) { - it(possibleQueries.join("; "), () => { - const ast = espree.parse(sourceText, ESPREE_CONFIG); - const emissions = getEmissions(ast, possibleQueries) - .filter(emission => possibleQueries.includes(emission[0])); - - assert.deepStrictEqual(emissions, expectedEmissions(ast)); - }); - } - - assertEmissions( - "foo + bar;", - ["Program", "Program:exit", "ExpressionStatement", "ExpressionStatement:exit", "BinaryExpression", "BinaryExpression:exit", "Identifier", "Identifier:exit"], - ast => [ - ["Program", ast], // entering program - ["ExpressionStatement", ast.body[0]], // entering 'foo + bar;' - ["BinaryExpression", ast.body[0].expression], // entering 'foo + bar' - ["Identifier", ast.body[0].expression.left], // entering 'foo' - ["Identifier:exit", ast.body[0].expression.left], // exiting 'foo' - ["Identifier", ast.body[0].expression.right], // entering 'bar' - ["Identifier:exit", ast.body[0].expression.right], // exiting 'bar' - ["BinaryExpression:exit", ast.body[0].expression], // exiting 'foo + bar' - ["ExpressionStatement:exit", ast.body[0]], // exiting 'foo + bar;' - ["Program:exit", ast] // exiting program - ] - ); - - assertEmissions( - "foo + 5", - [ - "BinaryExpression > Identifier", - "BinaryExpression", - "BinaryExpression Literal:exit", - "BinaryExpression > Identifier:exit", - "BinaryExpression:exit" - ], - ast => [ - ["BinaryExpression", ast.body[0].expression], // foo + 5 - ["BinaryExpression > Identifier", ast.body[0].expression.left], // foo - ["BinaryExpression > Identifier:exit", ast.body[0].expression.left], // exiting foo - ["BinaryExpression Literal:exit", ast.body[0].expression.right], // exiting 5 - ["BinaryExpression:exit", ast.body[0].expression] // exiting foo + 5 - ] - ); - - assertEmissions( - "foo + 5", - ["BinaryExpression > *[name='foo']"], - ast => [["BinaryExpression > *[name='foo']", ast.body[0].expression.left]] // entering foo - ); - - assertEmissions( - "foo", - ["*"], - ast => [ - ["*", ast], // Program - ["*", ast.body[0]], // ExpressionStatement - ["*", ast.body[0].expression] // Identifier - ] - ); - - assertEmissions( - "foo", - ["*:not(ExpressionStatement)"], - ast => [ - ["*:not(ExpressionStatement)", ast], // Program - ["*:not(ExpressionStatement)", ast.body[0].expression] // Identifier - ] - ); - - assertEmissions( - "foo()", - ["CallExpression[callee.name='foo']"], - ast => [["CallExpression[callee.name='foo']", ast.body[0].expression]] // foo() - ); - - assertEmissions( - "foo()", - ["CallExpression[callee.name='bar']"], - () => [] // (nothing emitted) - ); - - assertEmissions( - "foo + bar + baz", - [":not(*)"], - () => [] // (nothing emitted) - ); - - assertEmissions( - "foo + bar + baz", - [":matches(Identifier[name='foo'], Identifier[name='bar'], Identifier[name='baz'])"], - ast => [ - [":matches(Identifier[name='foo'], Identifier[name='bar'], Identifier[name='baz'])", ast.body[0].expression.left.left], // foo - [":matches(Identifier[name='foo'], Identifier[name='bar'], Identifier[name='baz'])", ast.body[0].expression.left.right], // bar - [":matches(Identifier[name='foo'], Identifier[name='bar'], Identifier[name='baz'])", ast.body[0].expression.right] // baz - ] - ); - - assertEmissions( - "foo + 5 + 6", - ["Identifier, Literal[value=5]"], - ast => [ - ["Identifier, Literal[value=5]", ast.body[0].expression.left.left], // foo - ["Identifier, Literal[value=5]", ast.body[0].expression.left.right] // 5 - ] - ); - - assertEmissions( - "[foo, 5, foo]", - ["Identifier + Literal"], - ast => [["Identifier + Literal", ast.body[0].expression.elements[1]]] // 5 - ); - - assertEmissions( - "[foo, {}, 5]", - ["Identifier + Literal", "Identifier ~ Literal"], - ast => [["Identifier ~ Literal", ast.body[0].expression.elements[2]]] // 5 - ); - - assertEmissions( - "foo; bar + baz; qux()", - [":expression", ":statement"], - ast => [ - [":statement", ast.body[0]], - [":expression", ast.body[0].expression], - [":statement", ast.body[1]], - [":expression", ast.body[1].expression], - [":expression", ast.body[1].expression.left], - [":expression", ast.body[1].expression.right], - [":statement", ast.body[2]], - [":expression", ast.body[2].expression], - [":expression", ast.body[2].expression.callee] - ] - ); - - assertEmissions( - "function foo(){} var x; (function (p){}); () => {};", - [":function", "ExpressionStatement > :function", "VariableDeclaration, :function[params.length=1]"], - ast => [ - [":function", ast.body[0]], // function foo(){} - ["VariableDeclaration, :function[params.length=1]", ast.body[1]], // var x; - [":function", ast.body[2].expression], // function (p){} - ["ExpressionStatement > :function", ast.body[2].expression], // function (p){} - ["VariableDeclaration, :function[params.length=1]", ast.body[2].expression], // function (p){} - [":function", ast.body[3].expression], // () => {} - ["ExpressionStatement > :function", ast.body[3].expression] // () => {} - ] - ); - - assertEmissions( - "foo;", - [ - "*", - ":not(*)", - "Identifier", - "ExpressionStatement > *", - "ExpressionStatement > Identifier", - "ExpressionStatement > [name='foo']", - "Identifier, ReturnStatement", - "FooStatement", - "[name = 'foo']", - "[name='foo']", - "[name ='foo']", - "Identifier[name='foo']", - "[name='foo'][name.length=3]", - ":not(Program, ExpressionStatement)", - ":not(Program, Identifier) > [name.length=3]" - ], - ast => [ - ["*", ast], // Program - ["*", ast.body[0]], // ExpressionStatement - - // selectors for the 'foo' identifier, in order of increasing specificity - ["*", ast.body[0].expression], // 0 identifiers, 0 pseudoclasses - ["ExpressionStatement > *", ast.body[0].expression], // 0 pseudoclasses, 1 identifier - ["Identifier", ast.body[0].expression], // 0 pseudoclasses, 1 identifier - [":not(Program, ExpressionStatement)", ast.body[0].expression], // 0 pseudoclasses, 2 identifiers - ["ExpressionStatement > Identifier", ast.body[0].expression], // 0 pseudoclasses, 2 identifiers - ["Identifier, ReturnStatement", ast.body[0].expression], // 0 pseudoclasses, 2 identifiers - ["[name = 'foo']", ast.body[0].expression], // 1 pseudoclass, 0 identifiers - ["[name ='foo']", ast.body[0].expression], // 1 pseudoclass, 0 identifiers - ["[name='foo']", ast.body[0].expression], // 1 pseudoclass, 0 identifiers - ["ExpressionStatement > [name='foo']", ast.body[0].expression], // 1 attribute, 1 identifier - ["Identifier[name='foo']", ast.body[0].expression], // 1 attribute, 1 identifier - [":not(Program, Identifier) > [name.length=3]", ast.body[0].expression], // 1 attribute, 2 identifiers - ["[name='foo'][name.length=3]", ast.body[0].expression] // 2 attributes, 0 identifiers - ] - ); - - assertEmissions( - "foo(); bar; baz;", - ["CallExpression, [name='bar']"], - ast => [ - ["CallExpression, [name='bar']", ast.body[0].expression], - ["CallExpression, [name='bar']", ast.body[1].expression] - ] - ); - - assertEmissions( - "foo; bar;", - ["[name.length=3]:exit"], - ast => [ - ["[name.length=3]:exit", ast.body[0].expression], - ["[name.length=3]:exit", ast.body[1].expression] - ] - ); - - // https://github.com/eslint/eslint/issues/14799 - assertEmissions( - "const {a = 1} = b;", - ["Property > .key"], - ast => [ - ["Property > .key", ast.body[0].declarations[0].id.properties[0].key] - ] - ); - }); - - describe("traversing the entire non-standard AST", () => { - - /** - * Gets a list of emitted types/selectors from the generator, in emission order - * @param {ASTNode} ast The AST to traverse - * @param {Record} visitorKeys The custom visitor keys. - * @param {Array|Set} possibleQueries Selectors to detect - * @returns {Array[]} A list of emissions, in the order that they were emitted. Each emission is a two-element - * array where the first element is a string, and the second element is the emitted AST node. - */ - function getEmissions(ast, visitorKeys, possibleQueries) { - const emissions = []; - const emitter = Object.create(createEmitter(), { - emit: { - value: (selector, node) => emissions.push([selector, node]) - } - }); - - possibleQueries.forEach(query => emitter.on(query, () => {})); - const generator = new NodeEventGenerator(emitter, { visitorKeys, fallback: Traverser.getKeys }); - - Traverser.traverse(ast, { - visitorKeys, - enter(node, parent) { - node.parent = parent; - generator.enterNode(node); - }, - leave(node) { - generator.leaveNode(node); - } - }); - - return emissions; - } - - /** - * Creates a test case that asserts a particular sequence of generator emissions - * @param {ASTNode} ast The AST to traverse - * @param {Record} visitorKeys The custom visitor keys. - * @param {string[]} possibleQueries A collection of selectors that rules are listening for - * @param {Array[]} expectedEmissions A function that accepts the AST and returns a list of the emissions that the - * generator is expected to produce, in order. - * Each element of this list is an array where the first element is a selector (string), and the second is an AST node - * This should only include emissions that appear in possibleQueries. - * @returns {void} - */ - function assertEmissions(ast, visitorKeys, possibleQueries, expectedEmissions) { - it(possibleQueries.join("; "), () => { - const emissions = getEmissions(ast, visitorKeys, possibleQueries) - .filter(emission => possibleQueries.includes(emission[0])); - - assert.deepStrictEqual(emissions, expectedEmissions(ast)); - }); - } - - assertEmissions( - espree.parse("const foo = [
,
]", { ...ESPREE_CONFIG, ecmaFeatures: { jsx: true } }), - vk.KEYS, - ["* ~ *"], - ast => [ - ["* ~ *", ast.body[0].declarations[0].init.elements[1]] // entering second JSXElement - ] - ); - - assertEmissions( - { - - // Parse `class A implements B {}` with typescript-eslint. - type: "Program", - errors: [], - comments: [], - sourceType: "module", - body: [ - { - type: "ClassDeclaration", - id: { - type: "Identifier", - name: "A" - }, - superClass: null, - implements: [ - { - type: "ClassImplements", - id: { - type: "Identifier", - name: "B" - }, - typeParameters: null - } - ], - body: { - type: "ClassBody", - body: [] - } - } - ] - }, - vk.unionWith({ - - // see https://github.com/typescript-eslint/typescript-eslint/blob/e4d737b47574ff2c53cabab22853035dfe48c1ed/packages/visitor-keys/src/visitor-keys.ts#L27 - ClassDeclaration: [ - "decorators", - "id", - "typeParameters", - "superClass", - "superTypeParameters", - "implements", - "body" - ] - }), - [":first-child"], - ast => [ - [":first-child", ast.body[0]], // entering first ClassDeclaration - [":first-child", ast.body[0].implements[0]] // entering first ClassImplements - ] - ); - }); - - describe("parsing an invalid selector", () => { - it("throws a useful error", () => { - const emitter = createEmitter(); - - emitter.on("Foo >", () => {}); - assert.throws( - () => new NodeEventGenerator(emitter, STANDARD_ESQUERY_OPTION), - /Syntax error in selector "Foo >" at position 5: Expected " ", "!", .*/u - ); - }); - }); -}); diff --git a/tests/lib/linter/report-translator.js b/tests/lib/linter/report-translator.js index 18c62f20729e..fd075e906626 100644 --- a/tests/lib/linter/report-translator.js +++ b/tests/lib/linter/report-translator.js @@ -9,1265 +9,1394 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert; -const { SourceCode } = require("../../../lib/source-code"); +const { SourceCode } = require("../../../lib/languages/js/source-code"); const espree = require("espree"); const createReportTranslator = require("../../../lib/linter/report-translator"); +const jslang = require("../../../lib/languages/js"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("createReportTranslator", () => { - - /** - * Creates a SourceCode instance out of JavaScript text - * @param {string} text Source text - * @returns {SourceCode} A SourceCode instance for that text - */ - function createSourceCode(text) { - return new SourceCode( - text, - espree.parse( - text.replace(/^\uFEFF/u, ""), - { - loc: true, - range: true, - raw: true, - tokens: true, - comment: true - } - ) - ); - } - - let node, location, message, translateReport, suggestion1, suggestion2; - - beforeEach(() => { - const sourceCode = createSourceCode("foo\nbar"); - - node = sourceCode.ast.body[0]; - location = sourceCode.ast.body[1].loc.start; - message = "foo"; - suggestion1 = "First suggestion"; - suggestion2 = "Second suggestion {{interpolated}}"; - translateReport = createReportTranslator({ - ruleId: "foo-rule", - severity: 2, - sourceCode, - messageIds: { - testMessage: message, - suggestion1, - suggestion2 - } - }); - }); - - describe("old-style call with location", () => { - it("should extract the location correctly", () => { - assert.deepStrictEqual( - translateReport(node, location, message, {}), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement" - } - ); - }); - }); - - describe("old-style call without location", () => { - it("should use the start location and end location of the node", () => { - assert.deepStrictEqual( - translateReport(node, message, {}), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 1, - column: 1, - endLine: 1, - endColumn: 4, - nodeType: "ExpressionStatement" - } - ); - }); - }); - - describe("new-style call with all options", () => { - it("should include the new-style options in the report", () => { - const reportDescriptor = { - node, - loc: location, - message, - fix: () => ({ range: [1, 2], text: "foo" }), - suggest: [{ - desc: "suggestion 1", - fix: () => ({ range: [2, 3], text: "s1" }) - }, { - desc: "suggestion 2", - fix: () => ({ range: [3, 4], text: "s2" }) - }] - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement", - fix: { - range: [1, 2], - text: "foo" - }, - suggestions: [{ - desc: "suggestion 1", - fix: { range: [2, 3], text: "s1" } - }, { - desc: "suggestion 2", - fix: { range: [3, 4], text: "s2" } - }] - } - ); - }); - - it("should translate the messageId into a message", () => { - const reportDescriptor = { - node, - loc: location, - messageId: "testMessage", - fix: () => ({ range: [1, 2], text: "foo" }) - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - messageId: "testMessage", - line: 2, - column: 1, - nodeType: "ExpressionStatement", - fix: { - range: [1, 2], - text: "foo" - } - } - ); - }); - - it("should throw when both messageId and message are provided", () => { - const reportDescriptor = { - node, - loc: location, - messageId: "testMessage", - message: "bar", - fix: () => ({ range: [1, 2], text: "foo" }) - }; - - assert.throws( - () => translateReport(reportDescriptor), - TypeError, - "context.report() called with a message and a messageId. Please only pass one." - ); - }); - - it("should throw when an invalid messageId is provided", () => { - const reportDescriptor = { - node, - loc: location, - messageId: "thisIsNotASpecifiedMessageId", - fix: () => ({ range: [1, 2], text: "foo" }) - }; - - assert.throws( - () => translateReport(reportDescriptor), - TypeError, - /^context\.report\(\) called with a messageId of '[^']+' which is not present in the 'messages' config:/u - ); - }); - - it("should throw when no message is provided", () => { - const reportDescriptor = { node }; - - assert.throws( - () => translateReport(reportDescriptor), - TypeError, - "Missing `message` property in report() call; add a message that describes the linting problem." - ); - }); - - it("should support messageIds for suggestions and output resulting descriptions", () => { - const reportDescriptor = { - node, - loc: location, - message, - suggest: [{ - messageId: "suggestion1", - fix: () => ({ range: [2, 3], text: "s1" }) - }, { - messageId: "suggestion2", - data: { interpolated: "'interpolated value'" }, - fix: () => ({ range: [3, 4], text: "s2" }) - }] - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement", - suggestions: [{ - messageId: "suggestion1", - desc: "First suggestion", - fix: { range: [2, 3], text: "s1" } - }, { - messageId: "suggestion2", - data: { interpolated: "'interpolated value'" }, - desc: "Second suggestion 'interpolated value'", - fix: { range: [3, 4], text: "s2" } - }] - } - ); - }); - - it("should throw when a suggestion defines both a desc and messageId", () => { - const reportDescriptor = { - node, - loc: location, - message, - suggest: [{ - desc: "The description", - messageId: "suggestion1", - fix: () => ({ range: [2, 3], text: "s1" }) - }] - }; - - assert.throws( - () => translateReport(reportDescriptor), - TypeError, - "context.report() called with a suggest option that defines both a 'messageId' and an 'desc'. Please only pass one." - ); - }); - - it("should throw when a suggestion uses an invalid messageId", () => { - const reportDescriptor = { - node, - loc: location, - message, - suggest: [{ - messageId: "noMatchingMessage", - fix: () => ({ range: [2, 3], text: "s1" }) - }] - }; - - assert.throws( - () => translateReport(reportDescriptor), - TypeError, - /^context\.report\(\) called with a suggest option with a messageId '[^']+' which is not present in the 'messages' config:/u - ); - }); - - it("should throw when a suggestion does not provide either a desc or messageId", () => { - const reportDescriptor = { - node, - loc: location, - message, - suggest: [{ - fix: () => ({ range: [2, 3], text: "s1" }) - }] - }; - - assert.throws( - () => translateReport(reportDescriptor), - TypeError, - "context.report() called with a suggest option that doesn't have either a `desc` or `messageId`" - ); - }); - - it("should throw when a suggestion does not provide a fix function", () => { - const reportDescriptor = { - node, - loc: location, - message, - suggest: [{ - desc: "The description", - fix: false - }] - }; - - assert.throws( - () => translateReport(reportDescriptor), - TypeError, - /^context\.report\(\) called with a suggest option without a fix function. See:/u - ); - }); - }); - - describe("combining autofixes", () => { - it("should merge fixes to one if 'fix' function returns an array of fixes.", () => { - const reportDescriptor = { - node, - loc: location, - message, - fix: () => [{ range: [1, 2], text: "foo" }, { range: [4, 5], text: "bar" }] - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement", - fix: { - range: [1, 5], - text: "fooo\nbar" - } - } - ); - }); - - it("should merge fixes to one if 'fix' function returns an iterator of fixes.", () => { - const reportDescriptor = { - node, - loc: location, - message, - *fix() { - yield { range: [1, 2], text: "foo" }; - yield { range: [4, 5], text: "bar" }; - } - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement", - fix: { - range: [1, 5], - text: "fooo\nbar" - } - } - ); - }); - - it("should respect ranges of empty insertions when merging fixes to one.", () => { - const reportDescriptor = { - node, - loc: location, - message, - *fix() { - yield { range: [4, 5], text: "cd" }; - yield { range: [2, 2], text: "" }; - yield { range: [7, 7], text: "" }; - } - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement", - fix: { - range: [2, 7], - text: "o\ncdar" - } - } - ); - }); - - it("should pass through fixes if only one is present", () => { - const reportDescriptor = { - node, - loc: location, - message, - fix: () => [{ range: [1, 2], text: "foo" }] - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement", - fix: { - range: [1, 2], - text: "foo" - } - } - ); - }); - - it("should handle inserting BOM correctly.", () => { - const reportDescriptor = { - node, - loc: location, - message, - fix: () => [{ range: [0, 3], text: "\uFEFFfoo" }, { range: [4, 5], text: "x" }] - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement", - fix: { - range: [0, 5], - text: "\uFEFFfoo\nx" - } - } - ); - }); - - - it("should handle removing BOM correctly.", () => { - const sourceCode = createSourceCode("\uFEFFfoo\nbar"); - - node = sourceCode.ast.body[0]; - - const reportDescriptor = { - node, - message, - fix: () => [{ range: [-1, 3], text: "foo" }, { range: [4, 5], text: "x" }] - }; - - assert.deepStrictEqual( - createReportTranslator({ ruleId: "foo-rule", severity: 1, sourceCode })(reportDescriptor), - { - ruleId: "foo-rule", - severity: 1, - message: "foo", - line: 1, - column: 1, - endLine: 1, - endColumn: 4, - nodeType: "ExpressionStatement", - fix: { - range: [-1, 5], - text: "foo\nx" - } - } - ); - }); - - it("should throw an assertion error if ranges are overlapped.", () => { - const reportDescriptor = { - node, - loc: location, - message, - fix: () => [{ range: [0, 3], text: "\uFEFFfoo" }, { range: [2, 5], text: "x" }] - }; - - assert.throws( - translateReport.bind(null, reportDescriptor), - "Fix objects must not be overlapped in a report." - ); - }); - - it("should include a fix passed as the last argument when location is passed", () => { - assert.deepStrictEqual( - translateReport( - node, - { line: 42, column: 23 }, - "my message {{1}}{{0}}", - ["!", "testing"], - () => ({ range: [1, 1], text: "" }) - ), - { - ruleId: "foo-rule", - severity: 2, - message: "my message testing!", - line: 42, - column: 24, - nodeType: "ExpressionStatement", - fix: { - range: [1, 1], - text: "" - } - } - ); - }); - }); - - describe("suggestions", () => { - it("should support multiple suggestions.", () => { - const reportDescriptor = { - node, - loc: location, - message, - suggest: [{ - desc: "A first suggestion for the issue", - fix: () => [{ range: [1, 2], text: "foo" }] - }, { - desc: "A different suggestion for the issue", - fix: () => [{ range: [1, 3], text: "foobar" }] - }] - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement", - suggestions: [{ - desc: "A first suggestion for the issue", - fix: { range: [1, 2], text: "foo" } - }, { - desc: "A different suggestion for the issue", - fix: { range: [1, 3], text: "foobar" } - }] - } - ); - }); - - it("should merge suggestion fixes to one if 'fix' function returns an array of fixes.", () => { - const reportDescriptor = { - node, - loc: location, - message, - suggest: [{ - desc: "A suggestion for the issue", - fix: () => [{ range: [1, 2], text: "foo" }, { range: [4, 5], text: "bar" }] - }] - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement", - suggestions: [{ - desc: "A suggestion for the issue", - fix: { - range: [1, 5], - text: "fooo\nbar" - } - }] - } - ); - }); - - it("should remove the whole suggestion if 'fix' function returned `null`.", () => { - const reportDescriptor = { - node, - loc: location, - message, - suggest: [{ - desc: "A suggestion for the issue", - fix: () => null - }] - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement" - } - ); - }); - - it("should remove the whole suggestion if 'fix' function returned an empty array.", () => { - const reportDescriptor = { - node, - loc: location, - message, - suggest: [{ - desc: "A suggestion for the issue", - fix: () => [] - }] - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement" - } - ); - }); - - it("should remove the whole suggestion if 'fix' function returned an empty sequence.", () => { - const reportDescriptor = { - node, - loc: location, - message, - suggest: [{ - desc: "A suggestion for the issue", - *fix() {} - }] - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement" - } - ); - }); - - // This isn't officially supported, but autofix works the same way - it("should remove the whole suggestion if 'fix' function didn't return anything.", () => { - const reportDescriptor = { - node, - loc: location, - message, - suggest: [{ - desc: "A suggestion for the issue", - fix() {} - }] - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement" - } - ); - }); - - it("should keep suggestion before a removed suggestion.", () => { - const reportDescriptor = { - node, - loc: location, - message, - suggest: [{ - desc: "Suggestion with a fix", - fix: () => ({ range: [1, 2], text: "foo" }) - }, { - desc: "Suggestion without a fix", - fix: () => null - }] - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement", - suggestions: [{ - desc: "Suggestion with a fix", - fix: { range: [1, 2], text: "foo" } - }] - } - ); - }); - - it("should keep suggestion after a removed suggestion.", () => { - const reportDescriptor = { - node, - loc: location, - message, - suggest: [{ - desc: "Suggestion without a fix", - fix: () => null - }, { - desc: "Suggestion with a fix", - fix: () => ({ range: [1, 2], text: "foo" }) - }] - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement", - suggestions: [{ - desc: "Suggestion with a fix", - fix: { range: [1, 2], text: "foo" } - }] - } - ); - }); - - it("should remove multiple suggestions that didn't provide a fix and keep those that did.", () => { - const reportDescriptor = { - node, - loc: location, - message, - suggest: [{ - desc: "Keep #1", - fix: () => ({ range: [1, 2], text: "foo" }) - }, { - desc: "Remove #1", - fix() { - return null; - } - }, { - desc: "Keep #2", - fix: () => ({ range: [1, 2], text: "bar" }) - }, { - desc: "Remove #2", - fix() { - return []; - } - }, { - desc: "Keep #3", - fix: () => ({ range: [1, 2], text: "baz" }) - }, { - desc: "Remove #3", - *fix() {} - }, { - desc: "Keep #4", - fix: () => ({ range: [1, 2], text: "quux" }) - }] - }; - - assert.deepStrictEqual( - translateReport(reportDescriptor), - { - ruleId: "foo-rule", - severity: 2, - message: "foo", - line: 2, - column: 1, - nodeType: "ExpressionStatement", - suggestions: [{ - desc: "Keep #1", - fix: { range: [1, 2], text: "foo" } - }, { - desc: "Keep #2", - fix: { range: [1, 2], text: "bar" } - }, { - desc: "Keep #3", - fix: { range: [1, 2], text: "baz" } - }, { - desc: "Keep #4", - fix: { range: [1, 2], text: "quux" } - }] - } - ); - }); - }); - - describe("message interpolation", () => { - it("should correctly parse a message when being passed all options in an old-style report", () => { - assert.deepStrictEqual( - translateReport(node, node.loc.end, "hello {{dynamic}}", { dynamic: node.type }), - { - severity: 2, - ruleId: "foo-rule", - message: "hello ExpressionStatement", - nodeType: "ExpressionStatement", - line: 1, - column: 4 - } - ); - }); - - it("should correctly parse a message when being passed all options in a new-style report", () => { - assert.deepStrictEqual( - translateReport({ node, loc: node.loc.end, message: "hello {{dynamic}}", data: { dynamic: node.type } }), - { - severity: 2, - ruleId: "foo-rule", - message: "hello ExpressionStatement", - nodeType: "ExpressionStatement", - line: 1, - column: 4 - } - ); - }); - - it("should correctly parse a message with object keys as numbers", () => { - assert.strictEqual( - translateReport(node, "my message {{name}}{{0}}", { 0: "!", name: "testing" }).message, - "my message testing!" - ); - }); - - it("should correctly parse a message with array", () => { - assert.strictEqual( - translateReport(node, "my message {{1}}{{0}}", ["!", "testing"]).message, - "my message testing!" - ); - }); - - it("should allow template parameter with inner whitespace", () => { - assert.strictEqual( - translateReport(node, "message {{parameter name}}", { "parameter name": "yay!" }).message, - "message yay!" - ); - }); - - it("should allow template parameter with non-identifier characters", () => { - assert.strictEqual( - translateReport(node, "message {{parameter-name}}", { "parameter-name": "yay!" }).message, - "message yay!" - ); - }); - - it("should allow template parameter wrapped in braces", () => { - assert.strictEqual( - translateReport(node, "message {{{param}}}", { param: "yay!" }).message, - "message {yay!}" - ); - }); - - it("should ignore template parameter with no specified value", () => { - assert.strictEqual( - translateReport(node, "message {{parameter}}", {}).message, - "message {{parameter}}" - ); - }); - - it("should handle leading whitespace in template parameter", () => { - assert.strictEqual( - translateReport({ node, message: "message {{ parameter}}", data: { parameter: "yay!" } }).message, - "message yay!" - ); - }); - - it("should handle trailing whitespace in template parameter", () => { - assert.strictEqual( - translateReport({ node, message: "message {{parameter }}", data: { parameter: "yay!" } }).message, - "message yay!" - ); - }); - - it("should still allow inner whitespace as well as leading/trailing", () => { - assert.strictEqual( - translateReport(node, "message {{ parameter name }}", { "parameter name": "yay!" }).message, - "message yay!" - ); - }); - - it("should still allow non-identifier characters as well as leading/trailing whitespace", () => { - assert.strictEqual( - translateReport(node, "message {{ parameter-name }}", { "parameter-name": "yay!" }).message, - "message yay!" - ); - }); - }); - - describe("location inference", () => { - it("should use the provided location when given in an old-style call", () => { - assert.deepStrictEqual( - translateReport(node, { line: 42, column: 13 }, "hello world"), - { - severity: 2, - ruleId: "foo-rule", - message: "hello world", - nodeType: "ExpressionStatement", - line: 42, - column: 14 - } - ); - }); - - it("should use the provided location when given in an new-style call", () => { - assert.deepStrictEqual( - translateReport({ node, loc: { line: 42, column: 13 }, message: "hello world" }), - { - severity: 2, - ruleId: "foo-rule", - message: "hello world", - nodeType: "ExpressionStatement", - line: 42, - column: 14 - } - ); - }); - - it("should extract the start and end locations from a node if no location is provided", () => { - assert.deepStrictEqual( - translateReport(node, "hello world"), - { - severity: 2, - ruleId: "foo-rule", - message: "hello world", - nodeType: "ExpressionStatement", - line: 1, - column: 1, - endLine: 1, - endColumn: 4 - } - ); - }); - - it("should have 'endLine' and 'endColumn' when 'loc' property has 'end' property.", () => { - assert.deepStrictEqual( - translateReport({ loc: node.loc, message: "hello world" }), - { - severity: 2, - ruleId: "foo-rule", - message: "hello world", - nodeType: null, - line: 1, - column: 1, - endLine: 1, - endColumn: 4 - } - ); - }); - - it("should not have 'endLine' and 'endColumn' when 'loc' property does not have 'end' property.", () => { - assert.deepStrictEqual( - translateReport({ loc: node.loc.start, message: "hello world" }), - { - severity: 2, - ruleId: "foo-rule", - message: "hello world", - nodeType: null, - line: 1, - column: 1 - } - ); - }); - - it("should infer an 'endLine' and 'endColumn' property when using the object-based context.report API", () => { - assert.deepStrictEqual( - translateReport({ node, message: "hello world" }), - { - severity: 2, - ruleId: "foo-rule", - message: "hello world", - nodeType: "ExpressionStatement", - line: 1, - column: 1, - endLine: 1, - endColumn: 4 - } - ); - }); - }); - - describe("converting old-style calls", () => { - it("should include a fix passed as the last argument when location is not passed", () => { - assert.deepStrictEqual( - translateReport(node, "my message {{1}}{{0}}", ["!", "testing"], () => ({ range: [1, 1], text: "" })), - { - severity: 2, - ruleId: "foo-rule", - message: "my message testing!", - nodeType: "ExpressionStatement", - line: 1, - column: 1, - endLine: 1, - endColumn: 4, - fix: { range: [1, 1], text: "" } - } - ); - }); - }); - - describe("validation", () => { - - it("should throw an error if node is not an object", () => { - assert.throws( - () => translateReport("not a node", "hello world"), - "Node must be an object" - ); - }); - - - it("should not throw an error if location is provided and node is not in an old-style call", () => { - assert.deepStrictEqual( - translateReport(null, { line: 1, column: 1 }, "hello world"), - { - severity: 2, - ruleId: "foo-rule", - message: "hello world", - nodeType: null, - line: 1, - column: 2 - } - ); - }); - - it("should not throw an error if location is provided and node is not in a new-style call", () => { - assert.deepStrictEqual( - translateReport({ loc: { line: 1, column: 1 }, message: "hello world" }), - { - severity: 2, - ruleId: "foo-rule", - message: "hello world", - nodeType: null, - line: 1, - column: 2 - } - ); - }); - - it("should throw an error if neither node nor location is provided", () => { - assert.throws( - () => translateReport(null, "hello world"), - "Node must be provided when reporting error if location is not provided" - ); - }); - - it("should throw an error if fix range is invalid", () => { - assert.throws( - () => translateReport({ node, messageId: "testMessage", fix: () => ({ text: "foo" }) }), - "Fix has invalid range" - ); - - for (const badRange of [[0], [0, null], [null, 0], [void 0, 1], [0, void 0], [void 0, void 0], []]) { - assert.throws( - // eslint-disable-next-line no-loop-func -- Using arrow functions - () => translateReport( - { node, messageId: "testMessage", fix: () => ({ range: badRange, text: "foo" }) } - ), - "Fix has invalid range" - ); - - assert.throws( - // eslint-disable-next-line no-loop-func -- Using arrow functions - () => translateReport( - { - node, - messageId: "testMessage", - fix: () => [ - { range: [0, 0], text: "foo" }, - { range: badRange, text: "bar" }, - { range: [1, 1], text: "baz" } - ] - } - ), - "Fix has invalid range" - ); - } - }); - }); - - // https://github.com/eslint/eslint/issues/16716 - describe("unique `fix` and `fix.range` objects", () => { - const range = [0, 3]; - const fix = { range, text: "baz" }; - const additionalRange = [4, 7]; - const additionalFix = { range: additionalRange, text: "qux" }; - - it("should deep clone returned fix object", () => { - const translatedReport = translateReport({ - node, - messageId: "testMessage", - fix: () => fix - }); - - assert.deepStrictEqual(translatedReport.fix, fix); - assert.notStrictEqual(translatedReport.fix, fix); - assert.notStrictEqual(translatedReport.fix.range, fix.range); - }); - - it("should create a new fix object with a new range array when `fix()` returns an array with a single item", () => { - const translatedReport = translateReport({ - node, - messageId: "testMessage", - fix: () => [fix] - }); - - assert.deepStrictEqual(translatedReport.fix, fix); - assert.notStrictEqual(translatedReport.fix, fix); - assert.notStrictEqual(translatedReport.fix.range, fix.range); - }); - - it("should create a new fix object with a new range array when `fix()` returns an array with multiple items", () => { - const translatedReport = translateReport({ - node, - messageId: "testMessage", - fix: () => [fix, additionalFix] - }); - - assert.notStrictEqual(translatedReport.fix, fix); - assert.notStrictEqual(translatedReport.fix.range, fix.range); - assert.notStrictEqual(translatedReport.fix, additionalFix); - assert.notStrictEqual(translatedReport.fix.range, additionalFix.range); - }); - - it("should create a new fix object with a new range array when `fix()` generator yields a single item", () => { - const translatedReport = translateReport({ - node, - messageId: "testMessage", - *fix() { - yield fix; - } - }); - - assert.deepStrictEqual(translatedReport.fix, fix); - assert.notStrictEqual(translatedReport.fix, fix); - assert.notStrictEqual(translatedReport.fix.range, fix.range); - }); - - it("should create a new fix object with a new range array when `fix()` generator yields multiple items", () => { - const translatedReport = translateReport({ - node, - messageId: "testMessage", - *fix() { - yield fix; - yield additionalFix; - } - }); - - assert.notStrictEqual(translatedReport.fix, fix); - assert.notStrictEqual(translatedReport.fix.range, fix.range); - assert.notStrictEqual(translatedReport.fix, additionalFix); - assert.notStrictEqual(translatedReport.fix.range, additionalFix.range); - }); - - it("should deep clone returned suggestion fix object", () => { - const translatedReport = translateReport({ - node, - messageId: "testMessage", - suggest: [{ - messageId: "suggestion1", - fix: () => fix - }] - }); - - assert.deepStrictEqual(translatedReport.suggestions[0].fix, fix); - assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); - assert.notStrictEqual(translatedReport.suggestions[0].fix.range, fix.range); - }); - - it("should create a new fix object with a new range array when suggestion `fix()` returns an array with a single item", () => { - const translatedReport = translateReport({ - node, - messageId: "testMessage", - suggest: [{ - messageId: "suggestion1", - fix: () => [fix] - }] - }); - - assert.deepStrictEqual(translatedReport.suggestions[0].fix, fix); - assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); - assert.notStrictEqual(translatedReport.suggestions[0].fix.range, fix.range); - }); - - it("should create a new fix object with a new range array when suggestion `fix()` returns an array with multiple items", () => { - const translatedReport = translateReport({ - node, - messageId: "testMessage", - suggest: [{ - messageId: "suggestion1", - fix: () => [fix, additionalFix] - }] - }); - - assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); - assert.notStrictEqual(translatedReport.suggestions[0].fix.range, fix.range); - assert.notStrictEqual(translatedReport.suggestions[0].fix, additionalFix); - assert.notStrictEqual(translatedReport.suggestions[0].fix.range, additionalFix.range); - }); - - it("should create a new fix object with a new range array when suggestion `fix()` generator yields a single item", () => { - const translatedReport = translateReport({ - node, - messageId: "testMessage", - suggest: [{ - messageId: "suggestion1", - *fix() { - yield fix; - } - }] - }); - - assert.deepStrictEqual(translatedReport.suggestions[0].fix, fix); - assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); - assert.notStrictEqual(translatedReport.suggestions[0].fix.range, fix.range); - }); - - it("should create a new fix object with a new range array when suggestion `fix()` generator yields multiple items", () => { - const translatedReport = translateReport({ - node, - messageId: "testMessage", - suggest: [{ - messageId: "suggestion1", - *fix() { - yield fix; - yield additionalFix; - } - }] - }); - - assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); - assert.notStrictEqual(translatedReport.suggestions[0].fix.range, fix.range); - assert.notStrictEqual(translatedReport.suggestions[0].fix, additionalFix); - assert.notStrictEqual(translatedReport.suggestions[0].fix.range, additionalFix.range); - }); - - it("should create different instances of range arrays when suggestions reuse the same instance", () => { - const translatedReport = translateReport({ - node, - messageId: "testMessage", - suggest: [ - { - messageId: "suggestion1", - fix: () => ({ range, text: "baz" }) - }, - { - messageId: "suggestion2", - data: { interpolated: "'interpolated value'" }, - fix: () => ({ range, text: "qux" }) - } - ] - }); - - assert.deepStrictEqual(translatedReport.suggestions[0].fix.range, range); - assert.deepStrictEqual(translatedReport.suggestions[1].fix.range, range); - assert.notStrictEqual(translatedReport.suggestions[0].fix.range, translatedReport.suggestions[1].fix.range); - }); - }); + /** + * Creates a SourceCode instance out of JavaScript text + * @param {string} text Source text + * @returns {SourceCode} A SourceCode instance for that text + */ + function createSourceCode(text) { + return new SourceCode( + text, + espree.parse(text.replace(/^\uFEFF/u, ""), { + loc: true, + range: true, + raw: true, + tokens: true, + comment: true, + }), + ); + } + + let node, location, message, translateReport, suggestion1, suggestion2; + + beforeEach(() => { + const sourceCode = createSourceCode("foo\nbar"); + + node = sourceCode.ast.body[0]; + location = sourceCode.ast.body[1].loc.start; + message = "foo"; + suggestion1 = "First suggestion"; + suggestion2 = "Second suggestion {{interpolated}}"; + translateReport = createReportTranslator({ + language: jslang, + ruleId: "foo-rule", + severity: 2, + sourceCode, + messageIds: { + testMessage: message, + suggestion1, + suggestion2, + }, + }); + }); + + describe("old-style call with location", () => { + it("should extract the location correctly", () => { + assert.deepStrictEqual( + translateReport(node, location, message, {}), + { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + }, + ); + }); + }); + + describe("old-style call without location", () => { + it("should use the start location and end location of the node", () => { + assert.deepStrictEqual(translateReport(node, message, {}), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 1, + column: 1, + endLine: 1, + endColumn: 4, + nodeType: "ExpressionStatement", + }); + }); + }); + + describe("new-style call with all options", () => { + it("should include the new-style options in the report", () => { + const reportDescriptor = { + node, + loc: location, + message, + fix: () => ({ range: [1, 2], text: "foo" }), + suggest: [ + { + desc: "suggestion 1", + fix: () => ({ range: [2, 3], text: "s1" }), + }, + { + desc: "suggestion 2", + fix: () => ({ range: [3, 4], text: "s2" }), + }, + ], + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + fix: { + range: [1, 2], + text: "foo", + }, + suggestions: [ + { + desc: "suggestion 1", + fix: { range: [2, 3], text: "s1" }, + }, + { + desc: "suggestion 2", + fix: { range: [3, 4], text: "s2" }, + }, + ], + }); + }); + + it("should translate the messageId into a message", () => { + const reportDescriptor = { + node, + loc: location, + messageId: "testMessage", + fix: () => ({ range: [1, 2], text: "foo" }), + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + messageId: "testMessage", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + fix: { + range: [1, 2], + text: "foo", + }, + }); + }); + + it("should throw when both messageId and message are provided", () => { + const reportDescriptor = { + node, + loc: location, + messageId: "testMessage", + message: "bar", + fix: () => ({ range: [1, 2], text: "foo" }), + }; + + assert.throws( + () => translateReport(reportDescriptor), + TypeError, + "context.report() called with a message and a messageId. Please only pass one.", + ); + }); + + it("should throw when an invalid messageId is provided", () => { + const reportDescriptor = { + node, + loc: location, + messageId: "thisIsNotASpecifiedMessageId", + fix: () => ({ range: [1, 2], text: "foo" }), + }; + + assert.throws( + () => translateReport(reportDescriptor), + TypeError, + /^context\.report\(\) called with a messageId of '[^']+' which is not present in the 'messages' config:/u, + ); + }); + + it("should throw when no message is provided", () => { + const reportDescriptor = { node }; + + assert.throws( + () => translateReport(reportDescriptor), + TypeError, + "Missing `message` property in report() call; add a message that describes the linting problem.", + ); + }); + + it("should support messageIds for suggestions and output resulting descriptions", () => { + const reportDescriptor = { + node, + loc: location, + message, + suggest: [ + { + messageId: "suggestion1", + fix: () => ({ range: [2, 3], text: "s1" }), + }, + { + messageId: "suggestion2", + data: { interpolated: "'interpolated value'" }, + fix: () => ({ range: [3, 4], text: "s2" }), + }, + ], + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + suggestions: [ + { + messageId: "suggestion1", + desc: "First suggestion", + fix: { range: [2, 3], text: "s1" }, + }, + { + messageId: "suggestion2", + data: { interpolated: "'interpolated value'" }, + desc: "Second suggestion 'interpolated value'", + fix: { range: [3, 4], text: "s2" }, + }, + ], + }); + }); + + it("should throw when a suggestion defines both a desc and messageId", () => { + const reportDescriptor = { + node, + loc: location, + message, + suggest: [ + { + desc: "The description", + messageId: "suggestion1", + fix: () => ({ range: [2, 3], text: "s1" }), + }, + ], + }; + + assert.throws( + () => translateReport(reportDescriptor), + TypeError, + "context.report() called with a suggest option that defines both a 'messageId' and an 'desc'. Please only pass one.", + ); + }); + + it("should throw when a suggestion uses an invalid messageId", () => { + const reportDescriptor = { + node, + loc: location, + message, + suggest: [ + { + messageId: "noMatchingMessage", + fix: () => ({ range: [2, 3], text: "s1" }), + }, + ], + }; + + assert.throws( + () => translateReport(reportDescriptor), + TypeError, + /^context\.report\(\) called with a suggest option with a messageId '[^']+' which is not present in the 'messages' config:/u, + ); + }); + + it("should throw when a suggestion does not provide either a desc or messageId", () => { + const reportDescriptor = { + node, + loc: location, + message, + suggest: [ + { + fix: () => ({ range: [2, 3], text: "s1" }), + }, + ], + }; + + assert.throws( + () => translateReport(reportDescriptor), + TypeError, + "context.report() called with a suggest option that doesn't have either a `desc` or `messageId`", + ); + }); + + it("should throw when a suggestion does not provide a fix function", () => { + const reportDescriptor = { + node, + loc: location, + message, + suggest: [ + { + desc: "The description", + fix: false, + }, + ], + }; + + assert.throws( + () => translateReport(reportDescriptor), + TypeError, + /^context\.report\(\) called with a suggest option without a fix function. See:/u, + ); + }); + }); + + describe("combining autofixes", () => { + it("should merge fixes to one if 'fix' function returns an array of fixes.", () => { + const reportDescriptor = { + node, + loc: location, + message, + fix: () => [ + { range: [1, 2], text: "foo" }, + { range: [4, 5], text: "bar" }, + ], + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + fix: { + range: [1, 5], + text: "fooo\nbar", + }, + }); + }); + + it("should merge fixes to one if 'fix' function returns an iterator of fixes.", () => { + const reportDescriptor = { + node, + loc: location, + message, + *fix() { + yield { range: [1, 2], text: "foo" }; + yield { range: [4, 5], text: "bar" }; + }, + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + fix: { + range: [1, 5], + text: "fooo\nbar", + }, + }); + }); + + it("should respect ranges of empty insertions when merging fixes to one.", () => { + const reportDescriptor = { + node, + loc: location, + message, + *fix() { + yield { range: [4, 5], text: "cd" }; + yield { range: [2, 2], text: "" }; + yield { range: [7, 7], text: "" }; + }, + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + fix: { + range: [2, 7], + text: "o\ncdar", + }, + }); + }); + + it("should pass through fixes if only one is present", () => { + const reportDescriptor = { + node, + loc: location, + message, + fix: () => [{ range: [1, 2], text: "foo" }], + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + fix: { + range: [1, 2], + text: "foo", + }, + }); + }); + + it("should handle inserting BOM correctly.", () => { + const reportDescriptor = { + node, + loc: location, + message, + fix: () => [ + { range: [0, 3], text: "\uFEFFfoo" }, + { range: [4, 5], text: "x" }, + ], + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + fix: { + range: [0, 5], + text: "\uFEFFfoo\nx", + }, + }); + }); + + it("should handle removing BOM correctly.", () => { + const sourceCode = createSourceCode("\uFEFFfoo\nbar"); + + node = sourceCode.ast.body[0]; + + const reportDescriptor = { + node, + message, + fix: () => [ + { range: [-1, 3], text: "foo" }, + { range: [4, 5], text: "x" }, + ], + }; + + assert.deepStrictEqual( + createReportTranslator({ + language: jslang, + ruleId: "foo-rule", + severity: 1, + sourceCode, + })(reportDescriptor), + { + ruleId: "foo-rule", + severity: 1, + message: "foo", + line: 1, + column: 1, + endLine: 1, + endColumn: 4, + nodeType: "ExpressionStatement", + fix: { + range: [-1, 5], + text: "foo\nx", + }, + }, + ); + }); + + it("should throw an assertion error if ranges are overlapped.", () => { + const reportDescriptor = { + node, + loc: location, + message, + fix: () => [ + { range: [0, 3], text: "\uFEFFfoo" }, + { range: [2, 5], text: "x" }, + ], + }; + + assert.throws( + translateReport.bind(null, reportDescriptor), + "Fix objects must not be overlapped in a report.", + ); + }); + + it("should include a fix passed as the last argument when location is passed", () => { + assert.deepStrictEqual( + translateReport( + node, + { line: 42, column: 23 }, + "my message {{1}}{{0}}", + ["!", "testing"], + () => ({ range: [1, 1], text: "" }), + ), + { + ruleId: "foo-rule", + severity: 2, + message: "my message testing!", + line: 42, + column: 24, + nodeType: "ExpressionStatement", + fix: { + range: [1, 1], + text: "", + }, + }, + ); + }); + }); + + describe("suggestions", () => { + it("should support multiple suggestions.", () => { + const reportDescriptor = { + node, + loc: location, + message, + suggest: [ + { + desc: "A first suggestion for the issue", + fix: () => [{ range: [1, 2], text: "foo" }], + }, + { + desc: "A different suggestion for the issue", + fix: () => [{ range: [1, 3], text: "foobar" }], + }, + ], + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + suggestions: [ + { + desc: "A first suggestion for the issue", + fix: { range: [1, 2], text: "foo" }, + }, + { + desc: "A different suggestion for the issue", + fix: { range: [1, 3], text: "foobar" }, + }, + ], + }); + }); + + it("should merge suggestion fixes to one if 'fix' function returns an array of fixes.", () => { + const reportDescriptor = { + node, + loc: location, + message, + suggest: [ + { + desc: "A suggestion for the issue", + fix: () => [ + { range: [1, 2], text: "foo" }, + { range: [4, 5], text: "bar" }, + ], + }, + ], + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + suggestions: [ + { + desc: "A suggestion for the issue", + fix: { + range: [1, 5], + text: "fooo\nbar", + }, + }, + ], + }); + }); + + it("should remove the whole suggestion if 'fix' function returned `null`.", () => { + const reportDescriptor = { + node, + loc: location, + message, + suggest: [ + { + desc: "A suggestion for the issue", + fix: () => null, + }, + ], + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + }); + }); + + it("should remove the whole suggestion if 'fix' function returned an empty array.", () => { + const reportDescriptor = { + node, + loc: location, + message, + suggest: [ + { + desc: "A suggestion for the issue", + fix: () => [], + }, + ], + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + }); + }); + + it("should remove the whole suggestion if 'fix' function returned an empty sequence.", () => { + const reportDescriptor = { + node, + loc: location, + message, + suggest: [ + { + desc: "A suggestion for the issue", + *fix() {}, + }, + ], + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + }); + }); + + // This isn't officially supported, but autofix works the same way + it("should remove the whole suggestion if 'fix' function didn't return anything.", () => { + const reportDescriptor = { + node, + loc: location, + message, + suggest: [ + { + desc: "A suggestion for the issue", + fix() {}, + }, + ], + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + }); + }); + + it("should keep suggestion before a removed suggestion.", () => { + const reportDescriptor = { + node, + loc: location, + message, + suggest: [ + { + desc: "Suggestion with a fix", + fix: () => ({ range: [1, 2], text: "foo" }), + }, + { + desc: "Suggestion without a fix", + fix: () => null, + }, + ], + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + suggestions: [ + { + desc: "Suggestion with a fix", + fix: { range: [1, 2], text: "foo" }, + }, + ], + }); + }); + + it("should keep suggestion after a removed suggestion.", () => { + const reportDescriptor = { + node, + loc: location, + message, + suggest: [ + { + desc: "Suggestion without a fix", + fix: () => null, + }, + { + desc: "Suggestion with a fix", + fix: () => ({ range: [1, 2], text: "foo" }), + }, + ], + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + suggestions: [ + { + desc: "Suggestion with a fix", + fix: { range: [1, 2], text: "foo" }, + }, + ], + }); + }); + + it("should remove multiple suggestions that didn't provide a fix and keep those that did.", () => { + const reportDescriptor = { + node, + loc: location, + message, + suggest: [ + { + desc: "Keep #1", + fix: () => ({ range: [1, 2], text: "foo" }), + }, + { + desc: "Remove #1", + fix() { + return null; + }, + }, + { + desc: "Keep #2", + fix: () => ({ range: [1, 2], text: "bar" }), + }, + { + desc: "Remove #2", + fix() { + return []; + }, + }, + { + desc: "Keep #3", + fix: () => ({ range: [1, 2], text: "baz" }), + }, + { + desc: "Remove #3", + *fix() {}, + }, + { + desc: "Keep #4", + fix: () => ({ range: [1, 2], text: "quux" }), + }, + ], + }; + + assert.deepStrictEqual(translateReport(reportDescriptor), { + ruleId: "foo-rule", + severity: 2, + message: "foo", + line: 2, + column: 1, + nodeType: "ExpressionStatement", + suggestions: [ + { + desc: "Keep #1", + fix: { range: [1, 2], text: "foo" }, + }, + { + desc: "Keep #2", + fix: { range: [1, 2], text: "bar" }, + }, + { + desc: "Keep #3", + fix: { range: [1, 2], text: "baz" }, + }, + { + desc: "Keep #4", + fix: { range: [1, 2], text: "quux" }, + }, + ], + }); + }); + }); + + describe("message interpolation", () => { + it("should correctly parse a message when being passed all options in an old-style report", () => { + assert.deepStrictEqual( + translateReport(node, node.loc.end, "hello {{dynamic}}", { + dynamic: node.type, + }), + { + severity: 2, + ruleId: "foo-rule", + message: "hello ExpressionStatement", + nodeType: "ExpressionStatement", + line: 1, + column: 4, + }, + ); + }); + + it("should correctly parse a message when being passed all options in a new-style report", () => { + assert.deepStrictEqual( + translateReport({ + node, + loc: node.loc.end, + message: "hello {{dynamic}}", + data: { dynamic: node.type }, + }), + { + severity: 2, + ruleId: "foo-rule", + message: "hello ExpressionStatement", + nodeType: "ExpressionStatement", + line: 1, + column: 4, + }, + ); + }); + + it("should correctly parse a message with object keys as numbers", () => { + assert.strictEqual( + translateReport(node, "my message {{name}}{{0}}", { + 0: "!", + name: "testing", + }).message, + "my message testing!", + ); + }); + + it("should correctly parse a message with array", () => { + assert.strictEqual( + translateReport(node, "my message {{1}}{{0}}", ["!", "testing"]) + .message, + "my message testing!", + ); + }); + + it("should allow template parameter with inner whitespace", () => { + assert.strictEqual( + translateReport(node, "message {{parameter name}}", { + "parameter name": "yay!", + }).message, + "message yay!", + ); + }); + + it("should allow template parameter with non-identifier characters", () => { + assert.strictEqual( + translateReport(node, "message {{parameter-name}}", { + "parameter-name": "yay!", + }).message, + "message yay!", + ); + }); + + it("should allow template parameter wrapped in braces", () => { + assert.strictEqual( + translateReport(node, "message {{{param}}}", { param: "yay!" }) + .message, + "message {yay!}", + ); + }); + + it("should ignore template parameter with no specified value", () => { + assert.strictEqual( + translateReport(node, "message {{parameter}}", {}).message, + "message {{parameter}}", + ); + }); + + it("should handle leading whitespace in template parameter", () => { + assert.strictEqual( + translateReport({ + node, + message: "message {{ parameter}}", + data: { parameter: "yay!" }, + }).message, + "message yay!", + ); + }); + + it("should handle trailing whitespace in template parameter", () => { + assert.strictEqual( + translateReport({ + node, + message: "message {{parameter }}", + data: { parameter: "yay!" }, + }).message, + "message yay!", + ); + }); + + it("should still allow inner whitespace as well as leading/trailing", () => { + assert.strictEqual( + translateReport(node, "message {{ parameter name }}", { + "parameter name": "yay!", + }).message, + "message yay!", + ); + }); + + it("should still allow non-identifier characters as well as leading/trailing whitespace", () => { + assert.strictEqual( + translateReport(node, "message {{ parameter-name }}", { + "parameter-name": "yay!", + }).message, + "message yay!", + ); + }); + }); + + describe("location inference", () => { + it("should use the provided location when given in an old-style call", () => { + assert.deepStrictEqual( + translateReport(node, { line: 42, column: 13 }, "hello world"), + { + severity: 2, + ruleId: "foo-rule", + message: "hello world", + nodeType: "ExpressionStatement", + line: 42, + column: 14, + }, + ); + }); + + it("should use the provided location when given in an new-style call", () => { + assert.deepStrictEqual( + translateReport({ + node, + loc: { line: 42, column: 13 }, + message: "hello world", + }), + { + severity: 2, + ruleId: "foo-rule", + message: "hello world", + nodeType: "ExpressionStatement", + line: 42, + column: 14, + }, + ); + }); + + it("should extract the start and end locations from a node if no location is provided", () => { + assert.deepStrictEqual(translateReport(node, "hello world"), { + severity: 2, + ruleId: "foo-rule", + message: "hello world", + nodeType: "ExpressionStatement", + line: 1, + column: 1, + endLine: 1, + endColumn: 4, + }); + }); + + it("should have 'endLine' and 'endColumn' when 'loc' property has 'end' property.", () => { + assert.deepStrictEqual( + translateReport({ loc: node.loc, message: "hello world" }), + { + severity: 2, + ruleId: "foo-rule", + message: "hello world", + nodeType: null, + line: 1, + column: 1, + endLine: 1, + endColumn: 4, + }, + ); + }); + + it("should not have 'endLine' and 'endColumn' when 'loc' property does not have 'end' property.", () => { + assert.deepStrictEqual( + translateReport({ + loc: node.loc.start, + message: "hello world", + }), + { + severity: 2, + ruleId: "foo-rule", + message: "hello world", + nodeType: null, + line: 1, + column: 1, + }, + ); + }); + + it("should infer an 'endLine' and 'endColumn' property when using the object-based context.report API", () => { + assert.deepStrictEqual( + translateReport({ node, message: "hello world" }), + { + severity: 2, + ruleId: "foo-rule", + message: "hello world", + nodeType: "ExpressionStatement", + line: 1, + column: 1, + endLine: 1, + endColumn: 4, + }, + ); + }); + }); + + describe("converting old-style calls", () => { + it("should include a fix passed as the last argument when location is not passed", () => { + assert.deepStrictEqual( + translateReport( + node, + "my message {{1}}{{0}}", + ["!", "testing"], + () => ({ range: [1, 1], text: "" }), + ), + { + severity: 2, + ruleId: "foo-rule", + message: "my message testing!", + nodeType: "ExpressionStatement", + line: 1, + column: 1, + endLine: 1, + endColumn: 4, + fix: { range: [1, 1], text: "" }, + }, + ); + }); + }); + + describe("validation", () => { + it("should throw an error if node is not an object", () => { + assert.throws( + () => translateReport("not a node", "hello world"), + "Node must be an object", + ); + }); + + it("should not throw an error if location is provided and node is not in an old-style call", () => { + assert.deepStrictEqual( + translateReport(null, { line: 1, column: 1 }, "hello world"), + { + severity: 2, + ruleId: "foo-rule", + message: "hello world", + nodeType: null, + line: 1, + column: 2, + }, + ); + }); + + it("should not throw an error if location is provided and node is not in a new-style call", () => { + assert.deepStrictEqual( + translateReport({ + loc: { line: 1, column: 1 }, + message: "hello world", + }), + { + severity: 2, + ruleId: "foo-rule", + message: "hello world", + nodeType: null, + line: 1, + column: 2, + }, + ); + }); + + it("should throw an error if neither node nor location is provided", () => { + assert.throws( + () => translateReport(null, "hello world"), + "Node must be provided when reporting error if location is not provided", + ); + }); + + it("should throw an error if fix range is invalid", () => { + assert.throws( + () => + translateReport({ + node, + messageId: "testMessage", + fix: () => ({ text: "foo" }), + }), + "Fix has invalid range", + ); + + for (const badRange of [ + [0], + [0, null], + [null, 0], + [void 0, 1], + [0, void 0], + [void 0, void 0], + [], + ]) { + assert.throws( + // eslint-disable-next-line no-loop-func -- Using arrow functions + () => + translateReport({ + node, + messageId: "testMessage", + fix: () => ({ range: badRange, text: "foo" }), + }), + "Fix has invalid range", + ); + + assert.throws( + // eslint-disable-next-line no-loop-func -- Using arrow functions + () => + translateReport({ + node, + messageId: "testMessage", + fix: () => [ + { range: [0, 0], text: "foo" }, + { range: badRange, text: "bar" }, + { range: [1, 1], text: "baz" }, + ], + }), + "Fix has invalid range", + ); + } + }); + }); + + // https://github.com/eslint/eslint/issues/16716 + describe("unique `fix` and `fix.range` objects", () => { + const range = [0, 3]; + const fix = { range, text: "baz" }; + const additionalRange = [4, 7]; + const additionalFix = { range: additionalRange, text: "qux" }; + + it("should deep clone returned fix object", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + fix: () => fix, + }); + + assert.deepStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix.range, fix.range); + }); + + it("should create a new fix object with a new range array when `fix()` returns an array with a single item", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + fix: () => [fix], + }); + + assert.deepStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix.range, fix.range); + }); + + it("should create a new fix object with a new range array when `fix()` returns an array with multiple items", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + fix: () => [fix, additionalFix], + }); + + assert.notStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix.range, fix.range); + assert.notStrictEqual(translatedReport.fix, additionalFix); + assert.notStrictEqual( + translatedReport.fix.range, + additionalFix.range, + ); + }); + + it("should create a new fix object with a new range array when `fix()` generator yields a single item", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + *fix() { + yield fix; + }, + }); + + assert.deepStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix.range, fix.range); + }); + + it("should create a new fix object with a new range array when `fix()` generator yields multiple items", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + *fix() { + yield fix; + yield additionalFix; + }, + }); + + assert.notStrictEqual(translatedReport.fix, fix); + assert.notStrictEqual(translatedReport.fix.range, fix.range); + assert.notStrictEqual(translatedReport.fix, additionalFix); + assert.notStrictEqual( + translatedReport.fix.range, + additionalFix.range, + ); + }); + + it("should deep clone returned suggestion fix object", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + suggest: [ + { + messageId: "suggestion1", + fix: () => fix, + }, + ], + }); + + assert.deepStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual( + translatedReport.suggestions[0].fix.range, + fix.range, + ); + }); + + it("should create a new fix object with a new range array when suggestion `fix()` returns an array with a single item", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + suggest: [ + { + messageId: "suggestion1", + fix: () => [fix], + }, + ], + }); + + assert.deepStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual( + translatedReport.suggestions[0].fix.range, + fix.range, + ); + }); + + it("should create a new fix object with a new range array when suggestion `fix()` returns an array with multiple items", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + suggest: [ + { + messageId: "suggestion1", + fix: () => [fix, additionalFix], + }, + ], + }); + + assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual( + translatedReport.suggestions[0].fix.range, + fix.range, + ); + assert.notStrictEqual( + translatedReport.suggestions[0].fix, + additionalFix, + ); + assert.notStrictEqual( + translatedReport.suggestions[0].fix.range, + additionalFix.range, + ); + }); + + it("should create a new fix object with a new range array when suggestion `fix()` generator yields a single item", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + suggest: [ + { + messageId: "suggestion1", + *fix() { + yield fix; + }, + }, + ], + }); + + assert.deepStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual( + translatedReport.suggestions[0].fix.range, + fix.range, + ); + }); + + it("should create a new fix object with a new range array when suggestion `fix()` generator yields multiple items", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + suggest: [ + { + messageId: "suggestion1", + *fix() { + yield fix; + yield additionalFix; + }, + }, + ], + }); + + assert.notStrictEqual(translatedReport.suggestions[0].fix, fix); + assert.notStrictEqual( + translatedReport.suggestions[0].fix.range, + fix.range, + ); + assert.notStrictEqual( + translatedReport.suggestions[0].fix, + additionalFix, + ); + assert.notStrictEqual( + translatedReport.suggestions[0].fix.range, + additionalFix.range, + ); + }); + + it("should create different instances of range arrays when suggestions reuse the same instance", () => { + const translatedReport = translateReport({ + node, + messageId: "testMessage", + suggest: [ + { + messageId: "suggestion1", + fix: () => ({ range, text: "baz" }), + }, + { + messageId: "suggestion2", + data: { interpolated: "'interpolated value'" }, + fix: () => ({ range, text: "qux" }), + }, + ], + }); + + assert.deepStrictEqual( + translatedReport.suggestions[0].fix.range, + range, + ); + assert.deepStrictEqual( + translatedReport.suggestions[1].fix.range, + range, + ); + assert.notStrictEqual( + translatedReport.suggestions[0].fix.range, + translatedReport.suggestions[1].fix.range, + ); + }); + }); }); diff --git a/tests/lib/linter/rule-fixer.js b/tests/lib/linter/rule-fixer.js index 7cc350b6152c..d4bedc75772f 100644 --- a/tests/lib/linter/rule-fixer.js +++ b/tests/lib/linter/rule-fixer.js @@ -9,177 +9,149 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - ruleFixer = require("../../../lib/linter/rule-fixer"); + { RuleFixer } = require("../../../lib/linter/rule-fixer"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("RuleFixer", () => { - - describe("insertTextBefore", () => { - - it("should return an object with the correct information when called", () => { - - const result = ruleFixer.insertTextBefore({ range: [0, 1] }, "Hi"); - - assert.deepStrictEqual(result, { - range: [0, 0], - text: "Hi" - }); - - }); - - it("should allow inserting empty text", () => { - - const result = ruleFixer.insertTextBefore({ range: [10, 20] }, ""); - - assert.deepStrictEqual(result, { - range: [10, 10], - text: "" - }); - - }); - - }); - - describe("insertTextBeforeRange", () => { - - it("should return an object with the correct information when called", () => { - - const result = ruleFixer.insertTextBeforeRange([0, 1], "Hi"); - - assert.deepStrictEqual(result, { - range: [0, 0], - text: "Hi" - }); - - }); - - it("should allow inserting empty text", () => { - - const result = ruleFixer.insertTextBeforeRange([10, 20], ""); - - assert.deepStrictEqual(result, { - range: [10, 10], - text: "" - }); - - }); - - }); - - describe("insertTextAfter", () => { - - it("should return an object with the correct information when called", () => { - - const result = ruleFixer.insertTextAfter({ range: [0, 1] }, "Hi"); - - assert.deepStrictEqual(result, { - range: [1, 1], - text: "Hi" - }); - - }); - - it("should allow inserting empty text", () => { - - const result = ruleFixer.insertTextAfter({ range: [10, 20] }, ""); - - assert.deepStrictEqual(result, { - range: [20, 20], - text: "" - }); - - }); - - }); - - describe("insertTextAfterRange", () => { - - it("should return an object with the correct information when called", () => { - - const result = ruleFixer.insertTextAfterRange([0, 1], "Hi"); - - assert.deepStrictEqual(result, { - range: [1, 1], - text: "Hi" - }); - - }); - - it("should allow inserting empty text", () => { - - const result = ruleFixer.insertTextAfterRange([10, 20], ""); - - assert.deepStrictEqual(result, { - range: [20, 20], - text: "" - }); - - }); - - }); - - describe("removeAfter", () => { - - it("should return an object with the correct information when called", () => { - - const result = ruleFixer.remove({ range: [0, 1] }); - - assert.deepStrictEqual(result, { - range: [0, 1], - text: "" - }); - - }); - - }); - - describe("removeAfterRange", () => { - - it("should return an object with the correct information when called", () => { - - const result = ruleFixer.removeRange([0, 1]); - - assert.deepStrictEqual(result, { - range: [0, 1], - text: "" - }); - - }); - - }); - - - describe("replaceText", () => { - - it("should return an object with the correct information when called", () => { - - const result = ruleFixer.replaceText({ range: [0, 1] }, "Hi"); - - assert.deepStrictEqual(result, { - range: [0, 1], - text: "Hi" - }); - - }); - - }); - - describe("replaceTextRange", () => { - - it("should return an object with the correct information when called", () => { - - const result = ruleFixer.replaceTextRange([0, 1], "Hi"); - - assert.deepStrictEqual(result, { - range: [0, 1], - text: "Hi" - }); - - }); - - }); - + let ruleFixer; + + beforeEach(() => { + ruleFixer = new RuleFixer({ + sourceCode: { + getLoc(node) { + return node.loc; + }, + getRange(node) { + return node.range; + }, + }, + }); + }); + + describe("insertTextBefore", () => { + it("should return an object with the correct information when called", () => { + const result = ruleFixer.insertTextBefore({ range: [0, 1] }, "Hi"); + + assert.deepStrictEqual(result, { + range: [0, 0], + text: "Hi", + }); + }); + + it("should allow inserting empty text", () => { + const result = ruleFixer.insertTextBefore({ range: [10, 20] }, ""); + + assert.deepStrictEqual(result, { + range: [10, 10], + text: "", + }); + }); + }); + + describe("insertTextBeforeRange", () => { + it("should return an object with the correct information when called", () => { + const result = ruleFixer.insertTextBeforeRange([0, 1], "Hi"); + + assert.deepStrictEqual(result, { + range: [0, 0], + text: "Hi", + }); + }); + + it("should allow inserting empty text", () => { + const result = ruleFixer.insertTextBeforeRange([10, 20], ""); + + assert.deepStrictEqual(result, { + range: [10, 10], + text: "", + }); + }); + }); + + describe("insertTextAfter", () => { + it("should return an object with the correct information when called", () => { + const result = ruleFixer.insertTextAfter({ range: [0, 1] }, "Hi"); + + assert.deepStrictEqual(result, { + range: [1, 1], + text: "Hi", + }); + }); + + it("should allow inserting empty text", () => { + const result = ruleFixer.insertTextAfter({ range: [10, 20] }, ""); + + assert.deepStrictEqual(result, { + range: [20, 20], + text: "", + }); + }); + }); + + describe("insertTextAfterRange", () => { + it("should return an object with the correct information when called", () => { + const result = ruleFixer.insertTextAfterRange([0, 1], "Hi"); + + assert.deepStrictEqual(result, { + range: [1, 1], + text: "Hi", + }); + }); + + it("should allow inserting empty text", () => { + const result = ruleFixer.insertTextAfterRange([10, 20], ""); + + assert.deepStrictEqual(result, { + range: [20, 20], + text: "", + }); + }); + }); + + describe("removeAfter", () => { + it("should return an object with the correct information when called", () => { + const result = ruleFixer.remove({ range: [0, 1] }); + + assert.deepStrictEqual(result, { + range: [0, 1], + text: "", + }); + }); + }); + + describe("removeAfterRange", () => { + it("should return an object with the correct information when called", () => { + const result = ruleFixer.removeRange([0, 1]); + + assert.deepStrictEqual(result, { + range: [0, 1], + text: "", + }); + }); + }); + + describe("replaceText", () => { + it("should return an object with the correct information when called", () => { + const result = ruleFixer.replaceText({ range: [0, 1] }, "Hi"); + + assert.deepStrictEqual(result, { + range: [0, 1], + text: "Hi", + }); + }); + }); + + describe("replaceTextRange", () => { + it("should return an object with the correct information when called", () => { + const result = ruleFixer.replaceTextRange([0, 1], "Hi"); + + assert.deepStrictEqual(result, { + range: [0, 1], + text: "Hi", + }); + }); + }); }); diff --git a/tests/lib/linter/rules.js b/tests/lib/linter/rules.js index 31d6764dc266..b617974ff1c9 100644 --- a/tests/lib/linter/rules.js +++ b/tests/lib/linter/rules.js @@ -10,88 +10,70 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - Rules = require("../../../lib/linter/rules"), - { Linter } = require("../../../lib/linter"); + Rules = require("../../../lib/linter/rules"), + { Linter } = require("../../../lib/linter"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("rules", () => { - let rules = null; - - beforeEach(() => { - rules = new Rules(); - }); - - describe("when a rule has been defined", () => { - it("should be able to retrieve the rule", () => { - const ruleId = "michaelficarra"; - - rules.define(ruleId, {}); - assert.ok(rules.get(ruleId)); - }); - - it("should return the rule as an object with a create() method if the rule was defined as a function", () => { - - /** - * A rule that does nothing - * @returns {void} - */ - function rule() {} - rule.schema = []; - rules.define("foo", rule); - assert.deepStrictEqual(rules.get("foo"), { create: rule, schema: [] }); - }); - - it("should return the rule as-is if it was defined as an object with a create() method", () => { - const rule = { create() {} }; - - rules.define("foo", rule); - assert.strictEqual(rules.get("foo"), rule); - }); - }); - - - describe("when a rule is not found", () => { - it("should report a linting error if the rule is unknown", () => { - - const linter = new Linter(); - - const problems = linter.verify("foo", { rules: { "test-rule": "error" } }); - - assert.lengthOf(problems, 1); - assert.strictEqual(problems[0].message, "Definition for rule 'test-rule' was not found."); - assert.strictEqual(problems[0].line, 1); - assert.strictEqual(problems[0].column, 1); - assert.strictEqual(problems[0].endLine, 1); - assert.strictEqual(problems[0].endColumn, 2); - }); - - - it("should report a linting error that lists replacements if a rule is known to have been replaced", () => { - const linter = new Linter(); - const problems = linter.verify("foo", { rules: { "no-arrow-condition": "error" } }); - - assert.lengthOf(problems, 1); - assert.strictEqual( - problems[0].message, - "Rule 'no-arrow-condition' was removed and replaced by: no-confusing-arrow, no-constant-condition" - ); - assert.strictEqual(problems[0].line, 1); - assert.strictEqual(problems[0].column, 1); - assert.strictEqual(problems[0].endLine, 1); - assert.strictEqual(problems[0].endColumn, 2); - }); - }); - - - describe("when loading all rules", () => { - it("should iterate all rules", () => { - const allRules = new Map(rules); - - assert.isAbove(allRules.size, 230); - assert.isObject(allRules.get("no-alert")); - }); - }); + let rules = null; + + beforeEach(() => { + rules = new Rules(); + }); + + describe("when a rule has been defined", () => { + it("should be able to retrieve the rule", () => { + const ruleId = "michaelficarra"; + + rules.define(ruleId, {}); + assert.ok(rules.get(ruleId)); + }); + + it("should return the rule as-is if it was defined as an object with a create() method", () => { + const rule = { create() {} }; + + rules.define("foo", rule); + assert.strictEqual(rules.get("foo"), rule); + }); + }); + + describe("when a rule is not found", () => { + it("should report a linting error if the rule is unknown", () => { + const linter = new Linter(); + + assert.throws( + () => { + linter.verify("foo", { rules: { "test-rule": "error" } }); + }, + TypeError, + 'Could not find "test-rule" in plugin "@".', + ); + }); + + it("should report a linting error that lists replacements if a rule is known to have been replaced", () => { + const linter = new Linter(); + + assert.throws( + () => { + linter.verify("foo", { + rules: { "no-arrow-condition": "error" }, + }); + }, + TypeError, + 'Key "rules": Key "no-arrow-condition": Rule "no-arrow-condition" was removed and replaced by "no-confusing-arrow,no-constant-condition".', + ); + }); + }); + + describe("when loading all rules", () => { + it("should iterate all rules", () => { + const allRules = new Map(rules); + + assert.isAbove(allRules.size, 230); + assert.isObject(allRules.get("no-alert")); + }); + }); }); diff --git a/tests/lib/linter/safe-emitter.js b/tests/lib/linter/safe-emitter.js deleted file mode 100644 index 0808b4c84e82..000000000000 --- a/tests/lib/linter/safe-emitter.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @fileoverview Tests for safe-emitter - * @author Teddy Katz - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const createEmitter = require("../../../lib/linter/safe-emitter"); -const assert = require("chai").assert; - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("safe-emitter", () => { - describe("emit() and on()", () => { - it("allows listeners to be registered calls them when emitted", () => { - const emitter = createEmitter(); - const colors = []; - - emitter.on("foo", () => colors.push("red")); - emitter.on("foo", () => colors.push("blue")); - emitter.on("bar", () => colors.push("green")); - - emitter.emit("foo"); - assert.deepStrictEqual(colors, ["red", "blue"]); - - emitter.on("bar", color => colors.push(color)); - emitter.emit("bar", "yellow"); - - assert.deepStrictEqual(colors, ["red", "blue", "green", "yellow"]); - }); - - it("calls listeners with no `this` value", () => { - const emitter = createEmitter(); - let called = false; - - emitter.on("foo", function() { - assert.strictEqual(this, void 0); // eslint-disable-line no-invalid-this -- Checking `this` value - called = true; - }); - - emitter.emit("foo"); - assert(called); - }); - }); -}); diff --git a/tests/lib/linter/source-code-fixer.js b/tests/lib/linter/source-code-fixer.js index 15df35cb9879..905dd33302d3 100644 --- a/tests/lib/linter/source-code-fixer.js +++ b/tests/lib/linter/source-code-fixer.js @@ -9,8 +9,8 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - sinon = require("sinon"), - SourceCodeFixer = require("../../../lib/linter/source-code-fixer"); + sinon = require("sinon"), + SourceCodeFixer = require("../../../lib/linter/source-code-fixer"); //------------------------------------------------------------------------------ // Helpers @@ -18,625 +18,884 @@ const assert = require("chai").assert, const TEST_CODE = "var answer = 6 * 7;"; const INSERT_AT_END = { - message: "End", - fix: { - range: [TEST_CODE.length, TEST_CODE.length], - text: "// end" - } - }, - INSERT_AT_START = { - message: "Start", - fix: { - range: [0, 0], - text: "// start\n" - } - }, - INSERT_IN_MIDDLE = { - message: "Multiply", - fix: { - range: [13, 13], - text: "5 *" - } - }, - REPLACE_ID = { - message: "foo", - fix: { - range: [4, 10], - text: "foo" - } - }, - REPLACE_VAR = { - message: "let", - fix: { - range: [0, 3], - text: "let" - } - }, - REPLACE_NUM = { - message: "5", - fix: { - range: [13, 14], - text: "5" - } - }, - REMOVE_START = { - message: "removestart", - fix: { - range: [0, 4], - text: "" - } - }, - REMOVE_MIDDLE = { - message: "removemiddle", - fix: { - range: [5, 10], - text: "" - } - }, - REMOVE_END = { - message: "removeend", - fix: { - range: [14, 18], - text: "" - } - }, - NO_FIX = { - message: "nofix" - }, - INSERT_BOM = { - message: "insert-bom", - fix: { - range: [0, 0], - text: "\uFEFF" - } - }, - INSERT_BOM_WITH_TEXT = { - message: "insert-bom", - fix: { - range: [0, 0], - text: "\uFEFF// start\n" - } - }, - REMOVE_BOM = { - message: "remove-bom", - fix: { - range: [-1, 0], - text: "" - } - }, - REPLACE_BOM_WITH_TEXT = { - message: "remove-bom", - fix: { - range: [-1, 0], - text: "// start\n" - } - }, - NO_FIX1 = { - message: "nofix1", - line: 1, - column: 3 - }, - NO_FIX2 = { - message: "nofix2", - line: 1, - column: 7 - }, - REVERSED_RANGE = { - message: "reversed range", - fix: { - range: [3, 0], - text: " " - } - }; + message: "End", + fix: { + range: [TEST_CODE.length, TEST_CODE.length], + text: "// end", + }, + }, + INSERT_AT_START = { + message: "Start", + fix: { + range: [0, 0], + text: "// start\n", + }, + }, + INSERT_IN_MIDDLE = { + message: "Multiply", + fix: { + range: [13, 13], + text: "5 *", + }, + }, + REPLACE_ID = { + message: "foo", + fix: { + range: [4, 10], + text: "foo", + }, + }, + REPLACE_VAR = { + message: "let", + fix: { + range: [0, 3], + text: "let", + }, + }, + REPLACE_NUM = { + message: "5", + fix: { + range: [13, 14], + text: "5", + }, + }, + REMOVE_START = { + message: "removestart", + fix: { + range: [0, 4], + text: "", + }, + }, + REMOVE_MIDDLE = { + message: "removemiddle", + fix: { + range: [5, 10], + text: "", + }, + }, + REMOVE_END = { + message: "removeend", + fix: { + range: [14, 18], + text: "", + }, + }, + NO_FIX = { + message: "nofix", + }, + INSERT_BOM = { + message: "insert-bom", + fix: { + range: [0, 0], + text: "\uFEFF", + }, + }, + INSERT_BOM_WITH_TEXT = { + message: "insert-bom", + fix: { + range: [0, 0], + text: "\uFEFF// start\n", + }, + }, + REMOVE_BOM = { + message: "remove-bom", + fix: { + range: [-1, 0], + text: "", + }, + }, + REPLACE_BOM_WITH_TEXT = { + message: "remove-bom", + fix: { + range: [-1, 0], + text: "// start\n", + }, + }, + NO_FIX1 = { + message: "nofix1", + line: 1, + column: 3, + }, + NO_FIX2 = { + message: "nofix2", + line: 1, + column: 7, + }, + REVERSED_RANGE = { + message: "reversed range", + fix: { + range: [3, 0], + text: " ", + }, + }, + UNDEFINED_FIX = { + message: "undefined", + fix: void 0, + }, + NULL_FIX = { + message: "null", + fix: null, + }; //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("SourceCodeFixer", () => { - - describe("constructor", () => { - - it("Should not be able to add anything to this", () => { - const result = new SourceCodeFixer(); - - assert.throws(() => { - result.test = 1; - }); - }); - }); - - describe("applyFixes() with no BOM", () => { - describe("shouldFix parameter", () => { - it("Should not perform any fixes if 'shouldFix' is false", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_AT_END], false); - - assert.isFalse(result.fixed); - assert.strictEqual(result.output, TEST_CODE); - }); - - it("Should perform fixes if 'shouldFix' is not provided", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_AT_END]); - - assert.isTrue(result.fixed); - }); - - it("should call a function provided as 'shouldFix' for each message", () => { - const shouldFixSpy = sinon.spy(); - - SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_IN_MIDDLE, INSERT_AT_START, INSERT_AT_END], shouldFixSpy); - assert.isTrue(shouldFixSpy.calledThrice); - }); - - it("should provide a message object as an argument to 'shouldFix'", () => { - const shouldFixSpy = sinon.spy(); - - SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_AT_START], shouldFixSpy); - assert.strictEqual(shouldFixSpy.firstCall.args[0], INSERT_AT_START); - }); - - it("should not perform fixes if 'shouldFix' function returns false", () => { - const shouldFixSpy = sinon.spy(() => false); - const result = SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_AT_START], shouldFixSpy); - - assert.isFalse(result.fixed); - }); - - it("should return original text as output if 'shouldFix' function prevents all fixes", () => { - const shouldFixSpy = sinon.spy(() => false); - const result = SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_AT_START], shouldFixSpy); - - assert.strictEqual(result.output, TEST_CODE); - }); - - it("should only apply fixes for which the 'shouldFix' function returns true", () => { - const shouldFixSpy = sinon.spy(problem => problem.message === "foo"); - const result = SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_AT_START, REPLACE_ID], shouldFixSpy); - - assert.strictEqual(result.output, "var foo = 6 * 7;"); - }); - - it("is called without access to internal eslint state", () => { - const shouldFixSpy = sinon.spy(); - - SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_AT_START], shouldFixSpy); - - assert.isUndefined(shouldFixSpy.thisValues[0]); - }); - }); - - describe("Text Insertion", () => { - - it("should insert text at the end of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_AT_END]); - - assert.strictEqual(result.output, TEST_CODE + INSERT_AT_END.fix.text); - assert.strictEqual(result.messages.length, 0); - }); - - it("should insert text at the beginning of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_AT_START]); - - assert.strictEqual(result.output, INSERT_AT_START.fix.text + TEST_CODE); - assert.strictEqual(result.messages.length, 0); - }); - - it("should insert text in the middle of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_IN_MIDDLE]); - - assert.strictEqual(result.output, TEST_CODE.replace("6 *", `${INSERT_IN_MIDDLE.fix.text}6 *`)); - assert.strictEqual(result.messages.length, 0); - }); - - it("should insert text at the beginning, middle, and end of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_IN_MIDDLE, INSERT_AT_START, INSERT_AT_END]); - - assert.strictEqual(result.output, INSERT_AT_START.fix.text + TEST_CODE.replace("6 *", `${INSERT_IN_MIDDLE.fix.text}6 *`) + INSERT_AT_END.fix.text); - assert.strictEqual(result.messages.length, 0); - }); - - - it("should ignore reversed ranges", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REVERSED_RANGE]); - - assert.strictEqual(result.output, TEST_CODE); - }); - - }); - - - describe("Text Replacement", () => { - - it("should replace text at the end of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REPLACE_VAR]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, TEST_CODE.replace("var", "let")); - assert.isTrue(result.fixed); - }); - - it("should replace text at the beginning of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REPLACE_ID]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, TEST_CODE.replace("answer", "foo")); - assert.isTrue(result.fixed); - }); - - it("should replace text in the middle of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REPLACE_NUM]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, TEST_CODE.replace("6", "5")); - assert.isTrue(result.fixed); - }); - - it("should replace text at the beginning and end of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REPLACE_ID, REPLACE_VAR, REPLACE_NUM]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, "let foo = 5 * 7;"); - assert.isTrue(result.fixed); - }); - - }); - - describe("Text Removal", () => { - - it("should remove text at the start of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REMOVE_START]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, TEST_CODE.replace("var ", "")); - assert.isTrue(result.fixed); - }); - - it("should remove text in the middle of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REMOVE_MIDDLE]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, TEST_CODE.replace("answer", "a")); - assert.isTrue(result.fixed); - }); - - it("should remove text towards the end of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REMOVE_END]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, TEST_CODE.replace(" * 7", "")); - assert.isTrue(result.fixed); - }); - - it("should remove text at the beginning, middle, and end of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REMOVE_END, REMOVE_START, REMOVE_MIDDLE]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, "a = 6;"); - assert.isTrue(result.fixed); - }); - }); - - describe("Combination", () => { - - it("should replace text at the beginning, remove text in the middle, and insert text at the end", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_AT_END, REMOVE_END, REPLACE_VAR]); - - assert.strictEqual(result.output, "let answer = 6;// end"); - assert.isTrue(result.fixed); - assert.strictEqual(result.messages.length, 0); - }); - - it("should only apply one fix when ranges overlap", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REMOVE_MIDDLE, REPLACE_ID]); - - assert.strictEqual(result.output, TEST_CODE.replace("answer", "foo")); - assert.strictEqual(result.messages.length, 1); - assert.strictEqual(result.messages[0].message, "removemiddle"); - assert.isTrue(result.fixed); - }); - - it("should apply one fix when the end of one range is the same as the start of a previous range overlap", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REMOVE_START, REPLACE_ID]); - - assert.strictEqual(result.output, TEST_CODE.replace("var ", "")); - assert.strictEqual(result.messages.length, 1); - assert.strictEqual(result.messages[0].message, "foo"); - assert.isTrue(result.fixed); - }); - - it("should only apply one fix when ranges overlap and one message has no fix", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REMOVE_MIDDLE, REPLACE_ID, NO_FIX]); - - assert.strictEqual(result.output, TEST_CODE.replace("answer", "foo")); - assert.strictEqual(result.messages.length, 2); - assert.strictEqual(result.messages[0].message, "nofix"); - assert.strictEqual(result.messages[1].message, "removemiddle"); - assert.isTrue(result.fixed); - }); - - it("should apply the same fix when ranges overlap regardless of order", () => { - const result1 = SourceCodeFixer.applyFixes(TEST_CODE, [REMOVE_MIDDLE, REPLACE_ID]); - const result2 = SourceCodeFixer.applyFixes(TEST_CODE, [REPLACE_ID, REMOVE_MIDDLE]); - - assert.strictEqual(result1.output, result2.output); - }); - }); - - describe("No Fixes", () => { - - it("should only apply one fix when ranges overlap and one message has no fix", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [NO_FIX]); - - assert.strictEqual(result.output, TEST_CODE); - assert.strictEqual(result.messages.length, 1); - assert.strictEqual(result.messages[0].message, "nofix"); - assert.isFalse(result.fixed); - }); - - it("should sort the no fix messages correctly", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REPLACE_ID, NO_FIX2, NO_FIX1]); - - assert.strictEqual(result.output, TEST_CODE.replace("answer", "foo")); - assert.strictEqual(result.messages.length, 2); - assert.strictEqual(result.messages[0].message, "nofix1"); - assert.strictEqual(result.messages[1].message, "nofix2"); - assert.isTrue(result.fixed); - }); - - }); - - describe("BOM manipulations", () => { - - it("should insert BOM with an insertion of '\uFEFF' at 0", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_BOM]); - - assert.strictEqual(result.output, `\uFEFF${TEST_CODE}`); - assert.isTrue(result.fixed); - assert.strictEqual(result.messages.length, 0); - }); - - it("should insert BOM with an insertion of '\uFEFFfoobar' at 0", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [INSERT_BOM_WITH_TEXT]); - - assert.strictEqual(result.output, `\uFEFF// start\n${TEST_CODE}`); - assert.isTrue(result.fixed); - assert.strictEqual(result.messages.length, 0); - }); - - it("should remove BOM with a negative range", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REMOVE_BOM]); - - assert.strictEqual(result.output, TEST_CODE); - assert.isTrue(result.fixed); - assert.strictEqual(result.messages.length, 0); - }); - - it("should replace BOM with a negative range and 'foobar'", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE, [REPLACE_BOM_WITH_TEXT]); - - assert.strictEqual(result.output, `// start\n${TEST_CODE}`); - assert.isTrue(result.fixed); - assert.strictEqual(result.messages.length, 0); - }); - - }); - - }); - - /* - * This section is almost same as "with no BOM". - * Just `result.output` has BOM. - */ - describe("applyFixes() with BOM", () => { - - const TEST_CODE_WITH_BOM = `\uFEFF${TEST_CODE}`; - - describe("Text Insertion", () => { - - it("should insert text at the end of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [INSERT_AT_END]); - - assert.strictEqual(result.output, `\uFEFF${TEST_CODE}${INSERT_AT_END.fix.text}`); - assert.strictEqual(result.messages.length, 0); - }); - - it("should insert text at the beginning of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [INSERT_AT_START]); - - assert.strictEqual(result.output, `\uFEFF${INSERT_AT_START.fix.text}${TEST_CODE}`); - assert.strictEqual(result.messages.length, 0); - }); - - it("should insert text in the middle of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [INSERT_IN_MIDDLE]); - - assert.strictEqual(result.output, `\uFEFF${TEST_CODE.replace("6 *", `${INSERT_IN_MIDDLE.fix.text}6 *`)}`); - assert.strictEqual(result.messages.length, 0); - }); - - it("should insert text at the beginning, middle, and end of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [INSERT_IN_MIDDLE, INSERT_AT_START, INSERT_AT_END]); - const insertInMiddle = TEST_CODE.replace("6 *", `${INSERT_IN_MIDDLE.fix.text}6 *`); - - assert.strictEqual(result.output, `\uFEFF${INSERT_AT_START.fix.text}${insertInMiddle}${INSERT_AT_END.fix.text}`); - assert.strictEqual(result.messages.length, 0); - }); - - it("should ignore reversed ranges", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REVERSED_RANGE]); - - assert.strictEqual(result.output, `\uFEFF${TEST_CODE}`); - }); - - }); - - describe("Text Replacement", () => { - - it("should replace text at the end of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REPLACE_VAR]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, `\uFEFF${TEST_CODE.replace("var", "let")}`); - assert.isTrue(result.fixed); - }); - - it("should replace text at the beginning of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REPLACE_ID]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, `\uFEFF${TEST_CODE.replace("answer", "foo")}`); - assert.isTrue(result.fixed); - }); - - it("should replace text in the middle of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REPLACE_NUM]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, `\uFEFF${TEST_CODE.replace("6", "5")}`); - assert.isTrue(result.fixed); - }); - - it("should replace text at the beginning and end of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REPLACE_ID, REPLACE_VAR, REPLACE_NUM]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, "\uFEFFlet foo = 5 * 7;"); - assert.isTrue(result.fixed); - }); - - }); - - describe("Text Removal", () => { - - it("should remove text at the start of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REMOVE_START]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, `\uFEFF${TEST_CODE.replace("var ", "")}`); - assert.isTrue(result.fixed); - }); - - it("should remove text in the middle of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REMOVE_MIDDLE]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, `\uFEFF${TEST_CODE.replace("answer", "a")}`); - assert.isTrue(result.fixed); - }); - - it("should remove text towards the end of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REMOVE_END]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, `\uFEFF${TEST_CODE.replace(" * 7", "")}`); - assert.isTrue(result.fixed); - }); - - it("should remove text at the beginning, middle, and end of the code", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REMOVE_END, REMOVE_START, REMOVE_MIDDLE]); - - assert.strictEqual(result.messages.length, 0); - assert.strictEqual(result.output, "\uFEFFa = 6;"); - assert.isTrue(result.fixed); - }); - }); - - describe("Combination", () => { - - it("should replace text at the beginning, remove text in the middle, and insert text at the end", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [INSERT_AT_END, REMOVE_END, REPLACE_VAR]); - - assert.strictEqual(result.output, "\uFEFFlet answer = 6;// end"); - assert.isTrue(result.fixed); - assert.strictEqual(result.messages.length, 0); - }); - - it("should only apply one fix when ranges overlap", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REMOVE_MIDDLE, REPLACE_ID]); - - assert.strictEqual(result.output, `\uFEFF${TEST_CODE.replace("answer", "foo")}`); - assert.strictEqual(result.messages.length, 1); - assert.strictEqual(result.messages[0].message, "removemiddle"); - assert.isTrue(result.fixed); - }); - - it("should apply one fix when the end of one range is the same as the start of a previous range overlap", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REMOVE_START, REPLACE_ID]); - - assert.strictEqual(result.output, `\uFEFF${TEST_CODE.replace("var ", "")}`); - assert.strictEqual(result.messages.length, 1); - assert.strictEqual(result.messages[0].message, "foo"); - assert.isTrue(result.fixed); - }); - - it("should only apply one fix when ranges overlap and one message has no fix", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REMOVE_MIDDLE, REPLACE_ID, NO_FIX]); - - assert.strictEqual(result.output, `\uFEFF${TEST_CODE.replace("answer", "foo")}`); - assert.strictEqual(result.messages.length, 2); - assert.strictEqual(result.messages[0].message, "nofix"); - assert.strictEqual(result.messages[1].message, "removemiddle"); - assert.isTrue(result.fixed); - }); - - it("should apply the same fix when ranges overlap regardless of order", () => { - const result1 = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REMOVE_MIDDLE, REPLACE_ID]); - const result2 = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REPLACE_ID, REMOVE_MIDDLE]); - - assert.strictEqual(result1.output, result2.output); - }); - - }); - - describe("No Fixes", () => { - - it("should only apply one fix when ranges overlap and one message has no fix", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [NO_FIX]); - - assert.strictEqual(result.output, `\uFEFF${TEST_CODE}`); - assert.strictEqual(result.messages.length, 1); - assert.strictEqual(result.messages[0].message, "nofix"); - assert.isFalse(result.fixed); - }); - - }); - - describe("BOM manipulations", () => { - - it("should insert BOM with an insertion of '\uFEFF' at 0", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [INSERT_BOM]); - - assert.strictEqual(result.output, `\uFEFF${TEST_CODE}`); - assert.isTrue(result.fixed); - assert.strictEqual(result.messages.length, 0); - }); - - it("should insert BOM with an insertion of '\uFEFFfoobar' at 0", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [INSERT_BOM_WITH_TEXT]); - - assert.strictEqual(result.output, `\uFEFF// start\n${TEST_CODE}`); - assert.isTrue(result.fixed); - assert.strictEqual(result.messages.length, 0); - }); - - it("should remove BOM with a negative range", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REMOVE_BOM]); - - assert.strictEqual(result.output, TEST_CODE); - assert.isTrue(result.fixed); - assert.strictEqual(result.messages.length, 0); - }); - - it("should replace BOM with a negative range and 'foobar'", () => { - const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [REPLACE_BOM_WITH_TEXT]); - - assert.strictEqual(result.output, `// start\n${TEST_CODE}`); - assert.isTrue(result.fixed); - assert.strictEqual(result.messages.length, 0); - }); - - }); - - }); - + describe("constructor", () => { + it("Should not be able to add anything to this", () => { + const result = new SourceCodeFixer(); + + assert.throws(() => { + result.test = 1; + }); + }); + }); + + describe("applyFixes() with no BOM", () => { + describe("shouldFix parameter", () => { + it("Should not perform any fixes if 'shouldFix' is false", () => { + const result = SourceCodeFixer.applyFixes( + TEST_CODE, + [INSERT_AT_END], + false, + ); + + assert.isFalse(result.fixed); + assert.strictEqual(result.output, TEST_CODE); + }); + + it("Should perform fixes if 'shouldFix' is not provided", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + INSERT_AT_END, + ]); + + assert.isTrue(result.fixed); + }); + + it("should call a function provided as 'shouldFix' for each message", () => { + const shouldFixSpy = sinon.spy(); + + SourceCodeFixer.applyFixes( + TEST_CODE, + [INSERT_IN_MIDDLE, INSERT_AT_START, INSERT_AT_END], + shouldFixSpy, + ); + assert.isTrue(shouldFixSpy.calledThrice); + }); + + it("should provide a message object as an argument to 'shouldFix'", () => { + const shouldFixSpy = sinon.spy(); + + SourceCodeFixer.applyFixes( + TEST_CODE, + [INSERT_AT_START], + shouldFixSpy, + ); + assert.strictEqual( + shouldFixSpy.firstCall.args[0], + INSERT_AT_START, + ); + }); + + it("should not perform fixes if 'shouldFix' function returns false", () => { + const shouldFixSpy = sinon.spy(() => false); + const result = SourceCodeFixer.applyFixes( + TEST_CODE, + [INSERT_AT_START], + shouldFixSpy, + ); + + assert.isFalse(result.fixed); + }); + + it("should return original text as output if 'shouldFix' function prevents all fixes", () => { + const shouldFixSpy = sinon.spy(() => false); + const result = SourceCodeFixer.applyFixes( + TEST_CODE, + [INSERT_AT_START], + shouldFixSpy, + ); + + assert.strictEqual(result.output, TEST_CODE); + }); + + it("should only apply fixes for which the 'shouldFix' function returns true", () => { + const shouldFixSpy = sinon.spy( + problem => problem.message === "foo", + ); + const result = SourceCodeFixer.applyFixes( + TEST_CODE, + [INSERT_AT_START, REPLACE_ID], + shouldFixSpy, + ); + + assert.strictEqual(result.output, "var foo = 6 * 7;"); + }); + + it("is called without access to internal eslint state", () => { + const shouldFixSpy = sinon.spy(); + + SourceCodeFixer.applyFixes( + TEST_CODE, + [INSERT_AT_START], + shouldFixSpy, + ); + + assert.isUndefined(shouldFixSpy.thisValues[0]); + }); + }); + + describe("Text Insertion", () => { + it("should insert text at the end of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + INSERT_AT_END, + ]); + + assert.strictEqual( + result.output, + TEST_CODE + INSERT_AT_END.fix.text, + ); + assert.strictEqual(result.messages.length, 0); + }); + + it("should insert text at the beginning of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + INSERT_AT_START, + ]); + + assert.strictEqual( + result.output, + INSERT_AT_START.fix.text + TEST_CODE, + ); + assert.strictEqual(result.messages.length, 0); + }); + + it("should insert text in the middle of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + INSERT_IN_MIDDLE, + ]); + + assert.strictEqual( + result.output, + TEST_CODE.replace("6 *", `${INSERT_IN_MIDDLE.fix.text}6 *`), + ); + assert.strictEqual(result.messages.length, 0); + }); + + it("should insert text at the beginning, middle, and end of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + INSERT_IN_MIDDLE, + INSERT_AT_START, + INSERT_AT_END, + ]); + + assert.strictEqual( + result.output, + INSERT_AT_START.fix.text + + TEST_CODE.replace( + "6 *", + `${INSERT_IN_MIDDLE.fix.text}6 *`, + ) + + INSERT_AT_END.fix.text, + ); + assert.strictEqual(result.messages.length, 0); + }); + + it("should ignore reversed ranges", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REVERSED_RANGE, + ]); + + assert.strictEqual(result.output, TEST_CODE); + }); + }); + + describe("Text Replacement", () => { + it("should replace text at the end of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REPLACE_VAR, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual( + result.output, + TEST_CODE.replace("var", "let"), + ); + assert.isTrue(result.fixed); + }); + + it("should replace text at the beginning of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REPLACE_ID, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual( + result.output, + TEST_CODE.replace("answer", "foo"), + ); + assert.isTrue(result.fixed); + }); + + it("should replace text in the middle of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REPLACE_NUM, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual(result.output, TEST_CODE.replace("6", "5")); + assert.isTrue(result.fixed); + }); + + it("should replace text at the beginning and end of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REPLACE_ID, + REPLACE_VAR, + REPLACE_NUM, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual(result.output, "let foo = 5 * 7;"); + assert.isTrue(result.fixed); + }); + }); + + describe("Text Removal", () => { + it("should remove text at the start of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REMOVE_START, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual( + result.output, + TEST_CODE.replace("var ", ""), + ); + assert.isTrue(result.fixed); + }); + + it("should remove text in the middle of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REMOVE_MIDDLE, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual( + result.output, + TEST_CODE.replace("answer", "a"), + ); + assert.isTrue(result.fixed); + }); + + it("should remove text towards the end of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REMOVE_END, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual( + result.output, + TEST_CODE.replace(" * 7", ""), + ); + assert.isTrue(result.fixed); + }); + + it("should remove text at the beginning, middle, and end of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REMOVE_END, + REMOVE_START, + REMOVE_MIDDLE, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual(result.output, "a = 6;"); + assert.isTrue(result.fixed); + }); + }); + + describe("Combination", () => { + it("should replace text at the beginning, remove text in the middle, and insert text at the end", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + INSERT_AT_END, + REMOVE_END, + REPLACE_VAR, + ]); + + assert.strictEqual(result.output, "let answer = 6;// end"); + assert.isTrue(result.fixed); + assert.strictEqual(result.messages.length, 0); + }); + + it("should only apply one fix when ranges overlap", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REMOVE_MIDDLE, + REPLACE_ID, + ]); + + assert.strictEqual( + result.output, + TEST_CODE.replace("answer", "foo"), + ); + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].message, "removemiddle"); + assert.isTrue(result.fixed); + }); + + it("should apply one fix when the end of one range is the same as the start of a previous range overlap", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REMOVE_START, + REPLACE_ID, + ]); + + assert.strictEqual( + result.output, + TEST_CODE.replace("var ", ""), + ); + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].message, "foo"); + assert.isTrue(result.fixed); + }); + + it("should only apply one fix when ranges overlap and one message has no fix", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REMOVE_MIDDLE, + REPLACE_ID, + NO_FIX, + ]); + + assert.strictEqual( + result.output, + TEST_CODE.replace("answer", "foo"), + ); + assert.strictEqual(result.messages.length, 2); + assert.strictEqual(result.messages[0].message, "nofix"); + assert.strictEqual(result.messages[1].message, "removemiddle"); + assert.isTrue(result.fixed); + }); + + it("should apply the same fix when ranges overlap regardless of order", () => { + const result1 = SourceCodeFixer.applyFixes(TEST_CODE, [ + REMOVE_MIDDLE, + REPLACE_ID, + ]); + const result2 = SourceCodeFixer.applyFixes(TEST_CODE, [ + REPLACE_ID, + REMOVE_MIDDLE, + ]); + + assert.strictEqual(result1.output, result2.output); + }); + }); + + describe("No Fixes", () => { + it("should only apply one fix when ranges overlap and one message has no fix", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [NO_FIX]); + + assert.strictEqual(result.output, TEST_CODE); + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].message, "nofix"); + assert.isFalse(result.fixed); + }); + + it("should sort the no fix messages correctly", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REPLACE_ID, + NO_FIX2, + NO_FIX1, + ]); + + assert.strictEqual( + result.output, + TEST_CODE.replace("answer", "foo"), + ); + assert.strictEqual(result.messages.length, 2); + assert.strictEqual(result.messages[0].message, "nofix1"); + assert.strictEqual(result.messages[1].message, "nofix2"); + assert.isTrue(result.fixed); + }); + }); + + describe("BOM manipulations", () => { + it("should insert BOM with an insertion of '\uFEFF' at 0", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + INSERT_BOM, + ]); + + assert.strictEqual(result.output, `\uFEFF${TEST_CODE}`); + assert.isTrue(result.fixed); + assert.strictEqual(result.messages.length, 0); + }); + + it("should insert BOM with an insertion of '\uFEFFfoobar' at 0", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + INSERT_BOM_WITH_TEXT, + ]); + + assert.strictEqual( + result.output, + `\uFEFF// start\n${TEST_CODE}`, + ); + assert.isTrue(result.fixed); + assert.strictEqual(result.messages.length, 0); + }); + + it("should remove BOM with a negative range", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REMOVE_BOM, + ]); + + assert.strictEqual(result.output, TEST_CODE); + assert.isTrue(result.fixed); + assert.strictEqual(result.messages.length, 0); + }); + + it("should replace BOM with a negative range and 'foobar'", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + REPLACE_BOM_WITH_TEXT, + ]); + + assert.strictEqual(result.output, `// start\n${TEST_CODE}`); + assert.isTrue(result.fixed); + assert.strictEqual(result.messages.length, 0); + }); + }); + + describe("Nullish fixes", () => { + it("should not throw if fix is null", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + NULL_FIX, + ]); + + assert.isFalse(result.fixed); + assert.strictEqual(result.output, TEST_CODE); + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].message, "null"); + }); + + it("should not throw if fix is undefined", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE, [ + UNDEFINED_FIX, + ]); + + assert.isFalse(result.fixed); + assert.strictEqual(result.output, TEST_CODE); + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].message, "undefined"); + }); + }); + }); + + /* + * This section is almost same as "with no BOM". + * Just `result.output` has BOM. + */ + describe("applyFixes() with BOM", () => { + const TEST_CODE_WITH_BOM = `\uFEFF${TEST_CODE}`; + + describe("Text Insertion", () => { + it("should insert text at the end of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + INSERT_AT_END, + ]); + + assert.strictEqual( + result.output, + `\uFEFF${TEST_CODE}${INSERT_AT_END.fix.text}`, + ); + assert.strictEqual(result.messages.length, 0); + }); + + it("should insert text at the beginning of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + INSERT_AT_START, + ]); + + assert.strictEqual( + result.output, + `\uFEFF${INSERT_AT_START.fix.text}${TEST_CODE}`, + ); + assert.strictEqual(result.messages.length, 0); + }); + + it("should insert text in the middle of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + INSERT_IN_MIDDLE, + ]); + + assert.strictEqual( + result.output, + `\uFEFF${TEST_CODE.replace("6 *", `${INSERT_IN_MIDDLE.fix.text}6 *`)}`, + ); + assert.strictEqual(result.messages.length, 0); + }); + + it("should insert text at the beginning, middle, and end of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + INSERT_IN_MIDDLE, + INSERT_AT_START, + INSERT_AT_END, + ]); + const insertInMiddle = TEST_CODE.replace( + "6 *", + `${INSERT_IN_MIDDLE.fix.text}6 *`, + ); + + assert.strictEqual( + result.output, + `\uFEFF${INSERT_AT_START.fix.text}${insertInMiddle}${INSERT_AT_END.fix.text}`, + ); + assert.strictEqual(result.messages.length, 0); + }); + + it("should ignore reversed ranges", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REVERSED_RANGE, + ]); + + assert.strictEqual(result.output, `\uFEFF${TEST_CODE}`); + }); + }); + + describe("Text Replacement", () => { + it("should replace text at the end of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REPLACE_VAR, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual( + result.output, + `\uFEFF${TEST_CODE.replace("var", "let")}`, + ); + assert.isTrue(result.fixed); + }); + + it("should replace text at the beginning of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REPLACE_ID, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual( + result.output, + `\uFEFF${TEST_CODE.replace("answer", "foo")}`, + ); + assert.isTrue(result.fixed); + }); + + it("should replace text in the middle of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REPLACE_NUM, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual( + result.output, + `\uFEFF${TEST_CODE.replace("6", "5")}`, + ); + assert.isTrue(result.fixed); + }); + + it("should replace text at the beginning and end of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REPLACE_ID, + REPLACE_VAR, + REPLACE_NUM, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual(result.output, "\uFEFFlet foo = 5 * 7;"); + assert.isTrue(result.fixed); + }); + }); + + describe("Text Removal", () => { + it("should remove text at the start of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REMOVE_START, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual( + result.output, + `\uFEFF${TEST_CODE.replace("var ", "")}`, + ); + assert.isTrue(result.fixed); + }); + + it("should remove text in the middle of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REMOVE_MIDDLE, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual( + result.output, + `\uFEFF${TEST_CODE.replace("answer", "a")}`, + ); + assert.isTrue(result.fixed); + }); + + it("should remove text towards the end of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REMOVE_END, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual( + result.output, + `\uFEFF${TEST_CODE.replace(" * 7", "")}`, + ); + assert.isTrue(result.fixed); + }); + + it("should remove text at the beginning, middle, and end of the code", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REMOVE_END, + REMOVE_START, + REMOVE_MIDDLE, + ]); + + assert.strictEqual(result.messages.length, 0); + assert.strictEqual(result.output, "\uFEFFa = 6;"); + assert.isTrue(result.fixed); + }); + }); + + describe("Combination", () => { + it("should replace text at the beginning, remove text in the middle, and insert text at the end", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + INSERT_AT_END, + REMOVE_END, + REPLACE_VAR, + ]); + + assert.strictEqual( + result.output, + "\uFEFFlet answer = 6;// end", + ); + assert.isTrue(result.fixed); + assert.strictEqual(result.messages.length, 0); + }); + + it("should only apply one fix when ranges overlap", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REMOVE_MIDDLE, + REPLACE_ID, + ]); + + assert.strictEqual( + result.output, + `\uFEFF${TEST_CODE.replace("answer", "foo")}`, + ); + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].message, "removemiddle"); + assert.isTrue(result.fixed); + }); + + it("should apply one fix when the end of one range is the same as the start of a previous range overlap", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REMOVE_START, + REPLACE_ID, + ]); + + assert.strictEqual( + result.output, + `\uFEFF${TEST_CODE.replace("var ", "")}`, + ); + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].message, "foo"); + assert.isTrue(result.fixed); + }); + + it("should only apply one fix when ranges overlap and one message has no fix", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REMOVE_MIDDLE, + REPLACE_ID, + NO_FIX, + ]); + + assert.strictEqual( + result.output, + `\uFEFF${TEST_CODE.replace("answer", "foo")}`, + ); + assert.strictEqual(result.messages.length, 2); + assert.strictEqual(result.messages[0].message, "nofix"); + assert.strictEqual(result.messages[1].message, "removemiddle"); + assert.isTrue(result.fixed); + }); + + it("should apply the same fix when ranges overlap regardless of order", () => { + const result1 = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REMOVE_MIDDLE, + REPLACE_ID, + ]); + const result2 = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REPLACE_ID, + REMOVE_MIDDLE, + ]); + + assert.strictEqual(result1.output, result2.output); + }); + }); + + describe("No Fixes", () => { + it("should only apply one fix when ranges overlap and one message has no fix", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + NO_FIX, + ]); + + assert.strictEqual(result.output, `\uFEFF${TEST_CODE}`); + assert.strictEqual(result.messages.length, 1); + assert.strictEqual(result.messages[0].message, "nofix"); + assert.isFalse(result.fixed); + }); + }); + + describe("BOM manipulations", () => { + it("should insert BOM with an insertion of '\uFEFF' at 0", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + INSERT_BOM, + ]); + + assert.strictEqual(result.output, `\uFEFF${TEST_CODE}`); + assert.isTrue(result.fixed); + assert.strictEqual(result.messages.length, 0); + }); + + it("should insert BOM with an insertion of '\uFEFFfoobar' at 0", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + INSERT_BOM_WITH_TEXT, + ]); + + assert.strictEqual( + result.output, + `\uFEFF// start\n${TEST_CODE}`, + ); + assert.isTrue(result.fixed); + assert.strictEqual(result.messages.length, 0); + }); + + it("should remove BOM with a negative range", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REMOVE_BOM, + ]); + + assert.strictEqual(result.output, TEST_CODE); + assert.isTrue(result.fixed); + assert.strictEqual(result.messages.length, 0); + }); + + it("should replace BOM with a negative range and 'foobar'", () => { + const result = SourceCodeFixer.applyFixes(TEST_CODE_WITH_BOM, [ + REPLACE_BOM_WITH_TEXT, + ]); + + assert.strictEqual(result.output, `// start\n${TEST_CODE}`); + assert.isTrue(result.fixed); + assert.strictEqual(result.messages.length, 0); + }); + }); + }); }); diff --git a/tests/lib/linter/source-code-traverser.js b/tests/lib/linter/source-code-traverser.js new file mode 100644 index 000000000000..bdaf586c0639 --- /dev/null +++ b/tests/lib/linter/source-code-traverser.js @@ -0,0 +1,822 @@ +/** + * @fileoverview Tests for SourceCodeTraverser. + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("node:assert"), + sinon = require("sinon"), + espree = require("espree"), + vk = require("eslint-visitor-keys"), + { SourceCodeVisitor } = require("../../../lib/linter/source-code-visitor"), + { + SourceCodeTraverser, + } = require("../../../lib/linter/source-code-traverser"), + jslang = require("../../../lib/languages/js"); + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +const ESPREE_CONFIG = { + ecmaVersion: 6, + comment: true, + tokens: true, + range: true, + loc: true, +}; + +// Mock Language object for tests +const MOCK_LANGUAGE = { + visitorKeys: vk.KEYS, + nodeTypeKey: "type", +}; + +// Step kinds for source code traversal +const STEP_KIND_VISIT = 1; +const STEP_KIND_CALL = 2; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const keysToSkip = new Set(["parent", "loc", "tokens", "comments", "range"]); + +/** + * Create a mock SourceCode object with traversal steps + * @param {Object} ast The AST to traverse + * @returns {Object} A mock SourceCode object + */ +function createMockSourceCode(ast) { + const steps = []; + + /** + * Recursively builds traversal steps for the AST + * @param {Object} node The current AST node + * @param {Object[]} ancestors The ancestry of the current node + * @returns {void} + */ + function buildSteps(node, ancestors = []) { + // Enter phase + steps.push({ + kind: STEP_KIND_VISIT, + target: node, + phase: 1, // enter phase + }); + + // Visit children + const keys = vk.getKeys(node); + if (keys) { + for (const key of keys) { + if (keysToSkip.has(key)) { + continue; // Skip keys that should not be traversed + } + + const child = node[key]; + if (Array.isArray(child)) { + for (const item of child) { + if (item && typeof item === "object") { + buildSteps(item, [node, ...ancestors]); + } + } + } else if (child && typeof child === "object") { + buildSteps(child, [node, ...ancestors]); + } + } + } + + // Exit phase + steps.push({ + kind: STEP_KIND_VISIT, + target: node, + phase: 2, // exit phase + }); + } + + buildSteps(ast); + + return { + ast, + visitorKeys: vk.KEYS, + *traverse() { + for (const step of steps) { + yield step; + } + }, + }; +} + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("SourceCodeTraverser", () => { + describe("traverseSync", () => { + let visitor, traverser; + + beforeEach(() => { + visitor = Object.assign(new SourceCodeVisitor(), { + callSync: sinon.spy(), + }); + + ["Foo", "Bar", "Foo > Bar", "Foo:exit"].forEach(selector => + visitor.add(selector, () => {}), + ); + traverser = SourceCodeTraverser.getInstance(MOCK_LANGUAGE); + }); + + it("should generate events for AST nodes.", () => { + const dummyNode = { type: "Foo", value: 1 }; + const sourceCode = createMockSourceCode(dummyNode); + + traverser.traverseSync(sourceCode, visitor); + + assert(visitor.callSync.calledTwice); + assert(visitor.callSync.firstCall.calledWith("Foo", dummyNode)); + assert( + visitor.callSync.secondCall.calledWith("Foo:exit", dummyNode), + ); + }); + + it("should use nodeTypeKey if provided.", () => { + traverser = new SourceCodeTraverser({ + ...MOCK_LANGUAGE, + nodeTypeKey: "customType", + }); + const dummyNode = { customType: "Foo", value: 1 }; + const sourceCode = createMockSourceCode(dummyNode); + + traverser.traverseSync(sourceCode, visitor); + + assert(visitor.callSync.calledTwice); + assert(visitor.callSync.firstCall.calledWith("Foo", dummyNode)); + assert( + visitor.callSync.secondCall.calledWith("Foo:exit", dummyNode), + ); + }); + + it("should generate events for nested AST nodes", () => { + const dummyNode = { + type: "Foo", + value: 1, + child: { type: "Bar", value: 2 }, + }; + + const sourceCode = createMockSourceCode(dummyNode); + + traverser.traverseSync(sourceCode, visitor); + + assert(visitor.callSync.callCount === 4); + assert( + visitor.callSync.firstCall.calledWith("Foo", dummyNode), + "First call was wrong", + ); + assert( + visitor.callSync.secondCall.calledWith("Bar", dummyNode.child), + "Second call was wrong", + ); + assert( + visitor.callSync.thirdCall.calledWith( + "Foo > Bar", + dummyNode.child, + ), + "Third call was wrong", + ); + assert( + visitor.callSync.lastCall.calledWith("Foo:exit", dummyNode), + "Last call was wrong", + ); + }); + + it("should handle call steps in traverse", () => { + const dummyNode = { type: "Foo", value: 1 }; + const sourceCode = { + ast: dummyNode, + visitorKeys: vk.KEYS, + *traverse() { + yield { + kind: STEP_KIND_VISIT, + target: dummyNode, + phase: 1, + }; + yield { + kind: STEP_KIND_CALL, + target: "customEvent", + args: [dummyNode, "extra"], + }; + yield { + kind: STEP_KIND_VISIT, + target: dummyNode, + phase: 2, + }; + }, + }; + + traverser.traverseSync(sourceCode, visitor); + + assert(visitor.callSync.calledThrice); + assert(visitor.callSync.firstCall.calledWith("Foo", dummyNode)); + assert( + visitor.callSync.secondCall.calledWith( + "customEvent", + dummyNode, + "extra", + ), + ); + assert( + visitor.callSync.thirdCall.calledWith("Foo:exit", dummyNode), + ); + }); + + it("should use provided steps instead of source code traverse", () => { + // Create a source code object with normal traverse behavior + const fooNode = { type: "Foo", value: 1 }; + const barNode = { type: "Bar", value: 2 }; + const sourceCode = createMockSourceCode(fooNode); + + // Create custom steps that don't match sourceCode.traverse() behavior + const customSteps = [ + { + kind: STEP_KIND_VISIT, + target: barNode, + phase: 1, // enter phase + }, + { + kind: STEP_KIND_CALL, + target: "customEvent", + args: [barNode, "customArg"], + }, + { + kind: STEP_KIND_VISIT, + target: barNode, + phase: 2, // exit phase + }, + ]; + + // Add a listener for the Bar node type and custom event + visitor.add("Bar", () => {}); + visitor.add("Bar:exit", () => {}); + visitor.add("customEvent", () => {}); + + // Call traverseSync with custom steps + traverser.traverseSync(sourceCode, visitor, { steps: customSteps }); + + // Verify that our custom steps were used, not the source code's traverse + assert(visitor.callSync.calledThrice); + assert( + visitor.callSync.firstCall.calledWith("Bar", barNode), + "Should call with custom Bar node", + ); + assert( + visitor.callSync.secondCall.calledWith( + "customEvent", + barNode, + "customArg", + ), + "Should emit custom event", + ); + assert( + visitor.callSync.thirdCall.calledWith("Bar:exit", barNode), + "Should call exit with custom Bar node", + ); + assert( + visitor.callSync.neverCalledWith("Foo", fooNode), + "Should not emit events for original Foo node", + ); + }); + + it("should throw error for invalid step kind", () => { + const dummyNode = { type: "Foo", value: 1 }; + const sourceCode = { + ast: dummyNode, + visitorKeys: vk.KEYS, + *traverse() { + yield { + kind: 999, // Invalid step kind + target: dummyNode, + }; + }, + }; + + assert.throws( + () => traverser.traverseSync(sourceCode, visitor), + /Invalid traversal step found:/u, + ); + }); + + it("should throw error with currentNode property when error occurs during traversal", () => { + const dummyNode = { type: "Foo", value: 1 }; + const visitorWithError = Object.assign(new SourceCodeVisitor(), { + callSync() { + throw new Error("Test error"); + }, + }); + + ["Foo"].forEach(selector => + visitorWithError.add(selector, () => {}), + ); + + const sourceCode = createMockSourceCode(dummyNode); + + try { + traverser.traverseSync(sourceCode, visitorWithError); + assert.fail("Should have thrown error"); + } catch (err) { + assert.strictEqual(err.message, "Test error"); + assert.strictEqual(err.currentNode, dummyNode); + } + }); + }); + + describe("caching behavior", () => { + it("should cache instances per language", () => { + const language1 = { ...MOCK_LANGUAGE }; + const language2 = { ...MOCK_LANGUAGE }; + + const instance1a = SourceCodeTraverser.getInstance(language1); + const instance1b = SourceCodeTraverser.getInstance(language1); + const instance2 = SourceCodeTraverser.getInstance(language2); + + assert.strictEqual( + instance1a, + instance1b, + "Should return same instance for same language", + ); + assert.notStrictEqual( + instance1a, + instance2, + "Should return different instance for different language", + ); + }); + }); + + describe("traversing the entire AST", () => { + /** + * Gets a list of emitted types/selectors from the traverser, in emission order + * @param {ASTNode} ast The AST to traverse + * @param {Array|Set} possibleQueries Selectors to detect + * @returns {Array[]} A list of emissions, in the order that they were emitted. Each emission is a two-element + * array where the first element is a string, and the second element is the emitted AST node. + */ + function getEmissions(ast, possibleQueries) { + const emissions = []; + const visitor = Object.assign(new SourceCodeVisitor(), { + callSync(selector, node) { + emissions.push([selector, node]); + }, + }); + + possibleQueries.forEach(query => visitor.add(query, () => {})); + + const sourceCode = createMockSourceCode(ast); + const traverser = SourceCodeTraverser.getInstance(jslang); + + traverser.traverseSync(sourceCode, visitor); + + return emissions.filter(emission => + possibleQueries.includes(emission[0]), + ); + } + + /** + * Creates a test case that asserts a particular sequence of traverser emissions + * @param {string} sourceText The source text that should be parsed and traversed + * @param {string[]} possibleQueries A collection of selectors that rules are listening for + * @param {(ast: ASTNode) => Array[]} getExpectedEmissions A function that accepts the AST and returns a list of the emissions that the + * traverser is expected to produce, in order. + * Each element of this list is an array where the first element is a selector (string), and the second is an AST node + * This should only include emissions that appear in possibleQueries. + * @returns {void} + */ + function assertEmissions( + sourceText, + possibleQueries, + getExpectedEmissions, + ) { + it(possibleQueries.join("; "), () => { + const ast = espree.parse(sourceText, ESPREE_CONFIG); + + const actualEmissions = getEmissions(ast, possibleQueries); + const expectedEmissions = getExpectedEmissions(ast); + + assert.deepStrictEqual(actualEmissions, expectedEmissions); + + /* + * `assert.deepStrictEqual()` compares objects by their properties. + * Here, we additionally compare node objects by reference to ensure + * the emitted objects are expected instances from the AST. + */ + actualEmissions.forEach((actualEmission, index) => { + assert.strictEqual( + actualEmission[1], + expectedEmissions[index][1], + "Expected a node instance from the AST", + ); + }); + }); + } + + assertEmissions( + "foo + bar;", + [ + "Program", + "Program:exit", + "ExpressionStatement", + "ExpressionStatement:exit", + "BinaryExpression", + "BinaryExpression:exit", + "Identifier", + "Identifier:exit", + ], + ast => [ + ["Program", ast], // entering program + ["ExpressionStatement", ast.body[0]], // entering 'foo + bar;' + ["BinaryExpression", ast.body[0].expression], // entering 'foo + bar' + ["Identifier", ast.body[0].expression.left], // entering 'foo' + ["Identifier:exit", ast.body[0].expression.left], // exiting 'foo' + ["Identifier", ast.body[0].expression.right], // entering 'bar' + ["Identifier:exit", ast.body[0].expression.right], // exiting 'bar' + ["BinaryExpression:exit", ast.body[0].expression], // exiting 'foo + bar' + ["ExpressionStatement:exit", ast.body[0]], // exiting 'foo + bar;' + ["Program:exit", ast], // exiting program + ], + ); + + assertEmissions( + "foo + 5", + [ + "BinaryExpression > Identifier", + "BinaryExpression", + "BinaryExpression Literal:exit", + "BinaryExpression > Identifier:exit", + "BinaryExpression:exit", + ], + ast => [ + ["BinaryExpression", ast.body[0].expression], // foo + 5 + ["BinaryExpression > Identifier", ast.body[0].expression.left], // foo + [ + "BinaryExpression > Identifier:exit", + ast.body[0].expression.left, + ], // exiting foo + ["BinaryExpression Literal:exit", ast.body[0].expression.right], // exiting 5 + ["BinaryExpression:exit", ast.body[0].expression], // exiting foo + 5 + ], + ); + + assertEmissions( + "foo + 5", + ["BinaryExpression > *[name='foo']"], + ast => [ + [ + "BinaryExpression > *[name='foo']", + ast.body[0].expression.left, + ], + ], // entering foo + ); + + assertEmissions("foo", ["*"], ast => [ + ["*", ast], // Program + ["*", ast.body[0]], // ExpressionStatement + ["*", ast.body[0].expression], // Identifier + ]); + + assertEmissions("foo", ["*:not(ExpressionStatement)"], ast => [ + ["*:not(ExpressionStatement)", ast], // Program + ["*:not(ExpressionStatement)", ast.body[0].expression], // Identifier + ]); + + assertEmissions( + "foo()", + ["CallExpression[callee.name='foo']"], + ast => [ + ["CallExpression[callee.name='foo']", ast.body[0].expression], + ], // foo() + ); + + assertEmissions( + "foo()", + ["CallExpression[callee.name='bar']"], + () => [], // (nothing emitted) + ); + + assertEmissions( + "foo + bar + baz", + [":not(*)"], + () => [], // (nothing emitted) + ); + + assertEmissions( + "foo + bar + baz", + [ + ":matches(Identifier[name='foo'], Identifier[name='bar'], Identifier[name='baz'])", + ], + ast => [ + [ + ":matches(Identifier[name='foo'], Identifier[name='bar'], Identifier[name='baz'])", + ast.body[0].expression.left.left, + ], // foo + [ + ":matches(Identifier[name='foo'], Identifier[name='bar'], Identifier[name='baz'])", + ast.body[0].expression.left.right, + ], // bar + [ + ":matches(Identifier[name='foo'], Identifier[name='bar'], Identifier[name='baz'])", + ast.body[0].expression.right, + ], // baz + ], + ); + + assertEmissions( + "foo + 5 + 6", + ["Identifier, Literal[value=5]"], + ast => [ + [ + "Identifier, Literal[value=5]", + ast.body[0].expression.left.left, + ], // foo + [ + "Identifier, Literal[value=5]", + ast.body[0].expression.left.right, + ], // 5 + ], + ); + + assertEmissions( + "[foo, 5, foo]", + ["Identifier + Literal"], + ast => [ + ["Identifier + Literal", ast.body[0].expression.elements[1]], + ], // 5 + ); + + assertEmissions( + "[foo, {}, 5]", + ["Identifier + Literal", "Identifier ~ Literal"], + ast => [ + ["Identifier ~ Literal", ast.body[0].expression.elements[2]], + ], // 5 + ); + + assertEmissions( + "foo; bar + baz; qux()", + [":expression", ":statement"], + ast => [ + [":statement", ast.body[0]], + [":expression", ast.body[0].expression], + [":statement", ast.body[1]], + [":expression", ast.body[1].expression], + [":expression", ast.body[1].expression.left], + [":expression", ast.body[1].expression.right], + [":statement", ast.body[2]], + [":expression", ast.body[2].expression], + [":expression", ast.body[2].expression.callee], + ], + ); + + assertEmissions( + "function foo(){} var x; (function (p){}); () => {};", + [ + ":function", + "ExpressionStatement > :function", + "VariableDeclaration, :function[params.length=1]", + ], + ast => [ + [":function", ast.body[0]], // function foo(){} + [ + "VariableDeclaration, :function[params.length=1]", + ast.body[1], + ], // var x; + [":function", ast.body[2].expression], // function (p){} + ["ExpressionStatement > :function", ast.body[2].expression], // function (p){} + [ + "VariableDeclaration, :function[params.length=1]", + ast.body[2].expression, + ], // function (p){} + [":function", ast.body[3].expression], // () => {} + ["ExpressionStatement > :function", ast.body[3].expression], // () => {} + ], + ); + + assertEmissions( + "foo;", + [ + "*", + ":not(*)", + "Identifier", + "ExpressionStatement > *", + "ExpressionStatement > Identifier", + "ExpressionStatement > [name='foo']", + "Identifier, ReturnStatement", + "FooStatement", + "[name = 'foo']", + "[name='foo']", + "[name ='foo']", + "Identifier[name='foo']", + "[name='foo'][name.length=3]", + ":not(Program, ExpressionStatement)", + ":not(Program, Identifier) > [name.length=3]", + ], + ast => [ + ["*", ast], // Program + ["*", ast.body[0]], // ExpressionStatement + + // selectors for the 'foo' identifier, in order of increasing specificity + ["*", ast.body[0].expression], // 0 identifiers, 0 pseudoclasses + ["ExpressionStatement > *", ast.body[0].expression], // 0 pseudoclasses, 1 identifier + ["Identifier", ast.body[0].expression], // 0 pseudoclasses, 1 identifier + [":not(Program, ExpressionStatement)", ast.body[0].expression], // 0 pseudoclasses, 2 identifiers + ["ExpressionStatement > Identifier", ast.body[0].expression], // 0 pseudoclasses, 2 identifiers + ["Identifier, ReturnStatement", ast.body[0].expression], // 0 pseudoclasses, 2 identifiers + ["[name = 'foo']", ast.body[0].expression], // 1 pseudoclass, 0 identifiers + ["[name ='foo']", ast.body[0].expression], // 1 pseudoclass, 0 identifiers + ["[name='foo']", ast.body[0].expression], // 1 pseudoclass, 0 identifiers + ["ExpressionStatement > [name='foo']", ast.body[0].expression], // 1 attribute, 1 identifier + ["Identifier[name='foo']", ast.body[0].expression], // 1 attribute, 1 identifier + [ + ":not(Program, Identifier) > [name.length=3]", + ast.body[0].expression, + ], // 1 attribute, 2 identifiers + ["[name='foo'][name.length=3]", ast.body[0].expression], // 2 attributes, 0 identifiers + ], + ); + + assertEmissions( + "foo(); bar; baz;", + ["CallExpression, [name='bar']"], + ast => [ + ["CallExpression, [name='bar']", ast.body[0].expression], + ["CallExpression, [name='bar']", ast.body[1].expression], + ], + ); + + assertEmissions("foo; bar;", ["[name.length=3]:exit"], ast => [ + ["[name.length=3]:exit", ast.body[0].expression], + ["[name.length=3]:exit", ast.body[1].expression], + ]); + + // https://github.com/eslint/eslint/issues/14799 + assertEmissions("const {a = 1} = b;", ["Property > .key"], ast => [ + [ + "Property > .key", + ast.body[0].declarations[0].id.properties[0].key, + ], + ]); + }); + + describe("traversing the entire non-standard AST", () => { + /** + * Gets a list of emitted types/selectors from the generator, in emission order + * @param {ASTNode} ast The AST to traverse + * @param {Record} visitorKeys The custom visitor keys. + * @param {Array|Set} possibleQueries Selectors to detect + * @returns {Array[]} A list of emissions, in the order that they were emitted. Each emission is a two-element + * array where the first element is a string, and the second element is the emitted AST node. + */ + function getEmissions(ast, visitorKeys, possibleQueries) { + const emissions = []; + const visitor = Object.assign(new SourceCodeVisitor(), { + callSync(selector, node) { + emissions.push([selector, node]); + }, + }); + + possibleQueries.forEach(query => visitor.add(query, () => {})); + + const sourceCode = createMockSourceCode(ast); + sourceCode.visitorKeys = visitorKeys; + const traverser = SourceCodeTraverser.getInstance(jslang); + + traverser.traverseSync(sourceCode, visitor); + + return emissions.filter(emission => + possibleQueries.includes(emission[0]), + ); + } + + /** + * Creates a test case that asserts a particular sequence of generator emissions + * @param {ASTNode} ast The AST to traverse + * @param {Record} visitorKeys The custom visitor keys. + * @param {string[]} possibleQueries A collection of selectors that rules are listening for + * @param {(ast: ASTNode) => Array[]} getExpectedEmissions A function that accepts the AST and returns a list of the emissions that the + * generator is expected to produce, in order. + * Each element of this list is an array where the first element is a selector (string), and the second is an AST node + * This should only include emissions that appear in possibleQueries. + * @returns {void} + */ + function assertEmissions( + ast, + visitorKeys, + possibleQueries, + getExpectedEmissions, + ) { + it(possibleQueries.join("; "), () => { + const actualEmissions = getEmissions( + ast, + visitorKeys, + possibleQueries, + ); + const expectedEmissions = getExpectedEmissions(ast); + + assert.deepStrictEqual(actualEmissions, expectedEmissions); + + /* + * `assert.deepStrictEqual()` compares objects by their properties. + * Here, we additionally compare node objects by reference to ensure + * the emitted objects are expected instances from the AST. + */ + actualEmissions.forEach((actualEmission, index) => { + assert.strictEqual( + actualEmission[1], + expectedEmissions[index][1], + "Expected a node instance from the AST", + ); + }); + }); + } + + assertEmissions( + espree.parse("const foo = [
,
]", { + ...ESPREE_CONFIG, + ecmaFeatures: { jsx: true }, + }), + vk.KEYS, + ["* ~ *"], + ast => [ + ["* ~ *", ast.body[0].declarations[0].init.elements[1]], // entering second JSXElement + ], + ); + + assertEmissions( + { + // Parse `class A implements B {}` with typescript-eslint. + type: "Program", + errors: [], + comments: [], + sourceType: "module", + body: [ + { + type: "ClassDeclaration", + id: { + type: "Identifier", + name: "A", + }, + superClass: null, + implements: [ + { + type: "ClassImplements", + id: { + type: "Identifier", + name: "B", + }, + typeParameters: null, + }, + ], + body: { + type: "ClassBody", + body: [], + }, + }, + ], + }, + vk.unionWith({ + // see https://github.com/typescript-eslint/typescript-eslint/blob/e4d737b47574ff2c53cabab22853035dfe48c1ed/packages/visitor-keys/src/visitor-keys.ts#L27 + ClassDeclaration: [ + "decorators", + "id", + "typeParameters", + "superClass", + "superTypeParameters", + "implements", + "body", + ], + }), + [":first-child"], + ast => [ + [":first-child", ast.body[0]], // entering first ClassDeclaration + [":first-child", ast.body[0].implements[0]], // entering first ClassImplements + ], + ); + }); + + describe("parsing an invalid selector", () => { + it("throws a useful error", () => { + const visitor = new SourceCodeVisitor(); + + visitor.add("Foo >", () => {}); + + assert.throws(() => { + const traverser = new SourceCodeTraverser(MOCK_LANGUAGE); + const sourceCode = createMockSourceCode({ + type: "Program", + body: [], + }); + traverser.traverseSync(sourceCode, visitor); + }, /Syntax error in selector "Foo >" at position 5: Expected " ", "!", .*/u); + }); + }); +}); diff --git a/tests/lib/linter/source-code-visitor.js b/tests/lib/linter/source-code-visitor.js new file mode 100644 index 000000000000..b2cc5a091b71 --- /dev/null +++ b/tests/lib/linter/source-code-visitor.js @@ -0,0 +1,186 @@ +/** + * @fileoverview Tests for SourceCodeVisitor. + * @author Nicholas C. Zakas + */ +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("node:assert"), + sinon = require("sinon"), + { SourceCodeVisitor } = require("../../../lib/linter/source-code-visitor"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +/** + * First test function + * @returns {void} + */ +function func1() {} + +/** + * Second test function + * @returns {void} + */ +function func2() {} + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("SourceCodeVisitor", () => { + let visitor; + + beforeEach(() => { + visitor = new SourceCodeVisitor(); + }); + + describe("add()", () => { + it("should add a function for a name that doesn't already exist", () => { + visitor.add("test", func1); + + const functions = visitor.get("test"); + assert.strictEqual(functions.length, 1); + assert.strictEqual(functions[0], func1); + }); + + it("should add a function to an existing list of functions", () => { + visitor.add("test", func1); + visitor.add("test", func2); + + const functions = visitor.get("test"); + assert.strictEqual(functions.length, 2); + assert.strictEqual(functions[0], func1); + assert.strictEqual(functions[1], func2); + }); + + it("should add functions for different names", () => { + visitor.add("test1", func1); + visitor.add("test2", func2); + + const functions1 = visitor.get("test1"); + const functions2 = visitor.get("test2"); + + assert.strictEqual(functions1.length, 1); + assert.strictEqual(functions1[0], func1); + + assert.strictEqual(functions2.length, 1); + assert.strictEqual(functions2[0], func2); + }); + }); + + describe("get()", () => { + it("should return an empty frozen array when no functions exist for a name", () => { + const functions = visitor.get("nonexistent"); + + assert.strictEqual(functions.length, 0); + assert.throws(() => { + functions.push(() => {}); + }, TypeError); + }); + + it("should return all functions for a name", () => { + visitor.add("test", func1); + visitor.add("test", func2); + + const functions = visitor.get("test"); + + assert.strictEqual(functions.length, 2); + assert.strictEqual(functions[0], func1); + assert.strictEqual(functions[1], func2); + }); + }); + + describe("forEachName()", () => { + it("should not call callback when there are no functions", () => { + const callback = sinon.spy(); + + visitor.forEachName(callback); + + assert.strictEqual(callback.callCount, 0); + }); + + it("should call callback once for each unique name", () => { + const callback = sinon.spy(); + + visitor.add("test1", func1); + visitor.add("test2", func1); + visitor.add("test1", func2); + + visitor.forEachName(callback); + + assert.strictEqual(callback.callCount, 2); + assert(callback.firstCall.calledWith("test1")); + assert(callback.secondCall.calledWith("test2")); + }); + }); + + describe("callSync()", () => { + it("should not error when no functions exist for a name", () => { + // Should not throw an error + visitor.callSync("nonexistent", {}); + }); + + it("should call all functions for a name with the arguments", () => { + const spyFunc1 = sinon.spy(); + const spyFunc2 = sinon.spy(); + const arg1 = {}; + const arg2 = "test"; + + visitor.add("test", spyFunc1); + visitor.add("test", spyFunc2); + + visitor.callSync("test", arg1, arg2); + + assert(spyFunc1.calledOnce); + assert(spyFunc1.calledWith(arg1, arg2)); + + assert(spyFunc2.calledOnce); + assert(spyFunc2.calledWith(arg1, arg2)); + }); + + it("should call functions in the order they were added", () => { + const calls = []; + + /** + * Pushes 1 to the calls array + * @returns {void} + */ + function pushOne() { + calls.push(1); + } + + /** + * Pushes 2 to the calls array + * @returns {void} + */ + function pushTwo() { + calls.push(2); + } + + visitor.add("test", pushOne); + visitor.add("test", pushTwo); + + visitor.callSync("test"); + + assert.deepStrictEqual(calls, [1, 2]); + }); + + it("should not call functions for other names", () => { + const spyFunc1 = sinon.spy(); + const spyFunc2 = sinon.spy(); + + visitor.add("test1", spyFunc1); + visitor.add("test2", spyFunc2); + + visitor.callSync("test1"); + + assert(spyFunc1.calledOnce); + assert(spyFunc2.notCalled); + }); + }); +}); diff --git a/tests/lib/linter/timing.js b/tests/lib/linter/timing.js index c8c08d0cee9b..06159576c71d 100644 --- a/tests/lib/linter/timing.js +++ b/tests/lib/linter/timing.js @@ -12,48 +12,48 @@ const assert = require("chai").assert; //------------------------------------------------------------------------------ describe("timing", () => { - describe("getListSize()", () => { - after(() => { - delete process.env.TIMING; - }); + describe("getListSize()", () => { + after(() => { + delete process.env.TIMING; + }); - it("returns minimum list size with small environment variable value", () => { - delete process.env.TIMING; // With no value. - assert.strictEqual(getListSize(), 10); + it("returns minimum list size with small environment variable value", () => { + delete process.env.TIMING; // With no value. + assert.strictEqual(getListSize(), 10); - process.env.TIMING = "true"; - assert.strictEqual(getListSize(), 10); + process.env.TIMING = "true"; + assert.strictEqual(getListSize(), 10); - process.env.TIMING = "foo"; - assert.strictEqual(getListSize(), 10); + process.env.TIMING = "foo"; + assert.strictEqual(getListSize(), 10); - process.env.TIMING = "0"; - assert.strictEqual(getListSize(), 10); + process.env.TIMING = "0"; + assert.strictEqual(getListSize(), 10); - process.env.TIMING = "1"; - assert.strictEqual(getListSize(), 10); + process.env.TIMING = "1"; + assert.strictEqual(getListSize(), 10); - process.env.TIMING = "5"; - assert.strictEqual(getListSize(), 10); + process.env.TIMING = "5"; + assert.strictEqual(getListSize(), 10); - process.env.TIMING = "10"; - assert.strictEqual(getListSize(), 10); - }); + process.env.TIMING = "10"; + assert.strictEqual(getListSize(), 10); + }); - it("returns longer list size with larger environment variable value", () => { - process.env.TIMING = "11"; - assert.strictEqual(getListSize(), 11); + it("returns longer list size with larger environment variable value", () => { + process.env.TIMING = "11"; + assert.strictEqual(getListSize(), 11); - process.env.TIMING = "100"; - assert.strictEqual(getListSize(), 100); - }); + process.env.TIMING = "100"; + assert.strictEqual(getListSize(), 100); + }); - it("returns maximum list size with environment variable value of 'all'", () => { - process.env.TIMING = "all"; - assert.strictEqual(getListSize(), Number.POSITIVE_INFINITY); + it("returns maximum list size with environment variable value of 'all'", () => { + process.env.TIMING = "all"; + assert.strictEqual(getListSize(), Number.POSITIVE_INFINITY); - process.env.TIMING = "ALL"; - assert.strictEqual(getListSize(), Number.POSITIVE_INFINITY); - }); - }); + process.env.TIMING = "ALL"; + assert.strictEqual(getListSize(), Number.POSITIVE_INFINITY); + }); + }); }); diff --git a/tests/lib/linter/vfile.js b/tests/lib/linter/vfile.js new file mode 100644 index 000000000000..55ea3ed3c17a --- /dev/null +++ b/tests/lib/linter/vfile.js @@ -0,0 +1,77 @@ +/** + * @fileoverview Tests for VFile + * @author Nicholas C. Zakas + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const { VFile } = require("../../../lib/linter/vfile"); +const assert = require("chai").assert; + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +describe("VFile", () => { + describe("new VFile()", () => { + it("should create a new instance", () => { + const vfile = new VFile("foo.js", "var foo = bar;"); + + assert.strictEqual(vfile.path, "foo.js"); + assert.strictEqual(vfile.physicalPath, "foo.js"); + assert.strictEqual(vfile.body, "var foo = bar;"); + assert.strictEqual(vfile.rawBody, "var foo = bar;"); + assert.isFalse(vfile.bom); + }); + + it("should create a new instance with a BOM", () => { + const vfile = new VFile("foo.js", "\uFEFFvar foo = bar;"); + + assert.strictEqual(vfile.path, "foo.js"); + assert.strictEqual(vfile.physicalPath, "foo.js"); + assert.strictEqual(vfile.body, "var foo = bar;"); + assert.strictEqual(vfile.rawBody, "\uFEFFvar foo = bar;"); + assert.isTrue(vfile.bom); + }); + + it("should create a new instance with a physicalPath", () => { + const vfile = new VFile("foo.js", "var foo = bar;", { + physicalPath: "foo/bar", + }); + + assert.strictEqual(vfile.path, "foo.js"); + assert.strictEqual(vfile.physicalPath, "foo/bar"); + assert.strictEqual(vfile.body, "var foo = bar;"); + assert.strictEqual(vfile.rawBody, "var foo = bar;"); + assert.isFalse(vfile.bom); + }); + + it("should create a new instance with a Uint8Array", () => { + const encoder = new TextEncoder(); + const body = encoder.encode("var foo = bar;"); + const vfile = new VFile("foo.js", body); + + assert.strictEqual(vfile.path, "foo.js"); + assert.strictEqual(vfile.physicalPath, "foo.js"); + assert.deepStrictEqual(vfile.body, body); + assert.deepStrictEqual(vfile.rawBody, body); + assert.isFalse(vfile.bom); + }); + + it("should create a new instance with a BOM in a Uint8Array", () => { + const encoder = new TextEncoder(); + const body = encoder.encode("\uFEFFvar foo = bar;"); + const vfile = new VFile("foo.js", body); + + assert.strictEqual(vfile.path, "foo.js"); + assert.strictEqual(vfile.physicalPath, "foo.js"); + assert.deepStrictEqual(vfile.body, body.slice(3)); + assert.deepStrictEqual(vfile.rawBody, body); + assert.isTrue(vfile.bom); + }); + }); +}); diff --git a/tests/lib/options.js b/tests/lib/options.js index b663e8623e36..867d7d156000 100644 --- a/tests/lib/options.js +++ b/tests/lib/options.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const assert = require("chai").assert, - createOptions = require("../../lib/options"); + createOptions = require("../../lib/options"); //----------------------------------------------------------------------------- // Data @@ -28,405 +28,472 @@ const flatOptions = createOptions(true); */ describe("options", () => { - - describe("Common options", () => { - - [eslintrcOptions, flatOptions].forEach(options => { - - describe("--help", () => { - it("should return true for .help when passed", () => { - const currentOptions = options.parse("--help"); - - assert.isTrue(currentOptions.help); - }); - }); - - describe("-h", () => { - it("should return true for .help when passed", () => { - const currentOptions = options.parse("-h"); - - assert.isTrue(currentOptions.help); - }); - }); - - describe("--config", () => { - it("should return a string for .config when passed a string", () => { - const currentOptions = options.parse("--config file"); - - assert.isString(currentOptions.config); - assert.strictEqual(currentOptions.config, "file"); - }); - }); - - describe("-c", () => { - it("should return a string for .config when passed a string", () => { - const currentOptions = options.parse("-c file"); - - assert.isString(currentOptions.config); - assert.strictEqual(currentOptions.config, "file"); - }); - }); - - describe("--format", () => { - it("should return a string for .format when passed a string", () => { - const currentOptions = options.parse("--format compact"); - - assert.isString(currentOptions.format); - assert.strictEqual(currentOptions.format, "compact"); - }); - - it("should return stylish for .format when not passed", () => { - const currentOptions = options.parse(""); - - assert.isString(currentOptions.format); - assert.strictEqual(currentOptions.format, "stylish"); - }); - }); - - describe("-f", () => { - it("should return a string for .format when passed a string", () => { - const currentOptions = options.parse("-f compact"); - - assert.isString(currentOptions.format); - assert.strictEqual(currentOptions.format, "compact"); - }); - }); - - describe("--version", () => { - it("should return true for .version when passed", () => { - const currentOptions = options.parse("--version"); - - assert.isTrue(currentOptions.version); - }); - }); - - describe("-v", () => { - it("should return true for .version when passed", () => { - const currentOptions = options.parse("-v"); - - assert.isTrue(currentOptions.version); - }); - }); - - describe("when asking for help", () => { - it("should return string of help text when called", () => { - const helpText = options.generateHelp(); - - assert.isString(helpText); - }); - }); - - describe("--no-ignore", () => { - it("should return false for .ignore when passed", () => { - const currentOptions = options.parse("--no-ignore"); - - assert.isFalse(currentOptions.ignore); - }); - }); - - describe("--ignore-pattern", () => { - it("should return a string array for .ignorePattern when passed", () => { - const currentOptions = options.parse("--ignore-pattern *.js"); - - assert.ok(currentOptions.ignorePattern); - assert.strictEqual(currentOptions.ignorePattern.length, 1); - assert.strictEqual(currentOptions.ignorePattern[0], "*.js"); - }); - - it("should return a string array for multiple values", () => { - const currentOptions = options.parse("--ignore-pattern *.js --ignore-pattern *.ts"); - - assert.ok(currentOptions.ignorePattern); - assert.strictEqual(currentOptions.ignorePattern.length, 2); - assert.strictEqual(currentOptions.ignorePattern[0], "*.js"); - assert.strictEqual(currentOptions.ignorePattern[1], "*.ts"); - }); - - it("should return a string array of properly parsed values, when those values include commas", () => { - const currentOptions = options.parse("--ignore-pattern *.js --ignore-pattern foo-{bar,baz}.js"); - - assert.ok(currentOptions.ignorePattern); - assert.strictEqual(currentOptions.ignorePattern.length, 2); - assert.strictEqual(currentOptions.ignorePattern[0], "*.js"); - assert.strictEqual(currentOptions.ignorePattern[1], "foo-{bar,baz}.js"); - }); - }); - - describe("--color", () => { - it("should return true for .color when passed --color", () => { - const currentOptions = options.parse("--color"); - - assert.isTrue(currentOptions.color); - }); - - it("should return false for .color when passed --no-color", () => { - const currentOptions = options.parse("--no-color"); - - assert.isFalse(currentOptions.color); - }); - }); - - describe("--stdin", () => { - it("should return true for .stdin when passed", () => { - const currentOptions = options.parse("--stdin"); - - assert.isTrue(currentOptions.stdin); - }); - }); - - describe("--stdin-filename", () => { - it("should return a string for .stdinFilename when passed", () => { - const currentOptions = options.parse("--stdin-filename test.js"); - - assert.strictEqual(currentOptions.stdinFilename, "test.js"); - }); - }); - - describe("--global", () => { - it("should return an array for a single occurrence", () => { - const currentOptions = options.parse("--global foo"); - - assert.isArray(currentOptions.global); - assert.strictEqual(currentOptions.global.length, 1); - assert.strictEqual(currentOptions.global[0], "foo"); - }); - - it("should split variable names using commas", () => { - const currentOptions = options.parse("--global foo,bar"); - - assert.isArray(currentOptions.global); - assert.strictEqual(currentOptions.global.length, 2); - assert.strictEqual(currentOptions.global[0], "foo"); - assert.strictEqual(currentOptions.global[1], "bar"); - }); - - it("should not split on colons", () => { - const currentOptions = options.parse("--global foo:false,bar:true"); - - assert.isArray(currentOptions.global); - assert.strictEqual(currentOptions.global.length, 2); - assert.strictEqual(currentOptions.global[0], "foo:false"); - assert.strictEqual(currentOptions.global[1], "bar:true"); - }); - - it("should concatenate successive occurrences", () => { - const currentOptions = options.parse("--global foo:true --global bar:false"); - - assert.isArray(currentOptions.global); - assert.strictEqual(currentOptions.global.length, 2); - assert.strictEqual(currentOptions.global[0], "foo:true"); - assert.strictEqual(currentOptions.global[1], "bar:false"); - }); - }); - - - describe("--quiet", () => { - it("should return true for .quiet when passed", () => { - const currentOptions = options.parse("--quiet"); - - assert.isTrue(currentOptions.quiet); - }); - }); - - describe("--max-warnings", () => { - it("should return correct value for .maxWarnings when passed", () => { - const currentOptions = options.parse("--max-warnings 10"); - - assert.strictEqual(currentOptions.maxWarnings, 10); - }); - - it("should return -1 for .maxWarnings when not passed", () => { - const currentOptions = options.parse(""); - - assert.strictEqual(currentOptions.maxWarnings, -1); - }); - - it("should throw an error when supplied with a non-integer", () => { - assert.throws(() => { - options.parse("--max-warnings 10.2"); - }, /Invalid value for option 'max-warnings' - expected type Int/u); - }); - }); - - describe("--init", () => { - it("should return true for --init when passed", () => { - const currentOptions = options.parse("--init"); - - assert.isTrue(currentOptions.init); - }); - }); - - describe("--fix", () => { - it("should return true for --fix when passed", () => { - const currentOptions = options.parse("--fix"); - - assert.isTrue(currentOptions.fix); - }); - }); - - describe("--fix-type", () => { - it("should return one value with --fix-type is passed", () => { - const currentOptions = options.parse("--fix-type problem"); - - assert.strictEqual(currentOptions.fixType.length, 1); - assert.strictEqual(currentOptions.fixType[0], "problem"); - }); - - it("should return two values when --fix-type is passed twice", () => { - const currentOptions = options.parse("--fix-type problem --fix-type suggestion"); - - assert.strictEqual(currentOptions.fixType.length, 2); - assert.strictEqual(currentOptions.fixType[0], "problem"); - assert.strictEqual(currentOptions.fixType[1], "suggestion"); - }); - - it("should return two values when --fix-type is passed a comma-separated value", () => { - const currentOptions = options.parse("--fix-type problem,suggestion"); - - assert.strictEqual(currentOptions.fixType.length, 2); - assert.strictEqual(currentOptions.fixType[0], "problem"); - assert.strictEqual(currentOptions.fixType[1], "suggestion"); - }); - }); - - describe("--debug", () => { - it("should return true for --debug when passed", () => { - const currentOptions = options.parse("--debug"); - - assert.isTrue(currentOptions.debug); - }); - }); - - describe("--inline-config", () => { - it("should return false when passed --no-inline-config", () => { - const currentOptions = options.parse("--no-inline-config"); - - assert.isFalse(currentOptions.inlineConfig); - }); - - it("should return true for --inline-config when empty", () => { - const currentOptions = options.parse(""); - - assert.isTrue(currentOptions.inlineConfig); - }); - }); - - describe("--print-config", () => { - it("should return file path when passed --print-config", () => { - const currentOptions = options.parse("--print-config file.js"); - - assert.strictEqual(currentOptions.printConfig, "file.js"); - }); - }); - }); - - }); - - - describe("--ext", () => { - it("should return an array with one item when passed .jsx", () => { - const currentOptions = eslintrcOptions.parse("--ext .jsx"); - - assert.isArray(currentOptions.ext); - assert.strictEqual(currentOptions.ext[0], ".jsx"); - }); - - it("should return an array with two items when passed .js and .jsx", () => { - const currentOptions = eslintrcOptions.parse("--ext .jsx --ext .js"); - - assert.isArray(currentOptions.ext); - assert.strictEqual(currentOptions.ext[0], ".jsx"); - assert.strictEqual(currentOptions.ext[1], ".js"); - }); - - it("should return an array with two items when passed .jsx,.js", () => { - const currentOptions = eslintrcOptions.parse("--ext .jsx,.js"); - - assert.isArray(currentOptions.ext); - assert.strictEqual(currentOptions.ext[0], ".jsx"); - assert.strictEqual(currentOptions.ext[1], ".js"); - }); - - it("should not exist when not passed", () => { - const currentOptions = eslintrcOptions.parse(""); - - assert.notProperty(currentOptions, "ext"); - }); - }); - - describe("--rulesdir", () => { - it("should return a string for .rulesdir when passed a string", () => { - const currentOptions = eslintrcOptions.parse("--rulesdir /morerules"); - - assert.isArray(currentOptions.rulesdir); - assert.deepStrictEqual(currentOptions.rulesdir, ["/morerules"]); - }); - }); - - describe("--ignore-path", () => { - it("should return a string for .ignorePath when passed", () => { - const currentOptions = eslintrcOptions.parse("--ignore-path .gitignore"); - - assert.strictEqual(currentOptions.ignorePath, ".gitignore"); - }); - }); - - describe("--parser", () => { - it("should return a string for --parser when passed", () => { - const currentOptions = eslintrcOptions.parse("--parser test"); - - assert.strictEqual(currentOptions.parser, "test"); - }); - }); - - describe("--plugin", () => { - it("should return an array when passed a single occurrence", () => { - const currentOptions = eslintrcOptions.parse("--plugin single"); - - assert.isArray(currentOptions.plugin); - assert.strictEqual(currentOptions.plugin.length, 1); - assert.strictEqual(currentOptions.plugin[0], "single"); - }); - - it("should return an array when passed a comma-delimited string", () => { - const currentOptions = eslintrcOptions.parse("--plugin foo,bar"); - - assert.isArray(currentOptions.plugin); - assert.strictEqual(currentOptions.plugin.length, 2); - assert.strictEqual(currentOptions.plugin[0], "foo"); - assert.strictEqual(currentOptions.plugin[1], "bar"); - }); - - it("should return an array when passed multiple times", () => { - const currentOptions = eslintrcOptions.parse("--plugin foo --plugin bar"); - - assert.isArray(currentOptions.plugin); - assert.strictEqual(currentOptions.plugin.length, 2); - assert.strictEqual(currentOptions.plugin[0], "foo"); - assert.strictEqual(currentOptions.plugin[1], "bar"); - }); - }); - - describe("--no-config-lookup", () => { - it("should return a string for .rulesdir when passed a string", () => { - const currentOptions = flatOptions.parse("--no-config-lookup foo.js"); - - assert.isFalse(currentOptions.configLookup); - }); - }); - - describe("--no-warn-ignored", () => { - it("should return false when --no-warn-ignored is passed", () => { - const currentOptions = flatOptions.parse("--no-warn-ignored"); - - assert.isFalse(currentOptions.warnIgnored); - }); - - it("should return true when --warn-ignored is passed", () => { - const currentOptions = flatOptions.parse("--warn-ignored"); - - assert.isTrue(currentOptions.warnIgnored); - }); - }); - + describe("Common options", () => { + [eslintrcOptions, flatOptions].forEach(options => { + describe("--help", () => { + it("should return true for .help when passed", () => { + const currentOptions = options.parse("--help"); + + assert.isTrue(currentOptions.help); + }); + }); + + describe("-h", () => { + it("should return true for .help when passed", () => { + const currentOptions = options.parse("-h"); + + assert.isTrue(currentOptions.help); + }); + }); + + describe("--config", () => { + it("should return a string for .config when passed a string", () => { + const currentOptions = options.parse("--config file"); + + assert.isString(currentOptions.config); + assert.strictEqual(currentOptions.config, "file"); + }); + }); + + describe("-c", () => { + it("should return a string for .config when passed a string", () => { + const currentOptions = options.parse("-c file"); + + assert.isString(currentOptions.config); + assert.strictEqual(currentOptions.config, "file"); + }); + }); + + describe("--format", () => { + it("should return a string for .format when passed a string", () => { + const currentOptions = options.parse("--format json"); + + assert.isString(currentOptions.format); + assert.strictEqual(currentOptions.format, "json"); + }); + + it("should return stylish for .format when not passed", () => { + const currentOptions = options.parse(""); + + assert.isString(currentOptions.format); + assert.strictEqual(currentOptions.format, "stylish"); + }); + }); + + describe("-f", () => { + it("should return a string for .format when passed a string", () => { + const currentOptions = options.parse("-f json"); + + assert.isString(currentOptions.format); + assert.strictEqual(currentOptions.format, "json"); + }); + }); + + describe("--version", () => { + it("should return true for .version when passed", () => { + const currentOptions = options.parse("--version"); + + assert.isTrue(currentOptions.version); + }); + }); + + describe("-v", () => { + it("should return true for .version when passed", () => { + const currentOptions = options.parse("-v"); + + assert.isTrue(currentOptions.version); + }); + }); + + describe("when asking for help", () => { + it("should return string of help text when called", () => { + const helpText = options.generateHelp(); + + assert.isString(helpText); + }); + }); + + describe("--no-ignore", () => { + it("should return false for .ignore when passed", () => { + const currentOptions = options.parse("--no-ignore"); + + assert.isFalse(currentOptions.ignore); + }); + }); + + describe("--ignore-pattern", () => { + it("should return a string array for .ignorePattern when passed", () => { + const currentOptions = options.parse( + "--ignore-pattern *.js", + ); + + assert.ok(currentOptions.ignorePattern); + assert.strictEqual(currentOptions.ignorePattern.length, 1); + assert.strictEqual(currentOptions.ignorePattern[0], "*.js"); + }); + + it("should return a string array for multiple values", () => { + const currentOptions = options.parse( + "--ignore-pattern *.js --ignore-pattern *.ts", + ); + + assert.ok(currentOptions.ignorePattern); + assert.strictEqual(currentOptions.ignorePattern.length, 2); + assert.strictEqual(currentOptions.ignorePattern[0], "*.js"); + assert.strictEqual(currentOptions.ignorePattern[1], "*.ts"); + }); + + it("should return a string array of properly parsed values, when those values include commas", () => { + const currentOptions = options.parse( + "--ignore-pattern *.js --ignore-pattern foo-{bar,baz}.js", + ); + + assert.ok(currentOptions.ignorePattern); + assert.strictEqual(currentOptions.ignorePattern.length, 2); + assert.strictEqual(currentOptions.ignorePattern[0], "*.js"); + assert.strictEqual( + currentOptions.ignorePattern[1], + "foo-{bar,baz}.js", + ); + }); + }); + + describe("--color", () => { + it("should return true for .color when passed --color", () => { + const currentOptions = options.parse("--color"); + + assert.isTrue(currentOptions.color); + }); + + it("should return false for .color when passed --no-color", () => { + const currentOptions = options.parse("--no-color"); + + assert.isFalse(currentOptions.color); + }); + }); + + describe("--stdin", () => { + it("should return true for .stdin when passed", () => { + const currentOptions = options.parse("--stdin"); + + assert.isTrue(currentOptions.stdin); + }); + }); + + describe("--stdin-filename", () => { + it("should return a string for .stdinFilename when passed", () => { + const currentOptions = options.parse( + "--stdin-filename test.js", + ); + + assert.strictEqual(currentOptions.stdinFilename, "test.js"); + }); + }); + + describe("--global", () => { + it("should return an array for a single occurrence", () => { + const currentOptions = options.parse("--global foo"); + + assert.isArray(currentOptions.global); + assert.strictEqual(currentOptions.global.length, 1); + assert.strictEqual(currentOptions.global[0], "foo"); + }); + + it("should split variable names using commas", () => { + const currentOptions = options.parse("--global foo,bar"); + + assert.isArray(currentOptions.global); + assert.strictEqual(currentOptions.global.length, 2); + assert.strictEqual(currentOptions.global[0], "foo"); + assert.strictEqual(currentOptions.global[1], "bar"); + }); + + it("should not split on colons", () => { + const currentOptions = options.parse( + "--global foo:false,bar:true", + ); + + assert.isArray(currentOptions.global); + assert.strictEqual(currentOptions.global.length, 2); + assert.strictEqual(currentOptions.global[0], "foo:false"); + assert.strictEqual(currentOptions.global[1], "bar:true"); + }); + + it("should concatenate successive occurrences", () => { + const currentOptions = options.parse( + "--global foo:true --global bar:false", + ); + + assert.isArray(currentOptions.global); + assert.strictEqual(currentOptions.global.length, 2); + assert.strictEqual(currentOptions.global[0], "foo:true"); + assert.strictEqual(currentOptions.global[1], "bar:false"); + }); + }); + + describe("--quiet", () => { + it("should return true for .quiet when passed", () => { + const currentOptions = options.parse("--quiet"); + + assert.isTrue(currentOptions.quiet); + }); + }); + + describe("--max-warnings", () => { + it("should return correct value for .maxWarnings when passed", () => { + const currentOptions = options.parse("--max-warnings 10"); + + assert.strictEqual(currentOptions.maxWarnings, 10); + }); + + it("should return -1 for .maxWarnings when not passed", () => { + const currentOptions = options.parse(""); + + assert.strictEqual(currentOptions.maxWarnings, -1); + }); + + it("should throw an error when supplied with a non-integer", () => { + assert.throws(() => { + options.parse("--max-warnings 10.2"); + }, /Invalid value for option 'max-warnings' - expected type Int/u); + }); + }); + + describe("--init", () => { + it("should return true for --init when passed", () => { + const currentOptions = options.parse("--init"); + + assert.isTrue(currentOptions.init); + }); + }); + + describe("--fix", () => { + it("should return true for --fix when passed", () => { + const currentOptions = options.parse("--fix"); + + assert.isTrue(currentOptions.fix); + }); + }); + + describe("--fix-type", () => { + it("should return one value with --fix-type is passed", () => { + const currentOptions = options.parse("--fix-type problem"); + + assert.strictEqual(currentOptions.fixType.length, 1); + assert.strictEqual(currentOptions.fixType[0], "problem"); + }); + + it("should return two values when --fix-type is passed twice", () => { + const currentOptions = options.parse( + "--fix-type problem --fix-type suggestion", + ); + + assert.strictEqual(currentOptions.fixType.length, 2); + assert.strictEqual(currentOptions.fixType[0], "problem"); + assert.strictEqual(currentOptions.fixType[1], "suggestion"); + }); + + it("should return two values when --fix-type is passed a comma-separated value", () => { + const currentOptions = options.parse( + "--fix-type problem,suggestion", + ); + + assert.strictEqual(currentOptions.fixType.length, 2); + assert.strictEqual(currentOptions.fixType[0], "problem"); + assert.strictEqual(currentOptions.fixType[1], "suggestion"); + }); + }); + + describe("--debug", () => { + it("should return true for --debug when passed", () => { + const currentOptions = options.parse("--debug"); + + assert.isTrue(currentOptions.debug); + }); + }); + + describe("--inline-config", () => { + it("should return false when passed --no-inline-config", () => { + const currentOptions = options.parse("--no-inline-config"); + + assert.isFalse(currentOptions.inlineConfig); + }); + + it("should return true for --inline-config when empty", () => { + const currentOptions = options.parse(""); + + assert.isTrue(currentOptions.inlineConfig); + }); + }); + + describe("--print-config", () => { + it("should return file path when passed --print-config", () => { + const currentOptions = options.parse( + "--print-config file.js", + ); + + assert.strictEqual(currentOptions.printConfig, "file.js"); + }); + }); + + describe("--ext", () => { + it("should return an array with one item when passed .jsx", () => { + const currentOptions = options.parse("--ext .jsx"); + + assert.isArray(currentOptions.ext); + assert.strictEqual(currentOptions.ext[0], ".jsx"); + }); + + it("should return an array with two items when passed .js and .jsx", () => { + const currentOptions = options.parse( + "--ext .jsx --ext .js", + ); + + assert.isArray(currentOptions.ext); + assert.strictEqual(currentOptions.ext[0], ".jsx"); + assert.strictEqual(currentOptions.ext[1], ".js"); + }); + + it("should return an array with two items when passed .jsx,.js", () => { + const currentOptions = options.parse("--ext .jsx,.js"); + + assert.isArray(currentOptions.ext); + assert.strictEqual(currentOptions.ext[0], ".jsx"); + assert.strictEqual(currentOptions.ext[1], ".js"); + }); + + it("should not exist when not passed", () => { + const currentOptions = options.parse(""); + + assert.notProperty(currentOptions, "ext"); + }); + }); + }); + }); + + describe("--rulesdir", () => { + it("should return a string for .rulesdir when passed a string", () => { + const currentOptions = eslintrcOptions.parse( + "--rulesdir /morerules", + ); + + assert.isArray(currentOptions.rulesdir); + assert.deepStrictEqual(currentOptions.rulesdir, ["/morerules"]); + }); + }); + + describe("--ignore-path", () => { + it("should return a string for .ignorePath when passed", () => { + const currentOptions = eslintrcOptions.parse( + "--ignore-path .gitignore", + ); + + assert.strictEqual(currentOptions.ignorePath, ".gitignore"); + }); + }); + + describe("--parser", () => { + it("should return a string for --parser when passed", () => { + const currentOptions = eslintrcOptions.parse("--parser test"); + + assert.strictEqual(currentOptions.parser, "test"); + }); + }); + + describe("--plugin", () => { + it("should return an array when passed a single occurrence", () => { + const currentOptions = eslintrcOptions.parse("--plugin single"); + + assert.isArray(currentOptions.plugin); + assert.strictEqual(currentOptions.plugin.length, 1); + assert.strictEqual(currentOptions.plugin[0], "single"); + }); + + it("should return an array when passed a comma-delimited string", () => { + const currentOptions = eslintrcOptions.parse("--plugin foo,bar"); + + assert.isArray(currentOptions.plugin); + assert.strictEqual(currentOptions.plugin.length, 2); + assert.strictEqual(currentOptions.plugin[0], "foo"); + assert.strictEqual(currentOptions.plugin[1], "bar"); + }); + + it("should return an array when passed multiple times", () => { + const currentOptions = eslintrcOptions.parse( + "--plugin foo --plugin bar", + ); + + assert.isArray(currentOptions.plugin); + assert.strictEqual(currentOptions.plugin.length, 2); + assert.strictEqual(currentOptions.plugin[0], "foo"); + assert.strictEqual(currentOptions.plugin[1], "bar"); + }); + }); + + describe("--no-config-lookup", () => { + it("should return a boolean for .configLookup when passed a string", () => { + const currentOptions = flatOptions.parse( + "--no-config-lookup foo.js", + ); + + assert.isFalse(currentOptions.configLookup); + }); + }); + + describe("--pass-on-no-patterns", () => { + it("should return a boolean for .passOnNoPatterns when passed a string", () => { + const currentOptions = flatOptions.parse("--pass-on-no-patterns"); + + assert.isTrue(currentOptions.passOnNoPatterns); + }); + }); + + describe("--no-warn-ignored", () => { + it("should return false when --no-warn-ignored is passed", () => { + const currentOptions = flatOptions.parse("--no-warn-ignored"); + + assert.isFalse(currentOptions.warnIgnored); + }); + + it("should return true when --warn-ignored is passed", () => { + const currentOptions = flatOptions.parse("--warn-ignored"); + + assert.isTrue(currentOptions.warnIgnored); + }); + }); + + describe("--stats", () => { + it("should return true --stats is passed", () => { + const currentOptions = flatOptions.parse("--stats"); + + assert.isTrue(currentOptions.stats); + }); + }); + + describe("--inspect-config", () => { + it("should return true when --inspect-config is passed", () => { + const currentOptions = flatOptions.parse("--inspect-config"); + + assert.isTrue(currentOptions.inspectConfig); + }); + }); + + describe("--flag", () => { + it("should return single-item array when --flag is passed once", () => { + const currentOptions = flatOptions.parse("--flag x_feature"); + + assert.deepStrictEqual(currentOptions.flag, ["x_feature"]); + }); + + it("should return multi-item array when --flag is passed multiple times", () => { + const currentOptions = flatOptions.parse( + "--flag x_feature --flag y_feature", + ); + + assert.deepStrictEqual(currentOptions.flag, [ + "x_feature", + "y_feature", + ]); + }); + }); }); diff --git a/tests/lib/rule-tester/flat-rule-tester.js b/tests/lib/rule-tester/flat-rule-tester.js deleted file mode 100644 index 679a87b99dae..000000000000 --- a/tests/lib/rule-tester/flat-rule-tester.js +++ /dev/null @@ -1,2858 +0,0 @@ -/** - * @fileoverview Tests for ESLint Tester - * @author Nicholas C. Zakas - */ -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ -const sinon = require("sinon"), - EventEmitter = require("events"), - FlatRuleTester = require("../../../lib/rule-tester/flat-rule-tester"), - assert = require("chai").assert, - nodeAssert = require("assert"); - -//----------------------------------------------------------------------------- -// Helpers -//----------------------------------------------------------------------------- - -const NODE_ASSERT_STRICT_EQUAL_OPERATOR = (() => { - try { - nodeAssert.strictEqual(1, 2); - } catch (err) { - return err.operator; - } - throw new Error("unexpected successful assertion"); -})(); - -/** - * A helper function to verify Node.js core error messages. - * @param {string} actual The actual input - * @param {string} expected The expected input - * @returns {Function} Error callback to verify that the message is correct - * for the actual and expected input. - */ -function assertErrorMatches(actual, expected) { - const err = new nodeAssert.AssertionError({ - actual, - expected, - operator: NODE_ASSERT_STRICT_EQUAL_OPERATOR - }); - - return err.message; -} - -/** - * Do nothing. - * @returns {void} - */ -function noop() { - - // do nothing. -} - -//------------------------------------------------------------------------------ -// Rewire Things -//------------------------------------------------------------------------------ - -/* - * So here's the situation. Because RuleTester uses it() and describe() from - * Mocha, any failures would show up in the output of this test file. That means - * when we tested that a failure is thrown, that would also count as a failure - * in the testing for RuleTester. In order to remove those results from the - * results of this file, we need to overwrite it() and describe() just in - * RuleTester to do nothing but run code. Effectively, it() and describe() - * just become regular functions inside of index.js, not at all related to Mocha. - * That allows the results of this file to be untainted and therefore accurate. - * - * To assert that the right arguments are passed to RuleTester.describe/it, an - * event emitter is used which emits the arguments. - */ - -const ruleTesterTestEmitter = new EventEmitter(); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("FlatRuleTester", () => { - - let ruleTester; - - // Stub `describe()` and `it()` while this test suite. - before(() => { - FlatRuleTester.describe = function(text, method) { - ruleTesterTestEmitter.emit("describe", text, method); - return method.call(this); - }; - FlatRuleTester.it = function(text, method) { - ruleTesterTestEmitter.emit("it", text, method); - return method.call(this); - }; - }); - - after(() => { - FlatRuleTester.describe = null; - FlatRuleTester.it = null; - }); - - beforeEach(() => { - ruleTester = new FlatRuleTester(); - }); - - describe("Default Config", () => { - - afterEach(() => { - FlatRuleTester.resetDefaultConfig(); - }); - - it("should correctly set the globals configuration", () => { - const config = { languageOptions: { globals: { test: true } } }; - - FlatRuleTester.setDefaultConfig(config); - assert( - FlatRuleTester.getDefaultConfig().languageOptions.globals.test, - "The default config object is incorrect" - ); - }); - - it("should correctly reset the global configuration", () => { - const config = { languageOptions: { globals: { test: true } } }; - - FlatRuleTester.setDefaultConfig(config); - FlatRuleTester.resetDefaultConfig(); - assert.deepStrictEqual( - FlatRuleTester.getDefaultConfig(), - { rules: {} }, - "The default configuration has not reset correctly" - ); - }); - - it("should enforce the global configuration to be an object", () => { - - /** - * Set the default config for the rules tester - * @param {Object} config configuration object - * @returns {Function} Function to be executed - * @private - */ - function setConfig(config) { - return function() { - FlatRuleTester.setDefaultConfig(config); - }; - } - const errorMessage = "FlatRuleTester.setDefaultConfig: config must be an object"; - - assert.throw(setConfig(), errorMessage); - assert.throw(setConfig(1), errorMessage); - assert.throw(setConfig(3.14), errorMessage); - assert.throw(setConfig("foo"), errorMessage); - assert.throw(setConfig(null), errorMessage); - assert.throw(setConfig(true), errorMessage); - }); - - it("should pass-through the globals config to the tester then to the to rule", () => { - const config = { languageOptions: { sourceType: "script", globals: { test: true } } }; - - FlatRuleTester.setDefaultConfig(config); - ruleTester = new FlatRuleTester(); - - ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), { - valid: [ - "var test = 'foo'", - "var test2 = test" - ], - invalid: [{ code: "bar", errors: 1, languageOptions: { globals: { foo: true } } }] - }); - }); - - it("should throw an error if node.start is accessed with parser in default config", () => { - const enhancedParser = require("../../fixtures/parsers/enhanced-parser"); - - FlatRuleTester.setDefaultConfig({ - languageOptions: { - parser: enhancedParser - } - }); - ruleTester = new FlatRuleTester(); - - /* - * Note: More robust test for start/end found later in file. - * This one is just for checking the default config has a - * parser that is wrapped. - */ - const usesStartEndRule = { - create() { - - return { - CallExpression(node) { - noop(node.arguments[1].start); - } - }; - } - }; - - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: ["foo(a, b)"], - invalid: [] - }); - }, "Use node.range[0] instead of node.start"); - }); - - }); - - describe("only", () => { - describe("`itOnly` accessor", () => { - describe("when `itOnly` is set", () => { - before(() => { - FlatRuleTester.itOnly = sinon.spy(); - }); - after(() => { - FlatRuleTester.itOnly = void 0; - }); - beforeEach(() => { - FlatRuleTester.itOnly.resetHistory(); - ruleTester = new FlatRuleTester(); - }); - - it("is called by exclusive tests", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - - sinon.assert.calledWith(FlatRuleTester.itOnly, "const notVar = 42;"); - }); - }); - - describe("when `it` is set and has an `only()` method", () => { - before(() => { - FlatRuleTester.it.only = () => {}; - sinon.spy(FlatRuleTester.it, "only"); - }); - after(() => { - FlatRuleTester.it.only = void 0; - }); - beforeEach(() => { - FlatRuleTester.it.only.resetHistory(); - ruleTester = new FlatRuleTester(); - }); - - it("is called by tests with `only` set", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - - sinon.assert.calledWith(FlatRuleTester.it.only, "const notVar = 42;"); - }); - }); - - describe("when global `it` is a function that has an `only()` method", () => { - let originalGlobalItOnly; - - before(() => { - - /* - * We run tests with `--forbid-only`, so we have to override - * `it.only` to prevent the real one from being called. - */ - originalGlobalItOnly = it.only; - it.only = () => {}; - sinon.spy(it, "only"); - }); - after(() => { - it.only = originalGlobalItOnly; - }); - beforeEach(() => { - it.only.resetHistory(); - ruleTester = new FlatRuleTester(); - }); - - it("is called by tests with `only` set", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - - sinon.assert.calledWith(it.only, "const notVar = 42;"); - }); - }); - - describe("when `describe` and `it` are overridden without `itOnly`", () => { - let originalGlobalItOnly; - - before(() => { - - /* - * These tests override `describe` and `it` already, so we - * don't need to override them here. We do, however, need to - * remove `only` from the global `it` to prevent it from - * being used instead. - */ - originalGlobalItOnly = it.only; - it.only = void 0; - }); - after(() => { - it.only = originalGlobalItOnly; - }); - beforeEach(() => { - ruleTester = new FlatRuleTester(); - }); - - it("throws an error recommending overriding `itOnly`", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - }, "Set `RuleTester.itOnly` to use `only` with a custom test framework."); - }); - }); - - describe("when global `it` is a function that does not have an `only()` method", () => { - let originalGlobalIt; - let originalRuleTesterDescribe; - let originalRuleTesterIt; - - before(() => { - originalGlobalIt = global.it; - - // eslint-disable-next-line no-global-assign -- Temporarily override Mocha global - it = () => {}; - - /* - * These tests override `describe` and `it`, so we need to - * un-override them here so they won't interfere. - */ - originalRuleTesterDescribe = FlatRuleTester.describe; - FlatRuleTester.describe = void 0; - originalRuleTesterIt = FlatRuleTester.it; - FlatRuleTester.it = void 0; - }); - after(() => { - - // eslint-disable-next-line no-global-assign -- Restore Mocha global - it = originalGlobalIt; - FlatRuleTester.describe = originalRuleTesterDescribe; - FlatRuleTester.it = originalRuleTesterIt; - }); - beforeEach(() => { - ruleTester = new FlatRuleTester(); - }); - - it("throws an error explaining that the current test framework does not support `only`", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - }, "The current test framework does not support exclusive tests with `only`."); - }); - }); - }); - - describe("test cases", () => { - const ruleName = "no-var"; - const rule = require("../../fixtures/testers/rule-tester/no-var"); - - let originalRuleTesterIt; - let spyRuleTesterIt; - let originalRuleTesterItOnly; - let spyRuleTesterItOnly; - - before(() => { - originalRuleTesterIt = FlatRuleTester.it; - spyRuleTesterIt = sinon.spy(); - FlatRuleTester.it = spyRuleTesterIt; - originalRuleTesterItOnly = FlatRuleTester.itOnly; - spyRuleTesterItOnly = sinon.spy(); - FlatRuleTester.itOnly = spyRuleTesterItOnly; - }); - after(() => { - FlatRuleTester.it = originalRuleTesterIt; - FlatRuleTester.itOnly = originalRuleTesterItOnly; - }); - beforeEach(() => { - spyRuleTesterIt.resetHistory(); - spyRuleTesterItOnly.resetHistory(); - ruleTester = new FlatRuleTester(); - }); - - it("isn't called for normal tests", () => { - ruleTester.run(ruleName, rule, { - valid: ["const notVar = 42;"], - invalid: [] - }); - sinon.assert.calledWith(spyRuleTesterIt, "const notVar = 42;"); - sinon.assert.notCalled(spyRuleTesterItOnly); - }); - - it("calls it or itOnly for every test case", () => { - - /* - * `RuleTester` doesn't implement test case exclusivity itself. - * Setting `only: true` just causes `RuleTester` to call - * whatever `only()` function is provided by the test framework - * instead of the regular `it()` function. - */ - - ruleTester.run(ruleName, rule, { - valid: [ - "const valid = 42;", - { - code: "const onlyValid = 42;", - only: true - } - ], - invalid: [ - { - code: "var invalid = 42;", - errors: [/^Bad var/u] - }, - { - code: "var onlyInvalid = 42;", - errors: [/^Bad var/u], - only: true - } - ] - }); - - sinon.assert.calledWith(spyRuleTesterIt, "const valid = 42;"); - sinon.assert.calledWith(spyRuleTesterItOnly, "const onlyValid = 42;"); - sinon.assert.calledWith(spyRuleTesterIt, "var invalid = 42;"); - sinon.assert.calledWith(spyRuleTesterItOnly, "var onlyInvalid = 42;"); - }); - }); - - describe("static helper wrapper", () => { - it("adds `only` to string test cases", () => { - const test = FlatRuleTester.only("const valid = 42;"); - - assert.deepStrictEqual(test, { - code: "const valid = 42;", - only: true - }); - }); - - it("adds `only` to object test cases", () => { - const test = FlatRuleTester.only({ code: "const valid = 42;" }); - - assert.deepStrictEqual(test, { - code: "const valid = 42;", - only: true - }); - }); - }); - }); - - it("should not throw an error when everything passes", () => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] } - ] - }); - }); - - it("should throw correct error when valid code is invalid and enables other core rule", () => { - - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "/*eslint semi: 2*/ eval(foo);" - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] } - ] - }); - }, /Should have no errors but had 1/u); - }); - - it("should throw an error when valid code is invalid", () => { - - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] } - ] - }); - }, /Should have no errors but had 1/u); - }); - - it("should throw an error when valid code is invalid", () => { - - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - { code: "eval(foo)" } - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] } - ] - }); - }, /Should have no errors but had 1/u); - }); - - it("should throw an error if invalid code is valid", () => { - - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "Eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] } - ] - }); - }, /Should have 1 error but had 0/u); - }); - - it("should throw an error when the error message is wrong", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: [{ message: "Bad error message." }] } - ] - }); - }, assertErrorMatches("Bad var.", "Bad error message.")); - }); - - it("should throw an error when the error message regex does not match", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { code: "var foo = bar;", errors: [{ message: /Bad error message/u }] } - ] - }); - }, /Expected 'Bad var.' to match \/Bad error message\//u); - }); - - it("should throw an error when the error is not a supported type", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: [42] } - ] - }); - }, /Error should be a string, object, or RegExp/u); - }); - - it("should throw an error when any of the errors is not a supported type", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar; var baz = quux", errors: [{ type: "VariableDeclaration" }, null] } - ] - }); - }, /Error should be a string, object, or RegExp/u); - }); - - it("should throw an error when the error is a string and it does not match error message", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: ["Bad error message."] } - ] - }); - }, assertErrorMatches("Bad var.", "Bad error message.")); - }); - - it("should throw an error when the error is a string and it does not match error message", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - - valid: [ - ], - invalid: [ - { code: "var foo = bar;", errors: [/Bad error message/u] } - ] - }); - }, /Expected 'Bad var.' to match \/Bad error message\//u); - }); - - it("should not throw an error when the error is a string and it matches error message", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: " foo = bar;", errors: ["Bad var."] } - ] - }); - }); - - it("should not throw an error when the error is a regex and it matches error message", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { code: "var foo = bar;", output: " foo = bar;", errors: [/^Bad var/u] } - ] - }); - }); - - it("should throw an error when the error is an object with an unknown property name", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: [{ Message: "Bad var." }] } - ] - }); - }, /Invalid error property name 'Message'/u); - }); - - it("should throw an error when any of the errors is an object with an unknown property name", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { - code: "var foo = bar; var baz = quux", - errors: [ - { message: "Bad var.", type: "VariableDeclaration" }, - { message: "Bad var.", typo: "VariableDeclaration" } - ] - } - ] - }); - }, /Invalid error property name 'typo'/u); - }); - - it("should not throw an error when the error is a regex in an object and it matches error message", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { code: "var foo = bar;", output: " foo = bar;", errors: [{ message: /^Bad var/u }] } - ] - }); - }); - - it("should throw an error when the expected output doesn't match", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration" }] } - ] - }); - }, /Output is incorrect/u); - }); - - it("should use strict equality to compare output", () => { - const replaceProgramWith5Rule = { - meta: { - fixable: "code" - }, - - create: context => ({ - Program(node) { - context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") }); - } - }) - }; - - // Should not throw. - ruleTester.run("foo", replaceProgramWith5Rule, { - valid: [], - invalid: [ - { code: "var foo = bar;", output: "5", errors: 1 } - ] - }); - - assert.throws(() => { - ruleTester.run("foo", replaceProgramWith5Rule, { - valid: [], - invalid: [ - { code: "var foo = bar;", output: 5, errors: 1 } - ] - }); - }, /Output is incorrect/u); - }); - - it("should throw an error when the expected output doesn't match and errors is just a number", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: "foo = bar", errors: 1 } - ] - }); - }, /Output is incorrect/u); - }); - - it("should not throw an error when the expected output is null and no errors produce output", () => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "eval(x)", errors: 1, output: null }, - { code: "eval(x); eval(y);", errors: 2, output: null } - ] - }); - }); - - it("should throw an error when the expected output is null and problems produce output", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: null, errors: 1 } - ] - }); - }, /Expected no autofixes to be suggested/u); - - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { - code: "var foo = bar; var qux = boop;", - output: null, - errors: 2 - } - ] - }); - }, /Expected no autofixes to be suggested/u); - }); - - it("should throw an error when the expected output is null and only some problems produce output", () => { - assert.throws(() => { - ruleTester.run("fixes-one-problem", require("../../fixtures/testers/rule-tester/fixes-one-problem"), { - valid: [], - invalid: [ - { code: "foo", output: null, errors: 2 } - ] - }); - }, /Expected no autofixes to be suggested/u); - }); - - it("should throw an error when the expected output isn't specified and problems produce output", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: 1 } - ] - }); - }, "The rule fixed the code. Please add 'output' property."); - }); - - it("should throw an error if invalid code specifies wrong type", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression2" }] } - ] - }); - }, /Error type should be CallExpression2, found CallExpression/u); - }); - - it("should throw an error if invalid code specifies wrong line", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression", line: 5 }] } - ] - }); - }, /Error line should be 5/u); - }); - - it("should not skip line assertion if line is a falsy value", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "\neval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression", line: 0 }] } - ] - }); - }, /Error line should be 0/u); - }); - - it("should throw an error if invalid code specifies wrong column", () => { - const wrongColumn = 10, - expectedErrorMessage = "Error column should be 1"; - - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: ["Eval(foo)"], - invalid: [{ - code: "eval(foo)", - errors: [{ - message: "eval sucks.", - column: wrongColumn - }] - }] - }); - }, expectedErrorMessage); - }); - - it("should throw error for empty error array", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [] - }] - }); - }, /Invalid cases must have at least one error/u); - }); - - it("should throw error for errors : 0", () => { - assert.throws(() => { - ruleTester.run( - "suggestions-messageIds", - require("../../fixtures/testers/rule-tester/suggestions") - .withMessageIds, - { - valid: [], - invalid: [ - { - code: "var foo;", - errors: 0 - } - ] - } - ); - }, /Invalid cases must have 'error' value greater than 0/u); - }); - - it("should not skip column assertion if column is a falsy value", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: ["Eval(foo)"], - invalid: [{ - code: "var foo; eval(foo)", - errors: [{ message: "eval sucks.", column: 0 }] - }] - }); - }, /Error column should be 0/u); - }); - - it("should throw an error if invalid code specifies wrong endLine", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration", endLine: 10 }] } - ] - }); - }, "Error endLine should be 10"); - }); - - it("should throw an error if invalid code specifies wrong endColumn", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration", endColumn: 10 }] } - ] - }); - }, "Error endColumn should be 10"); - }); - - it("should throw an error if invalid code has the wrong number of errors", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { - code: "eval(foo)", - errors: [ - { message: "eval sucks.", type: "CallExpression" }, - { message: "eval sucks.", type: "CallExpression" } - ] - } - ] - }); - }, /Should have 2 errors but had 1/u); - }); - - it("should throw an error if invalid code does not have errors", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)" } - ] - }); - }, /Did not specify errors for an invalid test of no-eval/u); - }); - - it("should throw an error if invalid code has the wrong explicit number of errors", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: 2 } - ] - }); - }, /Should have 2 errors but had 1/u); - }); - - it("should throw an error if there's a parsing error in a valid test", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "1eval('foo')" - ], - invalid: [ - { code: "eval('foo')", errors: [{}] } - ] - }); - }, /fatal parsing error/iu); - }); - - it("should throw an error if there's a parsing error in an invalid test", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "noeval('foo')" - ], - invalid: [ - { code: "1eval('foo')", errors: [{}] } - ] - }); - }, /fatal parsing error/iu); - }); - - it("should throw an error if there's a parsing error in an invalid test and errors is just a number", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "noeval('foo')" - ], - invalid: [ - { code: "1eval('foo')", errors: 1 } - ] - }); - }, /fatal parsing error/iu); - }); - - // https://github.com/eslint/eslint/issues/4779 - it("should throw an error if there's a parsing error and output doesn't match", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [], - invalid: [ - { code: "eval(`foo`", output: "eval(`foo`);", errors: [{}] } - ] - }); - }, /fatal parsing error/iu); - }); - - it("should not throw an error if invalid code has at least an expected empty error object", () => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: ["Eval(foo)"], - invalid: [{ - code: "eval(foo)", - errors: [{}] - }] - }); - }); - - it("should pass-through the globals config of valid tests to the to rule", () => { - ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), { - valid: [ - { - code: "var test = 'foo'", - languageOptions: { - sourceType: "script" - } - }, - { - code: "var test2 = 'bar'", - languageOptions: { - globals: { test: true } - } - } - ], - invalid: [{ code: "bar", errors: 1 }] - }); - }); - - it("should pass-through the globals config of invalid tests to the rule", () => { - ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), { - valid: [ - { - code: "var test = 'foo'", - languageOptions: { - sourceType: "script" - } - } - ], - invalid: [ - { - code: "var test = 'foo'; var foo = 'bar'", - languageOptions: { - sourceType: "script" - }, - errors: 1 - }, - { - code: "var test = 'foo'", - languageOptions: { - sourceType: "script", - globals: { foo: true } - }, - errors: [{ message: "Global variable foo should not be used." }] - } - ] - }); - }); - - it("should pass-through the settings config to rules", () => { - ruleTester.run("no-test-settings", require("../../fixtures/testers/rule-tester/no-test-settings"), { - valid: [ - { - code: "var test = 'bar'", settings: { test: 1 } - } - ], - invalid: [ - { - code: "var test = 'bar'", settings: { "no-test": 22 }, errors: 1 - } - ] - }); - }); - - it("should pass-through the filename to the rule", () => { - (function() { - ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), { - valid: [ - { - code: "var foo = 'bar'", - filename: "somefile.js" - } - ], - invalid: [ - { - code: "var foo = 'bar'", - errors: [ - { message: "Filename test was not defined." } - ] - } - ] - }); - }()); - }); - - it("should allow setting the filename to a non-JavaScript file", () => { - ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), { - valid: [ - { - code: "var foo = 'bar'", - filename: "somefile.ts" - } - ], - invalid: [] - }); - }); - - it("should allow setting the filename to a file path without extension", () => { - ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), { - valid: [ - { - code: "var foo = 'bar'", - filename: "somefile" - }, - { - code: "var foo = 'bar'", - filename: "path/to/somefile" - } - ], - invalid: [] - }); - }); - - it("should allow setting the filename to a file path with extension", () => { - ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), { - valid: [ - { - code: "var foo = 'bar'", - filename: "path/to/somefile.js" - }, - { - code: "var foo = 'bar'", - filename: "src/somefile.ts" - }, - { - code: "var foo = 'bar'", - filename: "components/Component.vue" - } - ], - invalid: [] - }); - }); - - it("should allow setting the filename to a file path without extension", () => { - ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), { - valid: [ - { - code: "var foo = 'bar'", - filename: "path/to/somefile" - }, - { - code: "var foo = 'bar'", - filename: "src/somefile" - } - ], - invalid: [] - }); - }); - - it("should keep allowing non-JavaScript files if the default config does not specify files", () => { - FlatRuleTester.setDefaultConfig({ rules: {} }); - ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), { - valid: [ - { - code: "var foo = 'bar'", - filename: "somefile.ts" - } - ], - invalid: [] - }); - FlatRuleTester.resetDefaultConfig(); - }); - - it("should pass-through the options to the rule", () => { - ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), { - valid: [ - { - code: "var foo = 'bar'", - options: [false] - } - ], - invalid: [ - { - code: "var foo = 'bar'", - options: [true], - errors: [{ message: "Invalid args" }] - } - ] - }); - }); - - it("should throw an error if the options are an object", () => { - assert.throws(() => { - ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), { - valid: [ - { - code: "foo", - options: { ok: true } - } - ], - invalid: [] - }); - }, /options must be an array/u); - }); - - it("should throw an error if the options are a number", () => { - assert.throws(() => { - ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), { - valid: [ - { - code: "foo", - options: 0 - } - ], - invalid: [] - }); - }, /options must be an array/u); - }); - - describe("Parsers", () => { - - it("should pass-through the parser to the rule", () => { - const spy = sinon.spy(ruleTester.linter, "verify"); - const esprima = require("esprima"); - - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - { - code: "Eval(foo)" - } - ], - invalid: [ - { - code: "eval(foo)", - languageOptions: { - parser: esprima - }, - errors: [{ line: 1 }] - } - ] - }); - - const configs = spy.args[1][1]; - const config = configs.getConfig("test.js"); - - assert.strictEqual( - config.languageOptions.parser[Symbol.for("eslint.RuleTester.parser")], - esprima - ); - }); - - it("should pass-through services from parseForESLint to the rule", () => { - const enhancedParser = require("../../fixtures/parsers/enhanced-parser"); - const disallowHiRule = { - create: context => ({ - Literal(node) { - assert.strictEqual(context.parserServices, context.sourceCode.parserServices); - - const disallowed = context.sourceCode.parserServices.test.getMessage(); // returns "Hi!" - - if (node.value === disallowed) { - context.report({ node, message: `Don't use '${disallowed}'` }); - } - } - }) - }; - - ruleTester.run("no-hi", disallowHiRule, { - valid: [ - { - code: "'Hello!'", - languageOptions: { - parser: enhancedParser - } - } - ], - invalid: [ - { - code: "'Hi!'", - languageOptions: { - parser: enhancedParser - }, - errors: [{ message: "Don't use 'Hi!'" }] - } - ] - }); - }); - - it("should throw an error when the parser is not an object", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [], - invalid: [{ - code: "var foo;", - languageOptions: { - parser: "esprima" - }, - errors: 1 - }] - }); - }, /Parser must be an object with a parse\(\) or parseForESLint\(\) method/u); - - }); - - }); - - - it("should prevent invalid options schemas", () => { - assert.throws(() => { - ruleTester.run("no-invalid-schema", require("../../fixtures/testers/rule-tester/no-invalid-schema"), { - valid: [ - "var answer = 6 * 7;", - { code: "var answer = 6 * 7;", options: [] } - ], - invalid: [ - { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected nothing." }] } - ] - }); - }, "Schema for rule no-invalid-schema is invalid:,\titems: should be object\n\titems[0].enum: should NOT have fewer than 1 items\n\titems: should match some schema in anyOf"); - - }); - - it("should prevent schema violations in options", () => { - assert.throws(() => { - ruleTester.run("no-schema-violation", require("../../fixtures/testers/rule-tester/no-schema-violation"), { - valid: [ - "var answer = 6 * 7;", - { code: "var answer = 6 * 7;", options: ["foo"] } - ], - invalid: [ - { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected foo." }] } - ] - }); - }, /Value "bar" should be equal to one of the allowed values./u); - - }); - - it("should disallow invalid defaults in rules", () => { - const ruleWithInvalidDefaults = { - meta: { - schema: [ - { - oneOf: [ - { enum: ["foo"] }, - { - type: "object", - properties: { - foo: { - enum: ["foo", "bar"], - default: "foo" - } - }, - additionalProperties: false - } - ] - } - ] - }, - create: () => ({}) - }; - - assert.throws(() => { - ruleTester.run("invalid-defaults", ruleWithInvalidDefaults, { - valid: [ - { - code: "foo", - options: [{}] - } - ], - invalid: [] - }); - }, /Schema for rule invalid-defaults is invalid: default is ignored for: data1\.foo/u); - }); - - it("throw an error when an unknown config option is included", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - { code: "Eval(foo)", foo: "bar" } - ], - invalid: [] - }); - }, /ESLint configuration in rule-tester is invalid./u); - }); - - it("throw an error when env is included in config", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - { code: "Eval(foo)", env: ["es6"] } - ], - invalid: [] - }); - }, /Key "env": This appears to be in eslintrc format rather than flat config format/u); - }); - - it("should pass-through the tester config to the rule", () => { - ruleTester = new FlatRuleTester({ - languageOptions: { - globals: { test: true } - } - }); - - ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), { - valid: [ - "var test = 'foo'", - "var test2 = test" - ], - invalid: [{ code: "bar", errors: 1, languageOptions: { globals: { foo: true } } }] - }); - }); - - it("should throw an error if AST was modified", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast"), { - valid: [ - "var foo = 0;" - ], - invalid: [] - }); - }, "Rule should not modify AST."); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast"), { - valid: [], - invalid: [ - { code: "var bar = 0;", errors: ["error"] } - ] - }); - }, "Rule should not modify AST."); - }); - - it("should throw an error node.start is accessed with custom parser", () => { - const enhancedParser = require("../../fixtures/parsers/enhanced-parser"); - - ruleTester = new FlatRuleTester({ - languageOptions: { - parser: enhancedParser - } - }); - - /* - * Note: More robust test for start/end found later in file. - * This one is just for checking the custom config has a - * parser that is wrapped. - */ - const usesStartEndRule = { - create() { - - return { - CallExpression(node) { - noop(node.arguments[1].start); - } - }; - } - }; - - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: ["foo(a, b)"], - invalid: [] - }); - }, "Use node.range[0] instead of node.start"); - }); - - it("should throw an error if AST was modified (at Program)", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-first"), { - valid: [ - "var foo = 0;" - ], - invalid: [] - }); - }, "Rule should not modify AST."); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-first"), { - valid: [], - invalid: [ - { code: "var bar = 0;", errors: ["error"] } - ] - }); - }, "Rule should not modify AST."); - }); - - it("should throw an error if AST was modified (at Program:exit)", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), { - valid: [ - "var foo = 0;" - ], - invalid: [] - }); - }, "Rule should not modify AST."); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), { - valid: [], - invalid: [ - { code: "var bar = 0;", errors: ["error"] } - ] - }); - }, "Rule should not modify AST."); - }); - - it("should throw an error if rule uses start and end properties on nodes, tokens or comments", () => { - const usesStartEndRule = { - create(context) { - - const sourceCode = context.sourceCode; - - return { - CallExpression(node) { - noop(node.arguments[1].start); - }, - "BinaryExpression[operator='+']"(node) { - noop(node.end); - }, - "UnaryExpression[operator='-']"(node) { - noop(sourceCode.getFirstToken(node).start); - }, - ConditionalExpression(node) { - noop(sourceCode.getFirstToken(node).end); - }, - BlockStatement(node) { - noop(sourceCode.getCommentsInside(node)[0].start); - }, - ObjectExpression(node) { - noop(sourceCode.getCommentsInside(node)[0].end); - }, - Decorator(node) { - noop(node.start); - } - }; - } - }; - - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: ["foo(a, b)"], - invalid: [] - }); - }, "Use node.range[0] instead of node.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var a = b * (c + d) / e;", errors: 1 }] - }); - }, "Use node.range[1] instead of node.end"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var a = -b * c;", errors: 1 }] - }); - }, "Use token.range[0] instead of token.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: ["var a = b ? c : d;"], - invalid: [] - }); - }, "Use token.range[1] instead of token.end"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: ["function f() { /* comment */ }"], - invalid: [] - }); - }, "Use token.range[0] instead of token.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var x = //\n {\n //comment\n //\n}", errors: 1 }] - }); - }, "Use token.range[1] instead of token.end"); - - const enhancedParser = require("../../fixtures/parsers/enhanced-parser"); - - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [{ code: "foo(a, b)", languageOptions: { parser: enhancedParser } }], - invalid: [] - }); - }, "Use node.range[0] instead of node.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var a = b * (c + d) / e;", languageOptions: { parser: enhancedParser }, errors: 1 }] - }); - }, "Use node.range[1] instead of node.end"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var a = -b * c;", languageOptions: { parser: enhancedParser }, errors: 1 }] - }); - }, "Use token.range[0] instead of token.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [{ code: "var a = b ? c : d;", languageOptions: { parser: enhancedParser } }], - invalid: [] - }); - }, "Use token.range[1] instead of token.end"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [{ code: "function f() { /* comment */ }", languageOptions: { parser: enhancedParser } }], - invalid: [] - }); - }, "Use token.range[0] instead of token.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var x = //\n {\n //comment\n //\n}", languageOptions: { parser: enhancedParser }, errors: 1 }] - }); - }, "Use token.range[1] instead of token.end"); - - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [{ code: "@foo class A {}", languageOptions: { parser: require("../../fixtures/parsers/enhanced-parser2") } }], - invalid: [] - }); - }, "Use node.range[0] instead of node.start"); - }); - - it("should throw an error if no test scenarios given", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last")); - }, "Test Scenarios for rule foo : Could not find test scenario object"); - }); - - it("should throw an error if no acceptable test scenario object is given", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), []); - }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios"); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), ""); - }, "Test Scenarios for rule foo : Could not find test scenario object"); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), 2); - }, "Test Scenarios for rule foo : Could not find test scenario object"); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {}); - }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios"); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), { - valid: [] - }); - }, "Test Scenarios for rule foo is invalid:\nCould not find any invalid test scenarios"); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), { - invalid: [] - }); - }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios"); - }); - - // Nominal message/messageId use cases - it("should assert match if message provided in both test and result.", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, { - valid: [], - invalid: [{ code: "foo", errors: [{ message: "something" }] }] - }); - }, /Avoid using variables named/u); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, { - valid: [], - invalid: [{ code: "foo", errors: [{ message: "Avoid using variables named 'foo'." }] }] - }); - }); - - it("should assert match between messageId if provided in both test and result.", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "unused" }] }] - }); - }, "messageId 'avoidFoo' does not match expected messageId 'unused'."); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }] - }); - }); - it("should assert match between resulting message output if messageId and data provided in both test and result", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo", data: { name: "notFoo" } }] }] - }); - }, "Hydrated message \"Avoid using variables named 'notFoo'.\" does not match \"Avoid using variables named 'foo'.\""); - }); - - // messageId/message misconfiguration cases - it("should throw if user tests for both message and messageId", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ message: "something", messageId: "avoidFoo" }] }] - }); - }, "Error should not specify both 'message' and a 'messageId'."); - }); - it("should throw if user tests for messageId but the rule doesn't use the messageId meta syntax.", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }] - }); - }, "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'"); - }); - it("should throw if user tests for messageId not listed in the rule's meta syntax.", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "useFoo" }] }] - }); - }, /Invalid messageId 'useFoo'/u); - }); - it("should throw if data provided without messageId.", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ data: "something" }] }] - }); - }, "Error must specify 'messageId' if 'data' is used."); - }); - - // fixable rules with or without `meta` property - it("should not throw an error if a rule that has `meta.fixable` produces fixes", () => { - const replaceProgramWith5Rule = { - meta: { - fixable: "code" - }, - create(context) { - return { - Program(node) { - context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") }); - } - }; - } - }; - - ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, { - valid: [], - invalid: [ - { code: "var foo = bar;", output: "5", errors: 1 } - ] - }); - }); - it("should throw an error if a new-format rule that doesn't have `meta` produces fixes", () => { - const replaceProgramWith5Rule = { - create(context) { - return { - Program(node) { - context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") }); - } - }; - } - }; - - assert.throws(() => { - ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, { - valid: [], - invalid: [ - { code: "var foo = bar;", output: "5", errors: 1 } - ] - }); - }, /Fixable rules must set the `meta\.fixable` property/u); - }); - it("should throw an error if a legacy-format rule produces fixes", () => { - - /** - * Legacy-format rule (a function instead of an object with `create` method). - * @param {RuleContext} context The ESLint rule context object. - * @returns {Object} Listeners. - */ - function replaceProgramWith5Rule(context) { - return { - Program(node) { - context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") }); - } - }; - } - - assert.throws(() => { - ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, { - valid: [], - invalid: [ - { code: "var foo = bar;", output: "5", errors: 1 } - ] - }); - }, /Fixable rules must set the `meta\.fixable` property/u); - }); - - describe("suggestions", () => { - it("should pass with valid suggestions (tested using desc)", () => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [ - "var boo;" - ], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var bar;" - }] - }] - }] - }); - }); - - it("should pass with suggestions on multiple lines", () => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [], - invalid: [ - { - code: "function foo() {\n var foo = 1;\n}", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "function bar() {\n var foo = 1;\n}" - }] - }, { - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "function foo() {\n var bar = 1;\n}" - }] - }] - } - ] - }); - }); - - it("should pass with valid suggestions (tested using messageIds)", () => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }, { - messageId: "renameFoo", - output: "var baz;" - }] - }] - }] - }); - }); - - it("should pass with valid suggestions (one tested using messageIds, the other using desc)", () => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }, { - desc: "Rename identifier 'foo' to 'baz'", - output: "var baz;" - }] - }] - }] - }); - }); - - it("should pass with valid suggestions (tested using both desc and messageIds for the same suggestion)", () => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - messageId: "renameFoo", - output: "var bar;" - }, { - desc: "Rename identifier 'foo' to 'baz'", - messageId: "renameFoo", - output: "var baz;" - }] - }] - }] - }); - }); - - it("should pass with valid suggestions (tested using only desc on a rule that utilizes meta.messages)", () => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var bar;" - }, { - desc: "Rename identifier 'foo' to 'baz'", - output: "var baz;" - }] - }] - }] - }); - }); - - it("should pass with valid suggestions (tested using messageIds and data)", () => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - data: { newName: "bar" }, - output: "var bar;" - }, { - messageId: "renameFoo", - data: { newName: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }); - - - it("should pass when tested using empty suggestion test objects if the array length is correct", () => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{}, {}] - }] - }] - }); - }); - - it("should support explicitly expecting no suggestions", () => { - [void 0, null, false, []].forEach(suggestions => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [], - invalid: [{ - code: "eval('var foo');", - errors: [{ - suggestions - }] - }] - }); - }); - }); - - it("should fail when expecting no suggestions and there are suggestions", () => { - [void 0, null, false, []].forEach(suggestions => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions - }] - }] - }); - }, "Error should have no suggestions on error with message: \"Avoid using identifiers named 'foo'.\""); - }); - }); - - it("should fail when testing for suggestions that don't exist", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "this-does-not-exist" - }] - }] - }] - }); - }, "Error should have an array of suggestions. Instead received \"undefined\" on error with message: \"Bad var.\""); - }); - - it("should fail when there are a different number of suggestions", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var bar;" - }, { - desc: "Rename identifier 'foo' to 'baz'", - output: "var baz;" - }] - }] - }] - }); - }, "Error should have 2 suggestions. Instead found 1 suggestions"); - }); - - it("should throw if the suggestion description doesn't match", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "not right", - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 0 : desc should be \"not right\" but got \"Rename identifier 'foo' to 'bar'\" instead."); - }); - - it("should throw if the suggestion description doesn't match (although messageIds match)", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - messageId: "renameFoo", - output: "var bar;" - }, { - desc: "Rename id 'foo' to 'baz'", - messageId: "renameFoo", - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 1 : desc should be \"Rename id 'foo' to 'baz'\" but got \"Rename identifier 'foo' to 'baz'\" instead."); - }); - - it("should throw if the suggestion messageId doesn't match", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "unused", - output: "var bar;" - }, { - messageId: "renameFoo", - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 0 : messageId should be 'unused' but got 'renameFoo' instead."); - }); - - it("should throw if the suggestion messageId doesn't match (although descriptions match)", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - messageId: "renameFoo", - output: "var bar;" - }, { - desc: "Rename identifier 'foo' to 'baz'", - messageId: "avoidFoo", - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 1 : messageId should be 'avoidFoo' but got 'renameFoo' instead."); - }); - - it("should throw if test specifies messageId for a rule that doesn't have meta.messages", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }] - }] - }] - }); - }, "Error Suggestion at index 0 : Test can not use 'messageId' if rule under test doesn't define 'meta.messages'."); - }); - - it("should throw if test specifies messageId that doesn't exist in the rule's meta.messages", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }, { - messageId: "removeFoo", - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 1 : Test has invalid messageId 'removeFoo', the rule under test allows only one of ['avoidFoo', 'unused', 'renameFoo']."); - }); - - it("should throw if hydrated desc doesn't match (wrong data value)", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - data: { newName: "car" }, - output: "var bar;" - }, { - messageId: "renameFoo", - data: { newName: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 0 : Hydrated test desc \"Rename identifier 'foo' to 'car'\" does not match received desc \"Rename identifier 'foo' to 'bar'\"."); - }); - - it("should throw if hydrated desc doesn't match (wrong data key)", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - data: { newName: "bar" }, - output: "var bar;" - }, { - messageId: "renameFoo", - data: { name: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 1 : Hydrated test desc \"Rename identifier 'foo' to '{{ newName }}'\" does not match received desc \"Rename identifier 'foo' to 'baz'\"."); - }); - - it("should throw if test specifies both desc and data", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - messageId: "renameFoo", - data: { newName: "bar" }, - output: "var bar;" - }, { - messageId: "renameFoo", - data: { newName: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 0 : Test should not specify both 'desc' and 'data'."); - }); - - it("should throw if test uses data but doesn't specify messageId", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - data: { newName: "bar" }, - output: "var bar;" - }, { - data: { newName: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 1 : Test must specify 'messageId' if 'data' is used."); - }); - - it("should throw if the resulting suggestion output doesn't match", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var baz;" - }] - }] - }] - }); - }, "Expected the applied suggestion fix to match the test suggestion output"); - }); - - it("should fail when specified suggestion isn't an object", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [null] - }] - }] - }); - }, "Test suggestion in 'suggestions' array must be an object."); - - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [ - { - messageId: "renameFoo", - output: "var bar;" - }, - "Rename identifier 'foo' to 'baz'" - ] - }] - }] - }); - }, "Test suggestion in 'suggestions' array must be an object."); - }); - - it("should fail when the suggestion is an object with an unknown property name", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [ - "var boo;" - ], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - message: "Rename identifier 'foo' to 'bar'" - }] - }] - }] - }); - }, /Invalid suggestion property name 'message'/u); - }); - - it("should fail when any of the suggestions is an object with an unknown property name", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }, { - messageId: "renameFoo", - outpt: "var baz;" - }] - }] - }] - }); - }, /Invalid suggestion property name 'outpt'/u); - }); - - it("should throw an error if a rule that doesn't have `meta.hasSuggestions` enabled produces suggestions", () => { - assert.throws(() => { - ruleTester.run("suggestions-missing-hasSuggestions-property", require("../../fixtures/testers/rule-tester/suggestions").withoutHasSuggestionsProperty, { - valid: [], - invalid: [ - { code: "var foo = bar;", output: "5", errors: 1 } - ] - }); - }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`."); - }); - }); - - describe("deprecations", () => { - let processStub; - - beforeEach(() => { - processStub = sinon.stub(process, "emitWarning"); - }); - - afterEach(() => { - processStub.restore(); - }); - - it("should emit a deprecation warning when CodePath#currentSegments is accessed", () => { - - const useCurrentSegmentsRule = { - create: () => ({ - onCodePathStart(codePath) { - codePath.currentSegments.forEach(() => { }); - } - }) - }; - - ruleTester.run("use-current-segments", useCurrentSegmentsRule, { - valid: ["foo"], - invalid: [] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - "\"use-current-segments\" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples", - "DeprecationWarning" - ] - ); - - }); - - }); - - /** - * Asserts that a particular value will be emitted from an EventEmitter. - * @param {EventEmitter} emitter The emitter that should emit a value - * @param {string} emitType The type of emission to listen for - * @param {any} expectedValue The value that should be emitted - * @returns {Promise} A Promise that fulfills if the value is emitted, and rejects if something else is emitted. - * The Promise will be indefinitely pending if no value is emitted. - */ - function assertEmitted(emitter, emitType, expectedValue) { - return new Promise((resolve, reject) => { - emitter.once(emitType, emittedValue => { - if (emittedValue === expectedValue) { - resolve(); - } else { - reject(new Error(`Expected ${expectedValue} to be emitted but ${emittedValue} was emitted instead.`)); - } - }); - }); - } - - describe("naming test cases", () => { - - it("should use the first argument as the name of the test suite", () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "describe", "this-is-a-rule-name"); - - ruleTester.run("this-is-a-rule-name", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [] - }); - - return assertion; - }); - - it("should use the test code as the name of the tests for valid code (string form)", () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "it", "valid(code);"); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "valid(code);" - ], - invalid: [] - }); - - return assertion; - }); - - it("should use the test code as the name of the tests for valid code (object form)", () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "it", "valid(code);"); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - { - code: "valid(code);" - } - ], - invalid: [] - }); - - return assertion; - }); - - it("should use the test code as the name of the tests for invalid code", () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "it", "var x = invalid(code);"); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - code: "var x = invalid(code);", - output: " x = invalid(code);", - errors: 1 - } - ] - }); - - return assertion; - }); - - // https://github.com/eslint/eslint/issues/8142 - it("should use the empty string as the name of the test if the test case is an empty string", () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "it", ""); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - { - code: "" - } - ], - invalid: [] - }); - - return assertion; - }); - - it('should use the "name" property if set to a non-empty string', () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "it", "my test"); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - name: "my test", - code: "var x = invalid(code);", - output: " x = invalid(code);", - errors: 1 - } - ] - }); - - return assertion; - }); - - it('should use the "name" property if set to a non-empty string for valid cases too', () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "it", "my test"); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - { - name: "my test", - code: "valid(code);" - } - ], - invalid: [] - }); - - return assertion; - }); - - - it('should use the test code as the name if the "name" property is set to an empty string', () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "it", "var x = invalid(code);"); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - name: "", - code: "var x = invalid(code);", - output: " x = invalid(code);", - errors: 1 - } - ] - }); - - return assertion; - }); - - it('should throw if "name" property is not a string', () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ code: "foo", name: 123 }], - invalid: [{ code: "foo" }] - - }); - }, /Optional test case property 'name' must be a string/u); - - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: ["foo"], - invalid: [{ code: "foo", name: 123 }] - }); - }, /Optional test case property 'name' must be a string/u); - }); - - it('should throw if "code" property is not a string', () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ code: 123 }], - invalid: [{ code: "foo" }] - - }); - }, /Test case must specify a string value for 'code'/u); - - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [123], - invalid: [{ code: "foo" }] - - }); - }, /Test case must specify a string value for 'code'/u); - - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: ["foo"], - invalid: [{ code: 123 }] - }); - }, /Test case must specify a string value for 'code'/u); - }); - - it('should throw if "code" property is missing', () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ }], - invalid: [{ code: "foo" }] - - }); - }, /Test case must specify a string value for 'code'/u); - - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: ["foo"], - invalid: [{ }] - }); - }, /Test case must specify a string value for 'code'/u); - }); - }); - - // https://github.com/eslint/eslint/issues/11615 - it("should fail the case if autofix made a syntax error.", () => { - assert.throw(() => { - ruleTester.run( - "foo", - { - meta: { - fixable: "code" - }, - create(context) { - return { - Identifier(node) { - context.report({ - node, - message: "make a syntax error", - fix(fixer) { - return fixer.replaceText(node, "one two"); - } - }); - } - }; - } - }, - { - valid: ["one()"], - invalid: [] - } - ); - }, /A fatal parsing error occurred in autofix.\nError: .+\nAutofix output:\n.+/u); - }); - - describe("sanitize test cases", () => { - let originalRuleTesterIt; - let spyRuleTesterIt; - - before(() => { - originalRuleTesterIt = FlatRuleTester.it; - spyRuleTesterIt = sinon.spy(); - FlatRuleTester.it = spyRuleTesterIt; - }); - after(() => { - FlatRuleTester.it = originalRuleTesterIt; - }); - beforeEach(() => { - spyRuleTesterIt.resetHistory(); - ruleTester = new FlatRuleTester(); - }); - it("should present newline when using back-tick as new line", () => { - const code = ` - var foo = bar;`; - - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - code, - errors: [/^Bad var/u] - } - ] - }); - sinon.assert.calledWith(spyRuleTesterIt, code); - }); - it("should present \\u0000 as a string", () => { - const code = "\u0000"; - - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - code, - errors: [/^Bad var/u] - } - ] - }); - sinon.assert.calledWith(spyRuleTesterIt, "\\u0000"); - }); - it("should present the pipe character correctly", () => { - const code = "var foo = bar || baz;"; - - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - code, - errors: [/^Bad var/u] - } - ] - }); - sinon.assert.calledWith(spyRuleTesterIt, code); - }); - - }); - - describe("SourceCode#getComments()", () => { - const useGetCommentsRule = { - create: context => ({ - Program(node) { - const sourceCode = context.sourceCode; - - sourceCode.getComments(node); - } - }) - }; - - it("should throw if called from a valid test case", () => { - assert.throws(() => { - ruleTester.run("use-get-comments", useGetCommentsRule, { - valid: [""], - invalid: [] - }); - }, /`SourceCode#getComments\(\)` is deprecated/u); - }); - - it("should throw if called from an invalid test case", () => { - assert.throws(() => { - ruleTester.run("use-get-comments", useGetCommentsRule, { - valid: [], - invalid: [{ - code: "", - errors: [{}] - }] - }); - }, /`SourceCode#getComments\(\)` is deprecated/u); - }); - }); - - describe("SourceCode forbidden methods", () => { - - [ - "applyInlineConfig", - "applyLanguageOptions", - "finalize" - ].forEach(methodName => { - - const useForbiddenMethodRule = { - create: context => ({ - Program() { - const sourceCode = context.sourceCode; - - sourceCode[methodName](); - } - }) - }; - - it(`should throw if ${methodName} is called from a valid test case`, () => { - assert.throws(() => { - ruleTester.run("use-forbidden-method", useForbiddenMethodRule, { - valid: [""], - invalid: [] - }); - }, `\`SourceCode#${methodName}()\` cannot be called inside a rule.`); - }); - - it(`should throw if ${methodName} is called from an invalid test case`, () => { - assert.throws(() => { - ruleTester.run("use-forbidden-method", useForbiddenMethodRule, { - valid: [], - invalid: [{ - code: "", - errors: [{}] - }] - }); - }, `\`SourceCode#${methodName}()\` cannot be called inside a rule.`); - }); - - }); - - }); - - describe("Subclassing", () => { - it("should allow subclasses to set the describe/it/itOnly statics and should correctly use those values", () => { - const assertionDescribe = assertEmitted(ruleTesterTestEmitter, "custom describe", "this-is-a-rule-name"); - const assertionIt = assertEmitted(ruleTesterTestEmitter, "custom it", "valid(code);"); - const assertionItOnly = assertEmitted(ruleTesterTestEmitter, "custom itOnly", "validOnly(code);"); - - /** - * Subclass for testing - */ - class RuleTesterSubclass extends FlatRuleTester { } - RuleTesterSubclass.describe = function(text, method) { - ruleTesterTestEmitter.emit("custom describe", text, method); - return method.call(this); - }; - RuleTesterSubclass.it = function(text, method) { - ruleTesterTestEmitter.emit("custom it", text, method); - return method.call(this); - }; - RuleTesterSubclass.itOnly = function(text, method) { - ruleTesterTestEmitter.emit("custom itOnly", text, method); - return method.call(this); - }; - - const ruleTesterSubclass = new RuleTesterSubclass(); - - ruleTesterSubclass.run("this-is-a-rule-name", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "valid(code);", - { - code: "validOnly(code);", - only: true - } - ], - invalid: [] - }); - - return Promise.all([ - assertionDescribe, - assertionIt, - assertionItOnly - ]); - }); - - }); - - describe("Optional Test Suites", () => { - let originalRuleTesterDescribe; - let spyRuleTesterDescribe; - - before(() => { - originalRuleTesterDescribe = FlatRuleTester.describe; - spyRuleTesterDescribe = sinon.spy((title, callback) => callback()); - FlatRuleTester.describe = spyRuleTesterDescribe; - }); - after(() => { - FlatRuleTester.describe = originalRuleTesterDescribe; - }); - beforeEach(() => { - spyRuleTesterDescribe.resetHistory(); - ruleTester = new FlatRuleTester(); - }); - - it("should create a test suite with the rule name even if there are no test cases", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [] - }); - sinon.assert.calledWith(spyRuleTesterDescribe, "no-var"); - }); - - it("should create a valid test suite if there is a valid test case", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: ["value = 0;"], - invalid: [] - }); - sinon.assert.calledWith(spyRuleTesterDescribe, "valid"); - }); - - it("should not create a valid test suite if there are no valid test cases", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - code: "var value = 0;", - errors: [/^Bad var/u], - output: " value = 0;" - } - ] - }); - sinon.assert.neverCalledWith(spyRuleTesterDescribe, "valid"); - }); - - it("should create an invalid test suite if there is an invalid test case", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - code: "var value = 0;", - errors: [/^Bad var/u], - output: " value = 0;" - } - ] - }); - sinon.assert.calledWith(spyRuleTesterDescribe, "invalid"); - }); - - it("should not create an invalid test suite if there are no invalid test cases", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: ["value = 0;"], - invalid: [] - }); - sinon.assert.neverCalledWith(spyRuleTesterDescribe, "invalid"); - }); - }); -}); diff --git a/tests/lib/rule-tester/no-test-runners.js b/tests/lib/rule-tester/no-test-runners.js index 8e5785cef98f..6260ea331439 100644 --- a/tests/lib/rule-tester/no-test-runners.js +++ b/tests/lib/rule-tester/no-test-runners.js @@ -5,7 +5,7 @@ */ "use strict"; -const assert = require("assert"); +const assert = require("node:assert"); const { RuleTester } = require("../../../lib/rule-tester"); const tmpIt = it; const tmpDescribe = describe; @@ -14,19 +14,32 @@ it = null; describe = null; try { - const ruleTester = new RuleTester(); + const ruleTester = new RuleTester(); - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: "invalid output", errors: 1 } - ] - }); - }, new assert.AssertionError({ actual: " foo = bar;", expected: "invalid output", operator: "===" }).message); + assert.throws( + () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["bar = baz;"], + invalid: [ + { + code: "var foo = bar;", + output: "invalid output", + errors: 1, + }, + ], + }, + ); + }, + new assert.AssertionError({ + actual: " foo = bar;", + expected: "invalid output", + operator: "===", + }).message, + ); } finally { - it = tmpIt; - describe = tmpDescribe; + it = tmpIt; + describe = tmpDescribe; } diff --git a/tests/lib/rule-tester/rule-tester.js b/tests/lib/rule-tester/rule-tester.js index a28b345501f2..74137086085a 100644 --- a/tests/lib/rule-tester/rule-tester.js +++ b/tests/lib/rule-tester/rule-tester.js @@ -8,28 +8,49 @@ // Requirements //------------------------------------------------------------------------------ const sinon = require("sinon"), - EventEmitter = require("events"), - { RuleTester } = require("../../../lib/rule-tester"), - assert = require("chai").assert, - nodeAssert = require("assert"), - espree = require("espree"); + EventEmitter = require("node:events"), + { RuleTester } = require("../../../lib/rule-tester"), + assert = require("chai").assert, + nodeAssert = require("node:assert"); + +const jsonPlugin = require("@eslint/json").default; + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- const NODE_ASSERT_STRICT_EQUAL_OPERATOR = (() => { - try { - nodeAssert.strictEqual(1, 2); - } catch (err) { - return err.operator; - } - throw new Error("unexpected successful assertion"); + try { + nodeAssert.strictEqual(1, 2); + } catch (err) { + return err.operator; + } + throw new Error("unexpected successful assertion"); })(); +/** + * A helper function to verify Node.js core error messages. + * @param {string} actual The actual input + * @param {string} expected The expected input + * @returns {Function} Error callback to verify that the message is correct + * for the actual and expected input. + */ +function assertErrorMatches(actual, expected) { + const err = new nodeAssert.AssertionError({ + actual, + expected, + operator: NODE_ASSERT_STRICT_EQUAL_OPERATOR, + }); + + return err.message; +} + /** * Do nothing. * @returns {void} */ function noop() { - - // do nothing. + // do nothing. } //------------------------------------------------------------------------------ @@ -57,3034 +78,5264 @@ const ruleTesterTestEmitter = new EventEmitter(); //------------------------------------------------------------------------------ describe("RuleTester", () => { - - // Stub `describe()` and `it()` while this test suite. - before(() => { - RuleTester.describe = function(text, method) { - ruleTesterTestEmitter.emit("describe", text, method); - return method.call(this); - }; - RuleTester.it = function(text, method) { - ruleTesterTestEmitter.emit("it", text, method); - return method.call(this); - }; - }); - after(() => { - RuleTester.describe = null; - RuleTester.it = null; - }); - - let ruleTester; - - /** - * A helper function to verify Node.js core error messages. - * @param {string} actual The actual input - * @param {string} expected The expected input - * @returns {Function} Error callback to verify that the message is correct - * for the actual and expected input. - */ - function assertErrorMatches(actual, expected) { - const err = new nodeAssert.AssertionError({ - actual, - expected, - operator: NODE_ASSERT_STRICT_EQUAL_OPERATOR - }); - - return err.message; - } - - beforeEach(() => { - RuleTester.resetDefaultConfig(); - ruleTester = new RuleTester(); - }); - - describe("only", () => { - describe("`itOnly` accessor", () => { - describe("when `itOnly` is set", () => { - before(() => { - RuleTester.itOnly = sinon.spy(); - }); - after(() => { - RuleTester.itOnly = void 0; - }); - beforeEach(() => { - RuleTester.itOnly.resetHistory(); - ruleTester = new RuleTester(); - }); - - it("is called by exclusive tests", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - - sinon.assert.calledWith(RuleTester.itOnly, "const notVar = 42;"); - }); - }); - - describe("when `it` is set and has an `only()` method", () => { - before(() => { - RuleTester.it.only = () => {}; - sinon.spy(RuleTester.it, "only"); - }); - after(() => { - RuleTester.it.only = void 0; - }); - beforeEach(() => { - RuleTester.it.only.resetHistory(); - ruleTester = new RuleTester(); - }); - - it("is called by tests with `only` set", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - - sinon.assert.calledWith(RuleTester.it.only, "const notVar = 42;"); - }); - }); - - describe("when global `it` is a function that has an `only()` method", () => { - let originalGlobalItOnly; - - before(() => { - - /* - * We run tests with `--forbid-only`, so we have to override - * `it.only` to prevent the real one from being called. - */ - originalGlobalItOnly = it.only; - it.only = () => {}; - sinon.spy(it, "only"); - }); - after(() => { - it.only = originalGlobalItOnly; - }); - beforeEach(() => { - it.only.resetHistory(); - ruleTester = new RuleTester(); - }); - - it("is called by tests with `only` set", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - - sinon.assert.calledWith(it.only, "const notVar = 42;"); - }); - }); - - describe("when `describe` and `it` are overridden without `itOnly`", () => { - let originalGlobalItOnly; - - before(() => { - - /* - * These tests override `describe` and `it` already, so we - * don't need to override them here. We do, however, need to - * remove `only` from the global `it` to prevent it from - * being used instead. - */ - originalGlobalItOnly = it.only; - it.only = void 0; - }); - after(() => { - it.only = originalGlobalItOnly; - }); - beforeEach(() => { - ruleTester = new RuleTester(); - }); - - it("throws an error recommending overriding `itOnly`", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - }, "Set `RuleTester.itOnly` to use `only` with a custom test framework."); - }); - }); - - describe("when global `it` is a function that does not have an `only()` method", () => { - let originalGlobalIt; - let originalRuleTesterDescribe; - let originalRuleTesterIt; - - before(() => { - originalGlobalIt = global.it; - - // eslint-disable-next-line no-global-assign -- Temporarily override Mocha global - it = () => {}; - - /* - * These tests override `describe` and `it`, so we need to - * un-override them here so they won't interfere. - */ - originalRuleTesterDescribe = RuleTester.describe; - RuleTester.describe = void 0; - originalRuleTesterIt = RuleTester.it; - RuleTester.it = void 0; - }); - after(() => { - - // eslint-disable-next-line no-global-assign -- Restore Mocha global - it = originalGlobalIt; - RuleTester.describe = originalRuleTesterDescribe; - RuleTester.it = originalRuleTesterIt; - }); - beforeEach(() => { - ruleTester = new RuleTester(); - }); - - it("throws an error explaining that the current test framework does not support `only`", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ - code: "const notVar = 42;", - only: true - }], - invalid: [] - }); - }, "The current test framework does not support exclusive tests with `only`."); - }); - }); - }); - - describe("test cases", () => { - const ruleName = "no-var"; - const rule = require("../../fixtures/testers/rule-tester/no-var"); - - let originalRuleTesterIt; - let spyRuleTesterIt; - let originalRuleTesterItOnly; - let spyRuleTesterItOnly; - - before(() => { - originalRuleTesterIt = RuleTester.it; - spyRuleTesterIt = sinon.spy(); - RuleTester.it = spyRuleTesterIt; - originalRuleTesterItOnly = RuleTester.itOnly; - spyRuleTesterItOnly = sinon.spy(); - RuleTester.itOnly = spyRuleTesterItOnly; - }); - after(() => { - RuleTester.it = originalRuleTesterIt; - RuleTester.itOnly = originalRuleTesterItOnly; - }); - beforeEach(() => { - spyRuleTesterIt.resetHistory(); - spyRuleTesterItOnly.resetHistory(); - ruleTester = new RuleTester(); - }); - - it("isn't called for normal tests", () => { - ruleTester.run(ruleName, rule, { - valid: ["const notVar = 42;"], - invalid: [] - }); - sinon.assert.calledWith(spyRuleTesterIt, "const notVar = 42;"); - sinon.assert.notCalled(spyRuleTesterItOnly); - }); - - it("calls it or itOnly for every test case", () => { - - /* - * `RuleTester` doesn't implement test case exclusivity itself. - * Setting `only: true` just causes `RuleTester` to call - * whatever `only()` function is provided by the test framework - * instead of the regular `it()` function. - */ - - ruleTester.run(ruleName, rule, { - valid: [ - "const valid = 42;", - { - code: "const onlyValid = 42;", - only: true - } - ], - invalid: [ - { - code: "var invalid = 42;", - errors: [/^Bad var/u] - }, - { - code: "var onlyInvalid = 42;", - errors: [/^Bad var/u], - only: true - } - ] - }); - - sinon.assert.calledWith(spyRuleTesterIt, "const valid = 42;"); - sinon.assert.calledWith(spyRuleTesterItOnly, "const onlyValid = 42;"); - sinon.assert.calledWith(spyRuleTesterIt, "var invalid = 42;"); - sinon.assert.calledWith(spyRuleTesterItOnly, "var onlyInvalid = 42;"); - }); - }); - - describe("static helper wrapper", () => { - it("adds `only` to string test cases", () => { - const test = RuleTester.only("const valid = 42;"); - - assert.deepStrictEqual(test, { - code: "const valid = 42;", - only: true - }); - }); - - it("adds `only` to object test cases", () => { - const test = RuleTester.only({ code: "const valid = 42;" }); - - assert.deepStrictEqual(test, { - code: "const valid = 42;", - only: true - }); - }); - }); - }); - - it("should not throw an error when everything passes", () => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] } - ] - }); - }); - - it("should throw an error when valid code is invalid", () => { - - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] } - ] - }); - }, /Should have no errors but had 1/u); - }); - - it("should throw an error when valid code is invalid", () => { - - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - { code: "eval(foo)" } - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] } - ] - }); - }, /Should have no errors but had 1/u); - }); - - it("should throw an error if invalid code is valid", () => { - - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "Eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] } - ] - }); - }, /Should have 1 error but had 0/u); - }); - - it("should throw an error when the error message is wrong", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: [{ message: "Bad error message." }] } - ] - }); - }, assertErrorMatches("Bad var.", "Bad error message.")); - }); - - it("should throw an error when the error message regex does not match", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { code: "var foo = bar;", errors: [{ message: /Bad error message/u }] } - ] - }); - }, /Expected 'Bad var.' to match \/Bad error message\//u); - }); - - it("should throw an error when the error is not a supported type", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: [42] } - ] - }); - }, /Error should be a string, object, or RegExp/u); - }); - - it("should throw an error when any of the errors is not a supported type", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar; var baz = quux", errors: [{ type: "VariableDeclaration" }, null] } - ] - }); - }, /Error should be a string, object, or RegExp/u); - }); - - it("should throw an error when the error is a string and it does not match error message", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: ["Bad error message."] } - ] - }); - }, assertErrorMatches("Bad var.", "Bad error message.")); - }); - - it("should throw an error when the error is a string and it does not match error message", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - - valid: [ - ], - invalid: [ - { code: "var foo = bar;", errors: [/Bad error message/u] } - ] - }); - }, /Expected 'Bad var.' to match \/Bad error message\//u); - }); - - it("should not throw an error when the error is a string and it matches error message", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - - // Only the invalid test matters here - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: " foo = bar;", errors: ["Bad var."] } - ] - }); - }); - - it("should not throw an error when the error is a regex and it matches error message", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { code: "var foo = bar;", output: " foo = bar;", errors: [/^Bad var/u] } - ] - }); - }); - - it("should throw an error when the error is an object with an unknown property name", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: [{ Message: "Bad var." }] } - ] - }); - }, /Invalid error property name 'Message'/u); - }); - - it("should throw an error when any of the errors is an object with an unknown property name", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { - code: "var foo = bar; var baz = quux", - errors: [ - { message: "Bad var.", type: "VariableDeclaration" }, - { message: "Bad var.", typo: "VariableDeclaration" } - ] - } - ] - }); - }, /Invalid error property name 'typo'/u); - }); - - it("should not throw an error when the error is a regex in an object and it matches error message", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { code: "var foo = bar;", output: " foo = bar;", errors: [{ message: /^Bad var/u }] } - ] - }); - }); - - it("should throw an error when the expected output doesn't match", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration" }] } - ] - }); - }, /Output is incorrect/u); - }); - - it("should use strict equality to compare output", () => { - const replaceProgramWith5Rule = { - meta: { - fixable: "code" - }, - - create: context => ({ - Program(node) { - context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") }); - } - }) - }; - - // Should not throw. - ruleTester.run("foo", replaceProgramWith5Rule, { - valid: [], - invalid: [ - { code: "var foo = bar;", output: "5", errors: 1 } - ] - }); - - assert.throws(() => { - ruleTester.run("foo", replaceProgramWith5Rule, { - valid: [], - invalid: [ - { code: "var foo = bar;", output: 5, errors: 1 } - ] - }); - }, /Output is incorrect/u); - }); - - it("should throw an error when the expected output doesn't match and errors is just a number", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: "foo = bar", errors: 1 } - ] - }); - }, /Output is incorrect/u); - }); - - it("should not throw an error when the expected output is null and no errors produce output", () => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "eval(x)", errors: 1, output: null }, - { code: "eval(x); eval(y);", errors: 2, output: null } - ] - }); - }); - - it("should throw an error when the expected output is null and problems produce output", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: null, errors: 1 } - ] - }); - }, /Expected no autofixes to be suggested/u); - - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { - code: "var foo = bar; var qux = boop;", - output: null, - errors: 2 - } - ] - }); - }, /Expected no autofixes to be suggested/u); - }); - - it("should throw an error when the expected output is null and only some problems produce output", () => { - assert.throws(() => { - ruleTester.run("fixes-one-problem", require("../../fixtures/testers/rule-tester/fixes-one-problem"), { - valid: [], - invalid: [ - { code: "foo", output: null, errors: 2 } - ] - }); - }, /Expected no autofixes to be suggested/u); - }); - - it("should throw an error when the expected output isn't specified and problems produce output", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", errors: 1 } - ] - }); - }, "The rule fixed the code. Please add 'output' property."); - }); - - it("should throw an error if invalid code specifies wrong type", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression2" }] } - ] - }); - }, /Error type should be CallExpression2, found CallExpression/u); - }); - - it("should throw an error if invalid code specifies wrong line", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression", line: 5 }] } - ] - }); - }, /Error line should be 5/u); - }); - - it("should not skip line assertion if line is a falsy value", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "\neval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression", line: 0 }] } - ] - }); - }, /Error line should be 0/u); - }); - - it("should throw an error if invalid code specifies wrong column", () => { - const wrongColumn = 10, - expectedErrorMessage = "Error column should be 1"; - - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: ["Eval(foo)"], - invalid: [{ - code: "eval(foo)", - errors: [{ - message: "eval sucks.", - column: wrongColumn - }] - }] - }); - }, expectedErrorMessage); - }); - - it("should throw error for empty error array", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [] - }] - }); - }, /Invalid cases must have at least one error/u); - }); - - it("should throw error for errors : 0", () => { - assert.throws(() => { - ruleTester.run( - "suggestions-messageIds", - require("../../fixtures/testers/rule-tester/suggestions") - .withMessageIds, - { - valid: [], - invalid: [ - { - code: "var foo;", - errors: 0 - } - ] - } - ); - }, /Invalid cases must have 'error' value greater than 0/u); - }); - - it("should not skip column assertion if column is a falsy value", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: ["Eval(foo)"], - invalid: [{ - code: "var foo; eval(foo)", - errors: [{ message: "eval sucks.", column: 0 }] - }] - }); - }, /Error column should be 0/u); - }); - - it("should throw an error if invalid code specifies wrong endLine", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration", endLine: 10 }] } - ] - }); - }, "Error endLine should be 10"); - }); - - it("should throw an error if invalid code specifies wrong endColumn", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "bar = baz;" - ], - invalid: [ - { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration", endColumn: 10 }] } - ] - }); - }, "Error endColumn should be 10"); - }); - - it("should throw an error if invalid code has the wrong number of errors", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { - code: "eval(foo)", - errors: [ - { message: "eval sucks.", type: "CallExpression" }, - { message: "eval sucks.", type: "CallExpression" } - ] - } - ] - }); - }, /Should have 2 errors but had 1/u); - }); - - it("should throw an error if invalid code does not have errors", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)" } - ] - }); - }, /Did not specify errors for an invalid test of no-eval/u); - }); - - it("should throw an error if invalid code has the wrong explicit number of errors", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "Eval(foo)" - ], - invalid: [ - { code: "eval(foo)", errors: 2 } - ] - }); - }, /Should have 2 errors but had 1/u); - }); - - it("should throw an error if there's a parsing error in a valid test", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "1eval('foo')" - ], - invalid: [ - { code: "eval('foo')", errors: [{}] } - ] - }); - }, /fatal parsing error/iu); - }); - - it("should throw an error if there's a parsing error in an invalid test", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "noeval('foo')" - ], - invalid: [ - { code: "1eval('foo')", errors: [{}] } - ] - }); - }, /fatal parsing error/iu); - }); - - it("should throw an error if there's a parsing error in an invalid test and errors is just a number", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - "noeval('foo')" - ], - invalid: [ - { code: "1eval('foo')", errors: 1 } - ] - }); - }, /fatal parsing error/iu); - }); - - // https://github.com/eslint/eslint/issues/4779 - it("should throw an error if there's a parsing error and output doesn't match", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [], - invalid: [ - { code: "eval(`foo`)", output: "eval(`foo`);", errors: [{}] } - ] - }); - }, /fatal parsing error/iu); - }); - - it("should not throw an error if invalid code has at least an expected empty error object", () => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: ["Eval(foo)"], - invalid: [{ - code: "eval(foo)", - errors: [{}] - }] - }); - }); - - it("should pass-through the globals config of valid tests to the to rule", () => { - ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), { - valid: [ - "var test = 'foo'", - { - code: "var test2 = 'bar'", - globals: { test: true } - } - ], - invalid: [{ code: "bar", errors: 1 }] - }); - }); - - it("should pass-through the globals config of invalid tests to the to rule", () => { - ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), { - valid: ["var test = 'foo'"], - invalid: [ - { - code: "var test = 'foo'; var foo = 'bar'", - errors: 1 - }, - { - code: "var test = 'foo'", - globals: { foo: true }, - errors: [{ message: "Global variable foo should not be used." }] - } - ] - }); - }); - - it("should pass-through the settings config to rules", () => { - ruleTester.run("no-test-settings", require("../../fixtures/testers/rule-tester/no-test-settings"), { - valid: [ - { - code: "var test = 'bar'", settings: { test: 1 } - } - ], - invalid: [ - { - code: "var test = 'bar'", settings: { "no-test": 22 }, errors: 1 - } - ] - }); - }); - - it("should pass-through the filename to the rule", () => { - (function() { - ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), { - valid: [ - { - code: "var foo = 'bar'", - filename: "somefile.js" - } - ], - invalid: [ - { - code: "var foo = 'bar'", - errors: [ - { message: "Filename test was not defined." } - ] - } - ] - }); - }()); - }); - - it("should pass-through the options to the rule", () => { - ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), { - valid: [ - { - code: "var foo = 'bar'", - options: [false] - } - ], - invalid: [ - { - code: "var foo = 'bar'", - options: [true], - errors: [{ message: "Invalid args" }] - } - ] - }); - }); - - it("should throw an error if the options are an object", () => { - assert.throws(() => { - ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), { - valid: [ - { - code: "foo", - options: { ok: true } - } - ], - invalid: [] - }); - }, /options must be an array/u); - }); - - it("should throw an error if the options are a number", () => { - assert.throws(() => { - ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), { - valid: [ - { - code: "foo", - options: 0 - } - ], - invalid: [] - }); - }, /options must be an array/u); - }); - - it("should pass-through the parser to the rule", () => { - const spy = sinon.spy(ruleTester.linter, "verify"); - - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - { - code: "Eval(foo)" - } - ], - invalid: [ - { - code: "eval(foo)", - parser: require.resolve("esprima"), - errors: [{ line: 1 }] - } - ] - }); - assert.strictEqual(spy.args[1][1].parser, require.resolve("esprima")); - }); - - it("should pass normalized ecmaVersion to the rule", () => { - const reportEcmaVersionRule = { - meta: { - messages: { - ecmaVersionMessage: "context.parserOptions.ecmaVersion is {{type}} {{ecmaVersion}}." - } - }, - create: context => ({ - Program(node) { - const { ecmaVersion } = context.parserOptions; - - context.report({ - node, - messageId: "ecmaVersionMessage", - data: { type: typeof ecmaVersion, ecmaVersion } - }); - } - }) - }; - - const notEspree = require.resolve("../../fixtures/parsers/empty-program-parser"); - - ruleTester.run("report-ecma-version", reportEcmaVersionRule, { - valid: [], - invalid: [ - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }] - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], - parserOptions: {} - }, - { - code: "
", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], - parserOptions: { ecmaFeatures: { jsx: true } } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], - parser: require.resolve("espree") - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], - parserOptions: { ecmaVersion: 6 } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], - parserOptions: { ecmaVersion: 2015 } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], - env: { browser: true } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], - env: { es6: false } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], - env: { es6: true } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "8" } }], - env: { es6: false, es2017: true } - }, - { - code: "let x", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], - env: { es6: "truthy" } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "8" } }], - env: { es2017: true } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "11" } }], - env: { es2020: true } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "12" } }], - env: { es2021: true } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], - parserOptions: { ecmaVersion: "latest" } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], - parser: require.resolve("espree"), - parserOptions: { ecmaVersion: "latest" } - }, - { - code: "
", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], - parserOptions: { ecmaVersion: "latest", ecmaFeatures: { jsx: true } } - }, - { - code: "import 'foo'", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], - parserOptions: { ecmaVersion: "latest", sourceType: "module" } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], - parserOptions: { ecmaVersion: "latest" }, - env: { es6: true } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: String(espree.latestEcmaVersion) } }], - parserOptions: { ecmaVersion: "latest" }, - env: { es2020: true } - }, - - // Non-Espree parsers normalize ecmaVersion if it's not "latest" - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], - parser: notEspree - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }], - parser: notEspree, - parserOptions: {} - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "5" } }], - parser: notEspree, - parserOptions: { ecmaVersion: 5 } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }], - parser: notEspree, - parserOptions: { ecmaVersion: 6 } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: 6 } }], - parser: notEspree, - parserOptions: { ecmaVersion: 2015 } - }, - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "string", ecmaVersion: "latest" } }], - parser: notEspree, - parserOptions: { ecmaVersion: "latest" } - } - ] - }); - - [{ parserOptions: { ecmaVersion: 6 } }, { env: { es6: true } }].forEach(options => { - new RuleTester(options).run("report-ecma-version", reportEcmaVersionRule, { - valid: [], - invalid: [ - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }] - }, - { - code: "", - parserOptions: {}, - errors: [{ messageId: "ecmaVersionMessage", data: { type: "number", ecmaVersion: "6" } }] - } - ] - }); - }); - - new RuleTester({ parser: notEspree }).run("report-ecma-version", reportEcmaVersionRule, { - valid: [], - invalid: [ - { - code: "", - errors: [{ messageId: "ecmaVersionMessage", data: { type: "undefined", ecmaVersion: "undefined" } }] - }, - { - code: "", - parserOptions: { ecmaVersion: "latest" }, - errors: [{ messageId: "ecmaVersionMessage", data: { type: "string", ecmaVersion: "latest" } }] - } - ] - }); - }); - - it("should prevent invalid options schemas", () => { - assert.throws(() => { - ruleTester.run("no-invalid-schema", require("../../fixtures/testers/rule-tester/no-invalid-schema"), { - valid: [ - "var answer = 6 * 7;", - { code: "var answer = 6 * 7;", options: [] } - ], - invalid: [ - { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected nothing." }] } - ] - }); - }, "Schema for rule no-invalid-schema is invalid:,\titems: should be object\n\titems[0].enum: should NOT have fewer than 1 items\n\titems: should match some schema in anyOf"); - - }); - - it("should prevent schema violations in options", () => { - assert.throws(() => { - ruleTester.run("no-schema-violation", require("../../fixtures/testers/rule-tester/no-schema-violation"), { - valid: [ - "var answer = 6 * 7;", - { code: "var answer = 6 * 7;", options: ["foo"] } - ], - invalid: [ - { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected foo." }] } - ] - }); - }, /Value "bar" should be equal to one of the allowed values./u); - - }); - - it("should disallow invalid defaults in rules", () => { - const ruleWithInvalidDefaults = { - meta: { - schema: [ - { - oneOf: [ - { enum: ["foo"] }, - { - type: "object", - properties: { - foo: { - enum: ["foo", "bar"], - default: "foo" - } - }, - additionalProperties: false - } - ] - } - ] - }, - create: () => ({}) - }; - - assert.throws(() => { - ruleTester.run("invalid-defaults", ruleWithInvalidDefaults, { - valid: [ - { - code: "foo", - options: [{}] - } - ], - invalid: [] - }); - }, /Schema for rule invalid-defaults is invalid: default is ignored for: data1\.foo/u); - }); - - it("throw an error when an unknown config option is included", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - { code: "Eval(foo)", foo: "bar" } - ], - invalid: [] - }); - }, /ESLint configuration in rule-tester is invalid./u); - }); - - it("throw an error when an invalid config value is included", () => { - assert.throws(() => { - ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [ - { code: "Eval(foo)", env: ["es6"] } - ], - invalid: [] - }); - }, /Property "env" is the wrong type./u); - }); - - it("should pass-through the tester config to the rule", () => { - ruleTester = new RuleTester({ - globals: { test: true } - }); - - ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), { - valid: [ - "var test = 'foo'", - "var test2 = test" - ], - invalid: [{ code: "bar", errors: 1, globals: { foo: true } }] - }); - }); - - it("should correctly set the globals configuration", () => { - const config = { globals: { test: true } }; - - RuleTester.setDefaultConfig(config); - assert( - RuleTester.getDefaultConfig().globals.test, - "The default config object is incorrect" - ); - }); - - it("should correctly reset the global configuration", () => { - const config = { globals: { test: true } }; - - RuleTester.setDefaultConfig(config); - RuleTester.resetDefaultConfig(); - assert.deepStrictEqual( - RuleTester.getDefaultConfig(), - { rules: {} }, - "The default configuration has not reset correctly" - ); - }); - - it("should enforce the global configuration to be an object", () => { - - /** - * Set the default config for the rules tester - * @param {Object} config configuration object - * @returns {Function} Function to be executed - * @private - */ - function setConfig(config) { - return function() { - RuleTester.setDefaultConfig(config); - }; - } - const errorMessage = "RuleTester.setDefaultConfig: config must be an object"; - - assert.throw(setConfig(), errorMessage); - assert.throw(setConfig(1), errorMessage); - assert.throw(setConfig(3.14), errorMessage); - assert.throw(setConfig("foo"), errorMessage); - assert.throw(setConfig(null), errorMessage); - assert.throw(setConfig(true), errorMessage); - }); - - it("should pass-through the globals config to the tester then to the to rule", () => { - const config = { globals: { test: true } }; - - RuleTester.setDefaultConfig(config); - ruleTester = new RuleTester(); - - ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), { - valid: [ - "var test = 'foo'", - "var test2 = test" - ], - invalid: [{ code: "bar", errors: 1, globals: { foo: true } }] - }); - }); - - it("should throw an error if AST was modified", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast"), { - valid: [ - "var foo = 0;" - ], - invalid: [] - }); - }, "Rule should not modify AST."); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast"), { - valid: [], - invalid: [ - { code: "var bar = 0;", errors: ["error"] } - ] - }); - }, "Rule should not modify AST."); - }); - - it("should throw an error if AST was modified (at Program)", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-first"), { - valid: [ - "var foo = 0;" - ], - invalid: [] - }); - }, "Rule should not modify AST."); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-first"), { - valid: [], - invalid: [ - { code: "var bar = 0;", errors: ["error"] } - ] - }); - }, "Rule should not modify AST."); - }); - - it("should throw an error if AST was modified (at Program:exit)", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), { - valid: [ - "var foo = 0;" - ], - invalid: [] - }); - }, "Rule should not modify AST."); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), { - valid: [], - invalid: [ - { code: "var bar = 0;", errors: ["error"] } - ] - }); - }, "Rule should not modify AST."); - }); - - it("should throw an error if rule uses start and end properties on nodes, tokens or comments", () => { - const usesStartEndRule = { - create(context) { - - const sourceCode = context.sourceCode; - - return { - CallExpression(node) { - noop(node.arguments[1].start); - }, - "BinaryExpression[operator='+']"(node) { - noop(node.end); - }, - "UnaryExpression[operator='-']"(node) { - noop(sourceCode.getFirstToken(node).start); - }, - ConditionalExpression(node) { - noop(sourceCode.getFirstToken(node).end); - }, - BlockStatement(node) { - noop(sourceCode.getCommentsInside(node)[0].start); - }, - ObjectExpression(node) { - noop(sourceCode.getCommentsInside(node)[0].end); - }, - Decorator(node) { - noop(node.start); - } - }; - } - }; - - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: ["foo(a, b)"], - invalid: [] - }); - }, "Use node.range[0] instead of node.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var a = b * (c + d) / e;", errors: 1 }] - }); - }, "Use node.range[1] instead of node.end"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var a = -b * c;", errors: 1 }] - }); - }, "Use token.range[0] instead of token.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: ["var a = b ? c : d;"], - invalid: [] - }); - }, "Use token.range[1] instead of token.end"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: ["function f() { /* comment */ }"], - invalid: [] - }); - }, "Use token.range[0] instead of token.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var x = //\n {\n //comment\n //\n}", errors: 1 }] - }); - }, "Use token.range[1] instead of token.end"); - - const enhancedParserPath = require.resolve("../../fixtures/parsers/enhanced-parser"); - - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [{ code: "foo(a, b)", parser: enhancedParserPath }], - invalid: [] - }); - }, "Use node.range[0] instead of node.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var a = b * (c + d) / e;", parser: enhancedParserPath, errors: 1 }] - }); - }, "Use node.range[1] instead of node.end"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var a = -b * c;", parser: enhancedParserPath, errors: 1 }] - }); - }, "Use token.range[0] instead of token.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [{ code: "var a = b ? c : d;", parser: enhancedParserPath }], - invalid: [] - }); - }, "Use token.range[1] instead of token.end"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [{ code: "function f() { /* comment */ }", parser: enhancedParserPath }], - invalid: [] - }); - }, "Use token.range[0] instead of token.start"); - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [], - invalid: [{ code: "var x = //\n {\n //comment\n //\n}", parser: enhancedParserPath, errors: 1 }] - }); - }, "Use token.range[1] instead of token.end"); - - assert.throws(() => { - ruleTester.run("uses-start-end", usesStartEndRule, { - valid: [{ code: "@foo class A {}", parser: require.resolve("../../fixtures/parsers/enhanced-parser2") }], - invalid: [] - }); - }, "Use node.range[0] instead of node.start"); - }); - - it("should throw an error if no test scenarios given", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last")); - }, "Test Scenarios for rule foo : Could not find test scenario object"); - }); - - it("should throw an error if no acceptable test scenario object is given", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), []); - }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios"); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), ""); - }, "Test Scenarios for rule foo : Could not find test scenario object"); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), 2); - }, "Test Scenarios for rule foo : Could not find test scenario object"); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {}); - }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios"); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), { - valid: [] - }); - }, "Test Scenarios for rule foo is invalid:\nCould not find any invalid test scenarios"); - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), { - invalid: [] - }); - }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios"); - }); - - // Nominal message/messageId use cases - it("should assert match if message provided in both test and result.", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, { - valid: [], - invalid: [{ code: "foo", errors: [{ message: "something" }] }] - }); - }, /Avoid using variables named/u); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, { - valid: [], - invalid: [{ code: "foo", errors: [{ message: "Avoid using variables named 'foo'." }] }] - }); - }); - - it("should assert match between messageId if provided in both test and result.", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "unused" }] }] - }); - }, "messageId 'avoidFoo' does not match expected messageId 'unused'."); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }] - }); - }); - it("should assert match between resulting message output if messageId and data provided in both test and result", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo", data: { name: "notFoo" } }] }] - }); - }, "Hydrated message \"Avoid using variables named 'notFoo'.\" does not match \"Avoid using variables named 'foo'.\""); - }); - - // messageId/message misconfiguration cases - it("should throw if user tests for both message and messageId", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ message: "something", messageId: "avoidFoo" }] }] - }); - }, "Error should not specify both 'message' and a 'messageId'."); - }); - it("should throw if user tests for messageId but the rule doesn't use the messageId meta syntax.", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }] - }); - }, "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'"); - }); - it("should throw if user tests for messageId not listed in the rule's meta syntax.", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ messageId: "useFoo" }] }] - }); - }, /Invalid messageId 'useFoo'/u); - }); - it("should throw if data provided without messageId.", () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, { - valid: [], - invalid: [{ code: "foo", errors: [{ data: "something" }] }] - }); - }, "Error must specify 'messageId' if 'data' is used."); - }); - - describe("suggestions", () => { - it("should pass with valid suggestions (tested using desc)", () => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [ - "var boo;" - ], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var bar;" - }] - }] - }] - }); - }); - - it("should pass with suggestions on multiple lines", () => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [], - invalid: [ - { - code: "function foo() {\n var foo = 1;\n}", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "function bar() {\n var foo = 1;\n}" - }] - }, { - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "function foo() {\n var bar = 1;\n}" - }] - }] - } - ] - }); - }); - - it("should pass with valid suggestions (tested using messageIds)", () => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }, { - messageId: "renameFoo", - output: "var baz;" - }] - }] - }] - }); - }); - - it("should pass with valid suggestions (one tested using messageIds, the other using desc)", () => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }, { - desc: "Rename identifier 'foo' to 'baz'", - output: "var baz;" - }] - }] - }] - }); - }); - - it("should pass with valid suggestions (tested using both desc and messageIds for the same suggestion)", () => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - messageId: "renameFoo", - output: "var bar;" - }, { - desc: "Rename identifier 'foo' to 'baz'", - messageId: "renameFoo", - output: "var baz;" - }] - }] - }] - }); - }); - - it("should pass with valid suggestions (tested using only desc on a rule that utilizes meta.messages)", () => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var bar;" - }, { - desc: "Rename identifier 'foo' to 'baz'", - output: "var baz;" - }] - }] - }] - }); - }); - - it("should pass with valid suggestions (tested using messageIds and data)", () => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - data: { newName: "bar" }, - output: "var bar;" - }, { - messageId: "renameFoo", - data: { newName: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }); - - - it("should pass when tested using empty suggestion test objects if the array length is correct", () => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{}, {}] - }] - }] - }); - }); - - it("should support explicitly expecting no suggestions", () => { - [void 0, null, false, []].forEach(suggestions => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/no-eval"), { - valid: [], - invalid: [{ - code: "eval('var foo');", - errors: [{ - suggestions - }] - }] - }); - }); - }); - - it("should fail when expecting no suggestions and there are suggestions", () => { - [void 0, null, false, []].forEach(suggestions => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions - }] - }] - }); - }, "Error should have no suggestions on error with message: \"Avoid using identifiers named 'foo'.\""); - }); - }); - - it("should fail when testing for suggestions that don't exist", () => { - assert.throws(() => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "this-does-not-exist" - }] - }] - }] - }); - }, "Error should have an array of suggestions. Instead received \"undefined\" on error with message: \"Bad var.\""); - }); - - it("should fail when there are a different number of suggestions", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var bar;" - }, { - desc: "Rename identifier 'foo' to 'baz'", - output: "var baz;" - }] - }] - }] - }); - }, "Error should have 2 suggestions. Instead found 1 suggestions"); - }); - - it("should throw if the suggestion description doesn't match", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "not right", - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 0 : desc should be \"not right\" but got \"Rename identifier 'foo' to 'bar'\" instead."); - }); - - it("should throw if the suggestion description doesn't match (although messageIds match)", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - messageId: "renameFoo", - output: "var bar;" - }, { - desc: "Rename id 'foo' to 'baz'", - messageId: "renameFoo", - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 1 : desc should be \"Rename id 'foo' to 'baz'\" but got \"Rename identifier 'foo' to 'baz'\" instead."); - }); - - it("should throw if the suggestion messageId doesn't match", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "unused", - output: "var bar;" - }, { - messageId: "renameFoo", - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 0 : messageId should be 'unused' but got 'renameFoo' instead."); - }); - - it("should throw if the suggestion messageId doesn't match (although descriptions match)", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - messageId: "renameFoo", - output: "var bar;" - }, { - desc: "Rename identifier 'foo' to 'baz'", - messageId: "avoidFoo", - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 1 : messageId should be 'avoidFoo' but got 'renameFoo' instead."); - }); - - it("should throw if test specifies messageId for a rule that doesn't have meta.messages", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }] - }] - }] - }); - }, "Error Suggestion at index 0 : Test can not use 'messageId' if rule under test doesn't define 'meta.messages'."); - }); - - it("should throw if test specifies messageId that doesn't exist in the rule's meta.messages", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }, { - messageId: "removeFoo", - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 1 : Test has invalid messageId 'removeFoo', the rule under test allows only one of ['avoidFoo', 'unused', 'renameFoo']."); - }); - - it("should throw if hydrated desc doesn't match (wrong data value)", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - data: { newName: "car" }, - output: "var bar;" - }, { - messageId: "renameFoo", - data: { newName: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 0 : Hydrated test desc \"Rename identifier 'foo' to 'car'\" does not match received desc \"Rename identifier 'foo' to 'bar'\"."); - }); - - it("should throw if hydrated desc doesn't match (wrong data key)", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - data: { newName: "bar" }, - output: "var bar;" - }, { - messageId: "renameFoo", - data: { name: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 1 : Hydrated test desc \"Rename identifier 'foo' to '{{ newName }}'\" does not match received desc \"Rename identifier 'foo' to 'baz'\"."); - }); - - it("should throw if test specifies both desc and data", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - messageId: "renameFoo", - data: { newName: "bar" }, - output: "var bar;" - }, { - messageId: "renameFoo", - data: { newName: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 0 : Test should not specify both 'desc' and 'data'."); - }); - - it("should throw if test uses data but doesn't specify messageId", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - data: { newName: "bar" }, - output: "var bar;" - }, { - data: { newName: "baz" }, - output: "var baz;" - }] - }] - }] - }); - }, "Error Suggestion at index 1 : Test must specify 'messageId' if 'data' is used."); - }); - - it("should throw if the resulting suggestion output doesn't match", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - desc: "Rename identifier 'foo' to 'bar'", - output: "var baz;" - }] - }] - }] - }); - }, "Expected the applied suggestion fix to match the test suggestion output"); - }); - - it("should fail when specified suggestion isn't an object", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [null] - }] - }] - }); - }, "Test suggestion in 'suggestions' array must be an object."); - - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [ - { - messageId: "renameFoo", - output: "var bar;" - }, - "Rename identifier 'foo' to 'baz'" - ] - }] - }] - }); - }, "Test suggestion in 'suggestions' array must be an object."); - }); - - it("should fail when the suggestion is an object with an unknown property name", () => { - assert.throws(() => { - ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, { - valid: [ - "var boo;" - ], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - message: "Rename identifier 'foo' to 'bar'" - }] - }] - }] - }); - }, /Invalid suggestion property name 'message'/u); - }); - - it("should fail when any of the suggestions is an object with an unknown property name", () => { - assert.throws(() => { - ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, { - valid: [], - invalid: [{ - code: "var foo;", - errors: [{ - suggestions: [{ - messageId: "renameFoo", - output: "var bar;" - }, { - messageId: "renameFoo", - outpt: "var baz;" - }] - }] - }] - }); - }, /Invalid suggestion property name 'outpt'/u); - }); - - it("should throw an error if a rule that doesn't have `meta.hasSuggestions` enabled produces suggestions", () => { - assert.throws(() => { - ruleTester.run("suggestions-missing-hasSuggestions-property", require("../../fixtures/testers/rule-tester/suggestions").withoutHasSuggestionsProperty, { - valid: [], - invalid: [ - { code: "var foo = bar;", output: "5", errors: 1 } - ] - }); - }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`."); - }); - }); - - describe("deprecations", () => { - let processStub; - const ruleWithNoSchema = { - meta: { - type: "suggestion" - }, - create(context) { - return { - Program(node) { - context.report({ node, message: "bad" }); - } - }; - } - }; - const ruleWithNoMeta = { - create(context) { - return { - Program(node) { - context.report({ node, message: "bad" }); - } - }; - } - }; - - beforeEach(() => { - processStub = sinon.stub(process, "emitWarning"); - }); - - afterEach(() => { - processStub.restore(); - }); - - it("should log a deprecation warning when using the legacy function-style API for rule", () => { - - /** - * Legacy-format rule (a function instead of an object with `create` method). - * @param {RuleContext} context The ESLint rule context object. - * @returns {Object} Listeners. - */ - function functionStyleRule(context) { - return { - Program(node) { - context.report({ node, message: "bad" }); - } - }; - } - - ruleTester.run("function-style-rule", functionStyleRule, { - valid: [], - invalid: [ - { code: "var foo = bar;", errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - "\"function-style-rule\" rule is using the deprecated function-style format and will stop working in ESLint v9. Please use object-style format: https://eslint.org/docs/latest/extend/custom-rules", - "DeprecationWarning" - ] - ); - }); - - it("should log a deprecation warning when meta is not defined for the rule", () => { - ruleTester.run("rule-with-no-meta-1", ruleWithNoMeta, { - valid: [], - invalid: [ - { code: "var foo = bar;", options: [{ foo: true }], errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - "\"rule-with-no-meta-1\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", - "DeprecationWarning" - ] - ); - }); - - it("should log a deprecation warning when schema is not defined for the rule", () => { - ruleTester.run("rule-with-no-schema-1", ruleWithNoSchema, { - valid: [], - invalid: [ - { code: "var foo = bar;", options: [{ foo: true }], errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - "\"rule-with-no-schema-1\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", - "DeprecationWarning" - ] - ); - }); - - it("should log a deprecation warning when schema is `undefined`", () => { - const ruleWithUndefinedSchema = { - meta: { - type: "problem", - // eslint-disable-next-line no-undefined -- intentionally added for test case - schema: undefined - }, - create(context) { - return { - Program(node) { - context.report({ node, message: "bad" }); - } - }; - } - }; - - ruleTester.run("rule-with-undefined-schema", ruleWithUndefinedSchema, { - valid: [], - invalid: [ - { code: "var foo = bar;", options: [{ foo: true }], errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - "\"rule-with-undefined-schema\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", - "DeprecationWarning" - ] - ); - }); - - it("should log a deprecation warning when schema is `null`", () => { - const ruleWithNullSchema = { - meta: { - type: "problem", - schema: null - }, - create(context) { - return { - Program(node) { - context.report({ node, message: "bad" }); - } - }; - } - }; - - ruleTester.run("rule-with-null-schema", ruleWithNullSchema, { - valid: [], - invalid: [ - { code: "var foo = bar;", options: [{ foo: true }], errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - "\"rule-with-null-schema\" rule has options but is missing the \"meta.schema\" property and will stop working in ESLint v9. Please add a schema: https://eslint.org/docs/latest/extend/custom-rules#options-schemas", - "DeprecationWarning" - ] - ); - }); - - it("should not log a deprecation warning when schema is an empty array", () => { - const ruleWithEmptySchema = { - meta: { - type: "suggestion", - schema: [] - }, - create(context) { - return { - Program(node) { - context.report({ node, message: "bad" }); - } - }; - } - }; - - ruleTester.run("rule-with-no-options", ruleWithEmptySchema, { - valid: [], - invalid: [{ code: "var foo = bar;", errors: 1 }] - }); - - assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); - }); - - it("When the rule is an object-style rule, the legacy rule API warning is not emitted", () => { - ruleTester.run("rule-with-no-schema-2", ruleWithNoSchema, { - valid: [], - invalid: [ - { code: "var foo = bar;", errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); - }); - - it("When the rule has meta.schema and there are test cases with options, the missing schema warning is not emitted", () => { - const ruleWithSchema = { - meta: { - type: "suggestion", - schema: [{ - type: "boolean" - }] - }, - create(context) { - return { - Program(node) { - context.report({ node, message: "bad" }); - } - }; - } - }; - - ruleTester.run("rule-with-schema", ruleWithSchema, { - valid: [], - invalid: [ - { code: "var foo = bar;", options: [true], errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); - }); - - it("When the rule does not have meta, but there are no test cases with options, the missing schema warning is not emitted", () => { - ruleTester.run("rule-with-no-meta-2", ruleWithNoMeta, { - valid: [], - invalid: [ - { code: "var foo = bar;", errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); - }); - - it("When the rule has meta without meta.schema, but there are no test cases with options, the missing schema warning is not emitted", () => { - ruleTester.run("rule-with-no-schema-3", ruleWithNoSchema, { - valid: [], - invalid: [ - { code: "var foo = bar;", errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); - }); - it("When the rule has meta without meta.schema, and some test cases have options property but it's an empty array, the missing schema warning is not emitted", () => { - ruleTester.run("rule-with-no-schema-4", ruleWithNoSchema, { - valid: [], - invalid: [ - { code: "var foo = bar;", options: [], errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 0, "never calls `process.emitWarning()`"); - }); - - it("should emit a deprecation warning when CodePath#currentSegments is accessed", () => { - - const useCurrentSegmentsRule = { - create: () => ({ - onCodePathStart(codePath) { - codePath.currentSegments.forEach(() => {}); - } - }) - }; - - ruleTester.run("use-current-segments", useCurrentSegmentsRule, { - valid: ["foo"], - invalid: [] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - "\"use-current-segments\" rule uses CodePath#currentSegments and will stop working in ESLint v9. Please read the documentation for how to update your code: https://eslint.org/docs/latest/extend/code-path-analysis#usage-examples", - "DeprecationWarning" - ] - ); - }); - - it("should pass-through services from parseForESLint to the rule and log deprecation notice", () => { - const enhancedParserPath = require.resolve("../../fixtures/parsers/enhanced-parser"); - const disallowHiRule = { - create: context => ({ - Literal(node) { - assert.strictEqual(context.parserServices, context.sourceCode.parserServices); - - const disallowed = context.sourceCode.parserServices.test.getMessage(); // returns "Hi!" - - if (node.value === disallowed) { - context.report({ node, message: `Don't use '${disallowed}'` }); - } - } - }) - }; - - ruleTester.run("no-hi", disallowHiRule, { - valid: [ - { - code: "'Hello!'", - parser: enhancedParserPath - } - ], - invalid: [ - { - code: "'Hi!'", - parser: enhancedParserPath, - errors: [{ message: "Don't use 'Hi!'" }] - } - ] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - "\"no-hi\" rule is using `context.parserServices`, which is deprecated and will be removed in ESLint v9. Please use `sourceCode.parserServices` instead.", - "DeprecationWarning" - ] - ); - - }); - Object.entries({ - getSource: "getText", - getSourceLines: "getLines", - getAllComments: "getAllComments", - getNodeByRangeIndex: "getNodeByRangeIndex", - getCommentsBefore: "getCommentsBefore", - getCommentsAfter: "getCommentsAfter", - getCommentsInside: "getCommentsInside", - getJSDocComment: "getJSDocComment", - getFirstToken: "getFirstToken", - getFirstTokens: "getFirstTokens", - getLastToken: "getLastToken", - getLastTokens: "getLastTokens", - getTokenAfter: "getTokenAfter", - getTokenBefore: "getTokenBefore", - getTokenByRangeStart: "getTokenByRangeStart", - getTokens: "getTokens", - getTokensAfter: "getTokensAfter", - getTokensBefore: "getTokensBefore", - getTokensBetween: "getTokensBetween", - getScope: "getScope", - getAncestors: "getAncestors", - getDeclaredVariables: "getDeclaredVariables", - markVariableAsUsed: "markVariableAsUsed" - }).forEach(([methodName, replacementName]) => { - - it(`should log a deprecation warning when calling \`context.${methodName}\``, () => { - const ruleToCheckDeprecation = { - meta: { - type: "problem", - schema: [] - }, - create(context) { - return { - Program(node) { - - // special case - if (methodName === "getTokensBetween") { - context[methodName](node, node); - } else { - context[methodName](node); - } - - context.report({ node, message: "bad" }); - } - }; - } - }; - - ruleTester.run("deprecated-method", ruleToCheckDeprecation, { - valid: [], - invalid: [ - { code: "var foo = bar;", options: [], errors: 1 } - ] - }); - - assert.strictEqual(processStub.callCount, 1, "calls `process.emitWarning()` once"); - assert.deepStrictEqual( - processStub.getCall(0).args, - [ - `"deprecated-method" rule is using \`context.${methodName}()\`, which is deprecated and will be removed in ESLint v9. Please use \`sourceCode.${replacementName}()\` instead.`, - "DeprecationWarning" - ] - ); - }); - - }); - - - }); - - /** - * Asserts that a particular value will be emitted from an EventEmitter. - * @param {EventEmitter} emitter The emitter that should emit a value - * @param {string} emitType The type of emission to listen for - * @param {any} expectedValue The value that should be emitted - * @returns {Promise} A Promise that fulfills if the value is emitted, and rejects if something else is emitted. - * The Promise will be indefinitely pending if no value is emitted. - */ - function assertEmitted(emitter, emitType, expectedValue) { - return new Promise((resolve, reject) => { - emitter.once(emitType, emittedValue => { - if (emittedValue === expectedValue) { - resolve(); - } else { - reject(new Error(`Expected ${expectedValue} to be emitted but ${emittedValue} was emitted instead.`)); - } - }); - }); - } - - describe("naming test cases", () => { - - it("should use the first argument as the name of the test suite", () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "describe", "this-is-a-rule-name"); - - ruleTester.run("this-is-a-rule-name", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [] - }); - - return assertion; - }); - - it("should use the test code as the name of the tests for valid code (string form)", () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "it", "valid(code);"); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "valid(code);" - ], - invalid: [] - }); - - return assertion; - }); - - it("should use the test code as the name of the tests for valid code (object form)", () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "it", "valid(code);"); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - { - code: "valid(code);" - } - ], - invalid: [] - }); - - return assertion; - }); - - it("should use the test code as the name of the tests for invalid code", () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "it", "var x = invalid(code);"); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - code: "var x = invalid(code);", - output: " x = invalid(code);", - errors: 1 - } - ] - }); - - return assertion; - }); - - // https://github.com/eslint/eslint/issues/8142 - it("should use the empty string as the name of the test if the test case is an empty string", () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "it", ""); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - { - code: "" - } - ], - invalid: [] - }); - - return assertion; - }); - - it('should use the "name" property if set to a non-empty string', () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "it", "my test"); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - name: "my test", - code: "var x = invalid(code);", - output: " x = invalid(code);", - errors: 1 - } - ] - }); - - return assertion; - }); - - it('should use the "name" property if set to a non-empty string for valid cases too', () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "it", "my test"); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - { - name: "my test", - code: "valid(code);" - } - ], - invalid: [] - }); - - return assertion; - }); - - - it('should use the test code as the name if the "name" property is set to an empty string', () => { - const assertion = assertEmitted(ruleTesterTestEmitter, "it", "var x = invalid(code);"); - - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - name: "", - code: "var x = invalid(code);", - output: " x = invalid(code);", - errors: 1 - } - ] - }); - - return assertion; - }); - - it('should throw if "name" property is not a string', () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ code: "foo", name: 123 }], - invalid: [{ code: "foo" }] - - }); - }, /Optional test case property 'name' must be a string/u); - - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: ["foo"], - invalid: [{ code: "foo", name: 123 }] - }); - }, /Optional test case property 'name' must be a string/u); - }); - - it('should throw if "code" property is not a string', () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ code: 123 }], - invalid: [{ code: "foo" }] - - }); - }, /Test case must specify a string value for 'code'/u); - - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [123], - invalid: [{ code: "foo" }] - - }); - }, /Test case must specify a string value for 'code'/u); - - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: ["foo"], - invalid: [{ code: 123 }] - }); - }, /Test case must specify a string value for 'code'/u); - }); - - it('should throw if "code" property is missing', () => { - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [{ }], - invalid: [{ code: "foo" }] - - }); - }, /Test case must specify a string value for 'code'/u); - - assert.throws(() => { - ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), { - valid: ["foo"], - invalid: [{ }] - }); - }, /Test case must specify a string value for 'code'/u); - }); - }); - - // https://github.com/eslint/eslint/issues/11615 - it("should fail the case if autofix made a syntax error.", () => { - assert.throw(() => { - ruleTester.run( - "foo", - { - meta: { - fixable: "code" - }, - create(context) { - return { - Identifier(node) { - context.report({ - node, - message: "make a syntax error", - fix(fixer) { - return fixer.replaceText(node, "one two"); - } - }); - } - }; - } - }, - { - valid: ["one()"], - invalid: [] - } - ); - }, /A fatal parsing error occurred in autofix.\nError: .+\nAutofix output:\n.+/u); - }); - - describe("sanitize test cases", () => { - let originalRuleTesterIt; - let spyRuleTesterIt; - - before(() => { - originalRuleTesterIt = RuleTester.it; - spyRuleTesterIt = sinon.spy(); - RuleTester.it = spyRuleTesterIt; - }); - after(() => { - RuleTester.it = originalRuleTesterIt; - }); - beforeEach(() => { - spyRuleTesterIt.resetHistory(); - ruleTester = new RuleTester(); - }); - it("should present newline when using back-tick as new line", () => { - const code = ` + let ruleTester; + + // Stub `describe()` and `it()` while this test suite. + before(() => { + RuleTester.describe = function (text, method) { + ruleTesterTestEmitter.emit("describe", text, method); + return method.call(this); + }; + RuleTester.it = function (text, method) { + ruleTesterTestEmitter.emit("it", text, method); + return method.call(this); + }; + }); + + after(() => { + RuleTester.describe = null; + RuleTester.it = null; + }); + + beforeEach(() => { + ruleTester = new RuleTester(); + }); + + describe("Default Config", () => { + afterEach(() => { + RuleTester.resetDefaultConfig(); + }); + + it("should correctly set the globals configuration", () => { + const config = { languageOptions: { globals: { test: true } } }; + + RuleTester.setDefaultConfig(config); + assert( + RuleTester.getDefaultConfig().languageOptions.globals.test, + "The default config object is incorrect", + ); + }); + + it("should correctly reset the global configuration", () => { + const config = { languageOptions: { globals: { test: true } } }; + + RuleTester.setDefaultConfig(config); + RuleTester.resetDefaultConfig(); + assert.deepStrictEqual( + RuleTester.getDefaultConfig(), + { rules: {} }, + "The default configuration has not reset correctly", + ); + }); + + it("should enforce the global configuration to be an object", () => { + /** + * Set the default config for the rules tester + * @param {Object} config configuration object + * @returns {Function} Function to be executed + * @private + */ + function setConfig(config) { + return function () { + RuleTester.setDefaultConfig(config); + }; + } + const errorMessage = + "RuleTester.setDefaultConfig: config must be an object"; + + assert.throw(setConfig(), errorMessage); + assert.throw(setConfig(1), errorMessage); + assert.throw(setConfig(3.14), errorMessage); + assert.throw(setConfig("foo"), errorMessage); + assert.throw(setConfig(null), errorMessage); + assert.throw(setConfig(true), errorMessage); + }); + + it("should pass-through the globals config to the tester then to the to rule", () => { + const config = { + languageOptions: { + sourceType: "script", + globals: { test: true }, + }, + }; + + RuleTester.setDefaultConfig(config); + ruleTester = new RuleTester(); + + ruleTester.run( + "no-test-global", + require("../../fixtures/testers/rule-tester/no-test-global"), + { + valid: ["var test = 'foo'", "var test2 = test"], + invalid: [ + { + code: "bar", + errors: 1, + languageOptions: { globals: { foo: true } }, + }, + ], + }, + ); + }); + + it("should throw an error if node.start is accessed with parser in default config", () => { + const enhancedParser = require("../../fixtures/parsers/enhanced-parser"); + + RuleTester.setDefaultConfig({ + languageOptions: { + parser: enhancedParser, + }, + }); + ruleTester = new RuleTester(); + + /* + * Note: More robust test for start/end found later in file. + * This one is just for checking the default config has a + * parser that is wrapped. + */ + const usesStartEndRule = { + create() { + return { + CallExpression(node) { + noop(node.arguments[1].start); + }, + }; + }, + }; + + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: ["foo(a, b)"], + invalid: [], + }); + }, "Use node.range[0] instead of node.start"); + }); + }); + + describe("only", () => { + describe("`itOnly` accessor", () => { + describe("when `itOnly` is set", () => { + before(() => { + RuleTester.itOnly = sinon.spy(); + }); + after(() => { + RuleTester.itOnly = void 0; + }); + beforeEach(() => { + RuleTester.itOnly.resetHistory(); + ruleTester = new RuleTester(); + }); + + it("is called by exclusive tests", () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [ + { + code: "const notVar = 42;", + only: true, + }, + ], + invalid: [], + }, + ); + + sinon.assert.calledWith( + RuleTester.itOnly, + "const notVar = 42;", + ); + }); + }); + + describe("when `it` is set and has an `only()` method", () => { + before(() => { + RuleTester.it.only = () => {}; + sinon.spy(RuleTester.it, "only"); + }); + after(() => { + RuleTester.it.only = void 0; + }); + beforeEach(() => { + RuleTester.it.only.resetHistory(); + ruleTester = new RuleTester(); + }); + + it("is called by tests with `only` set", () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [ + { + code: "const notVar = 42;", + only: true, + }, + ], + invalid: [], + }, + ); + + sinon.assert.calledWith( + RuleTester.it.only, + "const notVar = 42;", + ); + }); + }); + + describe("when global `it` is a function that has an `only()` method", () => { + let originalGlobalItOnly; + + before(() => { + /* + * We run tests with `--forbid-only`, so we have to override + * `it.only` to prevent the real one from being called. + */ + originalGlobalItOnly = it.only; + it.only = () => {}; + sinon.spy(it, "only"); + }); + after(() => { + it.only = originalGlobalItOnly; + }); + beforeEach(() => { + it.only.resetHistory(); + ruleTester = new RuleTester(); + }); + + it("is called by tests with `only` set", () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [ + { + code: "const notVar = 42;", + only: true, + }, + ], + invalid: [], + }, + ); + + sinon.assert.calledWith(it.only, "const notVar = 42;"); + }); + }); + + describe("when `describe` and `it` are overridden without `itOnly`", () => { + let originalGlobalItOnly; + + before(() => { + /* + * These tests override `describe` and `it` already, so we + * don't need to override them here. We do, however, need to + * remove `only` from the global `it` to prevent it from + * being used instead. + */ + originalGlobalItOnly = it.only; + it.only = void 0; + }); + after(() => { + it.only = originalGlobalItOnly; + }); + beforeEach(() => { + ruleTester = new RuleTester(); + }); + + it("throws an error recommending overriding `itOnly`", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [ + { + code: "const notVar = 42;", + only: true, + }, + ], + invalid: [], + }, + ); + }, "Set `RuleTester.itOnly` to use `only` with a custom test framework."); + }); + }); + + describe("when global `it` is a function that does not have an `only()` method", () => { + let originalGlobalIt; + let originalRuleTesterDescribe; + let originalRuleTesterIt; + + before(() => { + originalGlobalIt = global.it; + + // eslint-disable-next-line no-global-assign -- Temporarily override Mocha global + it = () => {}; + + /* + * These tests override `describe` and `it`, so we need to + * un-override them here so they won't interfere. + */ + originalRuleTesterDescribe = RuleTester.describe; + RuleTester.describe = void 0; + originalRuleTesterIt = RuleTester.it; + RuleTester.it = void 0; + }); + after(() => { + // eslint-disable-next-line no-global-assign -- Restore Mocha global + it = originalGlobalIt; + RuleTester.describe = originalRuleTesterDescribe; + RuleTester.it = originalRuleTesterIt; + }); + beforeEach(() => { + ruleTester = new RuleTester(); + }); + + it("throws an error explaining that the current test framework does not support `only`", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [ + { + code: "const notVar = 42;", + only: true, + }, + ], + invalid: [], + }, + ); + }, "The current test framework does not support exclusive tests with `only`."); + }); + }); + }); + + describe("test cases", () => { + const ruleName = "no-var"; + const rule = require("../../fixtures/testers/rule-tester/no-var"); + + let originalRuleTesterIt; + let spyRuleTesterIt; + let originalRuleTesterItOnly; + let spyRuleTesterItOnly; + + before(() => { + originalRuleTesterIt = RuleTester.it; + spyRuleTesterIt = sinon.spy(); + RuleTester.it = spyRuleTesterIt; + originalRuleTesterItOnly = RuleTester.itOnly; + spyRuleTesterItOnly = sinon.spy(); + RuleTester.itOnly = spyRuleTesterItOnly; + }); + after(() => { + RuleTester.it = originalRuleTesterIt; + RuleTester.itOnly = originalRuleTesterItOnly; + }); + beforeEach(() => { + spyRuleTesterIt.resetHistory(); + spyRuleTesterItOnly.resetHistory(); + ruleTester = new RuleTester(); + }); + + it("isn't called for normal tests", () => { + ruleTester.run(ruleName, rule, { + valid: ["const notVar = 42;"], + invalid: [], + }); + sinon.assert.calledWith(spyRuleTesterIt, "const notVar = 42;"); + sinon.assert.notCalled(spyRuleTesterItOnly); + }); + + it("calls it or itOnly for every test case", () => { + /* + * `RuleTester` doesn't implement test case exclusivity itself. + * Setting `only: true` just causes `RuleTester` to call + * whatever `only()` function is provided by the test framework + * instead of the regular `it()` function. + */ + + ruleTester.run(ruleName, rule, { + valid: [ + "const valid = 42;", + { + code: "const onlyValid = 42;", + only: true, + }, + ], + invalid: [ + { + code: "var invalid = 42;", + errors: [/^Bad var/u], + }, + { + code: "var onlyInvalid = 42;", + errors: [/^Bad var/u], + only: true, + }, + ], + }); + + sinon.assert.calledWith(spyRuleTesterIt, "const valid = 42;"); + sinon.assert.calledWith( + spyRuleTesterItOnly, + "const onlyValid = 42;", + ); + sinon.assert.calledWith(spyRuleTesterIt, "var invalid = 42;"); + sinon.assert.calledWith( + spyRuleTesterItOnly, + "var onlyInvalid = 42;", + ); + }); + }); + + describe("static helper wrapper", () => { + it("adds `only` to string test cases", () => { + const test = RuleTester.only("const valid = 42;"); + + assert.deepStrictEqual(test, { + code: "const valid = 42;", + only: true, + }); + }); + + it("adds `only` to object test cases", () => { + const test = RuleTester.only({ code: "const valid = 42;" }); + + assert.deepStrictEqual(test, { + code: "const valid = 42;", + only: true, + }); + }); + }); + }); + + describe("hooks", () => { + const ruleName = "no-var"; + const rule = require("../../fixtures/testers/rule-tester/no-var"); + + ["before", "after"].forEach(hookName => { + it(`${hookName} should be called when a function is assigned`, () => { + const hookForValid = sinon.stub(); + const hookForInvalid = sinon.stub(); + + ruleTester = new RuleTester(); + ruleTester.run(ruleName, rule, { + valid: [ + { + code: "const onlyValid = 42;", + [hookName]: hookForValid, + }, + ], + invalid: [ + { + code: "var onlyValid = 42;", + errors: [/^Bad var/u], + output: " onlyValid = 42;", + [hookName]: hookForInvalid, + }, + ], + }); + sinon.assert.calledOnce(hookForValid); + sinon.assert.calledOnce(hookForInvalid); + }); + + it(`${hookName} should cause test to fail when it throws error`, () => { + const hook = sinon + .stub() + .throws(new Error("Something happened")); + + ruleTester = new RuleTester(); + assert.throws( + () => + ruleTester.run(ruleName, rule, { + valid: [ + { + code: "const onlyValid = 42;", + [hookName]: hook, + }, + ], + invalid: [], + }), + "Something happened", + ); + assert.throws( + () => + ruleTester.run(ruleName, rule, { + valid: [], + invalid: [ + { + code: "var onlyValid = 42;", + errors: [/^Bad var/u], + output: " onlyValid = 42;", + [hookName]: hook, + }, + ], + }), + "Something happened", + ); + }); + + it(`${hookName} should throw when not a function is assigned`, () => { + ruleTester = new RuleTester(); + assert.throws( + () => + ruleTester.run(ruleName, rule, { + valid: [ + { + code: "const onlyValid = 42;", + [hookName]: 42, + }, + ], + invalid: [], + }), + `Optional test case property '${hookName}' must be a function`, + ); + assert.throws( + () => + ruleTester.run(ruleName, rule, { + valid: [], + invalid: [ + { + code: "var onlyValid = 42;", + errors: [/^Bad var/u], + output: " onlyValid = 42;", + [hookName]: 42, + }, + ], + }), + `Optional test case property '${hookName}' must be a function`, + ); + }); + }); + + it("should call both before() and after() hooks even when the case failed", () => { + const hookBefore = sinon.stub(); + const hookAfter = sinon.stub(); + + ruleTester = new RuleTester(); + assert.throws(() => + ruleTester.run(ruleName, rule, { + valid: [ + { + code: "var onlyValid = 42;", + before: hookBefore, + after: hookAfter, + }, + ], + invalid: [], + }), + ); + sinon.assert.calledOnce(hookBefore); + sinon.assert.calledOnce(hookAfter); + assert.throws(() => + ruleTester.run(ruleName, rule, { + valid: [], + invalid: [ + { + code: "const onlyValid = 42;", + errors: [/^Bad var/u], + output: " onlyValid = 42;", + before: hookBefore, + after: hookAfter, + }, + ], + }), + ); + sinon.assert.calledTwice(hookBefore); + sinon.assert.calledTwice(hookAfter); + }); + + it("should call both before() and after() hooks regardless syntax errors", () => { + const hookBefore = sinon.stub(); + const hookAfter = sinon.stub(); + + ruleTester = new RuleTester(); + assert.throws( + () => + ruleTester.run(ruleName, rule, { + valid: [ + { + code: "invalid javascript code", + before: hookBefore, + after: hookAfter, + }, + ], + invalid: [], + }), + /parsing error/u, + ); + sinon.assert.calledOnce(hookBefore); + sinon.assert.calledOnce(hookAfter); + assert.throws( + () => + ruleTester.run(ruleName, rule, { + valid: [], + invalid: [ + { + code: "invalid javascript code", + errors: [/^Bad var/u], + output: " onlyValid = 42;", + before: hookBefore, + after: hookAfter, + }, + ], + }), + /parsing error/u, + ); + sinon.assert.calledTwice(hookBefore); + sinon.assert.calledTwice(hookAfter); + }); + + it("should call after() hook even when before() throws", () => { + const hookBefore = sinon + .stub() + .throws(new Error("Something happened in before()")); + const hookAfter = sinon.stub(); + + ruleTester = new RuleTester(); + assert.throws( + () => + ruleTester.run(ruleName, rule, { + valid: [ + { + code: "const onlyValid = 42;", + before: hookBefore, + after: hookAfter, + }, + ], + invalid: [], + }), + "Something happened in before()", + ); + sinon.assert.calledOnce(hookBefore); + sinon.assert.calledOnce(hookAfter); + assert.throws( + () => + ruleTester.run(ruleName, rule, { + valid: [], + invalid: [ + { + code: "var onlyValid = 42;", + errors: [/^Bad var/u], + output: " onlyValid = 42;", + before: hookBefore, + after: hookAfter, + }, + ], + }), + "Something happened in before()", + ); + sinon.assert.calledTwice(hookBefore); + sinon.assert.calledTwice(hookAfter); + }); + }); + + it("should not throw an error when everything passes", () => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["Eval(foo)"], + invalid: [ + { + code: "eval(foo)", + errors: [ + { message: "eval sucks.", type: "CallExpression" }, + ], + }, + ], + }, + ); + }); + + it("should throw correct error when valid code is invalid and enables other core rule", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["/*eslint semi: 2*/ eval(foo);"], + invalid: [ + { + code: "eval(foo)", + errors: [ + { + message: "eval sucks.", + type: "CallExpression", + }, + ], + }, + ], + }, + ); + }, /Should have no errors but had 1/u); + }); + + it("should throw an error when valid code is invalid", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["eval(foo)"], + invalid: [ + { + code: "eval(foo)", + errors: [ + { + message: "eval sucks.", + type: "CallExpression", + }, + ], + }, + ], + }, + ); + }, /Should have no errors but had 1/u); + }); + + it("should throw an error when valid code is invalid", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: [{ code: "eval(foo)" }], + invalid: [ + { + code: "eval(foo)", + errors: [ + { + message: "eval sucks.", + type: "CallExpression", + }, + ], + }, + ], + }, + ); + }, /Should have no errors but had 1/u); + }); + + it("should throw an error if invalid code is valid", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["Eval(foo)"], + invalid: [ + { + code: "Eval(foo)", + errors: [ + { + message: "eval sucks.", + type: "CallExpression", + }, + ], + }, + ], + }, + ); + }, /Should have 1 error but had 0/u); + }); + + it("should throw an error when the error message is wrong", () => { + assert.throws( + () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + // Only the invalid test matters here + valid: ["bar = baz;"], + invalid: [ + { + code: "var foo = bar;", + errors: [{ message: "Bad error message." }], + }, + ], + }, + ); + }, + assertErrorMatches("Bad var.", "Bad error message."), + ); + }); + + it("should throw an error when the error message regex does not match", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [ + { + code: "var foo = bar;", + errors: [{ message: /Bad error message/u }], + }, + ], + }, + ); + }, /Expected 'Bad var.' to match \/Bad error message\//u); + }); + + it("should throw an error when the error is not a supported type", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + // Only the invalid test matters here + valid: ["bar = baz;"], + invalid: [{ code: "var foo = bar;", errors: [42] }], + }, + ); + }, /Error should be a string, object, or RegExp/u); + }); + + it("should throw an error when any of the errors is not a supported type", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + // Only the invalid test matters here + valid: ["bar = baz;"], + invalid: [ + { + code: "var foo = bar; var baz = quux", + errors: [ + { + message: "Bad var.", + type: "VariableDeclaration", + }, + null, + ], + }, + ], + }, + ); + }, /Error should be a string, object, or RegExp/u); + }); + + it("should throw an error when the error is a string and it does not match error message", () => { + assert.throws( + () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + // Only the invalid test matters here + valid: ["bar = baz;"], + invalid: [ + { + code: "var foo = bar;", + errors: ["Bad error message."], + }, + ], + }, + ); + }, + assertErrorMatches("Bad var.", "Bad error message."), + ); + }); + + it("should throw an error when the error is a string and it does not match error message", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [ + { + code: "var foo = bar;", + errors: [/Bad error message/u], + }, + ], + }, + ); + }, /Expected 'Bad var.' to match \/Bad error message\//u); + }); + + it("should not throw an error when the error is a string and it matches error message", () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + // Only the invalid test matters here + valid: ["bar = baz;"], + invalid: [ + { + code: "var foo = bar;", + output: " foo = bar;", + errors: ["Bad var."], + }, + ], + }, + ); + }); + + it("should not throw an error when the error is a regex and it matches error message", () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [ + { + code: "var foo = bar;", + output: " foo = bar;", + errors: [/^Bad var/u], + }, + ], + }, + ); + }); + + it("should not throw an error when the error is a string and the suggestion fixer is failing", () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/suggestions") + .withFailingFixer, + { + valid: [], + invalid: [{ code: "foo", errors: ["some message"] }], + }, + ); + }); + + it("throws an error when the error is a string and the suggestion fixer provides a fix", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/suggestions").basic, + { + valid: [], + invalid: [ + { + code: "foo", + errors: ["Avoid using identifiers named 'foo'."], + }, + ], + }, + ); + }, "Error at index 0 has suggestions. Please convert the test error into an object and specify 'suggestions' property on it to test suggestions."); + }); + + it("should throw an error when the error is an object with an unknown property name", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["bar = baz;"], + invalid: [ + { + code: "var foo = bar;", + errors: [{ Message: "Bad var." }], + }, + ], + }, + ); + }, /Invalid error property name 'Message'/u); + }); + + it("should throw an error when any of the errors is an object with an unknown property name", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["bar = baz;"], + invalid: [ + { + code: "var foo = bar; var baz = quux", + errors: [ + { + message: "Bad var.", + type: "VariableDeclaration", + }, + { + message: "Bad var.", + typo: "VariableDeclaration", + }, + ], + }, + ], + }, + ); + }, /Invalid error property name 'typo'/u); + }); + + it("should not throw an error when the error is a regex in an object and it matches error message", () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [ + { + code: "var foo = bar;", + output: " foo = bar;", + errors: [{ message: /^Bad var/u }], + }, + ], + }, + ); + }); + + it("should throw an error when the expected output doesn't match", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["bar = baz;"], + invalid: [ + { + code: "var foo = bar;", + output: "foo = bar", + errors: [ + { + message: "Bad var.", + type: "VariableDeclaration", + }, + ], + }, + ], + }, + ); + }, /Output is incorrect/u); + }); + + it("should use strict equality to compare output", () => { + const replaceProgramWith5Rule = { + meta: { + fixable: "code", + }, + + create: context => ({ + Program(node) { + context.report({ + node, + message: "bad", + fix: fixer => fixer.replaceText(node, "5"), + }); + }, + }), + }; + + // Should not throw. + ruleTester.run("foo", replaceProgramWith5Rule, { + valid: [], + invalid: [{ code: "var foo = bar;", output: "5", errors: 1 }], + }); + + assert.throws(() => { + ruleTester.run("foo", replaceProgramWith5Rule, { + valid: [], + invalid: [{ code: "var foo = bar;", output: 5, errors: 1 }], + }); + }, /Output is incorrect/u); + }); + + it("should throw an error when the expected output doesn't match and errors is just a number", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["bar = baz;"], + invalid: [ + { + code: "var foo = bar;", + output: "foo = bar", + errors: 1, + }, + ], + }, + ); + }, /Output is incorrect/u); + }); + + it("should not throw an error when the expected output is null and no errors produce output", () => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["bar = baz;"], + invalid: [ + { code: "eval(x)", errors: 1, output: null }, + { code: "eval(x); eval(y);", errors: 2, output: null }, + ], + }, + ); + }); + + it("should throw an error when the expected output is null and problems produce output", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["bar = baz;"], + invalid: [ + { code: "var foo = bar;", output: null, errors: 1 }, + ], + }, + ); + }, /Expected no autofixes to be suggested/u); + + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["bar = baz;"], + invalid: [ + { + code: "var foo = bar; var qux = boop;", + output: null, + errors: 2, + }, + ], + }, + ); + }, /Expected no autofixes to be suggested/u); + }); + + it("should throw an error when the expected output is null and only some problems produce output", () => { + assert.throws(() => { + ruleTester.run( + "fixes-one-problem", + require("../../fixtures/testers/rule-tester/fixes-one-problem"), + { + valid: [], + invalid: [{ code: "foo", output: null, errors: 2 }], + }, + ); + }, /Expected no autofixes to be suggested/u); + }); + + it("should throw an error when the expected output is not null and the output does not differ from the code", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: [], + invalid: [ + { code: "eval('')", output: "eval('')", errors: 1 }, + ], + }, + ); + }, "Test property 'output' matches 'code'. If no autofix is expected, then omit the 'output' property or set it to null."); + }); + + it("should throw an error when the expected output isn't specified and problems produce output", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["bar = baz;"], + invalid: [{ code: "var foo = bar;", errors: 1 }], + }, + ); + }, "The rule fixed the code. Please add 'output' property."); + }); + + it("should throw an error if invalid code specifies wrong type", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["Eval(foo)"], + invalid: [ + { + code: "eval(foo)", + errors: [ + { + message: "eval sucks.", + type: "CallExpression2", + }, + ], + }, + ], + }, + ); + }, /Error type should be CallExpression2, found CallExpression/u); + }); + + it("should throw an error if invalid code specifies wrong line", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["Eval(foo)"], + invalid: [ + { + code: "eval(foo)", + errors: [ + { + message: "eval sucks.", + type: "CallExpression", + line: 5, + }, + ], + }, + ], + }, + ); + }, "Actual error location does not match expected error location."); + }); + + it("should not skip line assertion if line is a falsy value", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["Eval(foo)"], + invalid: [ + { + code: "\neval(foo)", + errors: [ + { + message: "eval sucks.", + type: "CallExpression", + line: 0, + }, + ], + }, + ], + }, + ); + }, "Actual error location does not match expected error location."); + }); + + it("should throw an error if invalid code specifies wrong column", () => { + const wrongColumn = 10; + + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["Eval(foo)"], + invalid: [ + { + code: "eval(foo)", + errors: [ + { + message: "eval sucks.", + column: wrongColumn, + }, + ], + }, + ], + }, + ); + }, "Actual error location does not match expected error location."); + }); + + it("should throw error for empty error array", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [], + }, + ], + }, + ); + }, /Invalid cases must have at least one error/u); + }); + + it("should throw error for errors : 0", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: 0, + }, + ], + }, + ); + }, /Invalid cases must have 'error' value greater than 0/u); + }); + + it("should not skip column assertion if column is a falsy value", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["Eval(foo)"], + invalid: [ + { + code: "var foo; eval(foo)", + errors: [{ message: "eval sucks.", column: 0 }], + }, + ], + }, + ); + }, "Actual error location does not match expected error location."); + }); + + it("should throw an error if invalid code specifies wrong endLine", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["bar = baz;"], + invalid: [ + { + code: "var foo = bar;", + output: "foo = bar", + errors: [ + { + message: "Bad var.", + type: "VariableDeclaration", + endLine: 10, + }, + ], + }, + ], + }, + ); + }, "Actual error location does not match expected error location."); + }); + + it("should throw an error if invalid code specifies wrong endColumn", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["bar = baz;"], + invalid: [ + { + code: "var foo = bar;", + output: "foo = bar", + errors: [ + { + message: "Bad var.", + type: "VariableDeclaration", + endColumn: 10, + }, + ], + }, + ], + }, + ); + }, "Actual error location does not match expected error location."); + }); + + it("should throw an error if invalid code has the wrong number of errors", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["Eval(foo)"], + invalid: [ + { + code: "eval(foo)", + errors: [ + { + message: "eval sucks.", + type: "CallExpression", + }, + { + message: "eval sucks.", + type: "CallExpression", + }, + ], + }, + ], + }, + ); + }, /Should have 2 errors but had 1/u); + }); + + it("should throw an error if invalid code does not have errors", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["Eval(foo)"], + invalid: [{ code: "eval(foo)" }], + }, + ); + }, /Did not specify errors for an invalid test of no-eval/u); + }); + + it("should throw an error if invalid code has the wrong explicit number of errors", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["Eval(foo)"], + invalid: [{ code: "eval(foo)", errors: 2 }], + }, + ); + }, /Should have 2 errors but had 1/u); + }); + + it("should throw an error if there's a parsing error in a valid test", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["1eval('foo')"], + invalid: [{ code: "eval('foo')", errors: [{}] }], + }, + ); + }, /fatal parsing error/iu); + }); + + it("should throw an error if there's a parsing error in an invalid test", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["noeval('foo')"], + invalid: [{ code: "1eval('foo')", errors: [{}] }], + }, + ); + }, /fatal parsing error/iu); + }); + + it("should throw an error if there's a parsing error in an invalid test and errors is just a number", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["noeval('foo')"], + invalid: [{ code: "1eval('foo')", errors: 1 }], + }, + ); + }, /fatal parsing error/iu); + }); + + // https://github.com/eslint/eslint/issues/4779 + it("should throw an error if there's a parsing error and output doesn't match", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: [], + invalid: [ + { + code: "eval(`foo`", + output: "eval(`foo`);", + errors: [{}], + }, + ], + }, + ); + }, /fatal parsing error/iu); + }); + + it("should throw an error if an error object has no properties", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["Eval(foo)"], + invalid: [ + { + code: "eval(foo)", + errors: [{}], + }, + ], + }, + ); + }, "Test error must specify either a 'messageId' or 'message'."); + }); + + it("should throw an error if an error has a property besides message or messageId", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: ["Eval(foo)"], + invalid: [ + { + code: "eval(foo)", + errors: [{ line: 1 }], + }, + ], + }, + ); + }, "Test error must specify either a 'messageId' or 'message'."); + }); + + it("should pass-through the globals config of valid tests to the to rule", () => { + ruleTester.run( + "no-test-global", + require("../../fixtures/testers/rule-tester/no-test-global"), + { + valid: [ + { + code: "var test = 'foo'", + languageOptions: { + sourceType: "script", + }, + }, + { + code: "var test2 = 'bar'", + languageOptions: { + globals: { test: true }, + }, + }, + ], + invalid: [{ code: "bar", errors: 1 }], + }, + ); + }); + + it("should pass-through the globals config of invalid tests to the rule", () => { + ruleTester.run( + "no-test-global", + require("../../fixtures/testers/rule-tester/no-test-global"), + { + valid: [ + { + code: "var test = 'foo'", + languageOptions: { + sourceType: "script", + }, + }, + ], + invalid: [ + { + code: "var test = 'foo'; var foo = 'bar'", + languageOptions: { + sourceType: "script", + }, + errors: 1, + }, + { + code: "var test = 'foo'", + languageOptions: { + sourceType: "script", + globals: { foo: true }, + }, + errors: [ + { + message: + "Global variable foo should not be used.", + }, + ], + }, + ], + }, + ); + }); + + it("should pass-through the settings config to rules", () => { + ruleTester.run( + "no-test-settings", + require("../../fixtures/testers/rule-tester/no-test-settings"), + { + valid: [ + { + code: "var test = 'bar'", + settings: { test: 1 }, + }, + ], + invalid: [ + { + code: "var test = 'bar'", + settings: { "no-test": 22 }, + errors: 1, + }, + ], + }, + ); + }); + + it("should pass-through the filename to the rule", () => { + (function () { + ruleTester.run( + "", + require("../../fixtures/testers/rule-tester/no-test-filename"), + { + valid: [ + { + code: "var foo = 'bar'", + filename: "somefile.js", + }, + ], + invalid: [ + { + code: "var foo = 'bar'", + errors: [ + { message: "Filename test was not defined." }, + ], + }, + ], + }, + ); + })(); + }); + + it("should allow setting the filename to a non-JavaScript file", () => { + ruleTester.run( + "", + require("../../fixtures/testers/rule-tester/no-test-filename"), + { + valid: [ + { + code: "var foo = 'bar'", + filename: "somefile.ts", + }, + ], + invalid: [], + }, + ); + }); + + it("should allow setting the filename to a file path with extension", () => { + ruleTester.run( + "", + require("../../fixtures/testers/rule-tester/no-test-filename"), + { + valid: [ + { + code: "var foo = 'bar'", + filename: "path/to/somefile.js", + }, + { + code: "var foo = 'bar'", + filename: "src/somefile.ts", + }, + { + code: "var foo = 'bar'", + filename: "components/Component.vue", + }, + ], + invalid: [], + }, + ); + }); + + it("should allow setting the filename to a file path without extension", () => { + ruleTester.run( + "", + require("../../fixtures/testers/rule-tester/no-test-filename"), + { + valid: [ + { + code: "var foo = 'bar'", + filename: "somefile", + }, + { + code: "var foo = 'bar'", + filename: "path/to/somefile", + }, + { + code: "var foo = 'bar'", + filename: "src/somefile", + }, + ], + invalid: [], + }, + ); + }); + + it("should keep allowing non-JavaScript files if the default config does not specify files", () => { + RuleTester.setDefaultConfig({ rules: {} }); + ruleTester.run( + "", + require("../../fixtures/testers/rule-tester/no-test-filename"), + { + valid: [ + { + code: "var foo = 'bar'", + filename: "somefile.ts", + }, + ], + invalid: [], + }, + ); + RuleTester.resetDefaultConfig(); + }); + + it("should pass-through the options to the rule", () => { + ruleTester.run( + "no-invalid-args", + require("../../fixtures/testers/rule-tester/no-invalid-args"), + { + valid: [ + { + code: "var foo = 'bar'", + options: [false], + }, + ], + invalid: [ + { + code: "var foo = 'bar'", + options: [true], + errors: [{ message: "Invalid args" }], + }, + ], + }, + ); + }); + + it("should throw an error if the options are an object", () => { + assert.throws(() => { + ruleTester.run( + "no-invalid-args", + require("../../fixtures/testers/rule-tester/no-invalid-args"), + { + valid: [ + { + code: "foo", + options: { ok: true }, + }, + ], + invalid: [], + }, + ); + }, /options must be an array/u); + }); + + it("should throw an error if the options are a number", () => { + assert.throws(() => { + ruleTester.run( + "no-invalid-args", + require("../../fixtures/testers/rule-tester/no-invalid-args"), + { + valid: [ + { + code: "foo", + options: 0, + }, + ], + invalid: [], + }, + ); + }, /options must be an array/u); + }); + + describe("Parsers", () => { + it("should pass-through the parser to the rule", () => { + const spy = sinon.spy(ruleTester.linter, "verify"); + const esprima = require("esprima"); + + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: [ + { + code: "Eval(foo)", + }, + ], + invalid: [ + { + code: "eval(foo)", + languageOptions: { + parser: esprima, + }, + errors: [{ message: "eval sucks.", line: 1 }], + }, + ], + }, + ); + + const configs = spy.args[1][1]; + const config = configs.getConfig("test.js"); + + assert.strictEqual( + config.languageOptions.parser[ + Symbol.for("eslint.RuleTester.parser") + ], + esprima, + ); + }); + + it("should pass-through services from parseForESLint to the rule", () => { + const enhancedParser = require("../../fixtures/parsers/enhanced-parser"); + const disallowHiRule = { + create: context => ({ + Literal(node) { + const disallowed = + context.sourceCode.parserServices.test.getMessage(); // returns "Hi!" + + if (node.value === disallowed) { + context.report({ + node, + message: `Don't use '${disallowed}'`, + }); + } + }, + }), + }; + + ruleTester.run("no-hi", disallowHiRule, { + valid: [ + { + code: "'Hello!'", + languageOptions: { + parser: enhancedParser, + }, + }, + ], + invalid: [ + { + code: "'Hi!'", + languageOptions: { + parser: enhancedParser, + }, + errors: [{ message: "Don't use 'Hi!'" }], + }, + ], + }); + }); + + it("should throw an error when the parser is not an object", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: [], + invalid: [ + { + code: "var foo;", + languageOptions: { + parser: "esprima", + }, + errors: 1, + }, + ], + }, + ); + }, /Key "languageOptions": Key "parser": Expected object with parse\(\) or parseForESLint\(\) method\./u); + }); + }); + + describe("Languages", () => { + it("should work with a language that doesn't have language options", () => { + const ruleTesterJsonLanguage = new RuleTester({ + plugins: { + json: jsonPlugin, + }, + language: "json/json", + }); + + ruleTesterJsonLanguage.run( + "no-empty-keys", + jsonPlugin.rules["no-empty-keys"], + { + valid: ['{"foo": 1, "bar": 2}'], + invalid: [ + { + code: '{"": 1}', + errors: [{ messageId: "emptyKey" }], + }, + ], + }, + ); + }); + }); + + it("should throw an error with the original message and an additional description if rule has `meta.schema` of an invalid type", () => { + const rule = { + meta: { + schema: true, + }, + create(context) { + return { + Program(node) { + context.report({ node, message: "bad" }); + }, + }; + }, + }; + + assert.throws(() => { + ruleTester.run("rule-with-invalid-schema-type", rule, { + valid: [], + invalid: [{ code: "var foo = bar;", errors: 1 }], + }); + }, /Rule's `meta.schema` must be an array or object.*set `meta.schema` to an array or non-empty object to enable options validation/su); + }); + + it("should prevent invalid options schemas", () => { + assert.throws(() => { + ruleTester.run( + "no-invalid-schema", + require("../../fixtures/testers/rule-tester/no-invalid-schema"), + { + valid: [ + "var answer = 6 * 7;", + { code: "var answer = 6 * 7;", options: [] }, + ], + invalid: [ + { + code: "var answer = 6 * 7;", + options: ["bar"], + errors: [{ message: "Expected nothing." }], + }, + ], + }, + ); + }, "Schema for rule no-invalid-schema is invalid:,\titems: should be object\n\titems[0].enum: should NOT have fewer than 1 items\n\titems: should match some schema in anyOf"); + }); + + it("should throw an error if rule schema is `{}`", () => { + const rule = { + meta: { + schema: {}, + }, + create(context) { + return { + Program(node) { + context.report({ node, message: "bad" }); + }, + }; + }, + }; + + assert.throws(() => { + ruleTester.run("rule-with-empty-object-schema", rule, { + valid: [], + invalid: [{ code: "var foo = bar;", errors: 1 }], + }); + }, /`schema: \{\}` is a no-op.*set `meta.schema` to an array or non-empty object to enable options validation/su); + }); + + it("should throw an error if rule schema has only non-enumerable properties", () => { + const rule = { + meta: { + schema: Object.create(null, { + type: { + value: "array", + enumerable: false, + }, + items: { + value: [{ enum: ["foo"] }], + enumerable: false, + }, + }), + }, + create(context) { + return { + Program(node) { + context.report({ node, message: "bad" }); + }, + }; + }, + }; + + assert.throws(() => { + ruleTester.run("rule-with-empty-object-schema", rule, { + valid: [], + invalid: [{ code: "var foo = bar;", errors: 1 }], + }); + }, /`schema: \{\}` is a no-op.*set `meta.schema` to an array or non-empty object to enable options validation/su); + }); + + it("should throw an error if rule schema has only inherited enumerable properties", () => { + const rule = { + meta: { + schema: { + __proto__: { + type: "array", + items: [{ enum: ["foo"] }], + }, + }, + }, + create(context) { + return { + Program(node) { + context.report({ node, message: "bad" }); + }, + }; + }, + }; + + assert.throws(() => { + ruleTester.run("rule-with-empty-object-schema", rule, { + valid: [], + invalid: [{ code: "var foo = bar;", errors: 1 }], + }); + }, /`schema: \{\}` is a no-op.*set `meta.schema` to an array or non-empty object to enable options validation/su); + }); + + it("should prevent schema violations in options", () => { + assert.throws(() => { + ruleTester.run( + "no-schema-violation", + require("../../fixtures/testers/rule-tester/no-schema-violation"), + { + valid: [ + "var answer = 6 * 7;", + { code: "var answer = 6 * 7;", options: ["foo"] }, + ], + invalid: [ + { + code: "var answer = 6 * 7;", + options: ["bar"], + errors: [{ message: "Expected foo." }], + }, + ], + }, + ); + }, /Value "bar" should be equal to one of the allowed values./u); + }); + + it("should disallow invalid defaults in rules", () => { + const ruleWithInvalidDefaults = { + meta: { + schema: [ + { + oneOf: [ + { enum: ["foo"] }, + { + type: "object", + properties: { + foo: { + enum: ["foo", "bar"], + default: "foo", + }, + }, + additionalProperties: false, + }, + ], + }, + ], + }, + create: () => ({}), + }; + + assert.throws(() => { + ruleTester.run("invalid-defaults", ruleWithInvalidDefaults, { + valid: [ + { + code: "foo", + options: [{}], + }, + ], + invalid: [], + }); + }, /Schema for rule invalid-defaults is invalid: default is ignored for: data1\.foo/u); + }); + + it("throw an error when an unknown config option is included", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: [{ code: "Eval(foo)", foo: "bar" }], + invalid: [], + }, + ); + }, /ESLint configuration in rule-tester is invalid./u); + }); + + it("throw an error when env is included in config", () => { + assert.throws(() => { + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: [{ code: "Eval(foo)", env: ["es6"] }], + invalid: [], + }, + ); + }, /Key "env": This appears to be in eslintrc format rather than flat config format/u); + }); + + it("should pass-through the tester config to the rule", () => { + ruleTester = new RuleTester({ + languageOptions: { + globals: { test: true }, + }, + }); + + ruleTester.run( + "no-test-global", + require("../../fixtures/testers/rule-tester/no-test-global"), + { + valid: ["var test = 'foo'", "var test2 = test"], + invalid: [ + { + code: "bar", + errors: 1, + languageOptions: { globals: { foo: true } }, + }, + ], + }, + ); + }); + + it("should throw an error if AST was modified", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/modify-ast"), + { + valid: ["var foo = 0;"], + invalid: [], + }, + ); + }, "Rule should not modify AST."); + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/modify-ast"), + { + valid: [], + invalid: [{ code: "var bar = 0;", errors: ["error"] }], + }, + ); + }, "Rule should not modify AST."); + }); + + it("should throw an error node.start is accessed with custom parser", () => { + const enhancedParser = require("../../fixtures/parsers/enhanced-parser"); + + ruleTester = new RuleTester({ + languageOptions: { + parser: enhancedParser, + }, + }); + + /* + * Note: More robust test for start/end found later in file. + * This one is just for checking the custom config has a + * parser that is wrapped. + */ + const usesStartEndRule = { + create() { + return { + CallExpression(node) { + noop(node.arguments[1].start); + }, + }; + }, + }; + + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: ["foo(a, b)"], + invalid: [], + }); + }, "Use node.range[0] instead of node.start"); + }); + + it("should throw an error if AST was modified (at Program)", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/modify-ast-at-first"), + { + valid: ["var foo = 0;"], + invalid: [], + }, + ); + }, "Rule should not modify AST."); + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/modify-ast-at-first"), + { + valid: [], + invalid: [{ code: "var bar = 0;", errors: ["error"] }], + }, + ); + }, "Rule should not modify AST."); + }); + + it("should throw an error if AST was modified (at Program:exit)", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/modify-ast-at-last"), + { + valid: ["var foo = 0;"], + invalid: [], + }, + ); + }, "Rule should not modify AST."); + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/modify-ast-at-last"), + { + valid: [], + invalid: [{ code: "var bar = 0;", errors: ["error"] }], + }, + ); + }, "Rule should not modify AST."); + }); + + it("should throw an error if rule uses start and end properties on nodes, tokens or comments", () => { + const usesStartEndRule = { + create(context) { + const sourceCode = context.sourceCode; + + return { + CallExpression(node) { + noop(node.arguments[1].start); + }, + "BinaryExpression[operator='+']"(node) { + noop(node.end); + }, + "UnaryExpression[operator='-']"(node) { + noop(sourceCode.getFirstToken(node).start); + }, + ConditionalExpression(node) { + noop(sourceCode.getFirstToken(node).end); + }, + BlockStatement(node) { + noop(sourceCode.getCommentsInside(node)[0].start); + }, + ObjectExpression(node) { + noop(sourceCode.getCommentsInside(node)[0].end); + }, + Decorator(node) { + noop(node.start); + }, + }; + }, + }; + + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: ["foo(a, b)"], + invalid: [], + }); + }, "Use node.range[0] instead of node.start"); + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: [], + invalid: [{ code: "var a = b * (c + d) / e;", errors: 1 }], + }); + }, "Use node.range[1] instead of node.end"); + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: [], + invalid: [{ code: "var a = -b * c;", errors: 1 }], + }); + }, "Use token.range[0] instead of token.start"); + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: ["var a = b ? c : d;"], + invalid: [], + }); + }, "Use token.range[1] instead of token.end"); + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: ["function f() { /* comment */ }"], + invalid: [], + }); + }, "Use token.range[0] instead of token.start"); + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: [], + invalid: [ + { code: "var x = //\n {\n //comment\n //\n}", errors: 1 }, + ], + }); + }, "Use token.range[1] instead of token.end"); + + const enhancedParser = require("../../fixtures/parsers/enhanced-parser"); + + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: [ + { + code: "foo(a, b)", + languageOptions: { parser: enhancedParser }, + }, + ], + invalid: [], + }); + }, "Use node.range[0] instead of node.start"); + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: [], + invalid: [ + { + code: "var a = b * (c + d) / e;", + languageOptions: { parser: enhancedParser }, + errors: 1, + }, + ], + }); + }, "Use node.range[1] instead of node.end"); + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: [], + invalid: [ + { + code: "var a = -b * c;", + languageOptions: { parser: enhancedParser }, + errors: 1, + }, + ], + }); + }, "Use token.range[0] instead of token.start"); + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: [ + { + code: "var a = b ? c : d;", + languageOptions: { parser: enhancedParser }, + }, + ], + invalid: [], + }); + }, "Use token.range[1] instead of token.end"); + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: [ + { + code: "function f() { /* comment */ }", + languageOptions: { parser: enhancedParser }, + }, + ], + invalid: [], + }); + }, "Use token.range[0] instead of token.start"); + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: [], + invalid: [ + { + code: "var x = //\n {\n //comment\n //\n}", + languageOptions: { parser: enhancedParser }, + errors: 1, + }, + ], + }); + }, "Use token.range[1] instead of token.end"); + + assert.throws(() => { + ruleTester.run("uses-start-end", usesStartEndRule, { + valid: [ + { + code: "@foo class A {}", + languageOptions: { + parser: require("../../fixtures/parsers/enhanced-parser2"), + }, + }, + ], + invalid: [], + }); + }, "Use node.range[0] instead of node.start"); + }); + + it("should throw an error if rule is a function", () => { + /** + * Legacy-format rule (a function instead of an object with `create` method). + * @param {RuleContext} context The ESLint rule context object. + * @returns {Object} Listeners. + */ + function functionStyleRule(context) { + return { + Program(node) { + context.report({ node, message: "bad" }); + }, + }; + } + + assert.throws(() => { + ruleTester.run("function-style-rule", functionStyleRule, { + valid: [], + invalid: [{ code: "var foo = bar;", errors: 1 }], + }); + }, "Rule must be an object with a `create` method"); + }); + + it("should throw an error if rule is an object without 'create' method", () => { + const rule = { + create_(context) { + return { + Program(node) { + context.report({ node, message: "bad" }); + }, + }; + }, + }; + + assert.throws(() => { + ruleTester.run("object-rule-without-create", rule, { + valid: [], + invalid: [{ code: "var foo = bar;", errors: 1 }], + }); + }, "Rule must be an object with a `create` method"); + }); + + it("should throw an error if no test scenarios given", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/modify-ast-at-last"), + ); + }, "Test Scenarios for rule foo : Could not find test scenario object"); + }); + + it("should throw an error if no acceptable test scenario object is given", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/modify-ast-at-last"), + [], + ); + }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios"); + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/modify-ast-at-last"), + "", + ); + }, "Test Scenarios for rule foo : Could not find test scenario object"); + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/modify-ast-at-last"), + 2, + ); + }, "Test Scenarios for rule foo : Could not find test scenario object"); + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/modify-ast-at-last"), + {}, + ); + }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios"); + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/modify-ast-at-last"), + { + valid: [], + }, + ); + }, "Test Scenarios for rule foo is invalid:\nCould not find any invalid test scenarios"); + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/modify-ast-at-last"), + { + invalid: [], + }, + ); + }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios"); + }); + + // Nominal message/messageId use cases + it("should assert match if message provided in both test and result.", () => { + assert.throws( + () => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withMessageOnly, + { + valid: [], + invalid: [ + { code: "foo", errors: [{ message: "something" }] }, + ], + }, + ); + }, + assertErrorMatches( + "Avoid using variables named 'foo'.", + "something", + ), + ); + + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withMessageOnly, + { + valid: [], + invalid: [ + { + code: "foo", + errors: [ + { message: "Avoid using variables named 'foo'." }, + ], + }, + ], + }, + ); + }); + + it("should assert match between messageId if provided in both test and result.", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withMetaWithData, + { + valid: [], + invalid: [ + { code: "foo", errors: [{ messageId: "unused" }] }, + ], + }, + ); + }, "messageId 'avoidFoo' does not match expected messageId 'unused'."); + + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withMetaWithData, + { + valid: [], + invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }], + }, + ); + }); + + it("should assert match between resulting message output if messageId and data provided in both test and result", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withMetaWithData, + { + valid: [], + invalid: [ + { + code: "foo", + errors: [ + { + messageId: "avoidFoo", + data: { name: "notFoo" }, + }, + ], + }, + ], + }, + ); + }, "Hydrated message \"Avoid using variables named 'notFoo'.\" does not match \"Avoid using variables named 'foo'.\""); + }); + + it("should throw if the message has a single unsubstituted placeholder when data is not specified", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withMissingData, + { + valid: [], + invalid: [ + { code: "foo", errors: [{ messageId: "avoidFoo" }] }, + ], + }, + ); + }, "The reported message has an unsubstituted placeholder 'name'. Please provide the missing value via the 'data' property in the context.report() call."); + }); + + it("should throw if the message has a single unsubstituted placeholders when data is specified", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withMissingData, + { + valid: [], + invalid: [ + { + code: "foo", + errors: [ + { + messageId: "avoidFoo", + data: { name: "name" }, + }, + ], + }, + ], + }, + ); + }, "Hydrated message \"Avoid using variables named 'name'.\" does not match \"Avoid using variables named '{{ name }}'."); + }); + + it("should throw if the message has multiple unsubstituted placeholders when data is not specified", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withMultipleMissingDataProperties, + { + valid: [], + invalid: [ + { code: "foo", errors: [{ messageId: "avoidFoo" }] }, + ], + }, + ); + }, "The reported message has unsubstituted placeholders: 'type', 'name'. Please provide the missing values via the 'data' property in the context.report() call."); + }); + + it("should not throw if the data in the message contains placeholders not present in the raw message", () => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withPlaceholdersInData, + { + valid: [], + invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }], + }, + ); + }); + + it("should throw if the data in the message contains the same placeholder and data is not specified", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withSamePlaceholdersInData, + { + valid: [], + invalid: [ + { code: "foo", errors: [{ messageId: "avoidFoo" }] }, + ], + }, + ); + }, "The reported message has an unsubstituted placeholder 'name'. Please provide the missing value via the 'data' property in the context.report() call."); + }); + + it("should not throw if the data in the message contains the same placeholder and data is specified", () => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withSamePlaceholdersInData, + { + valid: [], + invalid: [ + { + code: "foo", + errors: [ + { + messageId: "avoidFoo", + data: { name: "{{ name }}" }, + }, + ], + }, + ], + }, + ); + }); + + it("should not throw an error for specifying non-string data values", () => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withNonStringData, + { + valid: [], + invalid: [ + { + code: "0", + errors: [{ messageId: "avoid", data: { value: 0 } }], + }, + ], + }, + ); + }); + + // messageId/message misconfiguration cases + it("should throw if user tests for both message and messageId", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withMetaWithData, + { + valid: [], + invalid: [ + { + code: "foo", + errors: [ + { message: "something", messageId: "avoidFoo" }, + ], + }, + ], + }, + ); + }, "Error should not specify both 'message' and a 'messageId'."); + }); + it("should throw if user tests for messageId but the rule doesn't use the messageId meta syntax.", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withMessageOnly, + { + valid: [], + invalid: [ + { code: "foo", errors: [{ messageId: "avoidFoo" }] }, + ], + }, + ); + }, "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'"); + }); + it("should throw if user tests for messageId not listed in the rule's meta syntax.", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withMetaWithData, + { + valid: [], + invalid: [ + { code: "foo", errors: [{ messageId: "useFoo" }] }, + ], + }, + ); + }, /Invalid messageId 'useFoo'/u); + }); + it("should throw if data provided without messageId.", () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/messageId") + .withMetaWithData, + { + valid: [], + invalid: [{ code: "foo", errors: [{ data: "something" }] }], + }, + ); + }, "Test error must specify either a 'messageId' or 'message'."); + }); + + // fixable rules with or without `meta` property + it("should not throw an error if a rule that has `meta.fixable` produces fixes", () => { + const replaceProgramWith5Rule = { + meta: { + fixable: "code", + }, + create(context) { + return { + Program(node) { + context.report({ + node, + message: "bad", + fix: fixer => fixer.replaceText(node, "5"), + }); + }, + }; + }, + }; + + ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, { + valid: [], + invalid: [{ code: "var foo = bar;", output: "5", errors: 1 }], + }); + }); + it("should throw an error if a new-format rule that doesn't have `meta` produces fixes", () => { + const replaceProgramWith5Rule = { + create(context) { + return { + Program(node) { + context.report({ + node, + message: "bad", + fix: fixer => fixer.replaceText(node, "5"), + }); + }, + }; + }, + }; + + assert.throws(() => { + ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, { + valid: [], + invalid: [{ code: "var foo = bar;", output: "5", errors: 1 }], + }); + }, /Fixable rules must set the `meta\.fixable` property/u); + }); + + it("should allow testing of any file", () => { + const filenames = [ + /* + * Ignored by default + * https://github.com/eslint/eslint/issues/19471 + */ + "node_modules/foo.js", + ".git/foo.js", + + /* + * Absolute paths + * https://github.com/eslint/eslint/issues/17962 + */ + "/an-absolute-path/foo.js", + "C:\\an-absolute-path\\foo.js", + ]; + + ruleTester.run( + "no-eval", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: filenames.map((filename, index) => ({ + code: `Eval(foo${index})`, + filename, + })), + invalid: filenames.map((filename, index) => ({ + code: `eval(foo${index})`, + errors: [ + { message: "eval sucks.", type: "CallExpression" }, + ], + filename, + })), + }, + ); + }); + + describe("suggestions", () => { + it("should throw if suggestions are available but not specified", () => { + assert.throw(() => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/suggestions") + .basic, + { + valid: ["var boo;"], + invalid: [ + { + code: "var foo;", + errors: [ + { + message: + "Avoid using identifiers named 'foo'.", + }, + ], + }, + ], + }, + ); + }, "Error at index 0 has suggestions. Please specify 'suggestions' property on the test error object."); + }); + + it("should pass with valid suggestions (tested using desc)", () => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/suggestions").basic, + { + valid: ["var boo;"], + invalid: [ + { + code: "var foo;", + errors: [ + { + message: + "Avoid using identifiers named 'foo'.", + suggestions: [ + { + desc: "Rename identifier 'foo' to 'bar'", + output: "var bar;", + }, + ], + }, + ], + }, + ], + }, + ); + }); + + it("should pass with suggestions on multiple lines", () => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/suggestions").basic, + { + valid: [], + invalid: [ + { + code: "function foo() {\n var foo = 1;\n}", + errors: [ + { + message: + "Avoid using identifiers named 'foo'.", + suggestions: [ + { + desc: "Rename identifier 'foo' to 'bar'", + output: "function bar() {\n var foo = 1;\n}", + }, + ], + }, + { + message: + "Avoid using identifiers named 'foo'.", + suggestions: [ + { + desc: "Rename identifier 'foo' to 'bar'", + output: "function foo() {\n var bar = 1;\n}", + }, + ], + }, + ], + }, + ], + }, + ); + }); + + it("should pass with valid suggestions (tested using messageIds)", () => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + messageId: "renameFoo", + output: "var bar;", + }, + { + messageId: "renameFoo", + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }); + + it("should pass with valid suggestions (one tested using messageIds, the other using desc)", () => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + messageId: "renameFoo", + output: "var bar;", + }, + { + desc: "Rename identifier 'foo' to 'baz'", + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }); + + it("should fail with valid suggestions when testing using both desc and messageIds for the same suggestion", () => { + assert.throw(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + desc: "Rename identifier 'foo' to 'bar'", + messageId: "renameFoo", + output: "var bar;", + }, + { + desc: "Rename identifier 'foo' to 'baz'", + messageId: "renameFoo", + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "Error Suggestion at index 0: Test should not specify both 'desc' and 'messageId'."); + }); + + it("should pass with valid suggestions (tested using only desc on a rule that utilizes meta.messages)", () => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + desc: "Rename identifier 'foo' to 'bar'", + output: "var bar;", + }, + { + desc: "Rename identifier 'foo' to 'baz'", + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }); + + it("should pass with valid suggestions (tested using messageIds and data)", () => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + messageId: "renameFoo", + data: { newName: "bar" }, + output: "var bar;", + }, + { + messageId: "renameFoo", + data: { newName: "baz" }, + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }); + + it("should fail with a single missing data placeholder when data is not specified", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMissingPlaceholderData, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + messageId: "renameFoo", + output: "var bar;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "The message of the suggestion has an unsubstituted placeholder 'newName'. Please provide the missing value via the 'data' property for the suggestion in the context.report() call."); + }); + + it("should fail with a single missing data placeholder when data is specified", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMissingPlaceholderData, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + messageId: "renameFoo", + data: { other: "name" }, + output: "var bar;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "The message of the suggestion has an unsubstituted placeholder 'newName'. Please provide the missing value via the 'data' property for the suggestion in the context.report() call."); + }); + + it("should fail with multiple missing data placeholders when data is not specified", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMultipleMissingPlaceholderDataProperties, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + messageId: "rename", + output: "var bar;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "The message of the suggestion has unsubstituted placeholders: 'currentName', 'newName'. Please provide the missing values via the 'data' property for the suggestion in the context.report() call."); + }); + + it("should fail when tested using empty suggestion test objects even if the array length is correct", () => { + assert.throw(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [{}, {}], + }, + ], + }, + ], + }, + ); + }, "Error Suggestion at index 0: Test must specify either 'messageId' or 'desc'"); + }); + + it("should fail when tested using non-empty suggestion test objects without an output property", () => { + assert.throw(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { messageId: "renameFoo" }, + {}, + ], + }, + ], + }, + ], + }, + ); + }, 'Error Suggestion at index 0: The "output" property is required.'); + }); + + it("should support explicitly expecting no suggestions", () => { + [void 0, null, false, []].forEach(suggestions => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/no-eval"), + { + valid: [], + invalid: [ + { + code: "eval('var foo');", + errors: [ + { + message: "eval sucks.", + suggestions, + }, + ], + }, + ], + }, + ); + }); + }); + + it("should fail when expecting no suggestions and there are suggestions", () => { + [void 0, null, false, []].forEach(suggestions => { + assert.throws(() => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/suggestions") + .basic, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + message: + "Avoid using identifiers named 'foo'.", + suggestions, + }, + ], + }, + ], + }, + ); + }, "Error should have no suggestions on error with message: \"Avoid using identifiers named 'foo'.\""); + }); + }); + + it("should fail when testing for suggestions that don't exist", () => { + assert.throws(() => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + message: "Bad var.", + suggestions: [ + { + messageId: + "this-does-not-exist", + }, + ], + }, + ], + }, + ], + }, + ); + }, 'Error should have suggestions on error with message: "Bad var."'); + }); + + it("should support specifying only the amount of suggestions", () => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/suggestions").basic, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + message: + "Avoid using identifiers named 'foo'.", + suggestions: 1, + }, + ], + }, + ], + }, + ); + }); + + it("should fail when there are a different number of suggestions", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/suggestions") + .basic, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + message: + "Avoid using identifiers named 'foo'.", + suggestions: 2, + }, + ], + }, + ], + }, + ); + }, "Error should have 2 suggestions. Instead found 1 suggestions"); + }); + + it("should fail when there are a different number of suggestions for arrays", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/suggestions") + .basic, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + message: + "Avoid using identifiers named 'foo'.", + suggestions: [ + { + desc: "Rename identifier 'foo' to 'bar'", + output: "var bar;", + }, + { + desc: "Rename identifier 'foo' to 'baz'", + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "Error should have 2 suggestions. Instead found 1 suggestions"); + }); + + it("should fail when the suggestion property is neither a number nor an array", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/suggestions") + .basic, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + message: + "Avoid using identifiers named 'foo'.", + suggestions: "1", + }, + ], + }, + ], + }, + ); + }, "Test error object property 'suggestions' should be an array or a number"); + }); + + it("should throw if suggestion fix made a syntax error.", () => { + assert.throw(() => { + ruleTester.run( + "foo", + { + meta: { hasSuggestions: true }, + create(context) { + return { + Identifier(node) { + context.report({ + node, + message: "make a syntax error", + suggest: [ + { + desc: "make a syntax error", + fix(fixer) { + return fixer.replaceText( + node, + "one two", + ); + }, + }, + ], + }); + }, + }; + }, + }, + { + valid: [""], + invalid: [ + { + code: "one()", + errors: [ + { + message: "make a syntax error", + suggestions: [ + { + desc: "make a syntax error", + output: "one two()", + }, + ], + }, + ], + }, + ], + }, + ); + }, /A fatal parsing error occurred in suggestion fix\.\nError: .+\nSuggestion output:\n.+/u); + }); + + it("should throw if the suggestion description doesn't match", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/suggestions") + .basic, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + message: + "Avoid using identifiers named 'foo'.", + suggestions: [ + { + desc: "not right", + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "Error Suggestion at index 0: desc should be \"not right\" but got \"Rename identifier 'foo' to 'bar'\" instead."); + }); + + it("should pass when different suggestion matchers use desc and messageId", () => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + desc: "Rename identifier 'foo' to 'bar'", + output: "var bar;", + }, + { + messageId: "renameFoo", + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }); + + it("should throw if the suggestion messageId doesn't match", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + messageId: "unused", + output: "var bar;", + }, + { + messageId: "renameFoo", + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "Error Suggestion at index 0: messageId should be 'unused' but got 'renameFoo' instead."); + }); + + it("should throw if test specifies messageId for a rule that doesn't have meta.messages", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/suggestions") + .basic, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + message: + "Avoid using identifiers named 'foo'.", + suggestions: [ + { + messageId: "renameFoo", + output: "var bar;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "Error Suggestion at index 0: Test can not use 'messageId' if rule under test doesn't define 'meta.messages'."); + }); + + it("should throw if test specifies messageId that doesn't exist in the rule's meta.messages", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + messageId: "renameFoo", + output: "var bar;", + }, + { + messageId: "removeFoo", + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "Error Suggestion at index 1: Test has invalid messageId 'removeFoo', the rule under test allows only one of ['avoidFoo', 'unused', 'renameFoo']."); + }); + + it("should throw if hydrated desc doesn't match (wrong data value)", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + messageId: "renameFoo", + data: { newName: "car" }, + output: "var bar;", + }, + { + messageId: "renameFoo", + data: { newName: "baz" }, + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "Error Suggestion at index 0: Hydrated test desc \"Rename identifier 'foo' to 'car'\" does not match received desc \"Rename identifier 'foo' to 'bar'\"."); + }); + + it("should throw if hydrated desc doesn't match (wrong data key)", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + messageId: "renameFoo", + data: { newName: "bar" }, + output: "var bar;", + }, + { + messageId: "renameFoo", + data: { name: "baz" }, + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "Error Suggestion at index 1: Hydrated test desc \"Rename identifier 'foo' to '{{ newName }}'\" does not match received desc \"Rename identifier 'foo' to 'baz'\"."); + }); + + it("should throw if test specifies both desc and data", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + desc: "Rename identifier 'foo' to 'bar'", + messageId: "renameFoo", + data: { newName: "bar" }, + output: "var bar;", + }, + { + messageId: "renameFoo", + data: { newName: "baz" }, + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "Error Suggestion at index 0: Test should not specify both 'desc' and 'data'."); + }); + + it("should throw if test uses data but doesn't specify messageId", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + messageId: "renameFoo", + data: { newName: "bar" }, + output: "var bar;", + }, + { + data: { newName: "baz" }, + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "Error Suggestion at index 1: Test must specify 'messageId' if 'data' is used."); + }); + + it("should throw if the resulting suggestion output doesn't match", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/suggestions") + .basic, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + message: + "Avoid using identifiers named 'foo'.", + suggestions: [ + { + desc: "Rename identifier 'foo' to 'bar'", + output: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "Expected the applied suggestion fix to match the test suggestion output"); + }); + + it("should throw if the resulting suggestion output is the same as the original source code", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/suggestions") + .withFixerWithoutChanges, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + message: + "Avoid using identifiers named 'foo'.", + suggestions: [ + { + desc: "Rename identifier 'foo' to 'bar'", + output: "var foo;", + }, + ], + }, + ], + }, + ], + }, + ); + }, "The output of a suggestion should differ from the original source code for suggestion at index: 0 on error with message: \"Avoid using identifiers named 'foo'.\""); + }); + + it("should fail when specified suggestion isn't an object", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/suggestions") + .basic, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + message: + "Avoid using identifiers named 'foo'.", + suggestions: [null], + }, + ], + }, + ], + }, + ); + }, "Test suggestion in 'suggestions' array must be an object."); + + assert.throws(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + messageId: "renameFoo", + output: "var bar;", + }, + "Rename identifier 'foo' to 'baz'", + ], + }, + ], + }, + ], + }, + ); + }, "Test suggestion in 'suggestions' array must be an object."); + }); + + it("should fail when the suggestion is an object with an unknown property name", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-basic", + require("../../fixtures/testers/rule-tester/suggestions") + .basic, + { + valid: ["var boo;"], + invalid: [ + { + code: "var foo;", + errors: [ + { + message: + "Avoid using identifiers named 'foo'.", + suggestions: [ + { + message: + "Rename identifier 'foo' to 'bar'", + }, + ], + }, + ], + }, + ], + }, + ); + }, /Invalid suggestion property name 'message'/u); + }); + + it("should fail when any of the suggestions is an object with an unknown property name", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-messageIds", + require("../../fixtures/testers/rule-tester/suggestions") + .withMessageIds, + { + valid: [], + invalid: [ + { + code: "var foo;", + errors: [ + { + messageId: "avoidFoo", + suggestions: [ + { + messageId: "renameFoo", + output: "var bar;", + }, + { + messageId: "renameFoo", + outpt: "var baz;", + }, + ], + }, + ], + }, + ], + }, + ); + }, /Invalid suggestion property name 'outpt'/u); + }); + + it("should fail if a rule produces two suggestions with the same description", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-with-duplicate-descriptions", + require("../../fixtures/testers/rule-tester/suggestions") + .withDuplicateDescriptions, + { + valid: [], + invalid: [{ code: "var foo = bar;", errors: 1 }], + }, + ); + }, "Suggestion message 'Rename 'foo' to 'bar'' reported from suggestion 1 was previously reported by suggestion 0. Suggestion messages should be unique within an error."); + }); + + it("should fail if a rule produces two suggestions with the same messageId without data", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-with-duplicate-messageids-no-data", + require("../../fixtures/testers/rule-tester/suggestions") + .withDuplicateMessageIdsNoData, + { + valid: [], + invalid: [{ code: "var foo = bar;", errors: 1 }], + }, + ); + }, "Suggestion message 'Rename identifier' reported from suggestion 1 was previously reported by suggestion 0. Suggestion messages should be unique within an error."); + }); + + it("should fail if a rule produces two suggestions with the same messageId with data", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-with-duplicate-messageids-with-data", + require("../../fixtures/testers/rule-tester/suggestions") + .withDuplicateMessageIdsWithData, + { + valid: [], + invalid: [{ code: "var foo = bar;", errors: 1 }], + }, + ); + }, "Suggestion message 'Rename identifier 'foo' to 'bar'' reported from suggestion 1 was previously reported by suggestion 0. Suggestion messages should be unique within an error."); + }); + + it("should throw an error if a rule that doesn't have `meta.hasSuggestions` enabled produces suggestions", () => { + assert.throws(() => { + ruleTester.run( + "suggestions-missing-hasSuggestions-property", + require("../../fixtures/testers/rule-tester/suggestions") + .withoutHasSuggestionsProperty, + { + valid: [], + invalid: [ + { code: "var foo = bar;", output: "5", errors: 1 }, + ], + }, + ); + }, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`."); + }); + }); + + /** + * Asserts that a particular value will be emitted from an EventEmitter. + * @param {EventEmitter} emitter The emitter that should emit a value + * @param {string} emitType The type of emission to listen for + * @param {any} expectedValue The value that should be emitted + * @returns {Promise} A Promise that fulfills if the value is emitted, and rejects if something else is emitted. + * The Promise will be indefinitely pending if no value is emitted. + */ + function assertEmitted(emitter, emitType, expectedValue) { + return new Promise((resolve, reject) => { + emitter.once(emitType, emittedValue => { + if (emittedValue === expectedValue) { + resolve(); + } else { + reject( + new Error( + `Expected ${expectedValue} to be emitted but ${emittedValue} was emitted instead.`, + ), + ); + } + }); + }); + } + + describe("naming test cases", () => { + it("should use the first argument as the name of the test suite", () => { + const assertion = assertEmitted( + ruleTesterTestEmitter, + "describe", + "this-is-a-rule-name", + ); + + ruleTester.run( + "this-is-a-rule-name", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [], + }, + ); + + return assertion; + }); + + it("should use the test code as the name of the tests for valid code (string form)", () => { + const assertion = assertEmitted( + ruleTesterTestEmitter, + "it", + "valid(code);", + ); + + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["valid(code);"], + invalid: [], + }, + ); + + return assertion; + }); + + it("should use the test code as the name of the tests for valid code (object form)", () => { + const assertion = assertEmitted( + ruleTesterTestEmitter, + "it", + "valid(code);", + ); + + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [ + { + code: "valid(code);", + }, + ], + invalid: [], + }, + ); + + return assertion; + }); + + it("should use the test code as the name of the tests for invalid code", () => { + const assertion = assertEmitted( + ruleTesterTestEmitter, + "it", + "var x = invalid(code);", + ); + + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [ + { + code: "var x = invalid(code);", + output: " x = invalid(code);", + errors: 1, + }, + ], + }, + ); + + return assertion; + }); + + // https://github.com/eslint/eslint/issues/8142 + it("should use the empty string as the name of the test if the test case is an empty string", () => { + const assertion = assertEmitted(ruleTesterTestEmitter, "it", ""); + + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [ + { + code: "", + }, + ], + invalid: [], + }, + ); + + return assertion; + }); + + it('should use the "name" property if set to a non-empty string', () => { + const assertion = assertEmitted( + ruleTesterTestEmitter, + "it", + "my test", + ); + + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [ + { + name: "my test", + code: "var x = invalid(code);", + output: " x = invalid(code);", + errors: 1, + }, + ], + }, + ); + + return assertion; + }); + + it('should use the "name" property if set to a non-empty string for valid cases too', () => { + const assertion = assertEmitted( + ruleTesterTestEmitter, + "it", + "my test", + ); + + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [ + { + name: "my test", + code: "valid(code);", + }, + ], + invalid: [], + }, + ); + + return assertion; + }); + + it('should use the test code as the name if the "name" property is set to an empty string', () => { + const assertion = assertEmitted( + ruleTesterTestEmitter, + "it", + "var x = invalid(code);", + ); + + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [ + { + name: "", + code: "var x = invalid(code);", + output: " x = invalid(code);", + errors: 1, + }, + ], + }, + ); + + return assertion; + }); + + it('should throw if "name" property is not a string', () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [{ code: "foo", name: 123 }], + invalid: [{ code: "foo" }], + }, + ); + }, /Optional test case property 'name' must be a string/u); + + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["foo"], + invalid: [{ code: "foo", name: 123 }], + }, + ); + }, /Optional test case property 'name' must be a string/u); + }); + + it('should throw if "code" property is not a string', () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [{ code: 123 }], + invalid: [{ code: "foo" }], + }, + ); + }, /Test case must specify a string value for 'code'/u); + + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [123], + invalid: [{ code: "foo" }], + }, + ); + }, /Test case must specify a string value for 'code'/u); + + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["foo"], + invalid: [{ code: 123 }], + }, + ); + }, /Test case must specify a string value for 'code'/u); + }); + + it('should throw if "code" property is missing', () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [{}], + invalid: [{ code: "foo" }], + }, + ); + }, /Test case must specify a string value for 'code'/u); + + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["foo"], + invalid: [{}], + }, + ); + }, /Test case must specify a string value for 'code'/u); + }); + }); + + // https://github.com/eslint/eslint/issues/11615 + it("should fail the case if autofix made a syntax error.", () => { + assert.throw(() => { + ruleTester.run( + "foo", + { + meta: { + fixable: "code", + }, + create(context) { + return { + Identifier(node) { + context.report({ + node, + message: "make a syntax error", + fix(fixer) { + return fixer.replaceText( + node, + "one two", + ); + }, + }); + }, + }; + }, + }, + { + valid: ["one()"], + invalid: [], + }, + ); + }, /A fatal parsing error occurred in autofix.\nError: .+\nAutofix output:\n.+/u); + }); + + describe("type checking", () => { + it('should throw if "only" property is not a boolean', () => { + // "only" has to be falsy as itOnly is not mocked for all test cases + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [{ code: "foo", only: "" }], + invalid: [], + }, + ); + }, /Optional test case property 'only' must be a boolean/u); + + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [{ code: "foo", only: 0, errors: 1 }], + }, + ); + }, /Optional test case property 'only' must be a boolean/u); + }); + + it('should throw if "filename" property is not a string', () => { + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [{ code: "foo", filename: false }], + invalid: [], + }, + ); + }, /Optional test case property 'filename' must be a string/u); + + assert.throws(() => { + ruleTester.run( + "foo", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["foo"], + invalid: [{ code: "foo", errors: 1, filename: 0 }], + }, + ); + }, /Optional test case property 'filename' must be a string/u); + }); + }); + + describe("sanitize test cases", () => { + let originalRuleTesterIt; + let spyRuleTesterIt; + + before(() => { + originalRuleTesterIt = RuleTester.it; + spyRuleTesterIt = sinon.spy(); + RuleTester.it = spyRuleTesterIt; + }); + after(() => { + RuleTester.it = originalRuleTesterIt; + }); + beforeEach(() => { + spyRuleTesterIt.resetHistory(); + ruleTester = new RuleTester(); + }); + it("should present newline when using back-tick as new line", () => { + const code = ` var foo = bar;`; - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - code, - errors: [/^Bad var/u] - } - ] - }); - sinon.assert.calledWith(spyRuleTesterIt, code); - }); - it("should present \\u0000 as a string", () => { - const code = "\u0000"; - - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - code, - errors: [/^Bad var/u] - } - ] - }); - sinon.assert.calledWith(spyRuleTesterIt, "\\u0000"); - }); - it("should present the pipe character correctly", () => { - const code = "var foo = bar || baz;"; - - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - code, - errors: [/^Bad var/u] - } - ] - }); - sinon.assert.calledWith(spyRuleTesterIt, code); - }); - - }); - - describe("SourceCode#getComments()", () => { - const useGetCommentsRule = { - create: context => ({ - Program(node) { - const sourceCode = context.sourceCode; - - sourceCode.getComments(node); - } - }) - }; - - it("should throw if called from a valid test case", () => { - assert.throws(() => { - ruleTester.run("use-get-comments", useGetCommentsRule, { - valid: [""], - invalid: [] - }); - }, /`SourceCode#getComments\(\)` is deprecated/u); - }); - - it("should throw if called from an invalid test case", () => { - assert.throws(() => { - ruleTester.run("use-get-comments", useGetCommentsRule, { - valid: [], - invalid: [{ - code: "", - errors: [{}] - }] - }); - }, /`SourceCode#getComments\(\)` is deprecated/u); - }); - }); - - - describe("SourceCode forbidden methods", () => { - - [ - "applyInlineConfig", - "applyLanguageOptions", - "finalize" - ].forEach(methodName => { - - const useForbiddenMethodRule = { - create: context => ({ - Program() { - const sourceCode = context.sourceCode; - - sourceCode[methodName](); - } - }) - }; - - it(`should throw if ${methodName} is called from a valid test case`, () => { - assert.throws(() => { - ruleTester.run("use-forbidden-method", useForbiddenMethodRule, { - valid: [""], - invalid: [] - }); - }, `\`SourceCode#${methodName}()\` cannot be called inside a rule.`); - }); - - it(`should throw if ${methodName} is called from an invalid test case`, () => { - assert.throws(() => { - ruleTester.run("use-forbidden-method", useForbiddenMethodRule, { - valid: [], - invalid: [{ - code: "", - errors: [{}] - }] - }); - }, `\`SourceCode#${methodName}()\` cannot be called inside a rule.`); - }); - - }); - - }); - - describe("Subclassing", () => { - - it("should allow subclasses to set the describe/it/itOnly statics and should correctly use those values", () => { - const assertionDescribe = assertEmitted(ruleTesterTestEmitter, "custom describe", "this-is-a-rule-name"); - const assertionIt = assertEmitted(ruleTesterTestEmitter, "custom it", "valid(code);"); - const assertionItOnly = assertEmitted(ruleTesterTestEmitter, "custom itOnly", "validOnly(code);"); - - /** - * Subclass for testing - */ - class RuleTesterSubclass extends RuleTester { } - RuleTesterSubclass.describe = function(text, method) { - ruleTesterTestEmitter.emit("custom describe", text, method); - return method.call(this); - }; - RuleTesterSubclass.it = function(text, method) { - ruleTesterTestEmitter.emit("custom it", text, method); - return method.call(this); - }; - RuleTesterSubclass.itOnly = function(text, method) { - ruleTesterTestEmitter.emit("custom itOnly", text, method); - return method.call(this); - }; - - const ruleTesterSubclass = new RuleTesterSubclass(); - - ruleTesterSubclass.run("this-is-a-rule-name", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [ - "valid(code);", - { - code: "validOnly(code);", - only: true - } - ], - invalid: [] - }); - - return Promise.all([ - assertionDescribe, - assertionIt, - assertionItOnly - ]); - }); - - }); - - describe("Optional Test Suites", () => { - let originalRuleTesterDescribe; - let spyRuleTesterDescribe; - - before(() => { - originalRuleTesterDescribe = RuleTester.describe; - spyRuleTesterDescribe = sinon.spy((title, callback) => callback()); - RuleTester.describe = spyRuleTesterDescribe; - }); - after(() => { - RuleTester.describe = originalRuleTesterDescribe; - }); - beforeEach(() => { - spyRuleTesterDescribe.resetHistory(); - ruleTester = new RuleTester(); - }); - - it("should create a test suite with the rule name even if there are no test cases", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [] - }); - sinon.assert.calledWith(spyRuleTesterDescribe, "no-var"); - }); - - it("should create a valid test suite if there is a valid test case", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: ["value = 0;"], - invalid: [] - }); - sinon.assert.calledWith(spyRuleTesterDescribe, "valid"); - }); - - it("should not create a valid test suite if there are no valid test cases", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - code: "var value = 0;", - errors: [/^Bad var/u], - output: " value = 0;" - } - ] - }); - sinon.assert.neverCalledWith(spyRuleTesterDescribe, "valid"); - }); - - it("should create an invalid test suite if there is an invalid test case", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: [], - invalid: [ - { - code: "var value = 0;", - errors: [/^Bad var/u], - output: " value = 0;" - } - ] - }); - sinon.assert.calledWith(spyRuleTesterDescribe, "invalid"); - }); - - it("should not create an invalid test suite if there are no invalid test cases", () => { - ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), { - valid: ["value = 0;"], - invalid: [] - }); - sinon.assert.neverCalledWith(spyRuleTesterDescribe, "invalid"); - }); - }); + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [ + { + code, + errors: [/^Bad var/u], + }, + ], + }, + ); + sinon.assert.calledWith(spyRuleTesterIt, code); + }); + it("should present \\u0000 as a string", () => { + const code = "\u0000"; + + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [ + { + code, + errors: [/^Bad var/u], + }, + ], + }, + ); + sinon.assert.calledWith(spyRuleTesterIt, "\\u0000"); + }); + it("should present the pipe character correctly", () => { + const code = "var foo = bar || baz;"; + + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [ + { + code, + errors: [/^Bad var/u], + }, + ], + }, + ); + sinon.assert.calledWith(spyRuleTesterIt, code); + }); + }); + + describe("duplicate test cases", () => { + describe("valid test cases", () => { + it("throws with duplicate string test cases", () => { + assert.throws(() => { + ruleTester.run( + "foo", + { + meta: {}, + create() { + return {}; + }, + }, + { + valid: ["foo", "foo"], + invalid: [], + }, + ); + }, "detected duplicate test case"); + }); + + it("throws with duplicate object test cases", () => { + assert.throws(() => { + ruleTester.run( + "foo", + { + meta: {}, + create() { + return {}; + }, + }, + { + valid: [{ code: "foo" }, { code: "foo" }], + invalid: [], + }, + ); + }, "detected duplicate test case"); + }); + + it("throws with duplicate object test cases when they are the same object", () => { + const test = { code: "foo" }; + assert.throws(() => { + ruleTester.run( + "foo", + { + meta: {}, + create() { + return {}; + }, + }, + { + valid: [test, test], + invalid: [], + }, + ); + }, "detected duplicate test case"); + }); + + it("throws with duplicate object test cases that have multiple references to the same object", () => { + const obj1 = { foo: { bar: "baz" } }; + const obj2 = { foo: { bar: "baz" } }; + + assert.throws(() => { + ruleTester.run( + "foo", + { + meta: {}, + create() { + return {}; + }, + }, + { + valid: [ + { + code: "foo", + settings: { qux: obj1, quux: obj1 }, + }, + { + code: "foo", + settings: { qux: obj2, quux: obj2 }, + }, + ], + invalid: [], + }, + ); + }, "detected duplicate test case"); + }); + + it("does not throw with duplicate object test cases that have circular references", () => { + const obj1 = { foo: "bar" }; + obj1.circular = obj1; + const obj2 = { foo: "bar" }; + obj2.circular = obj2; + + ruleTester.run( + "foo", + { + meta: {}, + create() { + return {}; + }, + }, + { + valid: [ + { code: "foo", settings: { baz: obj1 } }, + { code: "foo", settings: { baz: obj2 } }, + ], + invalid: [], + }, + ); + }); + + it("throws with string and object test cases", () => { + assert.throws(() => { + ruleTester.run( + "foo", + { + meta: {}, + create() { + return {}; + }, + }, + { + valid: ["foo", { code: "foo" }], + invalid: [], + }, + ); + }, "detected duplicate test case"); + }); + + it("ignores the name property", () => { + assert.throws(() => { + ruleTester.run( + "foo", + { + meta: {}, + create() { + return {}; + }, + }, + { + valid: [ + { code: "foo" }, + { name: "bar", code: "foo" }, + ], + invalid: [], + }, + ); + }, "detected duplicate test case"); + }); + + it("does not ignore top level test case properties nested in other test case properties", () => { + ruleTester.run( + "foo", + { + meta: { schema: [{ type: "object" }] }, + create() { + return {}; + }, + }, + { + valid: [ + { + options: [{ name: "foo" }], + name: "foo", + code: "same", + }, + { + options: [{ name: "bar" }], + name: "bar", + code: "same", + }, + ], + invalid: [], + }, + ); + }); + + it("does not throw an error for defining the same test case in different run calls", () => { + const rule = { + meta: {}, + create() { + return {}; + }, + }; + + ruleTester.run("foo", rule, { + valid: ["foo"], + invalid: [], + }); + + ruleTester.run("foo", rule, { + valid: ["foo"], + invalid: [], + }); + }); + }); + + describe("invalid test cases", () => { + it("throws with duplicate object test cases", () => { + assert.throws(() => { + ruleTester.run( + "foo", + { + meta: {}, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + }, + }; + }, + }, + { + valid: ["foo"], + invalid: [ + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + }, + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + }, + ], + }, + ); + }, "detected duplicate test case"); + }); + + it("throws with duplicate object test cases when they are the same object", () => { + const test = { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + }; + + assert.throws(() => { + ruleTester.run( + "foo", + { + meta: {}, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + }, + }; + }, + }, + { + valid: ["foo"], + invalid: [test, test], + }, + ); + }, "detected duplicate test case"); + }); + + it("throws with duplicate object test cases that have multiple references to the same object", () => { + const obj1 = { foo: { bar: "baz" } }; + const obj2 = { foo: { bar: "baz" } }; + + assert.throws(() => { + ruleTester.run( + "foo", + { + meta: {}, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + }, + }; + }, + }, + { + valid: ["foo"], + invalid: [ + { + code: "const x = 123;", + settings: { qux: obj1, quux: obj1 }, + errors: [{ message: "foo bar" }], + }, + { + code: "const x = 123;", + settings: { qux: obj2, quux: obj2 }, + errors: [{ message: "foo bar" }], + }, + ], + }, + ); + }, "detected duplicate test case"); + }); + + it("does not throw with duplicate object test cases that have circular references", () => { + const obj1 = { foo: "bar" }; + obj1.circular = obj1; + const obj2 = { foo: "bar" }; + obj2.circular = obj2; + + ruleTester.run( + "foo", + { + meta: {}, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + }, + }; + }, + }, + { + valid: ["foo"], + invalid: [ + { + code: "const x = 123;", + settings: { baz: obj1 }, + errors: [{ message: "foo bar" }], + }, + { + code: "const x = 123;", + settings: { baz: obj2 }, + errors: [{ message: "foo bar" }], + }, + ], + }, + ); + }); + + it("throws with duplicate object test cases when options is a primitive", () => { + assert.throws(() => { + ruleTester.run( + "foo", + { + meta: { schema: false }, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + }, + }; + }, + }, + { + valid: ["foo"], + invalid: [ + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + options: ["abc"], + }, + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + options: ["abc"], + }, + ], + }, + ); + }, "detected duplicate test case"); + }); + + it("throws with duplicate object test cases when options is a nested serializable object", () => { + assert.throws(() => { + ruleTester.run( + "foo", + { + meta: { schema: false }, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + }, + }; + }, + }, + { + valid: ["foo"], + invalid: [ + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + options: [ + { foo: [{ a: true, b: [1, 2, 3] }] }, + ], + }, + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + options: [ + { foo: [{ a: true, b: [1, 2, 3] }] }, + ], + }, + ], + }, + ); + }, "detected duplicate test case"); + }); + + it("throws with duplicate object test cases even when property order differs", () => { + assert.throws(() => { + ruleTester.run( + "foo", + { + meta: {}, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + }, + }; + }, + }, + { + valid: ["foo"], + invalid: [ + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + }, + { + errors: [{ message: "foo bar" }], + code: "const x = 123;", + }, + ], + }, + ); + }, "detected duplicate test case"); + }); + + it("ignores duplicate test case when non-serializable property present (settings)", () => { + ruleTester.run( + "foo", + { + meta: {}, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + }, + }; + }, + }, + { + valid: ["foo"], + invalid: [ + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + settings: { foo: /abc/u }, + }, + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + settings: { foo: /abc/u }, + }, + ], + }, + ); + }); + + it("ignores duplicate test case when non-serializable property present (languageOptions.parserOptions)", () => { + ruleTester.run( + "foo", + { + meta: {}, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + }, + }; + }, + }, + { + valid: ["foo"], + invalid: [ + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + languageOptions: { + parserOptions: { foo: /abc/u }, + }, + }, + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + languageOptions: { + parserOptions: { foo: /abc/u }, + }, + }, + ], + }, + ); + }); + + it("ignores duplicate test case when non-serializable property present (plugins)", () => { + ruleTester.run( + "foo", + { + meta: {}, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + }, + }; + }, + }, + { + valid: ["foo"], + invalid: [ + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + plugins: { foo: /abc/u }, + }, + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + plugins: { foo: /abc/u }, + }, + ], + }, + ); + }); + + it("ignores duplicate test case when non-serializable property present (options)", () => { + ruleTester.run( + "foo", + { + meta: { schema: false }, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + }, + }; + }, + }, + { + valid: ["foo"], + invalid: [ + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + options: [{ foo: /abc/u }], + }, + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + options: [{ foo: /abc/u }], + }, + ], + }, + ); + }); + + it("detects duplicate test cases even if the error matchers differ", () => { + assert.throws(() => { + ruleTester.run( + "foo", + { + meta: { schema: false }, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + }, + }; + }, + }, + { + valid: [], + invalid: [ + { + code: "const x = 123;", + errors: [{ message: "foo bar" }], + }, + { code: "const x = 123;", errors: 1 }, + ], + }, + ); + }, "detected duplicate test case"); + }); + + it("detects duplicate test cases even if the presence of the output property differs", () => { + assert.throws(() => { + ruleTester.run( + "foo", + { + meta: { schema: false }, + create(context) { + return { + VariableDeclaration(node) { + context.report(node, "foo bar"); + }, + }; + }, + }, + { + valid: [], + invalid: [ + { code: "const x = 123;", errors: 1 }, + { + code: "const x = 123;", + errors: 1, + output: null, + }, + ], + }, + ); + }, "detected duplicate test case"); + }); + }); + }); + + describe("SourceCode forbidden methods", () => { + ["applyInlineConfig", "applyLanguageOptions", "finalize"].forEach( + methodName => { + const useForbiddenMethodRule = { + create: context => ({ + Program() { + const sourceCode = context.sourceCode; + + sourceCode[methodName](); + }, + }), + }; + + it(`should throw if ${methodName} is called from a valid test case`, () => { + assert.throws(() => { + ruleTester.run( + "use-forbidden-method", + useForbiddenMethodRule, + { + valid: [""], + invalid: [], + }, + ); + }, `\`SourceCode#${methodName}()\` cannot be called inside a rule.`); + }); + + it(`should throw if ${methodName} is called from an invalid test case`, () => { + assert.throws(() => { + ruleTester.run( + "use-forbidden-method", + useForbiddenMethodRule, + { + valid: [], + invalid: [ + { + code: "", + errors: [{}], + }, + ], + }, + ); + }, `\`SourceCode#${methodName}()\` cannot be called inside a rule.`); + }); + }, + ); + }); + + describe("Subclassing", () => { + it("should allow subclasses to set the describe/it/itOnly statics and should correctly use those values", () => { + const assertionDescribe = assertEmitted( + ruleTesterTestEmitter, + "custom describe", + "this-is-a-rule-name", + ); + const assertionIt = assertEmitted( + ruleTesterTestEmitter, + "custom it", + "valid(code);", + ); + const assertionItOnly = assertEmitted( + ruleTesterTestEmitter, + "custom itOnly", + "validOnly(code);", + ); + + /** + * Subclass for testing + */ + class RuleTesterSubclass extends RuleTester {} + RuleTesterSubclass.describe = function (text, method) { + ruleTesterTestEmitter.emit("custom describe", text, method); + return method.call(this); + }; + RuleTesterSubclass.it = function (text, method) { + ruleTesterTestEmitter.emit("custom it", text, method); + return method.call(this); + }; + RuleTesterSubclass.itOnly = function (text, method) { + ruleTesterTestEmitter.emit("custom itOnly", text, method); + return method.call(this); + }; + + const ruleTesterSubclass = new RuleTesterSubclass(); + + ruleTesterSubclass.run( + "this-is-a-rule-name", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [ + "valid(code);", + { + code: "validOnly(code);", + only: true, + }, + ], + invalid: [], + }, + ); + + return Promise.all([ + assertionDescribe, + assertionIt, + assertionItOnly, + ]); + }); + }); + + describe("Optional Test Suites", () => { + let originalRuleTesterDescribe; + let spyRuleTesterDescribe; + + before(() => { + originalRuleTesterDescribe = RuleTester.describe; + spyRuleTesterDescribe = sinon.spy((title, callback) => callback()); + RuleTester.describe = spyRuleTesterDescribe; + }); + after(() => { + RuleTester.describe = originalRuleTesterDescribe; + }); + beforeEach(() => { + spyRuleTesterDescribe.resetHistory(); + ruleTester = new RuleTester(); + }); + + it("should create a test suite with the rule name even if there are no test cases", () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [], + }, + ); + sinon.assert.calledWith(spyRuleTesterDescribe, "no-var"); + }); + + it("should create a valid test suite if there is a valid test case", () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["value = 0;"], + invalid: [], + }, + ); + sinon.assert.calledWith(spyRuleTesterDescribe, "valid"); + }); + + it("should not create a valid test suite if there are no valid test cases", () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [ + { + code: "var value = 0;", + errors: [/^Bad var/u], + output: " value = 0;", + }, + ], + }, + ); + sinon.assert.neverCalledWith(spyRuleTesterDescribe, "valid"); + }); + + it("should create an invalid test suite if there is an invalid test case", () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: [], + invalid: [ + { + code: "var value = 0;", + errors: [/^Bad var/u], + output: " value = 0;", + }, + ], + }, + ); + sinon.assert.calledWith(spyRuleTesterDescribe, "invalid"); + }); + + it("should not create an invalid test suite if there are no invalid test cases", () => { + ruleTester.run( + "no-var", + require("../../fixtures/testers/rule-tester/no-var"), + { + valid: ["value = 0;"], + invalid: [], + }, + ); + sinon.assert.neverCalledWith(spyRuleTesterDescribe, "invalid"); + }); + }); }); diff --git a/tests/lib/rules/accessor-pairs.js b/tests/lib/rules/accessor-pairs.js index f9154fd1b634..836c933b1be7 100644 --- a/tests/lib/rules/accessor-pairs.js +++ b/tests/lib/rules/accessor-pairs.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/accessor-pairs"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Helpers @@ -19,1829 +19,3622 @@ const rule = require("../../../lib/rules/accessor-pairs"), const ruleTester = new RuleTester(); ruleTester.run("accessor-pairs", rule, { - valid: [ + valid: [ + //------------------------------------------------------------------------------ + // General + //------------------------------------------------------------------------------ - //------------------------------------------------------------------------------ - // General - //------------------------------------------------------------------------------ + // Does not check object patterns + { + code: "var { get: foo } = bar; ({ set: foo } = bar);", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { set } = foo; ({ get } = foo);", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, - // Does not check object patterns - { - code: "var { get: foo } = bar; ({ set: foo } = bar);", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var { set } = foo; ({ get } = foo);", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, + //------------------------------------------------------------------------------ + // Object literals + //------------------------------------------------------------------------------ - //------------------------------------------------------------------------------ - // Object literals - //------------------------------------------------------------------------------ + // Test default settings, this would be an error if `getWithoutSet` was set to `true` + "var o = { get a() {} }", + { + code: "var o = { get a() {} }", + options: [{}], + }, - // Test default settings, this would be an error if `getWithoutSet` was set to `true` - "var o = { get a() {} }", - { - code: "var o = { get a() {} }", - options: [{}] - }, + // No accessors + { + code: "var o = {};", + options: [{ setWithoutGet: true, getWithoutSet: true }], + }, + { + code: "var o = { a: 1 };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + }, + { + code: "var o = { a };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { a: get };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + }, + { + code: "var o = { a: set };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + }, + { + code: "var o = { get: function(){} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + }, + { + code: "var o = { set: function(foo){} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + }, + { + code: "var o = { get };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { set };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { [get]: function() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { [set]: function(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { set(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, - // No accessors - { - code: "var o = {};", - options: [{ setWithoutGet: true, getWithoutSet: true }] - }, - { - code: "var o = { a: 1 };", - options: [{ setWithoutGet: true, getWithoutSet: true }] - }, - { - code: "var o = { a };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { a: get };", - options: [{ setWithoutGet: true, getWithoutSet: true }] - }, - { - code: "var o = { a: set };", - options: [{ setWithoutGet: true, getWithoutSet: true }] - }, - { - code: "var o = { get: function(){} };", - options: [{ setWithoutGet: true, getWithoutSet: true }] - }, - { - code: "var o = { set: function(foo){} };", - options: [{ setWithoutGet: true, getWithoutSet: true }] - }, - { - code: "var o = { get };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { set };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { [get]: function() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { [set]: function(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { set(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, + // Disabled options + { + code: "var o = { get a() {} };", + options: [{ setWithoutGet: false, getWithoutSet: false }], + }, + { + code: "var o = { get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: false }], + }, + { + code: "var o = { set a(foo) {} };", + options: [{ setWithoutGet: false, getWithoutSet: false }], + }, + { + code: "var o = { set a(foo) {} };", + options: [{ setWithoutGet: false, getWithoutSet: true }], + }, + { + code: "var o = { set a(foo) {} };", + options: [{ setWithoutGet: false }], + }, - // Disabled options - { - code: "var o = { get a() {} };", - options: [{ setWithoutGet: false, getWithoutSet: false }] - }, - { - code: "var o = { get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: false }] - }, - { - code: "var o = { set a(foo) {} };", - options: [{ setWithoutGet: false, getWithoutSet: false }] - }, - { - code: "var o = { set a(foo) {} };", - options: [{ setWithoutGet: false, getWithoutSet: true }] - }, - { - code: "var o = { set a(foo) {} };", - options: [{ setWithoutGet: false }] - }, + // Valid pairs with identifiers + { + code: "var o = { get a() {}, set a(foo) {} };", + options: [{ setWithoutGet: false, getWithoutSet: true }], + }, + { + code: "var o = { get a() {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: false }], + }, + { + code: "var o = { get a() {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + }, + { + code: "var o = { set a(foo) {}, get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + }, - // Valid pairs with identifiers - { - code: "var o = { get a() {}, set a(foo) {} };", - options: [{ setWithoutGet: false, getWithoutSet: true }] - }, - { - code: "var o = { get a() {}, set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: false }] - }, - { - code: "var o = { get a() {}, set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }] - }, - { - code: "var o = { set a(foo) {}, get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }] - }, + // Valid pairs with statically computed names + { + code: "var o = { get 'a'() {}, set 'a'(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + }, + { + code: "var o = { get a() {}, set 'a'(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + }, + { + code: "var o = { get ['abc']() {}, set ['abc'](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get [1e2]() {}, set 100(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get abc() {}, set [`abc`](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get ['123']() {}, set 123(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, - // Valid pairs with statically computed names - { - code: "var o = { get 'a'() {}, set 'a'(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }] - }, - { - code: "var o = { get a() {}, set 'a'(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }] - }, - { - code: "var o = { get ['abc']() {}, set ['abc'](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get [1e2]() {}, set 100(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get abc() {}, set [`abc`](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get ['123']() {}, set 123(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, + // Valid pairs with expressions + { + code: "var o = { get [a]() {}, set [a](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get [a]() {}, set [(a)](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get [(a)]() {}, set [a](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get [a]() {}, set [ a ](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get [/*comment*/a/*comment*/]() {}, set [a](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get [f()]() {}, set [f()](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get [f(a)]() {}, set [f(a)](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get [a + b]() {}, set [a + b](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get [`${a}`]() {}, set [`${a}`](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, - // Valid pairs with expressions - { - code: "var o = { get [a]() {}, set [a](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get [a]() {}, set [(a)](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get [(a)]() {}, set [a](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get [a]() {}, set [ a ](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get [/*comment*/a/*comment*/]() {}, set [a](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get [f()]() {}, set [f()](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get [f(a)]() {}, set [f(a)](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get [a + b]() {}, set [a + b](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get [`${a}`]() {}, set [`${a}`](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, + // Multiple valid pairs in the same literal + { + code: "var o = { get a() {}, set a(foo) {}, get b() {}, set b(bar) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + }, + { + code: "var o = { get a() {}, set c(foo) {}, set a(bar) {}, get b() {}, get c() {}, set b(baz) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + }, - // Multiple valid pairs in the same literal - { - code: "var o = { get a() {}, set a(foo) {}, get b() {}, set b(bar) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }] - }, - { - code: "var o = { get a() {}, set c(foo) {}, set a(bar) {}, get b() {}, get c() {}, set b(baz) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }] - }, + // Valid pairs with other elements + { + code: "var o = { get a() {}, set a(foo) {}, b: bar };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + }, + { + code: "var o = { get a() {}, b, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get a() {}, ...b, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 2018 }, + }, + { + code: "var o = { get a() {}, set a(foo) {}, ...a };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 2018 }, + }, - // Valid pairs with other elements - { - code: "var o = { get a() {}, set a(foo) {}, b: bar };", - options: [{ setWithoutGet: true, getWithoutSet: true }] - }, - { - code: "var o = { get a() {}, b, set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get a() {}, ...b, set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 2018 } - }, - { - code: "var o = { get a() {}, set a(foo) {}, ...a };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 2018 } - }, + // Duplicate keys. This is the responsibility of no-dupe-keys, but this rule still checks is there the other accessor kind. + { + code: "var o = { get a() {}, get a() {}, set a(foo) {}, };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get a() {}, set a(foo) {}, get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get a() {}, set a(foo) {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { set a(bar) {}, get a() {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get a() {}, get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { set a(foo) {}, set a(foo) {} };", + options: [{ setWithoutGet: false, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { get a() {}, set a(foo) {}, a };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = { a, get a() {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, - // Duplicate keys. This is the responsibility of no-dupe-keys, but this rule still checks is there the other accessor kind. - { - code: "var o = { get a() {}, get a() {}, set a(foo) {}, };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get a() {}, set a(foo) {}, get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get a() {}, set a(foo) {}, set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { set a(bar) {}, get a() {}, set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get a() {}, get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: false }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { set a(foo) {}, set a(foo) {} };", - options: [{ setWithoutGet: false, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { get a() {}, set a(foo) {}, a };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = { a, get a() {}, set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, + /* + * This should be actually invalid by this rule! + * This code creates a property with the setter only, the getter will be ignored. + * It's treated as 3 attempts to define the same key, and the last wins. + * However, this edge case is not covered, it should be reported by no-dupe-keys anyway. + */ + { + code: "var o = { get a() {}, a:1, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + }, - /* - * This should be actually invalid by this rule! - * This code creates a property with the setter only, the getter will be ignored. - * It's treated as 3 attempts to define the same key, and the last wins. - * However, this edge case is not covered, it should be reported by no-dupe-keys anyway. - */ - { - code: "var o = { get a() {}, a:1, set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 } - }, + //------------------------------------------------------------------------------ + // Property descriptors + //------------------------------------------------------------------------------ - //------------------------------------------------------------------------------ - // Property descriptors - //------------------------------------------------------------------------------ + "var o = {a: 1};\n Object.defineProperty(o, 'b', \n{set: function(value) {\n val = value; \n},\n get: function() {\n return val; \n} \n});", - "var o = {a: 1};\n Object.defineProperty(o, 'b', \n{set: function(value) {\n val = value; \n},\n get: function() {\n return val; \n} \n});", + // https://github.com/eslint/eslint/issues/3262 + "var o = {set: function() {}}", + "Object.defineProperties(obj, {set: {value: function() {}}});", + "Object.create(null, {set: {value: function() {}}});", + { + code: "var o = {get: function() {}}", + options: [{ getWithoutSet: true }], + }, + { + code: "var o = {[set]: function() {}}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var set = 'value'; Object.defineProperty(obj, 'foo', {[set]: function(value) {}});", + languageOptions: { ecmaVersion: 6 }, + }, - // https://github.com/eslint/eslint/issues/3262 - "var o = {set: function() {}}", - "Object.defineProperties(obj, {set: {value: function() {}}});", - "Object.create(null, {set: {value: function() {}}});", - { code: "var o = {get: function() {}}", options: [{ getWithoutSet: true }] }, - { code: "var o = {[set]: function() {}}", languageOptions: { ecmaVersion: 6 } }, - { code: "var set = 'value'; Object.defineProperty(obj, 'foo', {[set]: function(value) {}});", languageOptions: { ecmaVersion: 6 } }, + //------------------------------------------------------------------------------ + // Classes + //------------------------------------------------------------------------------ - //------------------------------------------------------------------------------ - // Classes - //------------------------------------------------------------------------------ + // Test default settings + { + code: "class A { get a() {} }", + options: [{ enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { get #a() {} }", + options: [{ enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 13 }, + }, - // Test default settings - { - code: "class A { get a() {} }", - options: [{ enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { get #a() {} }", - options: [{ enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 13 } - }, + // Explicitly disabled option + { + code: "class A { set a(foo) {} }", + options: [{ enforceForClassMembers: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { get a() {} set b(foo) {} static get c() {} static set d(bar) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: false, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "(class A { get a() {} set b(foo) {} static get c() {} static set d(bar) {} });", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: false, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, - // Explicitly disabled option - { - code: "class A { set a(foo) {} }", - options: [{ enforceForClassMembers: false }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { get a() {} set b(foo) {} static get c() {} static set d(bar) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: false }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "(class A { get a() {} set b(foo) {} static get c() {} static set d(bar) {} });", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: false }], - languageOptions: { ecmaVersion: 6 } - }, + // Disabled accessor kind options + { + code: "class A { get a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: false, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { set a(foo) {} }", + options: [ + { + setWithoutGet: false, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { static get a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: false, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { static set a(foo) {} }", + options: [ + { + setWithoutGet: false, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { set a(foo) {} };", + options: [ + { + setWithoutGet: false, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { get a() {} set b(foo) {} static get c() {} static set d(bar) {} }", + options: [ + { + setWithoutGet: false, + getWithoutSet: false, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, - // Disabled accessor kind options - { - code: "class A { get a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: false, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { set a(foo) {} }", - options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { static get a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: false, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { static set a(foo) {} }", - options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { set a(foo) {} };", - options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { get a() {} set b(foo) {} static get c() {} static set d(bar) {} }", - options: [{ setWithoutGet: false, getWithoutSet: false, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, + // No accessors + { + code: "class A {}", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "(class {})", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { constructor () {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { static a() {} 'b'() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { [a]() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { a() {} static a() {} b() {} static c() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, - // No accessors - { - code: "class A {}", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "(class {})", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { constructor () {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { static a() {} 'b'() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { [a]() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { a() {} static a() {} b() {} static c() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, + // Valid pairs with identifiers + { + code: "class A { get a() {} set a(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { set a(foo) {} get a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { static get a() {} static set a(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { static set a(foo) {} static get a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "(class { set a(foo) {} get a() {} });", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, - // Valid pairs with identifiers - { - code: "class A { get a() {} set a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { set a(foo) {} get a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { static get a() {} static set a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { static set a(foo) {} static get a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "(class { set a(foo) {} get a() {} });", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, + // Valid pairs with statically computed names + { + code: "class A { get 'a'() {} set ['a'](foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { set [`a`](foo) {} get a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { get 'a'() {} set a(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { static get 1e2() {} static set [100](foo) {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, - // Valid pairs with statically computed names - { - code: "class A { get 'a'() {} set ['a'](foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { set [`a`](foo) {} get a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { get 'a'() {} set a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { static get 1e2() {} static set [100](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, + // Valid pairs with expressions + { + code: "class A { get [a]() {} set [a](foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { set [(f())](foo) {} get [(f())]() {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { static set [f(a)](foo) {} static get [f(a)]() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, - // Valid pairs with expressions - { - code: "class A { get [a]() {} set [a](foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { set [(f())](foo) {} get [(f())]() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { static set [f(a)](foo) {} static get [f(a)]() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, + // Multiple valid pairs in the same class + { + code: "class A { get a() {} set b(foo) {} set a(bar) {} get b() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { get a() {} set a(bar) {} b() {} set c(foo) {} get c() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "(class { get a() {} static set a(foo) {} set a(bar) {} static get a() {} });", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, - // Multiple valid pairs in the same class - { - code: "class A { get a() {} set b(foo) {} set a(bar) {} get b() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { get a() {} set a(bar) {} b() {} set c(foo) {} get c() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "(class { get a() {} static set a(foo) {} set a(bar) {} static get a() {} });", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, + // Valid pairs with other elements + { + code: "class A { get a() {} b() {} set a(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { set a(foo) {} get a() {} b() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { a() {} get b() {} c() {} set b(foo) {} d() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { get a() {} set a(foo) {} static a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { static get a() {} static b() {} static set a(foo) {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { static set a(foo) {} static get a() {} a() {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, - // Valid pairs with other elements - { - code: "class A { get a() {} b() {} set a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { set a(foo) {} get a() {} b() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { a() {} get b() {} c() {} set b(foo) {} d() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { get a() {} set a(foo) {} static a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { static get a() {} static b() {} static set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { static set a(foo) {} static get a() {} a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, + // Duplicate keys. This is the responsibility of no-dupe-class-members, but this rule still checks if there is the other accessor kind. + { + code: "class A { get a() {} get a() {} set a(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { get [a]() {} set [a](foo) {} set [a](foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { get a() {} set 'a'(foo) {} get [`a`]() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { get a() {} set a(foo) {} a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { a() {} get a() {} set a(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { static set a(foo) {} static set a(foo) {} static get a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { static get a() {} static set a(foo) {} static get a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { static set a(foo) {} static get a() {} static a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, - // Duplicate keys. This is the responsibility of no-dupe-class-members, but this rule still checks if there is the other accessor kind. - { - code: "class A { get a() {} get a() {} set a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { get [a]() {} set [a](foo) {} set [a](foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { get a() {} set 'a'(foo) {} get [`a`]() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { get a() {} set a(foo) {} a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { a() {} get a() {} set a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { static set a(foo) {} static set a(foo) {} static get a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { static get a() {} static set a(foo) {} static get a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { static set a(foo) {} static get a() {} static a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, + /* + * This code should be invalid by this rule because it creates a class with the setter only, while the getter is ignored. + * However, this edge case is not covered, it should be reported by no-dupe-class-members anyway. + */ + { + code: "class A { get a() {} a() {} set a(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { static set a(foo) {} static a() {} static get a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + ], - /* - * This code should be invalid by this rule because it creates a class with the setter only, while the getter is ignored. - * However, this edge case is not covered, it should be reported by no-dupe-class-members anyway. - */ - { - code: "class A { get a() {} a() {} set a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { static set a(foo) {} static a() {} static get a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - } - ], + invalid: [ + //------------------------------------------------------------------------------ + // Object literals + //------------------------------------------------------------------------------ - invalid: [ + // Test default settings + { + code: "var o = { set a(value) {} };", + errors: [ + { + message: "Getter is not present for setter 'a'.", + type: "Property", + }, + ], + }, + { + code: "var o = { set a(value) {} };", + options: [{}], + errors: [ + { + message: "Getter is not present for setter 'a'.", + type: "Property", + }, + ], + }, - //------------------------------------------------------------------------------ - // Object literals - //------------------------------------------------------------------------------ + // Test that the options do not affect each other + { + code: "var o = { set a(value) {} };", + options: [{ setWithoutGet: true, getWithoutSet: false }], + errors: [ + { + message: "Getter is not present for setter 'a'.", + type: "Property", + }, + ], + }, + { + code: "var o = { set a(value) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Getter is not present for setter 'a'.", + type: "Property", + }, + ], + }, + { + code: "var o = { get a() {} };", + options: [{ setWithoutGet: false, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + }, + ], + }, + { + code: "var o = { get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + }, + ], + }, + { + code: "var o = { get a() {} };", + options: [{ getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + }, + ], + }, - // Test default settings - { - code: "var o = { set a(value) {} };", - errors: [{ message: "Getter is not present for setter 'a'.", type: "Property" }] - }, - { - code: "var o = { set a(value) {} };", - options: [{}], - errors: [{ message: "Getter is not present for setter 'a'.", type: "Property" }] - }, + // Various kinds of the getter's key + { + code: "var o = { get abc() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'abc'.", + type: "Property", + }, + ], + }, + { + code: "var o = { get 'abc'() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'abc'.", + type: "Property", + }, + ], + }, + { + code: "var o = { get 123() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter '123'.", + type: "Property", + }, + ], + }, + { + code: "var o = { get 1e2() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter '100'.", + type: "Property", + }, + ], + }, + { + code: "var o = { get ['abc']() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter 'abc'.", + type: "Property", + }, + ], + }, + { + code: "var o = { get [`abc`]() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter 'abc'.", + type: "Property", + }, + ], + }, + { + code: "var o = { get [123]() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter '123'.", + type: "Property", + }, + ], + }, + { + code: "var o = { get [abc]() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter.", + type: "Property", + }, + ], + }, + { + code: "var o = { get [f(abc)]() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter.", + type: "Property", + }, + ], + }, + { + code: "var o = { get [a + b]() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter.", + type: "Property", + }, + ], + }, - // Test that the options do not affect each other - { - code: "var o = { set a(value) {} };", - options: [{ setWithoutGet: true, getWithoutSet: false }], - errors: [{ message: "Getter is not present for setter 'a'.", type: "Property" }] - }, - { - code: "var o = { set a(value) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Getter is not present for setter 'a'.", type: "Property" }] - }, - { - code: "var o = { get a() {} };", - options: [{ setWithoutGet: false, getWithoutSet: true }], - errors: [{ message: "Setter is not present for getter 'a'.", type: "Property" }] - }, - { - code: "var o = { get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Setter is not present for getter 'a'.", type: "Property" }] - }, - { - code: "var o = { get a() {} };", - options: [{ getWithoutSet: true }], - errors: [{ message: "Setter is not present for getter 'a'.", type: "Property" }] - }, + // Various kinds of the setter's key + { + code: "var o = { set abc(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Getter is not present for setter 'abc'.", + type: "Property", + }, + ], + }, + { + code: "var o = { set 'abc'(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Getter is not present for setter 'abc'.", + type: "Property", + }, + ], + }, + { + code: "var o = { set 123(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Getter is not present for setter '123'.", + type: "Property", + }, + ], + }, + { + code: "var o = { set 1e2(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Getter is not present for setter '100'.", + type: "Property", + }, + ], + }, + { + code: "var o = { set ['abc'](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for setter 'abc'.", + type: "Property", + }, + ], + }, + { + code: "var o = { set [`abc`](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for setter 'abc'.", + type: "Property", + }, + ], + }, + { + code: "var o = { set [123](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for setter '123'.", + type: "Property", + }, + ], + }, + { + code: "var o = { set [abc](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for setter.", + type: "Property", + }, + ], + }, + { + code: "var o = { set [f(abc)](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for setter.", + type: "Property", + }, + ], + }, + { + code: "var o = { set [a + b](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for setter.", + type: "Property", + }, + ], + }, - // Various kinds of the getter's key - { - code: "var o = { get abc() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Setter is not present for getter 'abc'.", type: "Property" }] - }, - { - code: "var o = { get 'abc'() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Setter is not present for getter 'abc'.", type: "Property" }] - }, - { - code: "var o = { get 123() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Setter is not present for getter '123'.", type: "Property" }] - }, - { - code: "var o = { get 1e2() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Setter is not present for getter '100'.", type: "Property" }] - }, - { - code: "var o = { get ['abc']() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for getter 'abc'.", type: "Property" }] - }, - { - code: "var o = { get [`abc`]() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for getter 'abc'.", type: "Property" }] - }, - { - code: "var o = { get [123]() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for getter '123'.", type: "Property" }] - }, - { - code: "var o = { get [abc]() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for getter.", type: "Property" }] - }, - { - code: "var o = { get [f(abc)]() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for getter.", type: "Property" }] - }, - { - code: "var o = { get [a + b]() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for getter.", type: "Property" }] - }, + // Different keys + { + code: "var o = { get a() {}, set b(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter 'b'.", + type: "Property", + column: 23, + }, + ], + }, + { + code: "var o = { set a(foo) {}, get b() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 11, + }, + { + message: "Setter is not present for getter 'b'.", + type: "Property", + column: 26, + }, + ], + }, + { + code: "var o = { get 1() {}, set b(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter '1'.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter 'b'.", + type: "Property", + column: 23, + }, + ], + }, + { + code: "var o = { get a() {}, set 1(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter '1'.", + type: "Property", + column: 23, + }, + ], + }, + { + code: "var o = { get a() {}, set 'a '(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter 'a '.", + type: "Property", + column: 23, + }, + ], + }, + { + code: "var o = { get ' a'() {}, set 'a'(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter ' a'.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 26, + }, + ], + }, + { + code: "var o = { get ''() {}, set ' '(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter ''.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter ' '.", + type: "Property", + column: 24, + }, + ], + }, + { + code: "var o = { get ''() {}, set null(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter ''.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter 'null'.", + type: "Property", + column: 24, + }, + ], + }, + { + code: "var o = { get [`a`]() {}, set b(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter 'b'.", + type: "Property", + column: 27, + }, + ], + }, + { + code: "var o = { get [a]() {}, set [b](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter.", + type: "Property", + column: 25, + }, + ], + }, + { + code: "var o = { get [a]() {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 25, + }, + ], + }, + { + code: "var o = { get a() {}, set [a](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter.", + type: "Property", + column: 23, + }, + ], + }, + { + code: "var o = { get [a + b]() {}, set [a - b](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter.", + type: "Property", + column: 29, + }, + ], + }, + { + code: "var o = { get [`${0} `]() {}, set [`${0}`](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter.", + type: "Property", + column: 31, + }, + ], + }, - // Various kinds of the setter's key - { - code: "var o = { set abc(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Getter is not present for setter 'abc'.", type: "Property" }] - }, - { - code: "var o = { set 'abc'(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Getter is not present for setter 'abc'.", type: "Property" }] - }, - { - code: "var o = { set 123(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Getter is not present for setter '123'.", type: "Property" }] - }, - { - code: "var o = { set 1e2(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Getter is not present for setter '100'.", type: "Property" }] - }, - { - code: "var o = { set ['abc'](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for setter 'abc'.", type: "Property" }] - }, - { - code: "var o = { set [`abc`](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for setter 'abc'.", type: "Property" }] - }, - { - code: "var o = { set [123](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for setter '123'.", type: "Property" }] - }, - { - code: "var o = { set [abc](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for setter.", type: "Property" }] - }, - { - code: "var o = { set [f(abc)](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for setter.", type: "Property" }] - }, - { - code: "var o = { set [a + b](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for setter.", type: "Property" }] - }, + // Multiple invalid of same and different kinds + { + code: "var o = { get a() {}, get b() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 11, + }, + { + message: "Setter is not present for getter 'b'.", + type: "Property", + column: 23, + }, + ], + }, + { + code: "var o = { set a(foo) {}, set b(bar) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter 'b'.", + type: "Property", + column: 26, + }, + ], + }, + { + code: "var o = { get a() {}, set b(foo) {}, set c(foo) {}, get d() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter 'b'.", + type: "Property", + column: 23, + }, + { + message: "Getter is not present for setter 'c'.", + type: "Property", + column: 38, + }, + { + message: "Setter is not present for getter 'd'.", + type: "Property", + column: 53, + }, + ], + }, - // Different keys - { - code: "var o = { get a() {}, set b(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [ - { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, - { message: "Getter is not present for setter 'b'.", type: "Property", column: 23 } - ] - }, - { - code: "var o = { set a(foo) {}, get b() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [ - { message: "Getter is not present for setter 'a'.", type: "Property", column: 11 }, - { message: "Setter is not present for getter 'b'.", type: "Property", column: 26 } - ] - }, - { - code: "var o = { get 1() {}, set b(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [ - { message: "Setter is not present for getter '1'.", type: "Property", column: 11 }, - { message: "Getter is not present for setter 'b'.", type: "Property", column: 23 } - ] - }, - { - code: "var o = { get a() {}, set 1(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [ - { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, - { message: "Getter is not present for setter '1'.", type: "Property", column: 23 } - ] - }, - { - code: "var o = { get a() {}, set 'a '(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [ - { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, - { message: "Getter is not present for setter 'a '.", type: "Property", column: 23 } - ] - }, - { - code: "var o = { get ' a'() {}, set 'a'(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [ - { message: "Setter is not present for getter ' a'.", type: "Property", column: 11 }, - { message: "Getter is not present for setter 'a'.", type: "Property", column: 26 } - ] - }, - { - code: "var o = { get ''() {}, set ' '(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [ - { message: "Setter is not present for getter ''.", type: "Property", column: 11 }, - { message: "Getter is not present for setter ' '.", type: "Property", column: 24 } - ] - }, - { - code: "var o = { get ''() {}, set null(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [ - { message: "Setter is not present for getter ''.", type: "Property", column: 11 }, - { message: "Getter is not present for setter 'null'.", type: "Property", column: 24 } - ] - }, - { - code: "var o = { get [`a`]() {}, set b(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, - { message: "Getter is not present for setter 'b'.", type: "Property", column: 27 } - ] - }, - { - code: "var o = { get [a]() {}, set [b](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for getter.", type: "Property", column: 11 }, - { message: "Getter is not present for setter.", type: "Property", column: 25 } - ] - }, - { - code: "var o = { get [a]() {}, set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for getter.", type: "Property", column: 11 }, - { message: "Getter is not present for setter 'a'.", type: "Property", column: 25 } - ] - }, - { - code: "var o = { get a() {}, set [a](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, - { message: "Getter is not present for setter.", type: "Property", column: 23 } - ] - }, - { - code: "var o = { get [a + b]() {}, set [a - b](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for getter.", type: "Property", column: 11 }, - { message: "Getter is not present for setter.", type: "Property", column: 29 } - ] - }, - { - code: "var o = { get [`${0} `]() {}, set [`${0}`](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for getter.", type: "Property", column: 11 }, - { message: "Getter is not present for setter.", type: "Property", column: 31 } - ] - }, + // Checks per object literal + { + code: "var o1 = { get a() {} }, o2 = { set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 12, + }, + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 33, + }, + ], + }, + { + code: "var o1 = { set a(foo) {} }, o2 = { get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 12, + }, + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 36, + }, + ], + }, - // Multiple invalid of same and different kinds - { - code: "var o = { get a() {}, get b() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [ - { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, - { message: "Setter is not present for getter 'b'.", type: "Property", column: 23 } - ] - }, - { - code: "var o = { set a(foo) {}, set b(bar) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [ - { message: "Getter is not present for setter 'a'.", type: "Property", column: 11 }, - { message: "Getter is not present for setter 'b'.", type: "Property", column: 26 } - ] - }, - { - code: "var o = { get a() {}, set b(foo) {}, set c(foo) {}, get d() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [ - { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, - { message: "Getter is not present for setter 'b'.", type: "Property", column: 23 }, - { message: "Getter is not present for setter 'c'.", type: "Property", column: 38 }, - { message: "Setter is not present for getter 'd'.", type: "Property", column: 53 } - ] - }, + // Combinations or valid and invalid + { + code: "var o = { get a() {}, get b() {}, set b(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 11, + }, + ], + }, + { + code: "var o = { get b() {}, get a() {}, set b(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 23, + }, + ], + }, + { + code: "var o = { get b() {}, set b(foo) {}, get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 38, + }, + ], + }, + { + code: "var o = { set a(foo) {}, get b() {}, set b(bar) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 11, + }, + ], + }, + { + code: "var o = { get b() {}, set a(foo) {}, set b(bar) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 23, + }, + ], + }, + { + code: "var o = { get b() {}, set b(bar) {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 38, + }, + ], + }, + { + code: "var o = { get v1() {}, set i1(foo) {}, get v2() {}, set v2(bar) {}, get i2() {}, set v1(baz) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Getter is not present for setter 'i1'.", + type: "Property", + column: 24, + }, + { + message: "Setter is not present for getter 'i2'.", + type: "Property", + column: 69, + }, + ], + }, - // Checks per object literal - { - code: "var o1 = { get a() {} }, o2 = { set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [ - { message: "Setter is not present for getter 'a'.", type: "Property", column: 12 }, - { message: "Getter is not present for setter 'a'.", type: "Property", column: 33 } - ] - }, - { - code: "var o1 = { set a(foo) {} }, o2 = { get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [ - { message: "Getter is not present for setter 'a'.", type: "Property", column: 12 }, - { message: "Setter is not present for getter 'a'.", type: "Property", column: 36 } - ] - }, + // In the case of duplicates which don't have the other kind, all nodes are reported + { + code: "var o = { get a() {}, get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 11, + }, + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 23, + }, + ], + }, + { + code: "var o = { set a(foo) {}, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 11, + }, + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 26, + }, + ], + }, - // Combinations or valid and invalid - { - code: "var o = { get a() {}, get b() {}, set b(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }] - }, - { - code: "var o = { get b() {}, get a() {}, set b(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Setter is not present for getter 'a'.", type: "Property", column: 23 }] - }, - { - code: "var o = { get b() {}, set b(foo) {}, get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Setter is not present for getter 'a'.", type: "Property", column: 38 }] - }, - { - code: "var o = { set a(foo) {}, get b() {}, set b(bar) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Getter is not present for setter 'a'.", type: "Property", column: 11 }] - }, - { - code: "var o = { get b() {}, set a(foo) {}, set b(bar) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Getter is not present for setter 'a'.", type: "Property", column: 23 }] - }, - { - code: "var o = { get b() {}, set b(bar) {}, set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ message: "Getter is not present for setter 'a'.", type: "Property", column: 38 }] - }, - { - code: "var o = { get v1() {}, set i1(foo) {}, get v2() {}, set v2(bar) {}, get i2() {}, set v1(baz) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [ - { message: "Getter is not present for setter 'i1'.", type: "Property", column: 24 }, - { message: "Setter is not present for getter 'i2'.", type: "Property", column: 69 } - ] - }, + // Other elements or even value property duplicates in the same literal do not affect this rule + { + code: "var o = { a, get b() {}, c };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter 'b'.", + type: "Property", + column: 14, + }, + ], + }, + { + code: "var o = { a, get b() {}, c, set d(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter 'b'.", + type: "Property", + column: 14, + }, + { + message: "Getter is not present for setter 'd'.", + type: "Property", + column: 29, + }, + ], + }, + { + code: "var o = { get a() {}, a:1 };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 11, + }, + ], + }, + { + code: "var o = { a, get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 14, + }, + ], + }, + { + code: "var o = { set a(foo) {}, a:1 };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 11, + }, + ], + }, + { + code: "var o = { a, set a(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 14, + }, + ], + }, + { + code: "var o = { get a() {}, ...b };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 2018 }, + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 11, + }, + ], + }, + { + code: "var o = { get a() {}, ...a };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 2018 }, + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 11, + }, + ], + }, + { + code: "var o = { set a(foo) {}, ...a };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 2018 }, + errors: [ + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 11, + }, + ], + }, - // In the case of duplicates which don't have the other kind, all nodes are reported - { - code: "var o = { get a() {}, get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }, - { message: "Setter is not present for getter 'a'.", type: "Property", column: 23 } - ] - }, - { - code: "var o = { set a(foo) {}, set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Getter is not present for setter 'a'.", type: "Property", column: 11 }, - { message: "Getter is not present for setter 'a'.", type: "Property", column: 26 } - ] - }, + // Full location tests + { + code: "var o = { get b() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + errors: [ + { + message: "Setter is not present for getter 'b'.", + type: "Property", + line: 1, + column: 11, + endLine: 1, + endColumn: 16, + }, + ], + }, + { + code: "var o = {\n set [\n a](foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 2015 }, + errors: [ + { + message: "Getter is not present for setter.", + type: "Property", + line: 2, + column: 3, + endLine: 3, + endColumn: 4, + }, + ], + }, - // Other elements or even value property duplicates in the same literal do not affect this rule - { - code: "var o = { a, get b() {}, c };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for getter 'b'.", type: "Property", column: 14 }] - }, - { - code: "var o = { a, get b() {}, c, set d(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for getter 'b'.", type: "Property", column: 14 }, - { message: "Getter is not present for setter 'd'.", type: "Property", column: 29 } - ] - }, - { - code: "var o = { get a() {}, a:1 };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }] - }, - { - code: "var o = { a, get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for getter 'a'.", type: "Property", column: 14 }] - }, - { - code: "var o = { set a(foo) {}, a:1 };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for setter 'a'.", type: "Property", column: 11 }] - }, - { - code: "var o = { a, set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for setter 'a'.", type: "Property", column: 14 }] - }, - { - code: "var o = { get a() {}, ...b };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 2018 }, - errors: [{ message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }] - }, - { - code: "var o = { get a() {}, ...a };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 2018 }, - errors: [{ message: "Setter is not present for getter 'a'.", type: "Property", column: 11 }] - }, - { - code: "var o = { set a(foo) {}, ...a };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 2018 }, - errors: [{ message: "Getter is not present for setter 'a'.", type: "Property", column: 11 }] - }, + //------------------------------------------------------------------------------ + // Property descriptors + //------------------------------------------------------------------------------ - // Full location tests - { - code: "var o = { get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - errors: [{ - message: "Setter is not present for getter 'a'.", - type: "Property", - line: 1, - column: 11, - endLine: 1, - endColumn: 16 - }] - }, - { - code: "var o = {\n set [\n a](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 2015 }, - errors: [{ - message: "Getter is not present for setter.", - type: "Property", - line: 2, - column: 3, - endLine: 3, - endColumn: 4 - }] - }, + { + code: "var o = {d: 1};\n Object.defineProperty(o, 'c', \n{set: function(value) {\n val = value; \n} \n});", + errors: [ + { + message: "Getter is not present in property descriptor.", + type: "ObjectExpression", + }, + ], + }, + { + code: "Reflect.defineProperty(obj, 'foo', {set: function(value) {}});", + errors: [ + { + message: "Getter is not present in property descriptor.", + type: "ObjectExpression", + }, + ], + }, + { + code: "Object.defineProperties(obj, {foo: {set: function(value) {}}});", + errors: [ + { + message: "Getter is not present in property descriptor.", + type: "ObjectExpression", + }, + ], + }, + { + code: "Object.create(null, {foo: {set: function(value) {}}});", + errors: [ + { + message: "Getter is not present in property descriptor.", + type: "ObjectExpression", + }, + ], + }, + { + code: "var o = {d: 1};\n Object?.defineProperty(o, 'c', \n{set: function(value) {\n val = value; \n} \n});", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + message: "Getter is not present in property descriptor.", + type: "ObjectExpression", + }, + ], + }, + { + code: "Reflect?.defineProperty(obj, 'foo', {set: function(value) {}});", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + message: "Getter is not present in property descriptor.", + type: "ObjectExpression", + }, + ], + }, + { + code: "Object?.defineProperties(obj, {foo: {set: function(value) {}}});", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + message: "Getter is not present in property descriptor.", + type: "ObjectExpression", + }, + ], + }, + { + code: "Object?.create(null, {foo: {set: function(value) {}}});", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + message: "Getter is not present in property descriptor.", + type: "ObjectExpression", + }, + ], + }, + { + code: "var o = {d: 1};\n (Object?.defineProperty)(o, 'c', \n{set: function(value) {\n val = value; \n} \n});", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + message: "Getter is not present in property descriptor.", + type: "ObjectExpression", + }, + ], + }, + { + code: "(Reflect?.defineProperty)(obj, 'foo', {set: function(value) {}});", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + message: "Getter is not present in property descriptor.", + type: "ObjectExpression", + }, + ], + }, + { + code: "(Object?.defineProperties)(obj, {foo: {set: function(value) {}}});", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + message: "Getter is not present in property descriptor.", + type: "ObjectExpression", + }, + ], + }, + { + code: "(Object?.create)(null, {foo: {set: function(value) {}}});", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + message: "Getter is not present in property descriptor.", + type: "ObjectExpression", + }, + ], + }, - //------------------------------------------------------------------------------ - // Property descriptors - //------------------------------------------------------------------------------ + //------------------------------------------------------------------------------ + // Classes + //------------------------------------------------------------------------------ - { - code: "var o = {d: 1};\n Object.defineProperty(o, 'c', \n{set: function(value) {\n val = value; \n} \n});", - errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] - }, - { - code: "Reflect.defineProperty(obj, 'foo', {set: function(value) {}});", - errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] - }, - { - code: "Object.defineProperties(obj, {foo: {set: function(value) {}}});", - errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] - }, - { - code: "Object.create(null, {foo: {set: function(value) {}}});", - errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] - }, - { - code: "var o = {d: 1};\n Object?.defineProperty(o, 'c', \n{set: function(value) {\n val = value; \n} \n});", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] - }, - { - code: "Reflect?.defineProperty(obj, 'foo', {set: function(value) {}});", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] - }, - { - code: "Object?.defineProperties(obj, {foo: {set: function(value) {}}});", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] - }, - { - code: "Object?.create(null, {foo: {set: function(value) {}}});", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] - }, - { - code: "var o = {d: 1};\n (Object?.defineProperty)(o, 'c', \n{set: function(value) {\n val = value; \n} \n});", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] - }, - { - code: "(Reflect?.defineProperty)(obj, 'foo', {set: function(value) {}});", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] - }, - { - code: "(Object?.defineProperties)(obj, {foo: {set: function(value) {}}});", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] - }, - { - code: "(Object?.create)(null, {foo: {set: function(value) {}}});", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ message: "Getter is not present in property descriptor.", type: "ObjectExpression" }] - }, + // Test default settings + { + code: "class A { set a(foo) {} }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { get a() {} set b(foo) {} }", + options: [{}], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'b'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { get a() {} }", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { set a(foo) {} }", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { static get a() {} }", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class static getter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { static set a(foo) {} }", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Getter is not present for class static setter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "A = class { get a() {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "A = class { get a() {} set b(foo) {} };", + options: [{ setWithoutGet: true, getWithoutSet: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + }, + { + message: "Getter is not present for class setter 'b'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { set a(value) {} }", + options: [{ enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { static set a(value) {} }", + options: [{ enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Getter is not present for class static setter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "A = class { set a(value) {} };", + options: [{ enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "(class A { static set a(value) {} });", + options: [{ enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Getter is not present for class static setter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { set '#a'(foo) {} }", + languageOptions: { ecmaVersion: 13 }, + errors: [ + { + message: "Getter is not present for class setter '#a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { set #a(foo) {} }", + languageOptions: { ecmaVersion: 13 }, + errors: [ + { + message: + "Getter is not present for class private setter #a.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { static set '#a'(foo) {} }", + languageOptions: { ecmaVersion: 13 }, + errors: [ + { + message: + "Getter is not present for class static setter '#a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { static set #a(foo) {} }", + languageOptions: { ecmaVersion: 13 }, + errors: [ + { + message: + "Getter is not present for class static private setter #a.", + type: "MethodDefinition", + }, + ], + }, - //------------------------------------------------------------------------------ - // Classes - //------------------------------------------------------------------------------ + // Test that the accessor kind options do not affect each other + { + code: "class A { set a(value) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: false, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "A = class { static set a(value) {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Getter is not present for class static setter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "let foo = class A { get a() {} };", + options: [ + { + setWithoutGet: false, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { static get a() {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class static getter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "(class { get a() {} });", + options: [{ getWithoutSet: true, enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { get '#a'() {} };", + options: [ + { + setWithoutGet: false, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 13 }, + errors: [ + { + message: "Setter is not present for class getter '#a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { get #a() {} };", + options: [ + { + setWithoutGet: false, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 13 }, + errors: [ + { + message: + "Setter is not present for class private getter #a.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { static get '#a'() {} };", + options: [ + { + setWithoutGet: false, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 13 }, + errors: [ + { + message: + "Setter is not present for class static getter '#a'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { static get #a() {} };", + options: [ + { + setWithoutGet: false, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 13 }, + errors: [ + { + message: + "Setter is not present for class static private getter #a.", + type: "MethodDefinition", + }, + ], + }, - // Test default settings - { - code: "class A { set a(foo) {} }", - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class setter 'a'.", type: "MethodDefinition" }] - }, - { - code: "class A { get a() {} set b(foo) {} }", - options: [{}], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class setter 'b'.", type: "MethodDefinition" }] - }, - { - code: "class A { get a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class getter 'a'.", type: "MethodDefinition" }] - }, - { - code: "class A { set a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class setter 'a'.", type: "MethodDefinition" }] - }, - { - code: "class A { static get a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class static getter 'a'.", type: "MethodDefinition" }] - }, - { - code: "class A { static set a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class static setter 'a'.", type: "MethodDefinition" }] - }, - { - code: "A = class { get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class getter 'a'.", type: "MethodDefinition" }] - }, - { - code: "A = class { get a() {} set b(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'a'.", type: "MethodDefinition" }, - { message: "Getter is not present for class setter 'b'.", type: "MethodDefinition" } - ] - }, - { - code: "class A { set a(value) {} }", - options: [{ enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class setter 'a'.", type: "MethodDefinition" }] - }, - { - code: "class A { static set a(value) {} }", - options: [{ enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class static setter 'a'.", type: "MethodDefinition" }] - }, - { - code: "A = class { set a(value) {} };", - options: [{ enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class setter 'a'.", type: "MethodDefinition" }] - }, - { - code: "(class A { static set a(value) {} });", - options: [{ enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class static setter 'a'.", type: "MethodDefinition" }] - }, - { - code: "class A { set '#a'(foo) {} }", - languageOptions: { ecmaVersion: 13 }, - errors: [{ message: "Getter is not present for class setter '#a'.", type: "MethodDefinition" }] - }, - { - code: "class A { set #a(foo) {} }", - languageOptions: { ecmaVersion: 13 }, - errors: [{ message: "Getter is not present for class private setter #a.", type: "MethodDefinition" }] - }, - { - code: "class A { static set '#a'(foo) {} }", - languageOptions: { ecmaVersion: 13 }, - errors: [{ message: "Getter is not present for class static setter '#a'.", type: "MethodDefinition" }] - }, - { - code: "class A { static set #a(foo) {} }", - languageOptions: { ecmaVersion: 13 }, - errors: [{ message: "Getter is not present for class static private setter #a.", type: "MethodDefinition" }] - }, + // Various kinds of keys + { + code: "class A { get abc() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'abc'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "A = class { static set 'abc'(foo) {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Getter is not present for class static setter 'abc'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "(class { get 123() {} });", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter '123'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { static get 1e2() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class static getter '100'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "A = class { get ['abc']() {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'abc'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { set [`abc`](foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'abc'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { static get [123]() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class static getter '123'.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { get [abc]() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { static get [f(abc)]() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class static getter.", + type: "MethodDefinition", + }, + ], + }, + { + code: "A = class { set [a + b](foo) {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter.", + type: "MethodDefinition", + }, + ], + }, + { + code: "class A { get ['constructor']() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class getter 'constructor'.", + type: "MethodDefinition", + }, + ], + }, - // Test that the accessor kind options do not affect each other - { - code: "class A { set a(value) {} }", - options: [{ setWithoutGet: true, getWithoutSet: false, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class setter 'a'.", type: "MethodDefinition" }] - }, - { - code: "A = class { static set a(value) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class static setter 'a'.", type: "MethodDefinition" }] - }, - { - code: "let foo = class A { get a() {} };", - options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class getter 'a'.", type: "MethodDefinition" }] - }, - { - code: "class A { static get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class static getter 'a'.", type: "MethodDefinition" }] - }, - { - code: "(class { get a() {} });", - options: [{ getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class getter 'a'.", type: "MethodDefinition" }] - }, - { - code: "class A { get '#a'() {} };", - options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 13 }, - errors: [{ message: "Setter is not present for class getter '#a'.", type: "MethodDefinition" }] - }, - { - code: "class A { get #a() {} };", - options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 13 }, - errors: [{ message: "Setter is not present for class private getter #a.", type: "MethodDefinition" }] - }, - { - code: "class A { static get '#a'() {} };", - options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 13 }, - errors: [{ message: "Setter is not present for class static getter '#a'.", type: "MethodDefinition" }] - }, - { - code: "class A { static get #a() {} };", - options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 13 }, - errors: [{ message: "Setter is not present for class static private getter #a.", type: "MethodDefinition" }] - }, + // Different keys + { + code: "class A { get a() {} set b(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Getter is not present for class setter 'b'.", + type: "MethodDefinition", + column: 22, + }, + ], + }, + { + code: "A = class { set a(foo) {} get b() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + column: 13, + }, + { + message: "Setter is not present for class getter 'b'.", + type: "MethodDefinition", + column: 27, + }, + ], + }, + { + code: "A = class { static get a() {} static set b(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class static getter 'a'.", + type: "MethodDefinition", + column: 13, + }, + { + message: + "Getter is not present for class static setter 'b'.", + type: "MethodDefinition", + column: 31, + }, + ], + }, + { + code: "class A { get a() {} set b(foo) {} }", + options: [ + { + setWithoutGet: false, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 11, + }, + ], + }, + { + code: "class A { get a() {} set b(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: false, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'b'.", + type: "MethodDefinition", + column: 22, + }, + ], + }, + { + code: "class A { get 'a '() {} set 'a'(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a '.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + column: 25, + }, + ], + }, + { + code: "class A { get 'a'() {} set 1(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Getter is not present for class setter '1'.", + type: "MethodDefinition", + column: 24, + }, + ], + }, + { + code: "class A { get 1() {} set 2(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter '1'.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Getter is not present for class setter '2'.", + type: "MethodDefinition", + column: 22, + }, + ], + }, + { + code: "class A { get ''() {} set null(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter ''.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Getter is not present for class setter 'null'.", + type: "MethodDefinition", + column: 23, + }, + ], + }, + { + code: "class A { get a() {} set [a](foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Getter is not present for class setter.", + type: "MethodDefinition", + column: 22, + }, + ], + }, + { + code: "class A { get [a]() {} set [b](foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Getter is not present for class setter.", + type: "MethodDefinition", + column: 24, + }, + ], + }, + { + code: "class A { get [a]() {} set [a++](foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Getter is not present for class setter.", + type: "MethodDefinition", + column: 24, + }, + ], + }, + { + code: "class A { get [a + b]() {} set [a - b](foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Getter is not present for class setter.", + type: "MethodDefinition", + column: 28, + }, + ], + }, + { + code: "class A { get #a() {} set '#a'(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 13 }, + errors: [ + { + message: + "Setter is not present for class private getter #a.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Getter is not present for class setter '#a'.", + type: "MethodDefinition", + column: 23, + }, + ], + }, + { + code: "class A { get '#a'() {} set #a(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 13 }, + errors: [ + { + message: "Setter is not present for class getter '#a'.", + type: "MethodDefinition", + column: 11, + }, + { + message: + "Getter is not present for class private setter #a.", + type: "MethodDefinition", + column: 25, + }, + ], + }, - // Various kinds of keys - { - code: "class A { get abc() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class getter 'abc'.", type: "MethodDefinition" }] - }, - { - code: "A = class { static set 'abc'(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class static setter 'abc'.", type: "MethodDefinition" }] - }, - { - code: "(class { get 123() {} });", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class getter '123'.", type: "MethodDefinition" }] - }, - { - code: "class A { static get 1e2() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class static getter '100'.", type: "MethodDefinition" }] - }, - { - code: "A = class { get ['abc']() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class getter 'abc'.", type: "MethodDefinition" }] - }, - { - code: "class A { set [`abc`](foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class setter 'abc'.", type: "MethodDefinition" }] - }, - { - code: "class A { static get [123]() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class static getter '123'.", type: "MethodDefinition" }] - }, - { - code: "class A { get [abc]() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class getter.", type: "MethodDefinition" }] - }, - { - code: "class A { static get [f(abc)]() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class static getter.", type: "MethodDefinition" }] - }, - { - code: "A = class { set [a + b](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class setter.", type: "MethodDefinition" }] - }, - { - code: "class A { get ['constructor']() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class getter 'constructor'.", type: "MethodDefinition" }] - }, + // Prototype and static accessors with same keys + { + code: "class A { get a() {} static set a(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 11, + }, + { + message: + "Getter is not present for class static setter 'a'.", + type: "MethodDefinition", + column: 22, + }, + ], + }, + { + code: "A = class { static get a() {} set a(foo) {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class static getter 'a'.", + type: "MethodDefinition", + column: 13, + }, + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + column: 31, + }, + ], + }, + { + code: "class A { set [a](foo) {} static get [a]() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Setter is not present for class static getter.", + type: "MethodDefinition", + column: 27, + }, + ], + }, + { + code: "class A { static set [a](foo) {} get [a]() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class static setter.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Setter is not present for class getter.", + type: "MethodDefinition", + column: 34, + }, + ], + }, - // Different keys - { - code: "class A { get a() {} set b(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class setter 'b'.", type: "MethodDefinition", column: 22 } - ] - }, - { - code: "A = class { set a(foo) {} get b() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Getter is not present for class setter 'a'.", type: "MethodDefinition", column: 13 }, - { message: "Setter is not present for class getter 'b'.", type: "MethodDefinition", column: 27 } - ] - }, - { - code: "A = class { static get a() {} static set b(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class static getter 'a'.", type: "MethodDefinition", column: 13 }, - { message: "Getter is not present for class static setter 'b'.", type: "MethodDefinition", column: 31 } - ] - }, - { - code: "class A { get a() {} set b(foo) {} }", - options: [{ setWithoutGet: false, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 11 } - ] - }, - { - code: "class A { get a() {} set b(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: false, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Getter is not present for class setter 'b'.", type: "MethodDefinition", column: 22 } - ] - }, - { - code: "class A { get 'a '() {} set 'a'(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'a '.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class setter 'a'.", type: "MethodDefinition", column: 25 } - ] - }, - { - code: "class A { get 'a'() {} set 1(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class setter '1'.", type: "MethodDefinition", column: 24 } - ] - }, - { - code: "class A { get 1() {} set 2(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter '1'.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class setter '2'.", type: "MethodDefinition", column: 22 } - ] - }, - { - code: "class A { get ''() {} set null(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter ''.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class setter 'null'.", type: "MethodDefinition", column: 23 } - ] - }, - { - code: "class A { get a() {} set [a](foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class setter.", type: "MethodDefinition", column: 22 } - ] - }, - { - code: "class A { get [a]() {} set [b](foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class setter.", type: "MethodDefinition", column: 24 } - ] - }, - { - code: "class A { get [a]() {} set [a++](foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class setter.", type: "MethodDefinition", column: 24 } - ] - }, - { - code: "class A { get [a + b]() {} set [a - b](foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class setter.", type: "MethodDefinition", column: 28 } - ] - }, - { - code: "class A { get #a() {} set '#a'(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 13 }, - errors: [ - { message: "Setter is not present for class private getter #a.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class setter '#a'.", type: "MethodDefinition", column: 23 } - ] - }, - { - code: "class A { get '#a'() {} set #a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 13 }, - errors: [ - { message: "Setter is not present for class getter '#a'.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class private setter #a.", type: "MethodDefinition", column: 25 } - ] - }, + // Multiple invalid of same and different kinds + { + code: "class A { get a() {} get b() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Setter is not present for class getter 'b'.", + type: "MethodDefinition", + column: 22, + }, + ], + }, + { + code: "A = class { get a() {} get [b]() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 13, + }, + { + message: "Setter is not present for class getter.", + type: "MethodDefinition", + column: 24, + }, + ], + }, + { + code: "class A { get [a]() {} get [b]() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Setter is not present for class getter.", + type: "MethodDefinition", + column: 24, + }, + ], + }, + { + code: "A = class { set a(foo) {} set b(bar) {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + column: 13, + }, + { + message: "Getter is not present for class setter 'b'.", + type: "MethodDefinition", + column: 27, + }, + ], + }, + { + code: "class A { static get a() {} static get b() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class static getter 'a'.", + type: "MethodDefinition", + column: 11, + }, + { + message: + "Setter is not present for class static getter 'b'.", + type: "MethodDefinition", + column: 29, + }, + ], + }, + { + code: "A = class { static set a(foo) {} static set b(bar) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Getter is not present for class static setter 'a'.", + type: "MethodDefinition", + column: 13, + }, + { + message: + "Getter is not present for class static setter 'b'.", + type: "MethodDefinition", + column: 34, + }, + ], + }, + { + code: "class A { static get a() {} set b(foo) {} static set c(bar) {} get d() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class static getter 'a'.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Getter is not present for class setter 'b'.", + type: "MethodDefinition", + column: 29, + }, + { + message: + "Getter is not present for class static setter 'c'.", + type: "MethodDefinition", + column: 43, + }, + { + message: "Setter is not present for class getter 'd'.", + type: "MethodDefinition", + column: 64, + }, + ], + }, - // Prototype and static accessors with same keys - { - code: "class A { get a() {} static set a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class static setter 'a'.", type: "MethodDefinition", column: 22 } - ] - }, - { - code: "A = class { static get a() {} set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class static getter 'a'.", type: "MethodDefinition", column: 13 }, - { message: "Getter is not present for class setter 'a'.", type: "MethodDefinition", column: 31 } - ] - }, - { - code: "class A { set [a](foo) {} static get [a]() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Getter is not present for class setter.", type: "MethodDefinition", column: 11 }, - { message: "Setter is not present for class static getter.", type: "MethodDefinition", column: 27 } - ] - }, - { - code: "class A { static set [a](foo) {} get [a]() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Getter is not present for class static setter.", type: "MethodDefinition", column: 11 }, - { message: "Setter is not present for class getter.", type: "MethodDefinition", column: 34 } - ] - }, + // Checks per class + { + code: "class A { get a() {} } class B { set a(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + column: 34, + }, + ], + }, + { + code: "A = class { set a(foo) {} }, class { get a() {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + column: 13, + }, + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 38, + }, + ], + }, + { + code: "A = class { get a() {} }, { set a(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 13, + }, + { + message: "Getter is not present for setter 'a'.", + type: "Property", + column: 29, + }, + ], + }, + { + code: "A = { get a() {} }, class { set a(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for getter 'a'.", + type: "Property", + column: 7, + }, + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + column: 29, + }, + ], + }, - // Multiple invalid of same and different kinds - { - code: "class A { get a() {} get b() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 11 }, - { message: "Setter is not present for class getter 'b'.", type: "MethodDefinition", column: 22 } - ] - }, - { - code: "A = class { get a() {} get [b]() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 13 }, - { message: "Setter is not present for class getter.", type: "MethodDefinition", column: 24 } - ] - }, - { - code: "class A { get [a]() {} get [b]() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter.", type: "MethodDefinition", column: 11 }, - { message: "Setter is not present for class getter.", type: "MethodDefinition", column: 24 } - ] - }, - { - code: "A = class { set a(foo) {} set b(bar) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Getter is not present for class setter 'a'.", type: "MethodDefinition", column: 13 }, - { message: "Getter is not present for class setter 'b'.", type: "MethodDefinition", column: 27 } - ] - }, - { - code: "class A { static get a() {} static get b() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class static getter 'a'.", type: "MethodDefinition", column: 11 }, - { message: "Setter is not present for class static getter 'b'.", type: "MethodDefinition", column: 29 } - ] - }, - { - code: "A = class { static set a(foo) {} static set b(bar) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Getter is not present for class static setter 'a'.", type: "MethodDefinition", column: 13 }, - { message: "Getter is not present for class static setter 'b'.", type: "MethodDefinition", column: 34 } - ] - }, - { - code: "class A { static get a() {} set b(foo) {} static set c(bar) {} get d() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class static getter 'a'.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class setter 'b'.", type: "MethodDefinition", column: 29 }, - { message: "Getter is not present for class static setter 'c'.", type: "MethodDefinition", column: 43 }, - { message: "Setter is not present for class getter 'd'.", type: "MethodDefinition", column: 64 } - ] - }, + // Combinations or valid and invalid + { + code: "class A { get a() {} get b() {} set b(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 11, + }, + ], + }, + { + code: "A = class { get b() {} get a() {} set b(foo) {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 24, + }, + ], + }, + { + code: "class A { set b(foo) {} get b() {} set a(bar) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + column: 36, + }, + ], + }, + { + code: "A = class { static get b() {} set a(foo) {} static set b(bar) {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + column: 31, + }, + ], + }, + { + code: "class A { static set a(foo) {} get b() {} set b(bar) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Getter is not present for class static setter 'a'.", + type: "MethodDefinition", + column: 11, + }, + ], + }, + { + code: "class A { get b() {} static get a() {} set b(bar) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class static getter 'a'.", + type: "MethodDefinition", + column: 22, + }, + ], + }, + { + code: "class A { static set b(foo) {} static get a() {} static get b() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class static getter 'a'.", + type: "MethodDefinition", + column: 32, + }, + ], + }, + { + code: "class A { get [v1](){} static set i1(foo){} static set v2(bar){} get [i2](){} static get i3(){} set [v1](baz){} static get v2(){} set i4(quux){} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Getter is not present for class static setter 'i1'.", + type: "MethodDefinition", + column: 24, + }, + { + message: "Setter is not present for class getter.", + type: "MethodDefinition", + column: 66, + }, + { + message: + "Setter is not present for class static getter 'i3'.", + type: "MethodDefinition", + column: 79, + }, + { + message: "Getter is not present for class setter 'i4'.", + type: "MethodDefinition", + column: 131, + }, + ], + }, - // Checks per class - { - code: "class A { get a() {} } class B { set a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class setter 'a'.", type: "MethodDefinition", column: 34 } - ] - }, - { - code: "A = class { set a(foo) {} }, class { get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Getter is not present for class setter 'a'.", type: "MethodDefinition", column: 13 }, - { message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 38 } - ] - }, - { - code: "A = class { get a() {} }, { set a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 13 }, - { message: "Getter is not present for setter 'a'.", type: "Property", column: 29 } - ] - }, - { - code: "A = { get a() {} }, class { set a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for getter 'a'.", type: "Property", column: 7 }, - { message: "Getter is not present for class setter 'a'.", type: "MethodDefinition", column: 29 } - ] - }, + // In the case of duplicates which don't have the other kind, all nodes are reported + { + code: "class A { get a() {} get a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 22, + }, + ], + }, + { + code: "A = class { set a(foo) {} set a(foo) {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + column: 13, + }, + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + column: 27, + }, + ], + }, + { + code: "A = class { static get a() {} static get a() {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class static getter 'a'.", + type: "MethodDefinition", + column: 13, + }, + { + message: + "Setter is not present for class static getter 'a'.", + type: "MethodDefinition", + column: 31, + }, + ], + }, + { + code: "class A { set a(foo) {} set a(foo) {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + column: 11, + }, + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + column: 25, + }, + ], + }, - // Combinations or valid and invalid - { - code: "class A { get a() {} get b() {} set b(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 11 }] - }, - { - code: "A = class { get b() {} get a() {} set b(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 24 }] - }, - { - code: "class A { set b(foo) {} get b() {} set a(bar) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class setter 'a'.", type: "MethodDefinition", column: 36 }] - }, - { - code: "A = class { static get b() {} set a(foo) {} static set b(bar) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class setter 'a'.", type: "MethodDefinition", column: 31 }] - }, - { - code: "class A { static set a(foo) {} get b() {} set b(bar) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Getter is not present for class static setter 'a'.", type: "MethodDefinition", column: 11 }] - }, - { - code: "class A { get b() {} static get a() {} set b(bar) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class static getter 'a'.", type: "MethodDefinition", column: 22 }] - }, - { - code: "class A { static set b(foo) {} static get a() {} static get b() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ message: "Setter is not present for class static getter 'a'.", type: "MethodDefinition", column: 32 }] - }, - { - code: "class A { get [v1](){} static set i1(foo){} static set v2(bar){} get [i2](){} static get i3(){} set [v1](baz){} static get v2(){} set i4(quux){} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Getter is not present for class static setter 'i1'.", type: "MethodDefinition", column: 24 }, - { message: "Setter is not present for class getter.", type: "MethodDefinition", column: 66 }, - { message: "Setter is not present for class static getter 'i3'.", type: "MethodDefinition", column: 79 }, - { message: "Getter is not present for class setter 'i4'.", type: "MethodDefinition", column: 131 } - ] - }, + // Other elements or even method duplicates in the same class do not affect this rule + { + code: "class A { a() {} get b() {} c() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'b'.", + type: "MethodDefinition", + column: 18, + }, + ], + }, + { + code: "A = class { a() {} get b() {} c() {} set d(foo) {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'b'.", + type: "MethodDefinition", + column: 20, + }, + { + message: "Getter is not present for class setter 'd'.", + type: "MethodDefinition", + column: 38, + }, + ], + }, + { + code: "class A { static a() {} get b() {} static c() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'b'.", + type: "MethodDefinition", + column: 25, + }, + ], + }, + { + code: "class A { a() {} get a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + column: 18, + }, + ], + }, + { + code: "A = class { static a() {} set a(foo) {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter 'a'.", + type: "MethodDefinition", + column: 27, + }, + ], + }, + { + code: "class A { a() {} static get b() {} c() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class static getter 'b'.", + type: "MethodDefinition", + column: 18, + }, + ], + }, + { + code: "A = class { static a() {} static set b(foo) {} static c() {} d() {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Getter is not present for class static setter 'b'.", + type: "MethodDefinition", + column: 27, + }, + ], + }, + { + code: "class A { a() {} static get a() {} a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class static getter 'a'.", + type: "MethodDefinition", + column: 18, + }, + ], + }, + { + code: "class A { static set a(foo) {} static a() {} }", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Getter is not present for class static setter 'a'.", + type: "MethodDefinition", + column: 11, + }, + ], + }, - // In the case of duplicates which don't have the other kind, all nodes are reported - { - code: "class A { get a() {} get a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 11 }, - { message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 22 } - ] - }, - { - code: "A = class { set a(foo) {} set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Getter is not present for class setter 'a'.", type: "MethodDefinition", column: 13 }, - { message: "Getter is not present for class setter 'a'.", type: "MethodDefinition", column: 27 } - ] - }, - { - code: "A = class { static get a() {} static get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class static getter 'a'.", type: "MethodDefinition", column: 13 }, - { message: "Setter is not present for class static getter 'a'.", type: "MethodDefinition", column: 31 } - ] - }, - { - code: "class A { set a(foo) {} set a(foo) {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Getter is not present for class setter 'a'.", type: "MethodDefinition", column: 11 }, - { message: "Getter is not present for class setter 'a'.", type: "MethodDefinition", column: 25 } - ] - }, - - // Other elements or even method duplicates in the same class do not affect this rule - { - code: "class A { a() {} get b() {} c() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'b'.", type: "MethodDefinition", column: 18 } - ] - }, - { - code: "A = class { a() {} get b() {} c() {} set d(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'b'.", type: "MethodDefinition", column: 20 }, - { message: "Getter is not present for class setter 'd'.", type: "MethodDefinition", column: 38 } - ] - }, - { - code: "class A { static a() {} get b() {} static c() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'b'.", type: "MethodDefinition", column: 25 } - ] - }, - { - code: "class A { a() {} get a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class getter 'a'.", type: "MethodDefinition", column: 18 } - ] - }, - { - code: "A = class { static a() {} set a(foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Getter is not present for class setter 'a'.", type: "MethodDefinition", column: 27 } - ] - }, - { - code: "class A { a() {} static get b() {} c() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class static getter 'b'.", type: "MethodDefinition", column: 18 } - ] - }, - { - code: "A = class { static a() {} static set b(foo) {} static c() {} d() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Getter is not present for class static setter 'b'.", type: "MethodDefinition", column: 27 } - ] - }, - { - code: "class A { a() {} static get a() {} a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Setter is not present for class static getter 'a'.", type: "MethodDefinition", column: 18 } - ] - }, - { - code: "class A { static set a(foo) {} static a() {} }", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { message: "Getter is not present for class static setter 'a'.", type: "MethodDefinition", column: 11 } - ] - }, - - // Full location tests - { - code: "class A { get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - message: "Setter is not present for class getter 'a'.", - type: "MethodDefinition", - line: 1, - column: 11, - endLine: 1, - endColumn: 16 - }] - }, - { - code: "A = class {\n set [\n a](foo) {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - message: "Getter is not present for class setter.", - type: "MethodDefinition", - line: 2, - column: 3, - endLine: 3, - endColumn: 4 - }] - }, - { - code: "class A { static get a() {} };", - options: [{ setWithoutGet: true, getWithoutSet: true, enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - message: "Setter is not present for class static getter 'a'.", - type: "MethodDefinition", - line: 1, - column: 11, - endLine: 1, - endColumn: 23 - }] - } - ] + // Full location tests + { + code: "class A { get a() {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Setter is not present for class getter 'a'.", + type: "MethodDefinition", + line: 1, + column: 11, + endLine: 1, + endColumn: 16, + }, + ], + }, + { + code: "A = class {\n set [\n a](foo) {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "Getter is not present for class setter.", + type: "MethodDefinition", + line: 2, + column: 3, + endLine: 3, + endColumn: 4, + }, + ], + }, + { + code: "class A { static get b() {} };", + options: [ + { + setWithoutGet: true, + getWithoutSet: true, + enforceForClassMembers: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Setter is not present for class static getter 'b'.", + type: "MethodDefinition", + line: 1, + column: 11, + endLine: 1, + endColumn: 23, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/array-bracket-newline.js b/tests/lib/rules/array-bracket-newline.js index 74f1ac3076c9..29735810f7b0 100644 --- a/tests/lib/rules/array-bracket-newline.js +++ b/tests/lib/rules/array-bracket-newline.js @@ -10,8 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/array-bracket-newline"); -const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); - +const RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -20,55 +19,53 @@ const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); const ruleTester = new RuleTester(); ruleTester.run("array-bracket-newline", rule, { + valid: [ + /* + * ArrayExpression + * "default" { multiline: true } + */ + "var foo = [];", + "var foo = [1];", + "var foo = /* any comment */[1];", + "var foo = /* any comment */\n[1];", + "var foo = [1, 2];", + "var foo = [ // any comment\n1, 2\n];", + "var foo = [\n// any comment\n1, 2\n];", + "var foo = [\n1, 2\n// any comment\n];", + "var foo = [\n1,\n2\n];", + "var foo = [\nfunction foo() {\nreturn dosomething();\n}\n];", + "var foo = [/* \nany comment\n */];", + "var foo = [/* single line multiline comment for no real reason */];", + "var foo = [[1,2]]", - valid: [ - - /* - * ArrayExpression - * "default" { multiline: true } - */ - "var foo = [];", - "var foo = [1];", - "var foo = /* any comment */[1];", - "var foo = /* any comment */\n[1];", - "var foo = [1, 2];", - "var foo = [ // any comment\n1, 2\n];", - "var foo = [\n// any comment\n1, 2\n];", - "var foo = [\n1, 2\n// any comment\n];", - "var foo = [\n1,\n2\n];", - "var foo = [\nfunction foo() {\nreturn dosomething();\n}\n];", - "var foo = [/* \nany comment\n */];", - "var foo = [/* single line multiline comment for no real reason */];", - "var foo = [[1,2]]", - - // "always" - { code: "var foo = [\n];", options: ["always"] }, - { code: "var foo = [\n1\n];", options: ["always"] }, - { code: "var foo = [\n// any\n1\n];", options: ["always"] }, - { code: "var foo = [\n/* any */\n1\n];", options: ["always"] }, - { code: "var foo = [\n1, 2\n];", options: ["always"] }, - { code: "var foo = [\n1, 2 // any comment\n];", options: ["always"] }, - { - code: "var foo = [\n1, 2 /* any comment */\n];", - options: ["always"] - }, - { code: "var foo = [\n1,\n2\n];", options: ["always"] }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", - options: ["always"] - }, - { - code: ` + // "always" + { code: "var foo = [\n];", options: ["always"] }, + { code: "var foo = [\n1\n];", options: ["always"] }, + { code: "var foo = [\n// any\n1\n];", options: ["always"] }, + { code: "var foo = [\n/* any */\n1\n];", options: ["always"] }, + { code: "var foo = [\n1, 2\n];", options: ["always"] }, + { code: "var foo = [\n1, 2 // any comment\n];", options: ["always"] }, + { + code: "var foo = [\n1, 2 /* any comment */\n];", + options: ["always"], + }, + { code: "var foo = [\n1,\n2\n];", options: ["always"] }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", + options: ["always"], + }, + { + code: ` var foo = [ [ 1,2 ] ] `, - options: ["always"] - }, - { - code: ` + options: ["always"], + }, + { + code: ` var foo = [ 0, [ @@ -77,1934 +74,1914 @@ ruleTester.run("array-bracket-newline", rule, { 3 ] `, - options: ["always"] - }, + options: ["always"], + }, - // "never" - { code: "var foo = [];", options: ["never"] }, - { code: "var foo = [1];", options: ["never"] }, - { code: "var foo = [/* any comment */1];", options: ["never"] }, - { code: "var foo = [1, 2];", options: ["never"] }, - { code: "var foo = [1,\n2];", options: ["never"] }, - { code: "var foo = [1,\n/* any comment */\n2];", options: ["never"] }, - { - code: "var foo = [function foo() {\ndosomething();\n}];", - options: ["never"] - }, - { - code: "var foo = [[1,2],3];", - options: ["never"] - }, + // "never" + { code: "var foo = [];", options: ["never"] }, + { code: "var foo = [1];", options: ["never"] }, + { code: "var foo = [/* any comment */1];", options: ["never"] }, + { code: "var foo = [1, 2];", options: ["never"] }, + { code: "var foo = [1,\n2];", options: ["never"] }, + { code: "var foo = [1,\n/* any comment */\n2];", options: ["never"] }, + { + code: "var foo = [function foo() {\ndosomething();\n}];", + options: ["never"], + }, + { + code: "var foo = [[1,2],3];", + options: ["never"], + }, - // "consistent" - { code: "var a = []", options: ["consistent"] }, - { code: "var a = [\n]", options: ["consistent"] }, - { code: "var a = [1]", options: ["consistent"] }, - { code: "var a = [\n1\n]", options: ["consistent"] }, - { code: "var a = [//\n1\n]", options: ["consistent"] }, - { code: "var a = [/**/\n1\n]", options: ["consistent"] }, - { code: "var a = [/*\n*/1\n]", options: ["consistent"] }, - { code: "var a = [//\n]", options: ["consistent"] }, - { - code: `var a = [ + // "consistent" + { code: "var a = []", options: ["consistent"] }, + { code: "var a = [\n]", options: ["consistent"] }, + { code: "var a = [1]", options: ["consistent"] }, + { code: "var a = [\n1\n]", options: ["consistent"] }, + { code: "var a = [//\n1\n]", options: ["consistent"] }, + { code: "var a = [/**/\n1\n]", options: ["consistent"] }, + { code: "var a = [/*\n*/1\n]", options: ["consistent"] }, + { code: "var a = [//\n]", options: ["consistent"] }, + { + code: `var a = [ [1,2] ]`, - options: ["consistent"] - }, - { - code: `var a = [ + options: ["consistent"], + }, + { + code: `var a = [ [[1,2]] ]`, - options: ["consistent"] - }, + options: ["consistent"], + }, - // { multiline: true } - { code: "var foo = [];", options: [{ multiline: true }] }, - { code: "var foo = [1];", options: [{ multiline: true }] }, - { - code: "var foo = /* any comment */[1];", - options: [{ multiline: true }] - }, - { - code: "var foo = /* any comment */\n[1];", - options: [{ multiline: true }] - }, - { code: "var foo = [1, 2];", options: [{ multiline: true }] }, - { - code: "var foo = [ // any comment\n1, 2\n];", - options: [{ multiline: true }] - }, - { - code: "var foo = [\n// any comment\n1, 2\n];", - options: [{ multiline: true }] - }, - { - code: "var foo = [\n1, 2\n// any comment\n];", - options: [{ multiline: true }] - }, - { code: "var foo = [\n1,\n2\n];", options: [{ multiline: true }] }, - { - code: "var foo = [\nfunction foo() {\nreturn dosomething();\n}\n];", - options: [{ multiline: true }] - }, - { - code: "var foo = [/* \nany comment\n */];", - options: [{ multiline: true }] - }, - { - code: "var foo = [\n1,\n2,\n[3,4],\n];", - options: [{ multiline: true }] - }, - { - code: "var foo = [\n1,\n2,\n[\n3,\n4\n],\n];", - options: [{ multiline: true }] - }, + // { multiline: true } + { code: "var foo = [];", options: [{ multiline: true }] }, + { code: "var foo = [1];", options: [{ multiline: true }] }, + { + code: "var foo = /* any comment */[1];", + options: [{ multiline: true }], + }, + { + code: "var foo = /* any comment */\n[1];", + options: [{ multiline: true }], + }, + { code: "var foo = [1, 2];", options: [{ multiline: true }] }, + { + code: "var foo = [ // any comment\n1, 2\n];", + options: [{ multiline: true }], + }, + { + code: "var foo = [\n// any comment\n1, 2\n];", + options: [{ multiline: true }], + }, + { + code: "var foo = [\n1, 2\n// any comment\n];", + options: [{ multiline: true }], + }, + { code: "var foo = [\n1,\n2\n];", options: [{ multiline: true }] }, + { + code: "var foo = [\nfunction foo() {\nreturn dosomething();\n}\n];", + options: [{ multiline: true }], + }, + { + code: "var foo = [/* \nany comment\n */];", + options: [{ multiline: true }], + }, + { + code: "var foo = [\n1,\n2,\n[3,4],\n];", + options: [{ multiline: true }], + }, + { + code: "var foo = [\n1,\n2,\n[\n3,\n4\n],\n];", + options: [{ multiline: true }], + }, - // { multiline: false } - { code: "var foo = [];", options: [{ multiline: false }] }, - { code: "var foo = [1];", options: [{ multiline: false }] }, - { - code: "var foo = [1]/* any comment*/;", - options: [{ multiline: false }] - }, - { - code: "var foo = [1]\n/* any comment*/\n;", - options: [{ multiline: false }] - }, - { code: "var foo = [1, 2];", options: [{ multiline: false }] }, - { code: "var foo = [1,\n2];", options: [{ multiline: false }] }, - { - code: "var foo = [function foo() {\nreturn dosomething();\n}];", - options: [{ multiline: false }] - }, - { code: "var foo = [1,\n2,[3,\n4]];", options: [{ multiline: false }] }, + // { multiline: false } + { code: "var foo = [];", options: [{ multiline: false }] }, + { code: "var foo = [1];", options: [{ multiline: false }] }, + { + code: "var foo = [1]/* any comment*/;", + options: [{ multiline: false }], + }, + { + code: "var foo = [1]\n/* any comment*/\n;", + options: [{ multiline: false }], + }, + { code: "var foo = [1, 2];", options: [{ multiline: false }] }, + { code: "var foo = [1,\n2];", options: [{ multiline: false }] }, + { + code: "var foo = [function foo() {\nreturn dosomething();\n}];", + options: [{ multiline: false }], + }, + { code: "var foo = [1,\n2,[3,\n4]];", options: [{ multiline: false }] }, - // { minItems: 2 } - { code: "var foo = [];", options: [{ minItems: 2 }] }, - { code: "var foo = [1];", options: [{ minItems: 2 }] }, - { code: "var foo = [\n1, 2\n];", options: [{ minItems: 2 }] }, - { code: "var foo = [\n1,\n2\n];", options: [{ minItems: 2 }] }, - { - code: "var foo = [function foo() {\ndosomething();\n}];", - options: [{ minItems: 2 }] - }, - { - code: `var foo = [ + // { minItems: 2 } + { code: "var foo = [];", options: [{ minItems: 2 }] }, + { code: "var foo = [1];", options: [{ minItems: 2 }] }, + { code: "var foo = [\n1, 2\n];", options: [{ minItems: 2 }] }, + { code: "var foo = [\n1,\n2\n];", options: [{ minItems: 2 }] }, + { + code: "var foo = [function foo() {\ndosomething();\n}];", + options: [{ minItems: 2 }], + }, + { + code: `var foo = [ 1,[ 2,3 ] ];`, - options: [{ minItems: 2 }] - }, - { - code: `var foo = [[ + options: [{ minItems: 2 }], + }, + { + code: `var foo = [[ 1,2 ]]`, - options: [{ minItems: 2 }] - }, + options: [{ minItems: 2 }], + }, - // { minItems: 0 } - { code: "var foo = [\n];", options: [{ minItems: 0 }] }, - { code: "var foo = [\n1\n];", options: [{ minItems: 0 }] }, - { code: "var foo = [\n1, 2\n];", options: [{ minItems: 0 }] }, - { code: "var foo = [\n1,\n2\n];", options: [{ minItems: 0 }] }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", - options: [{ minItems: 0 }] - }, + // { minItems: 0 } + { code: "var foo = [\n];", options: [{ minItems: 0 }] }, + { code: "var foo = [\n1\n];", options: [{ minItems: 0 }] }, + { code: "var foo = [\n1, 2\n];", options: [{ minItems: 0 }] }, + { code: "var foo = [\n1,\n2\n];", options: [{ minItems: 0 }] }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", + options: [{ minItems: 0 }], + }, - // { minItems: null } - { code: "var foo = [];", options: [{ minItems: null }] }, - { code: "var foo = [1];", options: [{ minItems: null }] }, - { code: "var foo = [1, 2];", options: [{ minItems: null }] }, - { code: "var foo = [1,\n2];", options: [{ minItems: null }] }, - { - code: "var foo = [function foo() {\ndosomething();\n}];", - options: [{ minItems: null }] - }, + // { minItems: null } + { code: "var foo = [];", options: [{ minItems: null }] }, + { code: "var foo = [1];", options: [{ minItems: null }] }, + { code: "var foo = [1, 2];", options: [{ minItems: null }] }, + { code: "var foo = [1,\n2];", options: [{ minItems: null }] }, + { + code: "var foo = [function foo() {\ndosomething();\n}];", + options: [{ minItems: null }], + }, - // { multiline: true, minItems: null } - { - code: "var foo = [];", - options: [{ multiline: true, minItems: null }] - }, - { - code: "var foo = [1];", - options: [{ multiline: true, minItems: null }] - }, - { - code: "var foo = [1, 2];", - options: [{ multiline: true, minItems: null }] - }, - { - code: "var foo = [\n1,\n2\n];", - options: [{ multiline: true, minItems: null }] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", - options: [{ multiline: true, minItems: null }] - }, + // { multiline: true, minItems: null } + { + code: "var foo = [];", + options: [{ multiline: true, minItems: null }], + }, + { + code: "var foo = [1];", + options: [{ multiline: true, minItems: null }], + }, + { + code: "var foo = [1, 2];", + options: [{ multiline: true, minItems: null }], + }, + { + code: "var foo = [\n1,\n2\n];", + options: [{ multiline: true, minItems: null }], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", + options: [{ multiline: true, minItems: null }], + }, - // { multiline: true, minItems: 2 } - { code: "var a = [];", options: [{ multiline: true, minItems: 2 }] }, - { code: "var b = [1];", options: [{ multiline: true, minItems: 2 }] }, - { - code: "var b = [ // any comment\n1\n];", - options: [{ multiline: true, minItems: 2 }] - }, - { - code: "var b = [ /* any comment */ 1];", - options: [{ multiline: true, minItems: 2 }] - }, - { - code: "var c = [\n1, 2\n];", - options: [{ multiline: true, minItems: 2 }] - }, - { - code: "var c = [\n/* any comment */1, 2\n];", - options: [{ multiline: true, minItems: 2 }] - }, - { - code: "var c = [\n1, /* any comment */ 2\n];", - options: [{ multiline: true, minItems: 2 }] - }, - { - code: "var d = [\n1,\n2\n];", - options: [{ multiline: true, minItems: 2 }] - }, - { - code: "var e = [\nfunction foo() {\ndosomething();\n}\n];", - options: [{ multiline: true, minItems: 2 }] - }, + // { multiline: true, minItems: 2 } + { code: "var a = [];", options: [{ multiline: true, minItems: 2 }] }, + { code: "var b = [1];", options: [{ multiline: true, minItems: 2 }] }, + { + code: "var b = [ // any comment\n1\n];", + options: [{ multiline: true, minItems: 2 }], + }, + { + code: "var b = [ /* any comment */ 1];", + options: [{ multiline: true, minItems: 2 }], + }, + { + code: "var c = [\n1, 2\n];", + options: [{ multiline: true, minItems: 2 }], + }, + { + code: "var c = [\n/* any comment */1, 2\n];", + options: [{ multiline: true, minItems: 2 }], + }, + { + code: "var c = [\n1, /* any comment */ 2\n];", + options: [{ multiline: true, minItems: 2 }], + }, + { + code: "var d = [\n1,\n2\n];", + options: [{ multiline: true, minItems: 2 }], + }, + { + code: "var e = [\nfunction foo() {\ndosomething();\n}\n];", + options: [{ multiline: true, minItems: 2 }], + }, - /* - * ArrayPattern - * default { multiline: true } - */ - { code: "var [] = foo", languageOptions: { ecmaVersion: 6 } }, - { code: "var [a] = foo;", languageOptions: { ecmaVersion: 6 } }, - { - code: "var /* any comment */[a] = foo;", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var /* any comment */\n[a] = foo;", - languageOptions: { ecmaVersion: 6 } - }, - { code: "var [a, b] = foo;", languageOptions: { ecmaVersion: 6 } }, - { - code: "var [ // any comment\na, b\n] = foo;", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\n// any comment\na, b\n] = foo;", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\na, b\n// any comment\n] = foo;", - languageOptions: { ecmaVersion: 6 } - }, - { code: "var [\na,\nb\n] = foo;", languageOptions: { ecmaVersion: 6 } }, + /* + * ArrayPattern + * default { multiline: true } + */ + { code: "var [] = foo", languageOptions: { ecmaVersion: 6 } }, + { code: "var [a] = foo;", languageOptions: { ecmaVersion: 6 } }, + { + code: "var /* any comment */[a] = foo;", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var /* any comment */\n[a] = foo;", + languageOptions: { ecmaVersion: 6 }, + }, + { code: "var [a, b] = foo;", languageOptions: { ecmaVersion: 6 } }, + { + code: "var [ // any comment\na, b\n] = foo;", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\n// any comment\na, b\n] = foo;", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\na, b\n// any comment\n] = foo;", + languageOptions: { ecmaVersion: 6 }, + }, + { code: "var [\na,\nb\n] = foo;", languageOptions: { ecmaVersion: 6 } }, - // "always" - { - code: "var [\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\na\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\n// any\na\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\n/* any */\na\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\na, b\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\na, b // any comment\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\na, b /* any comment */\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\na,\nb\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, + // "always" + { + code: "var [\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\na\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\n// any\na\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\n/* any */\na\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\na, b\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\na, b // any comment\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\na, b /* any comment */\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\na,\nb\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, - // "consistent" - { - code: "var [] = foo", - options: ["consistent"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\n] = foo", - options: ["consistent"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [a] = foo", - options: ["consistent"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\na\n] = foo", - options: ["consistent"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [//\na\n] = foo", - options: ["consistent"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [/**/\na\n] = foo", - options: ["consistent"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [/*\n*/a\n] = foo", - options: ["consistent"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [//\n] = foo", - options: ["consistent"], - languageOptions: { ecmaVersion: 6 } - }, + // "consistent" + { + code: "var [] = foo", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\n] = foo", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [a] = foo", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\na\n] = foo", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [//\na\n] = foo", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [/**/\na\n] = foo", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [/*\n*/a\n] = foo", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [//\n] = foo", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + }, - // { multiline: true } - { - code: "var [] = foo;", - options: [{ multiline: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [a] = foo;", - options: [{ multiline: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var /* any comment */[a] = foo;", - options: [{ multiline: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var /* any comment */\n[a] = foo;", - options: [{ multiline: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [a, b] = foo;", - options: [{ multiline: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [ // any comment\na, b\n] = foo;", - options: [{ multiline: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\n// any comment\na, b\n] = foo;", - options: [{ multiline: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\na, b\n// any comment\n] = foo;", - options: [{ multiline: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\na,\nb\n] = foo;", - options: [{ multiline: true }], - languageOptions: { ecmaVersion: 6 } - } - ], + // { multiline: true } + { + code: "var [] = foo;", + options: [{ multiline: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [a] = foo;", + options: [{ multiline: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var /* any comment */[a] = foo;", + options: [{ multiline: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var /* any comment */\n[a] = foo;", + options: [{ multiline: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [a, b] = foo;", + options: [{ multiline: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [ // any comment\na, b\n] = foo;", + options: [{ multiline: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\n// any comment\na, b\n] = foo;", + options: [{ multiline: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\na, b\n// any comment\n] = foo;", + options: [{ multiline: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\na,\nb\n] = foo;", + options: [{ multiline: true }], + languageOptions: { ecmaVersion: 6 }, + }, + ], - invalid: [ - - // default : { multiline : true} - { - code: `var foo = [ + invalid: [ + // default : { multiline : true} + { + code: `var foo = [ [1,2] ]`, - output: "var foo = [[1,2]]", - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 13, - endLine: 3, - endColumn: 14 - } - ] - }, - { - code: "var foo = [[2,\n3]]", - output: "var foo = [\n[\n2,\n3\n]\n]", - errors: [ - { - line: 1, - column: 11, - messageId: "missingOpeningLinebreak", - endLine: 1, - endColumn: 12 - }, - { - line: 1, - column: 12, - messageId: "missingOpeningLinebreak", - endLine: 1, - endColumn: 13 - }, - { - line: 2, - column: 2, - messageId: "missingClosingLinebreak", - endLine: 2, - endColumn: 3 - }, - { - line: 2, - column: 3, - messageId: "missingClosingLinebreak", - endLine: 2, - endColumn: 4 - } - ] - }, + output: "var foo = [[1,2]]", + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 13, + endLine: 3, + endColumn: 14, + }, + ], + }, + { + code: "var foo = [[2,\n3]]", + output: "var foo = [\n[\n2,\n3\n]\n]", + errors: [ + { + line: 1, + column: 11, + messageId: "missingOpeningLinebreak", + endLine: 1, + endColumn: 12, + }, + { + line: 1, + column: 12, + messageId: "missingOpeningLinebreak", + endLine: 1, + endColumn: 13, + }, + { + line: 2, + column: 2, + messageId: "missingClosingLinebreak", + endLine: 2, + endColumn: 3, + }, + { + line: 2, + column: 3, + messageId: "missingClosingLinebreak", + endLine: 2, + endColumn: 4, + }, + ], + }, - /* - * ArrayExpression - * "always" - */ - { - code: "var foo = [[1,2]]", - output: "var foo = [\n[\n1,2\n]\n]", - options: ["always"], - errors: [ - { - line: 1, - column: 11, - messageId: "missingOpeningLinebreak", - endLine: 1, - endColumn: 12 - }, - { - line: 1, - column: 12, - messageId: "missingOpeningLinebreak", - endLine: 1, - endColumn: 13 - }, - { - line: 1, - column: 16, - messageId: "missingClosingLinebreak", - endLine: 1, - endColumn: 17 - }, - { - line: 1, - column: 17, - messageId: "missingClosingLinebreak", - endLine: 1, - endColumn: 18 - } - ] - }, - { - code: "var foo = [];", - output: "var foo = [\n];", - options: ["always"], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - } - ] - }, - { - code: "var foo = [1];", - output: "var foo = [\n1\n];", - options: ["always"], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 1, - column: 13 - } - ] - }, - { - code: "var foo = [ // any comment\n1];", - output: "var foo = [ // any comment\n1\n];", - options: ["always"], - errors: [ - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 2, - endLine: 2, - endColumn: 3 - } - ] - }, - { - code: "var foo = [ /* any comment */\n1];", - output: "var foo = [ /* any comment */\n1\n];", - options: ["always"], - errors: [ - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 2 - } - ] - }, - { - code: "var foo = [1, 2];", - output: "var foo = [\n1, 2\n];", - options: ["always"], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 1, - column: 16, - endLine: 1, - endColumn: 17 - } - ] - }, - { - code: "var foo = [1, 2 // any comment\n];", - output: "var foo = [\n1, 2 // any comment\n];", - options: ["always"], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - } - ] - }, - { - code: "var foo = [1, 2 /* any comment */];", - output: "var foo = [\n1, 2 /* any comment */\n];", - options: ["always"], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 1, - column: 34 - } - ] - }, - { - code: "var foo = [1,\n2];", - output: "var foo = [\n1,\n2\n];", - options: ["always"], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 2 - } - ] - }, - { - code: "var foo = [function foo() {\ndosomething();\n}];", - output: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", - options: ["always"], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 2 - } - ] - }, + /* + * ArrayExpression + * "always" + */ + { + code: "var foo = [[1,2]]", + output: "var foo = [\n[\n1,2\n]\n]", + options: ["always"], + errors: [ + { + line: 1, + column: 11, + messageId: "missingOpeningLinebreak", + endLine: 1, + endColumn: 12, + }, + { + line: 1, + column: 12, + messageId: "missingOpeningLinebreak", + endLine: 1, + endColumn: 13, + }, + { + line: 1, + column: 16, + messageId: "missingClosingLinebreak", + endLine: 1, + endColumn: 17, + }, + { + line: 1, + column: 17, + messageId: "missingClosingLinebreak", + endLine: 1, + endColumn: 18, + }, + ], + }, + { + code: "var foo = [];", + output: "var foo = [\n];", + options: ["always"], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "var foo = [1];", + output: "var foo = [\n1\n];", + options: ["always"], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 1, + column: 13, + }, + ], + }, + { + code: "var foo = [ // any comment\n1];", + output: "var foo = [ // any comment\n1\n];", + options: ["always"], + errors: [ + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 2, + endLine: 2, + endColumn: 3, + }, + ], + }, + { + code: "var foo = [ /* any comment */\n1];", + output: "var foo = [ /* any comment */\n1\n];", + options: ["always"], + errors: [ + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 2, + }, + ], + }, + { + code: "var foo = [1, 2];", + output: "var foo = [\n1, 2\n];", + options: ["always"], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 1, + column: 16, + endLine: 1, + endColumn: 17, + }, + ], + }, + { + code: "var foo = [1, 2 // any comment\n];", + output: "var foo = [\n1, 2 // any comment\n];", + options: ["always"], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + ], + }, + { + code: "var foo = [1, 2 /* any comment */];", + output: "var foo = [\n1, 2 /* any comment */\n];", + options: ["always"], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 1, + column: 34, + }, + ], + }, + { + code: "var foo = [1,\n2];", + output: "var foo = [\n1,\n2\n];", + options: ["always"], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 2, + }, + ], + }, + { + code: "var foo = [function foo() {\ndosomething();\n}];", + output: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", + options: ["always"], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 2, + }, + ], + }, - // "never" - { - code: `var foo = [[ + // "never" + { + code: `var foo = [[ 1,2],3];`, - output: "var foo = [[1,2],3];", - options: ["never"], - errors: [ - { - line: 1, - column: 12, - messageId: "unexpectedOpeningLinebreak", - endLine: 1, - endColumn: 13 - } - ] - }, - { - code: "var foo = [\n];", - output: "var foo = [];", - options: ["never"], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 1 - } - ] - }, - { - code: "var foo = [\n1\n];", - output: "var foo = [1];", - options: ["never"], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1, - endLine: 3, - endColumn: 2 - } - ] - }, - { - code: "var foo = [\n1\n];", - output: "var foo = [1];", - options: ["never"], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, - { - code: "var foo = [ /* any comment */\n1, 2\n];", - output: "var foo = [ /* any comment */\n1, 2];", - options: ["never"], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, - { - code: "var foo = [\n1, 2\n/* any comment */];", - output: "var foo = [1, 2\n/* any comment */];", - options: ["never"], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 18 - } - ] - }, - { - code: "var foo = [ // any comment\n1, 2\n];", - output: "var foo = [ // any comment\n1, 2];", - options: ["never"], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, - { - code: "var foo = [\n1,\n2\n];", - output: "var foo = [1,\n2];", - options: ["never"], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 4, - column: 1 - } - ] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", - output: "var foo = [function foo() {\ndosomething();\n}];", - options: ["never"], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 5, - column: 1 - } - ] - }, + output: "var foo = [[1,2],3];", + options: ["never"], + errors: [ + { + line: 1, + column: 12, + messageId: "unexpectedOpeningLinebreak", + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "var foo = [\n];", + output: "var foo = [];", + options: ["never"], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1\n];", + output: "var foo = [1];", + options: ["never"], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 1, + endLine: 3, + endColumn: 2, + }, + ], + }, + { + code: "var foo = [ /* any comment */\n1, 2\n];", + output: "var foo = [ /* any comment */\n1, 2];", + options: ["never"], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1, 2\n/* any comment */];", + output: "var foo = [1, 2\n/* any comment */];", + options: ["never"], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 18, + }, + ], + }, + { + code: "var foo = [ // any comment\n1, 2\n];", + output: "var foo = [ // any comment\n1, 2];", + options: ["never"], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1,\n2\n];", + output: "var foo = [1,\n2];", + options: ["never"], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 4, + column: 1, + }, + ], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", + output: "var foo = [function foo() {\ndosomething();\n}];", + options: ["never"], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 5, + column: 1, + }, + ], + }, - // "consistent" - { - code: `var a = [[1,2] + // "consistent" + { + code: `var a = [[1,2] ]`, - output: "var a = [[1,2]]", - options: ["consistent"], - errors: [ - { - line: 2, - column: 13, - messageId: "unexpectedClosingLinebreak", - endLine: 2, - endColumn: 14 - } - ] - }, - { - code: "var a = [\n[\n[1,2]]\n]", - output: "var a = [\n[\n[1,2]\n]\n]", - options: ["consistent"], - errors: [ - { - line: 3, - column: 6, - messageId: "missingClosingLinebreak", - endLine: 3, - endColumn: 7 - } - ] - }, - { - code: "var foo = [\n1]", - output: "var foo = [\n1\n]", - options: ["consistent"], - errors: [ - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 2, - endLine: 2, - endColumn: 3 - } - ] - }, - { - code: "var foo = [1\n]", - output: "var foo = [1]", - options: ["consistent"], - errors: [ - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 1, - endLine: 2, - endColumn: 2 - } - ] - }, - { - code: "var foo = [//\n1]", - output: "var foo = [//\n1\n]", - options: ["consistent"], - errors: [ - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 2, - endLine: 2, - endColumn: 3 - } - ] - }, + output: "var a = [[1,2]]", + options: ["consistent"], + errors: [ + { + line: 2, + column: 13, + messageId: "unexpectedClosingLinebreak", + endLine: 2, + endColumn: 14, + }, + ], + }, + { + code: "var a = [\n[\n[1,2]]\n]", + output: "var a = [\n[\n[1,2]\n]\n]", + options: ["consistent"], + errors: [ + { + line: 3, + column: 6, + messageId: "missingClosingLinebreak", + endLine: 3, + endColumn: 7, + }, + ], + }, + { + code: "var foo = [\n1]", + output: "var foo = [\n1\n]", + options: ["consistent"], + errors: [ + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 2, + endLine: 2, + endColumn: 3, + }, + ], + }, + { + code: "var foo = [1\n]", + output: "var foo = [1]", + options: ["consistent"], + errors: [ + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 1, + endLine: 2, + endColumn: 2, + }, + ], + }, + { + code: "var foo = [//\n1]", + output: "var foo = [//\n1\n]", + options: ["consistent"], + errors: [ + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 2, + endLine: 2, + endColumn: 3, + }, + ], + }, - // { multiline: true } - { - code: "var foo = [\n];", - output: "var foo = [];", - options: [{ multiline: true }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 1 - } - ] - }, - { - code: "var foo = [\n// any comment\n];", - output: null, - options: [{ multiline: true }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, - { - code: "var foo = [\n1\n];", - output: "var foo = [1];", - options: [{ multiline: true }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, - { - code: "var foo = [\n1, 2\n];", - output: "var foo = [1, 2];", - options: [{ multiline: true }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, - { - code: "var foo = [1,\n2];", - output: "var foo = [\n1,\n2\n];", - options: [{ multiline: true }], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 2 - } - ] - }, - { - code: "var foo = [function foo() {\ndosomething();\n}];", - output: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", - options: [{ multiline: true }], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 2 - } - ] - }, + // { multiline: true } + { + code: "var foo = [\n];", + output: "var foo = [];", + options: [{ multiline: true }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "var foo = [\n// any comment\n];", + output: null, + options: [{ multiline: true }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1\n];", + output: "var foo = [1];", + options: [{ multiline: true }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1, 2\n];", + output: "var foo = [1, 2];", + options: [{ multiline: true }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "var foo = [1,\n2];", + output: "var foo = [\n1,\n2\n];", + options: [{ multiline: true }], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 2, + }, + ], + }, + { + code: "var foo = [function foo() {\ndosomething();\n}];", + output: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", + options: [{ multiline: true }], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 2, + }, + ], + }, - // { minItems: 2 } - { - code: "var foo = [1,[\n2,3\n]\n];", - output: "var foo = [\n1,[\n2,3\n]\n];", - options: [{ minItems: 2 }], - errors: [ - { - line: 1, - column: 11, - messageId: "missingOpeningLinebreak", - endLine: 1, - endColumn: 12 - } - ] - }, - { - code: "var foo = [[1,2\n]]", - output: "var foo = [[\n1,2\n]]", - options: [{ minItems: 2 }], - errors: [ - { - line: 1, - column: 12, - messageId: "missingOpeningLinebreak", - endLine: 1, - endColumn: 13 - } - ] - }, - { - code: "var foo = [\n];", - output: "var foo = [];", - options: [{ minItems: 2 }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 1 - } - ] - }, - { - code: "var foo = [\n1\n];", - output: "var foo = [1];", - options: [{ minItems: 2 }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, - { - code: "var foo = [1, 2];", - output: "var foo = [\n1, 2\n];", - options: [{ minItems: 2 }], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 1, - column: 16 - } - ] - }, - { - code: "var foo = [1,\n2];", - output: "var foo = [\n1,\n2\n];", - options: [{ minItems: 2 }], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 2 - } - ] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", - output: "var foo = [function foo() {\ndosomething();\n}];", - options: [{ minItems: 2 }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 5, - column: 1 - } - ] - }, + // { minItems: 2 } + { + code: "var foo = [1,[\n2,3\n]\n];", + output: "var foo = [\n1,[\n2,3\n]\n];", + options: [{ minItems: 2 }], + errors: [ + { + line: 1, + column: 11, + messageId: "missingOpeningLinebreak", + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: "var foo = [[1,2\n]]", + output: "var foo = [[\n1,2\n]]", + options: [{ minItems: 2 }], + errors: [ + { + line: 1, + column: 12, + messageId: "missingOpeningLinebreak", + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "var foo = [\n];", + output: "var foo = [];", + options: [{ minItems: 2 }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1\n];", + output: "var foo = [1];", + options: [{ minItems: 2 }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "var foo = [1, 2];", + output: "var foo = [\n1, 2\n];", + options: [{ minItems: 2 }], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 1, + column: 16, + }, + ], + }, + { + code: "var foo = [1,\n2];", + output: "var foo = [\n1,\n2\n];", + options: [{ minItems: 2 }], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 2, + }, + ], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", + output: "var foo = [function foo() {\ndosomething();\n}];", + options: [{ minItems: 2 }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 5, + column: 1, + }, + ], + }, - // { minItems: 0 } - { - code: "var foo = [];", - output: "var foo = [\n];", - options: [{ minItems: 0 }], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 1, - column: 12 - } - ] - }, - { - code: "var foo = [1];", - output: "var foo = [\n1\n];", - options: [{ minItems: 0 }], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 1, - column: 13 - } - ] - }, - { - code: "var foo = [1, 2];", - output: "var foo = [\n1, 2\n];", - options: [{ minItems: 0 }], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 1, - column: 16 - } - ] - }, - { - code: "var foo = [1,\n2];", - output: "var foo = [\n1,\n2\n];", - options: [{ minItems: 0 }], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 2 - } - ] - }, - { - code: "var foo = [function foo() {\ndosomething();\n}];", - output: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", - options: [{ minItems: 0 }], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 2 - } - ] - }, + // { minItems: 0 } + { + code: "var foo = [];", + output: "var foo = [\n];", + options: [{ minItems: 0 }], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 1, + column: 12, + }, + ], + }, + { + code: "var foo = [1];", + output: "var foo = [\n1\n];", + options: [{ minItems: 0 }], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 1, + column: 13, + }, + ], + }, + { + code: "var foo = [1, 2];", + output: "var foo = [\n1, 2\n];", + options: [{ minItems: 0 }], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 1, + column: 16, + }, + ], + }, + { + code: "var foo = [1,\n2];", + output: "var foo = [\n1,\n2\n];", + options: [{ minItems: 0 }], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 2, + }, + ], + }, + { + code: "var foo = [function foo() {\ndosomething();\n}];", + output: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", + options: [{ minItems: 0 }], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 2, + }, + ], + }, - // { minItems: null } - { - code: "var foo = [\n];", - output: "var foo = [];", - options: [{ minItems: null }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 1 - } - ] - }, - { - code: "var foo = [\n1\n];", - output: "var foo = [1];", - options: [{ minItems: null }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, - { - code: "var foo = [\n1, 2\n];", - output: "var foo = [1, 2];", - options: [{ minItems: null }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, - { - code: "var foo = [\n1,\n2\n];", - output: "var foo = [1,\n2];", - options: [{ minItems: null }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 4, - column: 1 - } - ] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", - output: "var foo = [function foo() {\ndosomething();\n}];", - options: [{ minItems: null }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 5, - column: 1 - } - ] - }, + // { minItems: null } + { + code: "var foo = [\n];", + output: "var foo = [];", + options: [{ minItems: null }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1\n];", + output: "var foo = [1];", + options: [{ minItems: null }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1, 2\n];", + output: "var foo = [1, 2];", + options: [{ minItems: null }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1,\n2\n];", + output: "var foo = [1,\n2];", + options: [{ minItems: null }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 4, + column: 1, + }, + ], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", + output: "var foo = [function foo() {\ndosomething();\n}];", + options: [{ minItems: null }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 5, + column: 1, + }, + ], + }, - // { multiline: true, minItems: null } - { - code: "var foo = [\n];", - output: "var foo = [];", - options: [{ multiline: true, minItems: null }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 1 - } - ] - }, - { - code: "var foo = [\n1\n];", - output: "var foo = [1];", - options: [{ multiline: true, minItems: null }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, - { - code: "var foo = [\n1, 2\n];", - output: "var foo = [1, 2];", - options: [{ multiline: true, minItems: null }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, - { - code: "var foo = [1,\n2];", - output: "var foo = [\n1,\n2\n];", - options: [{ multiline: true, minItems: null }], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 2 - } - ] - }, - { - code: "var foo = [function foo() {\ndosomething();\n}];", - output: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", - options: [{ multiline: true, minItems: null }], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 2 - } - ] - }, + // { multiline: true, minItems: null } + { + code: "var foo = [\n];", + output: "var foo = [];", + options: [{ multiline: true, minItems: null }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1\n];", + output: "var foo = [1];", + options: [{ multiline: true, minItems: null }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1, 2\n];", + output: "var foo = [1, 2];", + options: [{ multiline: true, minItems: null }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "var foo = [1,\n2];", + output: "var foo = [\n1,\n2\n];", + options: [{ multiline: true, minItems: null }], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 2, + }, + ], + }, + { + code: "var foo = [function foo() {\ndosomething();\n}];", + output: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", + options: [{ multiline: true, minItems: null }], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 2, + }, + ], + }, - // { multiline: true, minItems: 2 } - { - code: "var foo = [\n];", - output: "var foo = [];", - options: [{ multiline: true, minItems: 2 }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 1 - } - ] - }, - { - code: "var foo = [\n1\n];", - output: "var foo = [1];", - options: [{ multiline: true, minItems: 2 }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, - { - code: "var foo = [1, 2];", - output: "var foo = [\n1, 2\n];", - options: [{ multiline: true, minItems: 2 }], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 1, - column: 16 - } - ] - }, - { - code: "var foo = [1,\n2];", - output: "var foo = [\n1,\n2\n];", - options: [{ multiline: true, minItems: 2 }], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 2 - } - ] - }, - { - code: "var foo = [function foo() {\ndosomething();\n}];", - output: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", - options: [{ multiline: true, minItems: 2 }], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 2 - } - ] - }, + // { multiline: true, minItems: 2 } + { + code: "var foo = [\n];", + output: "var foo = [];", + options: [{ multiline: true, minItems: 2 }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1\n];", + output: "var foo = [1];", + options: [{ multiline: true, minItems: 2 }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "var foo = [1, 2];", + output: "var foo = [\n1, 2\n];", + options: [{ multiline: true, minItems: 2 }], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 1, + column: 16, + }, + ], + }, + { + code: "var foo = [1,\n2];", + output: "var foo = [\n1,\n2\n];", + options: [{ multiline: true, minItems: 2 }], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 2, + }, + ], + }, + { + code: "var foo = [function foo() {\ndosomething();\n}];", + output: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", + options: [{ multiline: true, minItems: 2 }], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 2, + }, + ], + }, - /* - * extra test cases - * "always" - */ - { - code: "var foo = [\n1, 2];", - output: "var foo = [\n1, 2\n];", - options: ["always"], - errors: [ - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 5 - } - ] - }, - { - code: "var foo = [\t1, 2];", - output: "var foo = [\n\t1, 2\n];", - options: ["always"], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayExpression", - line: 1, - column: 17 - } - ] - }, - { - code: "var foo = [1,\n2\n];", - output: "var foo = [\n1,\n2\n];", - options: ["always"], - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - } - ] - }, + /* + * extra test cases + * "always" + */ + { + code: "var foo = [\n1, 2];", + output: "var foo = [\n1, 2\n];", + options: ["always"], + errors: [ + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 5, + }, + ], + }, + { + code: "var foo = [\t1, 2];", + output: "var foo = [\n\t1, 2\n];", + options: ["always"], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayExpression", + line: 1, + column: 17, + }, + ], + }, + { + code: "var foo = [1,\n2\n];", + output: "var foo = [\n1,\n2\n];", + options: ["always"], + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + ], + }, - // { multiline: false } - { - code: "var foo = [\n];", - output: "var foo = [];", - options: [{ multiline: false }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 2, - column: 1 - } - ] - }, - { - code: "var foo = [\n1\n];", - output: "var foo = [1];", - options: [{ multiline: false }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, - { - code: "var foo = [\n1, 2\n];", - output: "var foo = [1, 2];", - options: [{ multiline: false }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 3, - column: 1 - } - ] - }, - { - code: "var foo = [\n1,\n2\n];", - output: "var foo = [1,\n2];", - options: [{ multiline: false }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 4, - column: 1 - } - ] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", - output: "var foo = [function foo() {\ndosomething();\n}];", - options: [{ multiline: false }], - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayExpression", - line: 1, - column: 11 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayExpression", - line: 5, - column: 1 - } - ] - }, + // { multiline: false } + { + code: "var foo = [\n];", + output: "var foo = [];", + options: [{ multiline: false }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1\n];", + output: "var foo = [1];", + options: [{ multiline: false }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1, 2\n];", + output: "var foo = [1, 2];", + options: [{ multiline: false }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "var foo = [\n1,\n2\n];", + output: "var foo = [1,\n2];", + options: [{ multiline: false }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 4, + column: 1, + }, + ], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}\n];", + output: "var foo = [function foo() {\ndosomething();\n}];", + options: [{ multiline: false }], + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayExpression", + line: 1, + column: 11, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayExpression", + line: 5, + column: 1, + }, + ], + }, - /* - * ArrayPattern - * "always" - */ - { - code: "var [] = foo;", - output: "var [\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayPattern", - line: 1, - column: 5 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayPattern", - line: 1, - column: 6 - } - ] - }, - { - code: "var [a] = foo;", - output: "var [\na\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayPattern", - line: 1, - column: 5 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayPattern", - line: 1, - column: 7 - } - ] - }, - { - code: "var [ // any comment\na] = foo;", - output: "var [ // any comment\na\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingClosingLinebreak", - type: "ArrayPattern", - line: 2, - column: 2 - } - ] - }, - { - code: "var [ /* any comment */\na] = foo;", - output: "var [ /* any comment */\na\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingClosingLinebreak", - type: "ArrayPattern", - line: 2, - column: 2 - } - ] - }, - { - code: "var [a, b] = foo;", - output: "var [\na, b\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayPattern", - line: 1, - column: 5 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayPattern", - line: 1, - column: 10 - } - ] - }, - { - code: "var [a, b // any comment\n] = foo;", - output: "var [\na, b // any comment\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayPattern", - line: 1, - column: 5 - } - ] - }, - { - code: "var [a, b /* any comment */] = foo;", - output: "var [\na, b /* any comment */\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayPattern", - line: 1, - column: 5 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayPattern", - line: 1, - column: 28 - } - ] - }, - { - code: "var [a,\nb] = foo;", - output: "var [\na,\nb\n] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayPattern", - line: 1, - column: 5 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayPattern", - line: 2, - column: 2 - } - ] - }, + /* + * ArrayPattern + * "always" + */ + { + code: "var [] = foo;", + output: "var [\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayPattern", + line: 1, + column: 5, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayPattern", + line: 1, + column: 6, + }, + ], + }, + { + code: "var [a] = foo;", + output: "var [\na\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayPattern", + line: 1, + column: 5, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayPattern", + line: 1, + column: 7, + }, + ], + }, + { + code: "var [ // any comment\na] = foo;", + output: "var [ // any comment\na\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingClosingLinebreak", + type: "ArrayPattern", + line: 2, + column: 2, + }, + ], + }, + { + code: "var [ /* any comment */\na] = foo;", + output: "var [ /* any comment */\na\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingClosingLinebreak", + type: "ArrayPattern", + line: 2, + column: 2, + }, + ], + }, + { + code: "var [a, b] = foo;", + output: "var [\na, b\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayPattern", + line: 1, + column: 5, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayPattern", + line: 1, + column: 10, + }, + ], + }, + { + code: "var [a, b // any comment\n] = foo;", + output: "var [\na, b // any comment\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayPattern", + line: 1, + column: 5, + }, + ], + }, + { + code: "var [a, b /* any comment */] = foo;", + output: "var [\na, b /* any comment */\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayPattern", + line: 1, + column: 5, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayPattern", + line: 1, + column: 28, + }, + ], + }, + { + code: "var [a,\nb] = foo;", + output: "var [\na,\nb\n] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayPattern", + line: 1, + column: 5, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayPattern", + line: 2, + column: 2, + }, + ], + }, - // "consistent" - { - code: "var [\na] = foo", - output: "var [\na\n] = foo", - options: ["consistent"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingClosingLinebreak", - type: "ArrayPattern", - line: 2, - column: 2, - endLine: 2, - endColumn: 3 - } - ] - }, - { - code: "var [a\n] = foo", - output: "var [a] = foo", - options: ["consistent"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayPattern", - line: 2, - column: 1, - endLine: 2, - endColumn: 2 - } - ] - }, - { - code: "var [//\na] = foo", - output: "var [//\na\n] = foo", - options: ["consistent"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingClosingLinebreak", - type: "ArrayPattern", - line: 2, - column: 2, - endLine: 2, - endColumn: 3 - } - ] - }, + // "consistent" + { + code: "var [\na] = foo", + output: "var [\na\n] = foo", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingClosingLinebreak", + type: "ArrayPattern", + line: 2, + column: 2, + endLine: 2, + endColumn: 3, + }, + ], + }, + { + code: "var [a\n] = foo", + output: "var [a] = foo", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayPattern", + line: 2, + column: 1, + endLine: 2, + endColumn: 2, + }, + ], + }, + { + code: "var [//\na] = foo", + output: "var [//\na\n] = foo", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingClosingLinebreak", + type: "ArrayPattern", + line: 2, + column: 2, + endLine: 2, + endColumn: 3, + }, + ], + }, - // { minItems: 2 } - { - code: "var [\n] = foo;", - output: "var [] = foo;", - options: [{ minItems: 2 }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayPattern", - line: 1, - column: 5 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayPattern", - line: 2, - column: 1 - } - ] - }, - { - code: "var [\na\n] = foo;", - output: "var [a] = foo;", - options: [{ minItems: 2 }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedOpeningLinebreak", - type: "ArrayPattern", - line: 1, - column: 5 - }, - { - messageId: "unexpectedClosingLinebreak", - type: "ArrayPattern", - line: 3, - column: 1 - } - ] - }, - { - code: "var [a, b] = foo;", - output: "var [\na, b\n] = foo;", - options: [{ minItems: 2 }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayPattern", - line: 1, - column: 5 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayPattern", - line: 1, - column: 10 - } - ] - }, - { - code: "var [a,\nb] = foo;", - output: "var [\na,\nb\n] = foo;", - options: [{ minItems: 2 }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingOpeningLinebreak", - type: "ArrayPattern", - line: 1, - column: 5 - }, - { - messageId: "missingClosingLinebreak", - type: "ArrayPattern", - line: 2, - column: 2 - } - ] - } - ] + // { minItems: 2 } + { + code: "var [\n] = foo;", + output: "var [] = foo;", + options: [{ minItems: 2 }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayPattern", + line: 1, + column: 5, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayPattern", + line: 2, + column: 1, + }, + ], + }, + { + code: "var [\na\n] = foo;", + output: "var [a] = foo;", + options: [{ minItems: 2 }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedOpeningLinebreak", + type: "ArrayPattern", + line: 1, + column: 5, + }, + { + messageId: "unexpectedClosingLinebreak", + type: "ArrayPattern", + line: 3, + column: 1, + }, + ], + }, + { + code: "var [a, b] = foo;", + output: "var [\na, b\n] = foo;", + options: [{ minItems: 2 }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayPattern", + line: 1, + column: 5, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayPattern", + line: 1, + column: 10, + }, + ], + }, + { + code: "var [a,\nb] = foo;", + output: "var [\na,\nb\n] = foo;", + options: [{ minItems: 2 }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingOpeningLinebreak", + type: "ArrayPattern", + line: 1, + column: 5, + }, + { + messageId: "missingClosingLinebreak", + type: "ArrayPattern", + line: 2, + column: 2, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/array-bracket-spacing.js b/tests/lib/rules/array-bracket-spacing.js index 2120833114a5..8e086391cdbf 100644 --- a/tests/lib/rules/array-bracket-spacing.js +++ b/tests/lib/rules/array-bracket-spacing.js @@ -8,9 +8,9 @@ // Requirements //------------------------------------------------------------------------------ -const path = require("path"), - rule = require("../../../lib/rules/array-bracket-spacing"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); +const path = require("node:path"), + rule = require("../../../lib/rules/array-bracket-spacing"), + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Helpers @@ -22,7 +22,12 @@ const path = require("path"), * @returns {Object} The specified parser. */ function parser(name) { - return require(path.resolve(__dirname, `../../fixtures/parsers/array-bracket-spacing/${name}.js`)); + return require( + path.resolve( + __dirname, + `../../fixtures/parsers/array-bracket-spacing/${name}.js`, + ), + ); } //------------------------------------------------------------------------------ @@ -32,1099 +37,1380 @@ function parser(name) { const ruleTester = new RuleTester(); ruleTester.run("array-bracket-spacing", rule, { + valid: [ + { code: "var foo = obj[ 1 ]", options: ["always"] }, + { code: "var foo = obj[ 'foo' ];", options: ["always"] }, + { code: "var foo = obj[ [ 1, 1 ] ];", options: ["always"] }, - valid: [ - { code: "var foo = obj[ 1 ]", options: ["always"] }, - { code: "var foo = obj[ 'foo' ];", options: ["always"] }, - { code: "var foo = obj[ [ 1, 1 ] ];", options: ["always"] }, + // always - singleValue + { + code: "var foo = ['foo']", + options: ["always", { singleValue: false }], + }, + { code: "var foo = [2]", options: ["always", { singleValue: false }] }, + { + code: "var foo = [[ 1, 1 ]]", + options: ["always", { singleValue: false }], + }, + { + code: "var foo = [{ 'foo': 'bar' }]", + options: ["always", { singleValue: false }], + }, + { + code: "var foo = [bar]", + options: ["always", { singleValue: false }], + }, - // always - singleValue - { code: "var foo = ['foo']", options: ["always", { singleValue: false }] }, - { code: "var foo = [2]", options: ["always", { singleValue: false }] }, - { code: "var foo = [[ 1, 1 ]]", options: ["always", { singleValue: false }] }, - { code: "var foo = [{ 'foo': 'bar' }]", options: ["always", { singleValue: false }] }, - { code: "var foo = [bar]", options: ["always", { singleValue: false }] }, + // always - objectsInArrays + { + code: "var foo = [{ 'bar': 'baz' }, 1, 5 ];", + options: ["always", { objectsInArrays: false }], + }, + { + code: "var foo = [ 1, 5, { 'bar': 'baz' }];", + options: ["always", { objectsInArrays: false }], + }, + { + code: "var foo = [{\n'bar': 'baz', \n'qux': [{ 'bar': 'baz' }], \n'quxx': 1 \n}]", + options: ["always", { objectsInArrays: false }], + }, + { + code: "var foo = [{ 'bar': 'baz' }]", + options: ["always", { objectsInArrays: false }], + }, + { + code: "var foo = [{ 'bar': 'baz' }, 1, { 'bar': 'baz' }];", + options: ["always", { objectsInArrays: false }], + }, + { + code: "var foo = [ 1, { 'bar': 'baz' }, 5 ];", + options: ["always", { objectsInArrays: false }], + }, + { + code: "var foo = [ 1, { 'bar': 'baz' }, [{ 'bar': 'baz' }] ];", + options: ["always", { objectsInArrays: false }], + }, + { + code: "var foo = [ function(){} ];", + options: ["always", { objectsInArrays: false }], + }, - // always - objectsInArrays - { code: "var foo = [{ 'bar': 'baz' }, 1, 5 ];", options: ["always", { objectsInArrays: false }] }, - { code: "var foo = [ 1, 5, { 'bar': 'baz' }];", options: ["always", { objectsInArrays: false }] }, - { code: "var foo = [{\n'bar': 'baz', \n'qux': [{ 'bar': 'baz' }], \n'quxx': 1 \n}]", options: ["always", { objectsInArrays: false }] }, - { code: "var foo = [{ 'bar': 'baz' }]", options: ["always", { objectsInArrays: false }] }, - { code: "var foo = [{ 'bar': 'baz' }, 1, { 'bar': 'baz' }];", options: ["always", { objectsInArrays: false }] }, - { code: "var foo = [ 1, { 'bar': 'baz' }, 5 ];", options: ["always", { objectsInArrays: false }] }, - { code: "var foo = [ 1, { 'bar': 'baz' }, [{ 'bar': 'baz' }] ];", options: ["always", { objectsInArrays: false }] }, - { code: "var foo = [ function(){} ];", options: ["always", { objectsInArrays: false }] }, + // always - arraysInArrays + { + code: "var arr = [[ 1, 2 ], 2, 3, 4 ];", + options: ["always", { arraysInArrays: false }], + }, + { + code: "var arr = [[ 1, 2 ], [[[ 1 ]]], 3, 4 ];", + options: ["always", { arraysInArrays: false }], + }, + { + code: "var foo = [ arr[i], arr[j] ];", + options: ["always", { arraysInArrays: false }], + }, - // always - arraysInArrays - { code: "var arr = [[ 1, 2 ], 2, 3, 4 ];", options: ["always", { arraysInArrays: false }] }, - { code: "var arr = [[ 1, 2 ], [[[ 1 ]]], 3, 4 ];", options: ["always", { arraysInArrays: false }] }, - { code: "var foo = [ arr[i], arr[j] ];", options: ["always", { arraysInArrays: false }] }, + // always - arraysInArrays, objectsInArrays + { + code: "var arr = [[ 1, 2 ], 2, 3, { 'foo': 'bar' }];", + options: [ + "always", + { arraysInArrays: false, objectsInArrays: false }, + ], + }, - // always - arraysInArrays, objectsInArrays - { code: "var arr = [[ 1, 2 ], 2, 3, { 'foo': 'bar' }];", options: ["always", { arraysInArrays: false, objectsInArrays: false }] }, + // always - arraysInArrays, objectsInArrays, singleValue + { + code: "var arr = [[ 1, 2 ], [2], 3, { 'foo': 'bar' }];", + options: [ + "always", + { + arraysInArrays: false, + objectsInArrays: false, + singleValue: false, + }, + ], + }, - // always - arraysInArrays, objectsInArrays, singleValue - { code: "var arr = [[ 1, 2 ], [2], 3, { 'foo': 'bar' }];", options: ["always", { arraysInArrays: false, objectsInArrays: false, singleValue: false }] }, + // always + { code: "obj[ foo ]", options: ["always"] }, + { code: "obj[\nfoo\n]", options: ["always"] }, + { code: "obj[ 'foo' ]", options: ["always"] }, + { code: "obj[ 'foo' + 'bar' ]", options: ["always"] }, + { code: "obj[ obj2[ foo ] ]", options: ["always"] }, + { + code: "obj.map(function(item) { return [\n1,\n2,\n3,\n4\n]; })", + options: ["always"], + }, + { + code: "obj[ 'map' ](function(item) { return [\n1,\n2,\n3,\n4\n]; })", + options: ["always"], + }, + { + code: "obj[ 'for' + 'Each' ](function(item) { return [\n1,\n2,\n3,\n4\n]; })", + options: ["always"], + }, - // always - { code: "obj[ foo ]", options: ["always"] }, - { code: "obj[\nfoo\n]", options: ["always"] }, - { code: "obj[ 'foo' ]", options: ["always"] }, - { code: "obj[ 'foo' + 'bar' ]", options: ["always"] }, - { code: "obj[ obj2[ foo ] ]", options: ["always"] }, - { code: "obj.map(function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["always"] }, - { code: "obj[ 'map' ](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["always"] }, - { code: "obj[ 'for' + 'Each' ](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["always"] }, + { code: "var arr = [ 1, 2, 3, 4 ];", options: ["always"] }, + { code: "var arr = [ [ 1, 2 ], 2, 3, 4 ];", options: ["always"] }, + { code: "var arr = [\n1, 2, 3, 4\n];", options: ["always"] }, + { code: "var foo = [];", options: ["always"] }, - { code: "var arr = [ 1, 2, 3, 4 ];", options: ["always"] }, - { code: "var arr = [ [ 1, 2 ], 2, 3, 4 ];", options: ["always"] }, - { code: "var arr = [\n1, 2, 3, 4\n];", options: ["always"] }, - { code: "var foo = [];", options: ["always"] }, + // singleValue: false, objectsInArrays: true, arraysInArrays + { + code: "this.db.mappings.insert([\n { alias: 'a', url: 'http://www.amazon.de' },\n { alias: 'g', url: 'http://www.google.de' }\n], function() {});", + options: [ + "always", + { + singleValue: false, + objectsInArrays: true, + arraysInArrays: true, + }, + ], + }, - // singleValue: false, objectsInArrays: true, arraysInArrays - { code: "this.db.mappings.insert([\n { alias: 'a', url: 'http://www.amazon.de' },\n { alias: 'g', url: 'http://www.google.de' }\n], function() {});", options: ["always", { singleValue: false, objectsInArrays: true, arraysInArrays: true }] }, + // always - destructuring assignment + { + code: "var [ x, y ] = z", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [ x,y ] = z", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [ x, y\n] = z", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\nx, y ] = z", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\nx, y\n] = z", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\nx,,,\n] = z", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [ ,x, ] = z", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\nx, ...y\n] = z", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\nx, ...y ] = z", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [[ x, y ], z ] = arr;", + options: ["always", { arraysInArrays: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [ x, [ y, z ]] = arr;", + options: ["always", { arraysInArrays: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "[{ x, y }, z ] = arr;", + options: ["always", { objectsInArrays: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "[ x, { y, z }] = arr;", + options: ["always", { objectsInArrays: false }], + languageOptions: { ecmaVersion: 6 }, + }, - // always - destructuring assignment - { code: "var [ x, y ] = z", options: ["always"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [ x,y ] = z", options: ["always"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [ x, y\n] = z", options: ["always"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [\nx, y ] = z", options: ["always"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [\nx, y\n] = z", options: ["always"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [\nx,,,\n] = z", options: ["always"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [ ,x, ] = z", options: ["always"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [\nx, ...y\n] = z", options: ["always"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [\nx, ...y ] = z", options: ["always"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [[ x, y ], z ] = arr;", options: ["always", { arraysInArrays: false }], languageOptions: { ecmaVersion: 6 } }, - { code: "var [ x, [ y, z ]] = arr;", options: ["always", { arraysInArrays: false }], languageOptions: { ecmaVersion: 6 } }, - { code: "[{ x, y }, z ] = arr;", options: ["always", { objectsInArrays: false }], languageOptions: { ecmaVersion: 6 } }, - { code: "[ x, { y, z }] = arr;", options: ["always", { objectsInArrays: false }], languageOptions: { ecmaVersion: 6 } }, + // never + { code: "obj[foo]", options: ["never"] }, + { code: "obj['foo']", options: ["never"] }, + { code: "obj['foo' + 'bar']", options: ["never"] }, + { code: "obj['foo'+'bar']", options: ["never"] }, + { code: "obj[obj2[foo]]", options: ["never"] }, + { + code: "obj.map(function(item) { return [\n1,\n2,\n3,\n4\n]; })", + options: ["never"], + }, + { + code: "obj['map'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", + options: ["never"], + }, + { + code: "obj['for' + 'Each'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", + options: ["never"], + }, + { code: "var arr = [1, 2, 3, 4];", options: ["never"] }, + { code: "var arr = [[1, 2], 2, 3, 4];", options: ["never"] }, + { code: "var arr = [\n1, 2, 3, 4\n];", options: ["never"] }, + { code: "obj[\nfoo]", options: ["never"] }, + { code: "obj[foo\n]", options: ["never"] }, + { code: "var arr = [1,\n2,\n3,\n4\n];", options: ["never"] }, + { code: "var arr = [\n1,\n2,\n3,\n4];", options: ["never"] }, - // never - { code: "obj[foo]", options: ["never"] }, - { code: "obj['foo']", options: ["never"] }, - { code: "obj['foo' + 'bar']", options: ["never"] }, - { code: "obj['foo'+'bar']", options: ["never"] }, - { code: "obj[obj2[foo]]", options: ["never"] }, - { code: "obj.map(function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["never"] }, - { code: "obj['map'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["never"] }, - { code: "obj['for' + 'Each'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["never"] }, - { code: "var arr = [1, 2, 3, 4];", options: ["never"] }, - { code: "var arr = [[1, 2], 2, 3, 4];", options: ["never"] }, - { code: "var arr = [\n1, 2, 3, 4\n];", options: ["never"] }, - { code: "obj[\nfoo]", options: ["never"] }, - { code: "obj[foo\n]", options: ["never"] }, - { code: "var arr = [1,\n2,\n3,\n4\n];", options: ["never"] }, - { code: "var arr = [\n1,\n2,\n3,\n4];", options: ["never"] }, + // never - destructuring assignment + { + code: "var [x, y] = z", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [x,y] = z", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [x, y\n] = z", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\nx, y] = z", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\nx, y\n] = z", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\nx,,,\n] = z", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [,x,] = z", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\nx, ...y\n] = z", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\nx, ...y] = z", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [ [x, y], z] = arr;", + options: ["never", { arraysInArrays: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [x, [y, z] ] = arr;", + options: ["never", { arraysInArrays: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "[ { x, y }, z] = arr;", + options: ["never", { objectsInArrays: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "[x, { y, z } ] = arr;", + options: ["never", { objectsInArrays: true }], + languageOptions: { ecmaVersion: 6 }, + }, - // never - destructuring assignment - { code: "var [x, y] = z", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [x,y] = z", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [x, y\n] = z", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [\nx, y] = z", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [\nx, y\n] = z", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [\nx,,,\n] = z", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [,x,] = z", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [\nx, ...y\n] = z", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [\nx, ...y] = z", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [ [x, y], z] = arr;", options: ["never", { arraysInArrays: true }], languageOptions: { ecmaVersion: 6 } }, - { code: "var [x, [y, z] ] = arr;", options: ["never", { arraysInArrays: true }], languageOptions: { ecmaVersion: 6 } }, - { code: "[ { x, y }, z] = arr;", options: ["never", { objectsInArrays: true }], languageOptions: { ecmaVersion: 6 } }, - { code: "[x, { y, z } ] = arr;", options: ["never", { objectsInArrays: true }], languageOptions: { ecmaVersion: 6 } }, + // never - singleValue + { + code: "var foo = [ 'foo' ]", + options: ["never", { singleValue: true }], + }, + { code: "var foo = [ 2 ]", options: ["never", { singleValue: true }] }, + { + code: "var foo = [ [1, 1] ]", + options: ["never", { singleValue: true }], + }, + { + code: "var foo = [ {'foo': 'bar'} ]", + options: ["never", { singleValue: true }], + }, + { + code: "var foo = [ bar ]", + options: ["never", { singleValue: true }], + }, - // never - singleValue - { code: "var foo = [ 'foo' ]", options: ["never", { singleValue: true }] }, - { code: "var foo = [ 2 ]", options: ["never", { singleValue: true }] }, - { code: "var foo = [ [1, 1] ]", options: ["never", { singleValue: true }] }, - { code: "var foo = [ {'foo': 'bar'} ]", options: ["never", { singleValue: true }] }, - { code: "var foo = [ bar ]", options: ["never", { singleValue: true }] }, + // never - objectsInArrays + { + code: "var foo = [ {'bar': 'baz'}, 1, 5];", + options: ["never", { objectsInArrays: true }], + }, + { + code: "var foo = [1, 5, {'bar': 'baz'} ];", + options: ["never", { objectsInArrays: true }], + }, + { + code: "var foo = [ {\n'bar': 'baz', \n'qux': [ {'bar': 'baz'} ], \n'quxx': 1 \n} ]", + options: ["never", { objectsInArrays: true }], + }, + { + code: "var foo = [ {'bar': 'baz'} ]", + options: ["never", { objectsInArrays: true }], + }, + { + code: "var foo = [ {'bar': 'baz'}, 1, {'bar': 'baz'} ];", + options: ["never", { objectsInArrays: true }], + }, + { + code: "var foo = [1, {'bar': 'baz'} , 5];", + options: ["never", { objectsInArrays: true }], + }, + { + code: "var foo = [1, {'bar': 'baz'}, [ {'bar': 'baz'} ]];", + options: ["never", { objectsInArrays: true }], + }, + { + code: "var foo = [function(){}];", + options: ["never", { objectsInArrays: true }], + }, + { + code: "var foo = [];", + options: ["never", { objectsInArrays: true }], + }, - // never - objectsInArrays - { code: "var foo = [ {'bar': 'baz'}, 1, 5];", options: ["never", { objectsInArrays: true }] }, - { code: "var foo = [1, 5, {'bar': 'baz'} ];", options: ["never", { objectsInArrays: true }] }, - { code: "var foo = [ {\n'bar': 'baz', \n'qux': [ {'bar': 'baz'} ], \n'quxx': 1 \n} ]", options: ["never", { objectsInArrays: true }] }, - { code: "var foo = [ {'bar': 'baz'} ]", options: ["never", { objectsInArrays: true }] }, - { code: "var foo = [ {'bar': 'baz'}, 1, {'bar': 'baz'} ];", options: ["never", { objectsInArrays: true }] }, - { code: "var foo = [1, {'bar': 'baz'} , 5];", options: ["never", { objectsInArrays: true }] }, - { code: "var foo = [1, {'bar': 'baz'}, [ {'bar': 'baz'} ]];", options: ["never", { objectsInArrays: true }] }, - { code: "var foo = [function(){}];", options: ["never", { objectsInArrays: true }] }, - { code: "var foo = [];", options: ["never", { objectsInArrays: true }] }, + // never - arraysInArrays + { + code: "var arr = [ [1, 2], 2, 3, 4];", + options: ["never", { arraysInArrays: true }], + }, + { + code: "var foo = [arr[i], arr[j]];", + options: ["never", { arraysInArrays: true }], + }, + { code: "var foo = [];", options: ["never", { arraysInArrays: true }] }, - // never - arraysInArrays - { code: "var arr = [ [1, 2], 2, 3, 4];", options: ["never", { arraysInArrays: true }] }, - { code: "var foo = [arr[i], arr[j]];", options: ["never", { arraysInArrays: true }] }, - { code: "var foo = [];", options: ["never", { arraysInArrays: true }] }, + // never - arraysInArrays, singleValue + { + code: "var arr = [ [1, 2], [ [ [ 1 ] ] ], 3, 4];", + options: ["never", { arraysInArrays: true, singleValue: true }], + }, - // never - arraysInArrays, singleValue - { code: "var arr = [ [1, 2], [ [ [ 1 ] ] ], 3, 4];", options: ["never", { arraysInArrays: true, singleValue: true }] }, + // never - arraysInArrays, objectsInArrays + { + code: "var arr = [ [1, 2], 2, 3, {'foo': 'bar'} ];", + options: ["never", { arraysInArrays: true, objectsInArrays: true }], + }, - // never - arraysInArrays, objectsInArrays - { code: "var arr = [ [1, 2], 2, 3, {'foo': 'bar'} ];", options: ["never", { arraysInArrays: true, objectsInArrays: true }] }, + // should not warn + { code: "var foo = {};", options: ["never"] }, + { code: "var foo = [];", options: ["never"] }, - // should not warn - { code: "var foo = {};", options: ["never"] }, - { code: "var foo = [];", options: ["never"] }, + { + code: "var foo = [{'bar':'baz'}, 1, {'bar': 'baz'}];", + options: ["never"], + }, + { code: "var foo = [{'bar': 'baz'}];", options: ["never"] }, + { + code: "var foo = [{\n'bar': 'baz', \n'qux': [{'bar': 'baz'}], \n'quxx': 1 \n}]", + options: ["never"], + }, + { code: "var foo = [1, {'bar': 'baz'}, 5];", options: ["never"] }, + { code: "var foo = [{'bar': 'baz'}, 1, 5];", options: ["never"] }, + { code: "var foo = [1, 5, {'bar': 'baz'}];", options: ["never"] }, + { code: "var obj = {'foo': [1, 2]}", options: ["never"] }, - { code: "var foo = [{'bar':'baz'}, 1, {'bar': 'baz'}];", options: ["never"] }, - { code: "var foo = [{'bar': 'baz'}];", options: ["never"] }, - { code: "var foo = [{\n'bar': 'baz', \n'qux': [{'bar': 'baz'}], \n'quxx': 1 \n}]", options: ["never"] }, - { code: "var foo = [1, {'bar': 'baz'}, 5];", options: ["never"] }, - { code: "var foo = [{'bar': 'baz'}, 1, 5];", options: ["never"] }, - { code: "var foo = [1, 5, {'bar': 'baz'}];", options: ["never"] }, - { code: "var obj = {'foo': [1, 2]}", options: ["never"] }, + // destructuring with type annotation + { + code: "([ a, b ]: Array) => {}", + options: ["always"], + languageOptions: { + ecmaVersion: 6, + parser: parser("flow-destructuring-1"), + }, + }, + { + code: "([a, b]: Array< any >) => {}", + options: ["never"], + languageOptions: { + ecmaVersion: 6, + parser: parser("flow-destructuring-2"), + }, + }, + ], - // destructuring with type annotation - { code: "([ a, b ]: Array) => {}", options: ["always"], languageOptions: { ecmaVersion: 6, parser: parser("flow-destructuring-1") } }, - { code: "([a, b]: Array< any >) => {}", options: ["never"], languageOptions: { ecmaVersion: 6, parser: parser("flow-destructuring-2") } } - ], + invalid: [ + { + code: "var foo = [ ]", + output: "var foo = []", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, - invalid: [ - { - code: "var foo = [ ]", - output: "var foo = []", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - } - ] - }, + // objectsInArrays + { + code: "var foo = [ { 'bar': 'baz' }, 1, 5];", + output: "var foo = [{ 'bar': 'baz' }, 1, 5 ];", + options: ["always", { objectsInArrays: false }], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + messageId: "missingSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 36, + endLine: 1, + endColumn: 37, + }, + ], + }, + { + code: "var foo = [1, 5, { 'bar': 'baz' } ];", + output: "var foo = [ 1, 5, { 'bar': 'baz' }];", + options: ["always", { objectsInArrays: false }], + errors: [ + { + messageId: "missingSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 34, + endLine: 1, + endColumn: 35, + }, + ], + }, + { + code: "var foo = [ { 'bar':'baz' }, 1, { 'bar': 'baz' } ];", + output: "var foo = [{ 'bar':'baz' }, 1, { 'bar': 'baz' }];", + options: ["always", { objectsInArrays: false }], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 49, + endLine: 1, + endColumn: 50, + }, + ], + }, - // objectsInArrays - { - code: "var foo = [ { 'bar': 'baz' }, 1, 5];", - output: "var foo = [{ 'bar': 'baz' }, 1, 5 ];", - options: ["always", { objectsInArrays: false }], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - }, - { - messageId: "missingSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 36, - endLine: 1, - endColumn: 37 - } - ] - }, - { - code: "var foo = [1, 5, { 'bar': 'baz' } ];", - output: "var foo = [ 1, 5, { 'bar': 'baz' }];", - options: ["always", { objectsInArrays: false }], - errors: [ - { - messageId: "missingSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - }, - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 34, - endLine: 1, - endColumn: 35 - } - ] - }, - { - code: "var foo = [ { 'bar':'baz' }, 1, { 'bar': 'baz' } ];", - output: "var foo = [{ 'bar':'baz' }, 1, { 'bar': 'baz' }];", - options: ["always", { objectsInArrays: false }], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - }, - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 49, - endLine: 1, - endColumn: 50 - } - ] - }, + // singleValue + { + code: "var obj = [ 'foo' ];", + output: "var obj = ['foo'];", + options: ["always", { singleValue: false }], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 18, + endLine: 1, + endColumn: 19, + }, + ], + }, + { + code: "var obj = ['foo' ];", + output: "var obj = ['foo'];", + options: ["always", { singleValue: false }], + errors: [ + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 17, + endLine: 1, + endColumn: 18, + }, + ], + }, + { + code: "var obj = ['foo'];", + output: "var obj = [ 'foo' ];", + options: ["never", { singleValue: true }], + errors: [ + { + messageId: "missingSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: "missingSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 17, + endLine: 1, + endColumn: 18, + }, + ], + }, - // singleValue - { - code: "var obj = [ 'foo' ];", - output: "var obj = ['foo'];", - options: ["always", { singleValue: false }], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - }, - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 18, - endLine: 1, - endColumn: 19 - } - ] - }, - { - code: "var obj = ['foo' ];", - output: "var obj = ['foo'];", - options: ["always", { singleValue: false }], - errors: [ - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 17, - endLine: 1, - endColumn: 18 - } - ] - }, - { - code: "var obj = ['foo'];", - output: "var obj = [ 'foo' ];", - options: ["never", { singleValue: true }], - errors: [ - { - messageId: "missingSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - }, - { - messageId: "missingSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 17, - endLine: 1, - endColumn: 18 - } - ] - }, + // always - arraysInArrays + { + code: "var arr = [ [ 1, 2 ], 2, 3, 4 ];", + output: "var arr = [[ 1, 2 ], 2, 3, 4 ];", + options: ["always", { arraysInArrays: false }], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "var arr = [ 1, 2, 2, [ 3, 4 ] ];", + output: "var arr = [ 1, 2, 2, [ 3, 4 ]];", + options: ["always", { arraysInArrays: false }], + errors: [ + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 30, + endLine: 1, + endColumn: 31, + }, + ], + }, + { + code: "var arr = [[ 1, 2 ], 2, [ 3, 4 ] ];", + output: "var arr = [[ 1, 2 ], 2, [ 3, 4 ]];", + options: ["always", { arraysInArrays: false }], + errors: [ + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 33, + endLine: 1, + endColumn: 34, + }, + ], + }, + { + code: "var arr = [ [ 1, 2 ], 2, [ 3, 4 ]];", + output: "var arr = [[ 1, 2 ], 2, [ 3, 4 ]];", + options: ["always", { arraysInArrays: false }], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "var arr = [ [ 1, 2 ], 2, [ 3, 4 ] ];", + output: "var arr = [[ 1, 2 ], 2, [ 3, 4 ]];", + options: ["always", { arraysInArrays: false }], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 34, + endLine: 1, + endColumn: 35, + }, + ], + }, - // always - arraysInArrays - { - code: "var arr = [ [ 1, 2 ], 2, 3, 4 ];", - output: "var arr = [[ 1, 2 ], 2, 3, 4 ];", - options: ["always", { arraysInArrays: false }], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - } - ] - }, - { - code: "var arr = [ 1, 2, 2, [ 3, 4 ] ];", - output: "var arr = [ 1, 2, 2, [ 3, 4 ]];", - options: ["always", { arraysInArrays: false }], - errors: [ - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 30, - endLine: 1, - endColumn: 31 - } - ] - }, - { - code: "var arr = [[ 1, 2 ], 2, [ 3, 4 ] ];", - output: "var arr = [[ 1, 2 ], 2, [ 3, 4 ]];", - options: ["always", { arraysInArrays: false }], - errors: [ - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 33, - endLine: 1, - endColumn: 34 - } - ] - }, - { - code: "var arr = [ [ 1, 2 ], 2, [ 3, 4 ]];", - output: "var arr = [[ 1, 2 ], 2, [ 3, 4 ]];", - options: ["always", { arraysInArrays: false }], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - } - ] - }, - { - code: "var arr = [ [ 1, 2 ], 2, [ 3, 4 ] ];", - output: "var arr = [[ 1, 2 ], 2, [ 3, 4 ]];", - options: ["always", { arraysInArrays: false }], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - }, - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 34, - endLine: 1, - endColumn: 35 - } - ] - }, + // always - destructuring + { + code: "var [x,y] = y", + output: "var [ x,y ] = y", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayPattern", + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + { + messageId: "missingSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayPattern", + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + ], + }, + { + code: "var [x,y ] = y", + output: "var [ x,y ] = y", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayPattern", + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + ], + }, + { + code: "var [,,,x,,] = y", + output: "var [ ,,,x,, ] = y", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayPattern", + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + { + messageId: "missingSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayPattern", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "var [ ,,,x,,] = y", + output: "var [ ,,,x,, ] = y", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayPattern", + line: 1, + column: 13, + endLine: 1, + endColumn: 14, + }, + ], + }, + { + code: "var [...horse] = y", + output: "var [ ...horse ] = y", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayPattern", + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + { + messageId: "missingSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayPattern", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "var [...horse ] = y", + output: "var [ ...horse ] = y", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayPattern", + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + ], + }, + { + code: "var [ [ x, y ], z ] = arr;", + output: "var [[ x, y ], z ] = arr;", + options: ["always", { arraysInArrays: false }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayPattern", + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + ], + }, + { + code: "[ { x, y }, z ] = arr;", + output: "[{ x, y }, z ] = arr;", + options: ["always", { objectsInArrays: false }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayPattern", + line: 1, + column: 2, + endLine: 1, + endColumn: 3, + }, + ], + }, + { + code: "[ x, { y, z } ] = arr;", + output: "[ x, { y, z }] = arr;", + options: ["always", { objectsInArrays: false }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayPattern", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + ], + }, - // always - destructuring - { - code: "var [x,y] = y", - output: "var [ x,y ] = y", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "missingSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayPattern", - line: 1, - column: 5, - endLine: 1, - endColumn: 6 - }, - { - messageId: "missingSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayPattern", - line: 1, - column: 9, - endLine: 1, - endColumn: 10 - }] - }, - { - code: "var [x,y ] = y", - output: "var [ x,y ] = y", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "missingSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayPattern", - line: 1, - column: 5, - endLine: 1, - endColumn: 6 - }] - }, - { - code: "var [,,,x,,] = y", - output: "var [ ,,,x,, ] = y", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "missingSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayPattern", - line: 1, - column: 5, - endLine: 1, - endColumn: 6 - }, - { - messageId: "missingSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayPattern", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - }] - }, - { - code: "var [ ,,,x,,] = y", - output: "var [ ,,,x,, ] = y", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "missingSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayPattern", - line: 1, - column: 13, - endLine: 1, - endColumn: 14 - }] - }, - { - code: "var [...horse] = y", - output: "var [ ...horse ] = y", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "missingSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayPattern", - line: 1, - column: 5, - endLine: 1, - endColumn: 6 - }, - { - messageId: "missingSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayPattern", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - }] - }, - { - code: "var [...horse ] = y", - output: "var [ ...horse ] = y", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "missingSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayPattern", - line: 1, - column: 5, - endLine: 1, - endColumn: 6 - }] - }, - { - code: "var [ [ x, y ], z ] = arr;", - output: "var [[ x, y ], z ] = arr;", - options: ["always", { arraysInArrays: false }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayPattern", - line: 1, - column: 6, - endLine: 1, - endColumn: 7 - }] - }, - { - code: "[ { x, y }, z ] = arr;", - output: "[{ x, y }, z ] = arr;", - options: ["always", { objectsInArrays: false }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayPattern", - line: 1, - column: 2, - endLine: 1, - endColumn: 3 - }] - }, - { - code: "[ x, { y, z } ] = arr;", - output: "[ x, { y, z }] = arr;", - options: ["always", { objectsInArrays: false }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayPattern", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - }] - }, + // never - arraysInArrays + { + code: "var arr = [[1, 2], 2, [3, 4]];", + output: "var arr = [ [1, 2], 2, [3, 4] ];", + options: ["never", { arraysInArrays: true }], + errors: [ + { + messageId: "missingSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: "missingSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 29, + endLine: 1, + endColumn: 30, + }, + ], + }, + { + code: "var arr = [ ];", + output: "var arr = [];", + options: ["never", { arraysInArrays: true }], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, - // never - arraysInArrays - { - code: "var arr = [[1, 2], 2, [3, 4]];", - output: "var arr = [ [1, 2], 2, [3, 4] ];", - options: ["never", { arraysInArrays: true }], - errors: [ - { - messageId: "missingSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - }, - { - messageId: "missingSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 29, - endLine: 1, - endColumn: 30 - } - ] - }, - { - code: "var arr = [ ];", - output: "var arr = [];", - options: ["never", { arraysInArrays: true }], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - } - ] - }, + // never - objectsInArrays + { + code: "var arr = [ ];", + output: "var arr = [];", + options: ["never", { objectsInArrays: true }], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, - // never - objectsInArrays - { - code: "var arr = [ ];", - output: "var arr = [];", - options: ["never", { objectsInArrays: true }], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - } - ] - }, + // always + { + code: "var arr = [1, 2, 3, 4];", + output: "var arr = [ 1, 2, 3, 4 ];", + options: ["always"], + errors: [ + { + messageId: "missingSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: "missingSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 22, + endLine: 1, + endColumn: 23, + }, + ], + }, + { + code: "var arr = [1, 2, 3, 4 ];", + output: "var arr = [ 1, 2, 3, 4 ];", + options: ["always"], + errors: [ + { + messageId: "missingSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: "var arr = [ 1, 2, 3, 4];", + output: "var arr = [ 1, 2, 3, 4 ];", + options: ["always"], + errors: [ + { + messageId: "missingSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 23, + endLine: 1, + endColumn: 24, + }, + ], + }, - // always - { - code: "var arr = [1, 2, 3, 4];", - output: "var arr = [ 1, 2, 3, 4 ];", - options: ["always"], - errors: [ - { - messageId: "missingSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - }, - { - messageId: "missingSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 22, - endLine: 1, - endColumn: 23 - } - ] - }, - { - code: "var arr = [1, 2, 3, 4 ];", - output: "var arr = [ 1, 2, 3, 4 ];", - options: ["always"], - errors: [ - { - messageId: "missingSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - } - ] - }, - { - code: "var arr = [ 1, 2, 3, 4];", - output: "var arr = [ 1, 2, 3, 4 ];", - options: ["always"], - errors: [ - { - messageId: "missingSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 23, - endLine: 1, - endColumn: 24 - } - ] - }, + // never + { + code: "var arr = [ 1, 2, 3, 4 ];", + output: "var arr = [1, 2, 3, 4];", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 23, + endLine: 1, + endColumn: 24, + }, + ], + }, + { + code: "var arr = [1, 2, 3, 4 ];", + output: "var arr = [1, 2, 3, 4];", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 22, + endLine: 1, + endColumn: 23, + }, + ], + }, + { + code: "var arr = [ 1, 2, 3, 4];", + output: "var arr = [1, 2, 3, 4];", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "var arr = [ [ 1], 2, 3, 4];", + output: "var arr = [[1], 2, 3, 4];", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "var arr = [[1 ], 2, 3, 4 ];", + output: "var arr = [[1], 2, 3, 4];", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 25, + endLine: 1, + endColumn: 26, + }, + ], + }, - // never - { - code: "var arr = [ 1, 2, 3, 4 ];", - output: "var arr = [1, 2, 3, 4];", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - }, - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 23, - endLine: 1, - endColumn: 24 - } - ] - }, - { - code: "var arr = [1, 2, 3, 4 ];", - output: "var arr = [1, 2, 3, 4];", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 22, - endLine: 1, - endColumn: 23 - } - ] - }, - { - code: "var arr = [ 1, 2, 3, 4];", - output: "var arr = [1, 2, 3, 4];", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - } - ] - }, - { - code: "var arr = [ [ 1], 2, 3, 4];", - output: "var arr = [[1], 2, 3, 4];", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - }, - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - } - ] - }, - { - code: "var arr = [[1 ], 2, 3, 4 ];", - output: "var arr = [[1], 2, 3, 4];", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - }, - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 25, - endLine: 1, - endColumn: 26 - } - ] - }, + // destructuring with type annotation + { + code: "([ a, b ]: Array) => {}", + output: "([a, b]: Array) => {}", + options: ["never"], + languageOptions: { + ecmaVersion: 6, + parser: parser("flow-destructuring-1"), + }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayPattern", + line: 1, + column: 3, + endLine: 1, + endColumn: 4, + }, + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayPattern", + line: 1, + column: 8, + endLine: 1, + endColumn: 9, + }, + ], + }, + { + code: "([a, b]: Array< any >) => {}", + output: "([ a, b ]: Array< any >) => {}", + options: ["always"], + languageOptions: { + parser: parser("flow-destructuring-2"), + ecmaVersion: 6, + }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayPattern", + line: 1, + column: 2, + endLine: 1, + endColumn: 3, + }, + { + messageId: "missingSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayPattern", + line: 1, + column: 7, + endLine: 1, + endColumn: 8, + }, + ], + }, - // destructuring with type annotation - { - code: "([ a, b ]: Array) => {}", - output: "([a, b]: Array) => {}", - options: ["never"], - languageOptions: { - ecmaVersion: 6, - parser: parser("flow-destructuring-1") - }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayPattern", - line: 1, - column: 3, - endLine: 1, - endColumn: 4 - }, - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayPattern", - line: 1, - column: 8, - endLine: 1, - endColumn: 9 - } - ] - }, - { - code: "([a, b]: Array< any >) => {}", - output: "([ a, b ]: Array< any >) => {}", - options: ["always"], - languageOptions: { - parser: parser("flow-destructuring-2"), - ecmaVersion: 6 - }, - errors: [ - { - messageId: "missingSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayPattern", - line: 1, - column: 2, - endLine: 1, - endColumn: 3 - }, - { - messageId: "missingSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayPattern", - line: 1, - column: 7, - endLine: 1, - endColumn: 8 - } - ] - }, - - // multiple spaces - { - code: "var arr = [ 1, 2 ];", - output: "var arr = [1, 2];", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 14 - }, - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 18, - endLine: 1, - endColumn: 21 - } - ] - }, - { - code: "function f( [ a, b ] ) {}", - output: "function f( [a, b] ) {}", - options: ["never"], - languageOptions: { - ecmaVersion: 6 - }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayPattern", - line: 1, - column: 14, - endLine: 1, - endColumn: 17 - }, - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayPattern", - line: 1, - column: 21, - endLine: 1, - endColumn: 23 - } - ] - }, - { - code: "var arr = [ 1,\n 2 ];", - output: "var arr = [1,\n 2];", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - }, - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 2, - column: 5, - endLine: 2, - endColumn: 8 - } - ] - }, - { - code: "var arr = [ 1, [ 2, 3 ] ];", - output: "var arr = [1, [2, 3]];", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 14 - }, - { - messageId: "unexpectedSpaceAfter", - data: { - tokenValue: "[" - }, - type: "ArrayExpression", - line: 1, - column: 18, - endLine: 1, - endColumn: 19 - }, - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 23, - endLine: 1, - endColumn: 25 - }, - { - messageId: "unexpectedSpaceBefore", - data: { - tokenValue: "]" - }, - type: "ArrayExpression", - line: 1, - column: 26, - endLine: 1, - endColumn: 27 - } - ] - } - ] + // multiple spaces + { + code: "var arr = [ 1, 2 ];", + output: "var arr = [1, 2];", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 14, + }, + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 18, + endLine: 1, + endColumn: 21, + }, + ], + }, + { + code: "function f( [ a, b ] ) {}", + output: "function f( [a, b] ) {}", + options: ["never"], + languageOptions: { + ecmaVersion: 6, + }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayPattern", + line: 1, + column: 14, + endLine: 1, + endColumn: 17, + }, + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayPattern", + line: 1, + column: 21, + endLine: 1, + endColumn: 23, + }, + ], + }, + { + code: "var arr = [ 1,\n 2 ];", + output: "var arr = [1,\n 2];", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 2, + column: 5, + endLine: 2, + endColumn: 8, + }, + ], + }, + { + code: "var arr = [ 1, [ 2, 3 ] ];", + output: "var arr = [1, [2, 3]];", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 14, + }, + { + messageId: "unexpectedSpaceAfter", + data: { + tokenValue: "[", + }, + type: "ArrayExpression", + line: 1, + column: 18, + endLine: 1, + endColumn: 19, + }, + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 23, + endLine: 1, + endColumn: 25, + }, + { + messageId: "unexpectedSpaceBefore", + data: { + tokenValue: "]", + }, + type: "ArrayExpression", + line: 1, + column: 26, + endLine: 1, + endColumn: 27, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/array-callback-return.js b/tests/lib/rules/array-callback-return.js index 6c78cb8025ee..5a4a12e55fae 100644 --- a/tests/lib/rules/array-callback-return.js +++ b/tests/lib/rules/array-callback-return.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/array-callback-return"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -27,661 +27,2074 @@ const allowImplicitCheckForEach = [{ allowImplicit: true, checkForEach: true }]; const checkForEachAllowVoid = [{ checkForEach: true, allowVoid: true }]; ruleTester.run("array-callback-return", rule, { - valid: [ + valid: [ + "foo.every(function(){}())", + "foo.every(function(){ return function() { return true; }; }())", + "foo.every(function(){ return function() { return; }; })", - "foo.every(function(){}())", - "foo.every(function(){ return function() { return true; }; }())", - "foo.every(function(){ return function() { return; }; })", + "foo.forEach(bar || function(x) { var a=0; })", + "foo.forEach(bar || function(x) { return a; })", + "foo.forEach(function() {return function() { var a = 0;}}())", + "foo.forEach(function(x) { var a=0; })", + "foo.forEach(function(x) { return a;})", + "foo.forEach(function(x) { return; })", + "foo.forEach(function(x) { if (a === b) { return;} var a=0; })", + "foo.forEach(function(x) { if (a === b) { return x;} var a=0; })", + "foo.bar().forEach(function(x) { return; })", + '["foo","bar","baz"].forEach(function(x) { return x; })', + { + code: "foo.forEach(x => { var a=0; })", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "foo.forEach(x => { if (a === b) { return;} var a=0; })", + languageOptions: { ecmaVersion: 6 }, + }, + { code: "foo.forEach(x => x)", languageOptions: { ecmaVersion: 6 } }, + { + code: "foo.forEach(val => y += val)", + languageOptions: { ecmaVersion: 6 }, + }, - "foo.forEach(bar || function(x) { var a=0; })", - "foo.forEach(bar || function(x) { return a; })", - "foo.forEach(function() {return function() { var a = 0;}}())", - "foo.forEach(function(x) { var a=0; })", - "foo.forEach(function(x) { return a;})", - "foo.forEach(function(x) { return; })", - "foo.forEach(function(x) { if (a === b) { return;} var a=0; })", - "foo.forEach(function(x) { if (a === b) { return x;} var a=0; })", - "foo.bar().forEach(function(x) { return; })", - "[\"foo\",\"bar\",\"baz\"].forEach(function(x) { return x; })", - { code: "foo.forEach(x => { var a=0; })", languageOptions: { ecmaVersion: 6 } }, - { code: "foo.forEach(x => { if (a === b) { return;} var a=0; })", languageOptions: { ecmaVersion: 6 } }, - { code: "foo.forEach(x => x)", languageOptions: { ecmaVersion: 6 } }, - { code: "foo.forEach(val => y += val)", languageOptions: { ecmaVersion: 6 } }, + { + code: "foo.map(async function(){})", + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo.map(async () => {})", + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo.map(function* () {})", + languageOptions: { ecmaVersion: 6 }, + }, - { code: "foo.map(async function(){})", languageOptions: { ecmaVersion: 8 } }, - { code: "foo.map(async () => {})", languageOptions: { ecmaVersion: 8 } }, - { code: "foo.map(function* () {})", languageOptions: { ecmaVersion: 6 } }, + // options: { allowImplicit: false } + { + code: "Array.from(x, function() { return true; })", + options: [{ allowImplicit: false }], + }, + { + code: "Int32Array.from(x, function() { return true; })", + options: [{ allowImplicit: false }], + }, + "foo.every(function() { return true; })", + "foo.filter(function() { return true; })", + "foo.find(function() { return true; })", + "foo.findIndex(function() { return true; })", + "foo.findLast(function() { return true; })", + "foo.findLastIndex(function() { return true; })", + "foo.flatMap(function() { return true; })", + "foo.forEach(function() { return; })", + "foo.map(function() { return true; })", + "foo.reduce(function() { return true; })", + "foo.reduceRight(function() { return true; })", + "foo.some(function() { return true; })", + "foo.sort(function() { return 0; })", + "foo.toSorted(function() { return 0; })", + { + code: "foo.every(() => { return true; })", + languageOptions: { ecmaVersion: 6 }, + }, + "foo.every(function() { if (a) return true; else return false; })", + "foo.every(function() { switch (a) { case 0: bar(); default: return true; } })", + "foo.every(function() { try { bar(); return true; } catch (err) { return false; } })", + "foo.every(function() { try { bar(); } finally { return true; } })", - // options: { allowImplicit: false } - { code: "Array.from(x, function() { return true; })", options: [{ allowImplicit: false }] }, - { code: "Int32Array.from(x, function() { return true; })", options: [{ allowImplicit: false }] }, - "foo.every(function() { return true; })", - "foo.filter(function() { return true; })", - "foo.find(function() { return true; })", - "foo.findIndex(function() { return true; })", - "foo.findLast(function() { return true; })", - "foo.findLastIndex(function() { return true; })", - "foo.flatMap(function() { return true; })", - "foo.forEach(function() { return; })", - "foo.map(function() { return true; })", - "foo.reduce(function() { return true; })", - "foo.reduceRight(function() { return true; })", - "foo.some(function() { return true; })", - "foo.sort(function() { return 0; })", - "foo.toSorted(function() { return 0; })", - { code: "foo.every(() => { return true; })", languageOptions: { ecmaVersion: 6 } }, - "foo.every(function() { if (a) return true; else return false; })", - "foo.every(function() { switch (a) { case 0: bar(); default: return true; } })", - "foo.every(function() { try { bar(); return true; } catch (err) { return false; } })", - "foo.every(function() { try { bar(); } finally { return true; } })", + // options: { allowImplicit: true } + { + code: "Array.from(x, function() { return; })", + options: allowImplicitOptions, + }, + { + code: "Int32Array.from(x, function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.every(function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.filter(function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.find(function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.findIndex(function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.findLast(function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.findLastIndex(function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.flatMap(function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.forEach(function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.map(function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.reduce(function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.reduceRight(function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.some(function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.sort(function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.toSorted(function() { return; })", + options: allowImplicitOptions, + }, + { + code: "foo.every(() => { return; })", + options: allowImplicitOptions, + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "foo.every(function() { if (a) return; else return a; })", + options: allowImplicitOptions, + }, + { + code: "foo.every(function() { switch (a) { case 0: bar(); default: return; } })", + options: allowImplicitOptions, + }, + { + code: "foo.every(function() { try { bar(); return; } catch (err) { return; } })", + options: allowImplicitOptions, + }, + { + code: "foo.every(function() { try { bar(); } finally { return; } })", + options: allowImplicitOptions, + }, - // options: { allowImplicit: true } - { code: "Array.from(x, function() { return; })", options: allowImplicitOptions }, - { code: "Int32Array.from(x, function() { return; })", options: allowImplicitOptions }, - { code: "foo.every(function() { return; })", options: allowImplicitOptions }, - { code: "foo.filter(function() { return; })", options: allowImplicitOptions }, - { code: "foo.find(function() { return; })", options: allowImplicitOptions }, - { code: "foo.findIndex(function() { return; })", options: allowImplicitOptions }, - { code: "foo.findLast(function() { return; })", options: allowImplicitOptions }, - { code: "foo.findLastIndex(function() { return; })", options: allowImplicitOptions }, - { code: "foo.flatMap(function() { return; })", options: allowImplicitOptions }, - { code: "foo.forEach(function() { return; })", options: allowImplicitOptions }, - { code: "foo.map(function() { return; })", options: allowImplicitOptions }, - { code: "foo.reduce(function() { return; })", options: allowImplicitOptions }, - { code: "foo.reduceRight(function() { return; })", options: allowImplicitOptions }, - { code: "foo.some(function() { return; })", options: allowImplicitOptions }, - { code: "foo.sort(function() { return; })", options: allowImplicitOptions }, - { code: "foo.toSorted(function() { return; })", options: allowImplicitOptions }, - { code: "foo.every(() => { return; })", options: allowImplicitOptions, languageOptions: { ecmaVersion: 6 } }, - { code: "foo.every(function() { if (a) return; else return a; })", options: allowImplicitOptions }, - { code: "foo.every(function() { switch (a) { case 0: bar(); default: return; } })", options: allowImplicitOptions }, - { code: "foo.every(function() { try { bar(); return; } catch (err) { return; } })", options: allowImplicitOptions }, - { code: "foo.every(function() { try { bar(); } finally { return; } })", options: allowImplicitOptions }, + // options: { checkForEach: true } + { + code: "foo.forEach(function(x) { return; })", + options: checkForEachOptions, + }, + { + code: "foo.forEach(function(x) { var a=0; })", + options: checkForEachOptions, + }, + { + code: "foo.forEach(function(x) { if (a === b) { return;} var a=0; })", + options: checkForEachOptions, + }, + { + code: "foo.forEach(function() {return function() { if (a == b) { return; }}}())", + options: checkForEachOptions, + }, + { + code: "foo.forEach(x => { var a=0; })", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "foo.forEach(x => { if (a === b) { return;} var a=0; })", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "foo.forEach(x => { x })", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "foo.forEach(bar || function(x) { return; })", + options: checkForEachOptions, + }, + { + code: "Array.from(x, function() { return true; })", + options: checkForEachOptions, + }, + { + code: "Int32Array.from(x, function() { return true; })", + options: checkForEachOptions, + }, + { + code: "foo.every(() => { return true; })", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "foo.every(function() { if (a) return 1; else return a; })", + options: checkForEachOptions, + }, + { + code: "foo.every(function() { switch (a) { case 0: return bar(); default: return a; } })", + options: checkForEachOptions, + }, + { + code: "foo.every(function() { try { bar(); return 1; } catch (err) { return err; } })", + options: checkForEachOptions, + }, + { + code: "foo.every(function() { try { bar(); } finally { return 1; } })", + options: checkForEachOptions, + }, + { + code: "foo.every(function() { return; })", + options: allowImplicitCheckForEach, + }, - // options: { checkForEach: true } - { code: "foo.forEach(function(x) { return; })", options: checkForEachOptions }, - { code: "foo.forEach(function(x) { var a=0; })", options: checkForEachOptions }, - { code: "foo.forEach(function(x) { if (a === b) { return;} var a=0; })", options: checkForEachOptions }, - { code: "foo.forEach(function() {return function() { if (a == b) { return; }}}())", options: checkForEachOptions }, - { code: "foo.forEach(x => { var a=0; })", options: checkForEachOptions, languageOptions: { ecmaVersion: 6 } }, - { code: "foo.forEach(x => { if (a === b) { return;} var a=0; })", options: checkForEachOptions, languageOptions: { ecmaVersion: 6 } }, - { code: "foo.forEach(x => { x })", options: checkForEachOptions, languageOptions: { ecmaVersion: 6 } }, - { code: "foo.forEach(bar || function(x) { return; })", options: checkForEachOptions }, - { code: "Array.from(x, function() { return true; })", options: checkForEachOptions }, - { code: "Int32Array.from(x, function() { return true; })", options: checkForEachOptions }, - { code: "foo.every(() => { return true; })", options: checkForEachOptions, languageOptions: { ecmaVersion: 6 } }, - { code: "foo.every(function() { if (a) return 1; else return a; })", options: checkForEachOptions }, - { code: "foo.every(function() { switch (a) { case 0: return bar(); default: return a; } })", options: checkForEachOptions }, - { code: "foo.every(function() { try { bar(); return 1; } catch (err) { return err; } })", options: checkForEachOptions }, - { code: "foo.every(function() { try { bar(); } finally { return 1; } })", options: checkForEachOptions }, - { code: "foo.every(function() { return; })", options: allowImplicitCheckForEach }, + // options: { checkForEach: true, allowVoid: true } + { + code: "foo.forEach((x) => void x)", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "foo.forEach((x) => void bar(x))", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "foo.forEach(function (x) { return void bar(x); })", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "foo.forEach((x) => { return void bar(x); })", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "foo.forEach((x) => { if (a === b) { return void a; } bar(x) })", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + }, - // options: { checkForEach: true, allowVoid: true } - { code: "foo.forEach((x) => void x)", options: checkForEachAllowVoid, languageOptions: { ecmaVersion: 6 } }, - { code: "foo.forEach((x) => void bar(x))", options: checkForEachAllowVoid, languageOptions: { ecmaVersion: 6 } }, - { code: "foo.forEach(function (x) { return void bar(x); })", options: checkForEachAllowVoid, languageOptions: { ecmaVersion: 6 } }, - { code: "foo.forEach((x) => { return void bar(x); })", options: checkForEachAllowVoid, languageOptions: { ecmaVersion: 6 } }, - { code: "foo.forEach((x) => { if (a === b) { return void a; } bar(x) })", options: checkForEachAllowVoid, languageOptions: { ecmaVersion: 6 } }, + "Arrow.from(x, function() {})", + "foo.abc(function() {})", + "every(function() {})", + "foo[every](function() {})", + "var every = function() {}", + { + code: "foo[`${every}`](function() {})", + languageOptions: { ecmaVersion: 6 }, + }, + { code: "foo.every(() => true)", languageOptions: { ecmaVersion: 6 } }, + ], + invalid: [ + { + code: "Array.from(x, function() {})", + errors: [ + { + messageId: "expectedInside", + data: { name: "function", arrayMethodName: "Array.from" }, + }, + ], + }, + { + code: "Array.from(x, function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.from", + }, + }, + ], + }, + { + code: "Int32Array.from(x, function() {})", + errors: [ + { + messageId: "expectedInside", + data: { name: "function", arrayMethodName: "Array.from" }, + }, + ], + }, + { + code: "Int32Array.from(x, function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.from", + }, + }, + ], + }, + { + code: "foo.every(function() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.every(function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.filter(function() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.filter", + }, + }, + ], + }, + { + code: "foo.filter(function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.filter", + }, + }, + ], + }, + { + code: "foo.find(function() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.find", + }, + }, + ], + }, + { + code: "foo.find(function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.find", + }, + }, + ], + }, + { + code: "foo.findLast(function() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.findLast", + }, + }, + ], + }, + { + code: "foo.findLast(function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.findLast", + }, + }, + ], + }, + { + code: "foo.findIndex(function() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.findIndex", + }, + }, + ], + }, + { + code: "foo.findIndex(function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.findIndex", + }, + }, + ], + }, + { + code: "foo.findLastIndex(function() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.findLastIndex", + }, + }, + ], + }, + { + code: "foo.findLastIndex(function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.findLastIndex", + }, + }, + ], + }, + { + code: "foo.flatMap(function() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.flatMap", + }, + }, + ], + }, + { + code: "foo.flatMap(function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.flatMap", + }, + }, + ], + }, + { + code: "foo.map(function() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.map", + }, + }, + ], + }, + { + code: "foo.map(function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.map", + }, + }, + ], + }, + { + code: "foo.reduce(function() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.reduce", + }, + }, + ], + }, + { + code: "foo.reduce(function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.reduce", + }, + }, + ], + }, + { + code: "foo.reduceRight(function() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.reduceRight", + }, + }, + ], + }, + { + code: "foo.reduceRight(function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.reduceRight", + }, + }, + ], + }, + { + code: "foo.some(function() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.some", + }, + }, + ], + }, + { + code: "foo.some(function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.some", + }, + }, + ], + }, + { + code: "foo.sort(function() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.sort", + }, + }, + ], + }, + { + code: "foo.sort(function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.sort", + }, + }, + ], + }, + { + code: "foo.toSorted(function() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.toSorted", + }, + }, + ], + }, + { + code: "foo.toSorted(function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.toSorted", + }, + }, + ], + }, + { + code: "foo.bar.baz.every(function() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.bar.baz.every(function foo() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: 'foo["every"](function() {})', + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: 'foo["every"](function foo() {})', + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo[`every`](function() {})", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo[`every`](function foo() {})", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.every(() => {})", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Array.prototype.every() expects a return value from arrow function.", + column: 14, + }, + ], + }, + { + code: "foo.every(function() { if (a) return true; })", + errors: [ + { + message: + "Array.prototype.every() expects a value to be returned at the end of function.", + column: 11, + }, + ], + }, + { + code: "foo.every(function cb() { if (a) return true; })", + errors: [ + { + message: + "Array.prototype.every() expects a value to be returned at the end of function 'cb'.", + column: 11, + }, + ], + }, + { + code: "foo.every(function() { switch (a) { case 0: break; default: return true; } })", + errors: [ + { + messageId: "expectedAtEnd", + data: { + name: "function", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.every(function foo() { switch (a) { case 0: break; default: return true; } })", + errors: [ + { + messageId: "expectedAtEnd", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.every(function() { try { bar(); } catch (err) { return true; } })", + errors: [ + { + messageId: "expectedAtEnd", + data: { + name: "function", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.every(function foo() { try { bar(); } catch (err) { return true; } })", + errors: [ + { + messageId: "expectedAtEnd", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.every(function() { return; })", + errors: [ + { + messageId: "expectedReturnValue", + data: { + name: "function", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.every(function foo() { return; })", + errors: [ + { + messageId: "expectedReturnValue", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.every(function() { if (a) return; })", + errors: [ + "Array.prototype.every() expects a value to be returned at the end of function.", + { + messageId: "expectedReturnValue", + data: { + name: "function", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.every(function foo() { if (a) return; })", + errors: [ + "Array.prototype.every() expects a value to be returned at the end of function 'foo'.", + { + messageId: "expectedReturnValue", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.every(function() { if (a) return; else return; })", + errors: [ + { + messageId: "expectedReturnValue", + data: { + name: "function", + arrayMethodName: "Array.prototype.every", + }, + }, + { + messageId: "expectedReturnValue", + data: { + name: "function", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.every(function foo() { if (a) return; else return; })", + errors: [ + { + messageId: "expectedReturnValue", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.every", + }, + }, + { + messageId: "expectedReturnValue", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.every(cb || function() {})", + errors: [ + "Array.prototype.every() expects a return value from function.", + ], + }, + { + code: "foo.every(cb || function foo() {})", + errors: [ + "Array.prototype.every() expects a return value from function 'foo'.", + ], + }, + { + code: "foo.every(a ? function() {} : function() {})", + errors: [ + "Array.prototype.every() expects a return value from function.", + "Array.prototype.every() expects a return value from function.", + ], + }, + { + code: "foo.every(a ? function foo() {} : function bar() {})", + errors: [ + "Array.prototype.every() expects a return value from function 'foo'.", + "Array.prototype.every() expects a return value from function 'bar'.", + ], + }, + { + code: "foo.every(function(){ return function() {}; }())", + errors: [ + { + message: + "Array.prototype.every() expects a return value from function.", + column: 30, + }, + ], + }, + { + code: "foo.every(function(){ return function foo() {}; }())", + errors: [ + { + message: + "Array.prototype.every() expects a return value from function 'foo'.", + column: 30, + }, + ], + }, + { + code: "foo.every(() => {})", + options: [{ allowImplicit: false }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Array.prototype.every() expects a return value from arrow function.", + }, + ], + }, + { + code: "foo.every(() => {})", + options: [{ allowImplicit: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Array.prototype.every() expects a return value from arrow function.", + }, + ], + }, - "Arrow.from(x, function() {})", - "foo.abc(function() {})", - "every(function() {})", - "foo[every](function() {})", - "var every = function() {}", - { code: "foo[`${every}`](function() {})", languageOptions: { ecmaVersion: 6 } }, - { code: "foo.every(() => true)", languageOptions: { ecmaVersion: 6 } } + // options: { allowImplicit: true } + { + code: "Array.from(x, function() {})", + options: allowImplicitOptions, + errors: [ + { + messageId: "expectedInside", + data: { name: "function", arrayMethodName: "Array.from" }, + }, + ], + }, + { + code: "foo.every(function() {})", + options: allowImplicitOptions, + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.filter(function foo() {})", + options: allowImplicitOptions, + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.filter", + }, + }, + ], + }, + { + code: "foo.find(function foo() {})", + options: allowImplicitOptions, + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.find", + }, + }, + ], + }, + { + code: "foo.map(function() {})", + options: allowImplicitOptions, + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.map", + }, + }, + ], + }, + { + code: "foo.reduce(function() {})", + options: allowImplicitOptions, + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.reduce", + }, + }, + ], + }, + { + code: "foo.reduceRight(function() {})", + options: allowImplicitOptions, + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.reduceRight", + }, + }, + ], + }, + { + code: "foo.bar.baz.every(function foo() {})", + options: allowImplicitOptions, + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.every(cb || function() {})", + options: allowImplicitOptions, + errors: [ + "Array.prototype.every() expects a return value from function.", + ], + }, + { + code: '["foo","bar"].sort(function foo() {})', + options: allowImplicitOptions, + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.sort", + }, + }, + ], + }, + { + code: '["foo","bar"].toSorted(function foo() {})', + options: allowImplicitOptions, + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.toSorted", + }, + }, + ], + }, + { + code: "foo.forEach(x => x)", + options: allowImplicitCheckForEach, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + messageId: "wrapBraces", + output: "foo.forEach(x => {x})", + }, + ], + }, + ], + }, + { + code: "foo.forEach(function(x) { if (a == b) {return x;}})", + options: allowImplicitCheckForEach, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "function", + arrayMethodName: "Array.prototype.forEach", + }, + }, + ], + }, + { + code: "foo.forEach(function bar(x) { return x;})", + options: allowImplicitCheckForEach, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "function 'bar'", + arrayMethodName: "Array.prototype.forEach", + }, + }, + ], + }, - ], - invalid: [ + // // options: { checkForEach: true } + { + code: "foo.forEach(x => x)", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: "foo.forEach(x => {x})", + messageId: "wrapBraces", + }, + ], + }, + ], + }, + { + code: "foo.forEach(x => (x))", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: "foo.forEach(x => {(x)})", + messageId: "wrapBraces", + }, + ], + }, + ], + }, + { + code: "foo.forEach(val => y += val)", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: "foo.forEach(val => {y += val})", + messageId: "wrapBraces", + }, + ], + }, + ], + }, + { + code: '["foo","bar"].forEach(x => ++x)', + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: '["foo","bar"].forEach(x => {++x})', + messageId: "wrapBraces", + }, + ], + }, + ], + }, + { + code: "foo.bar().forEach(x => x === y)", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: "foo.bar().forEach(x => {x === y})", + messageId: "wrapBraces", + }, + ], + }, + ], + }, + { + code: "foo.forEach(function() {return function() { if (a == b) { return a; }}}())", + options: checkForEachOptions, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "function", + arrayMethodName: "Array.prototype.forEach", + }, + }, + ], + }, + { + code: "foo.forEach(function(x) { if (a == b) {return x;}})", + options: checkForEachOptions, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "function", + arrayMethodName: "Array.prototype.forEach", + }, + }, + ], + }, + { + code: "foo.forEach(function(x) { if (a == b) {return undefined;}})", + options: checkForEachOptions, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "function", + arrayMethodName: "Array.prototype.forEach", + }, + }, + ], + }, + { + code: "foo.forEach(function bar(x) { return x;})", + options: checkForEachOptions, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "function 'bar'", + arrayMethodName: "Array.prototype.forEach", + }, + }, + ], + }, + { + code: "foo.bar().forEach(function bar(x) { return x;})", + options: checkForEachOptions, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "function 'bar'", + arrayMethodName: "Array.prototype.forEach", + }, + }, + ], + }, + { + code: '["foo","bar"].forEach(function bar(x) { return x;})', + options: checkForEachOptions, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "function 'bar'", + arrayMethodName: "Array.prototype.forEach", + }, + }, + ], + }, + { + code: "foo.forEach((x) => { return x;})", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + }, + ], + }, + { + code: "Array.from(x, function() {})", + options: checkForEachOptions, + errors: [ + { + messageId: "expectedInside", + data: { name: "function", arrayMethodName: "Array.from" }, + }, + ], + }, + { + code: "foo.every(function() {})", + options: checkForEachOptions, + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.every", + }, + }, + ], + }, + { + code: "foo.filter(function foo() {})", + options: checkForEachOptions, + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.filter", + }, + }, + ], + }, + { + code: "foo.filter(function foo() { return; })", + options: checkForEachOptions, + errors: [ + { + messageId: "expectedReturnValue", + data: { + name: "function 'foo'", + arrayMethodName: "Array.prototype.filter", + }, + }, + ], + }, + { + code: "foo.every(cb || function() {})", + options: checkForEachOptions, + errors: [ + "Array.prototype.every() expects a return value from function.", + ], + }, + { + code: "foo.forEach((x) => void x)", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + messageId: "wrapBraces", + output: "foo.forEach((x) => {void x})", + }, + ], + }, + ], + }, + { + code: "foo.forEach((x) => void bar(x))", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + messageId: "wrapBraces", + output: "foo.forEach((x) => {void bar(x)})", + }, + ], + }, + ], + }, + { + code: "foo.forEach((x) => { return void bar(x); })", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + }, + ], + }, + { + code: "foo.forEach((x) => { if (a === b) { return void a; } bar(x) })", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + }, + ], + }, - { code: "Array.from(x, function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.from" } }] }, - { code: "Array.from(x, function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.from" } }] }, - { code: "Int32Array.from(x, function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.from" } }] }, - { code: "Int32Array.from(x, function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.from" } }] }, - { code: "foo.every(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.every(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.filter(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.filter" } }] }, - { code: "foo.filter(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] }, - { code: "foo.find(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.find" } }] }, - { code: "foo.find(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.find" } }] }, - { code: "foo.findLast(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.findLast" } }] }, - { code: "foo.findLast(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.findLast" } }] }, - { code: "foo.findIndex(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.findIndex" } }] }, - { code: "foo.findIndex(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.findIndex" } }] }, - { code: "foo.findLastIndex(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.findLastIndex" } }] }, - { code: "foo.findLastIndex(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.findLastIndex" } }] }, - { code: "foo.flatMap(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.flatMap" } }] }, - { code: "foo.flatMap(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.flatMap" } }] }, - { code: "foo.map(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.map" } }] }, - { code: "foo.map(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.map" } }] }, - { code: "foo.reduce(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.reduce" } }] }, - { code: "foo.reduce(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.reduce" } }] }, - { code: "foo.reduceRight(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.reduceRight" } }] }, - { code: "foo.reduceRight(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.reduceRight" } }] }, - { code: "foo.some(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.some" } }] }, - { code: "foo.some(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.some" } }] }, - { code: "foo.sort(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.sort" } }] }, - { code: "foo.sort(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.sort" } }] }, - { code: "foo.toSorted(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.toSorted" } }] }, - { code: "foo.toSorted(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.toSorted" } }] }, - { code: "foo.bar.baz.every(function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.bar.baz.every(function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo[\"every\"](function() {})", errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo[\"every\"](function foo() {})", errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo[`every`](function() {})", languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo[`every`](function foo() {})", languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.every(() => {})", languageOptions: { ecmaVersion: 6 }, errors: [{ message: "Array.prototype.every() expects a return value from arrow function.", column: 14 }] }, - { code: "foo.every(function() { if (a) return true; })", errors: [{ message: "Array.prototype.every() expects a value to be returned at the end of function.", column: 11 }] }, - { code: "foo.every(function cb() { if (a) return true; })", errors: [{ message: "Array.prototype.every() expects a value to be returned at the end of function 'cb'.", column: 11 }] }, - { code: "foo.every(function() { switch (a) { case 0: break; default: return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.every(function foo() { switch (a) { case 0: break; default: return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.every(function() { try { bar(); } catch (err) { return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.every(function foo() { try { bar(); } catch (err) { return true; } })", errors: [{ messageId: "expectedAtEnd", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.every(function() { return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.every(function foo() { return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.every(function() { if (a) return; })", errors: ["Array.prototype.every() expects a value to be returned at the end of function.", { messageId: "expectedReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.every(function foo() { if (a) return; })", errors: ["Array.prototype.every() expects a value to be returned at the end of function 'foo'.", { messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.every(function() { if (a) return; else return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.every" } }, { messageId: "expectedReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.every(function foo() { if (a) return; else return; })", errors: [{ messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }, { messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.every(cb || function() {})", errors: ["Array.prototype.every() expects a return value from function."] }, - { code: "foo.every(cb || function foo() {})", errors: ["Array.prototype.every() expects a return value from function 'foo'."] }, - { code: "foo.every(a ? function() {} : function() {})", errors: ["Array.prototype.every() expects a return value from function.", "Array.prototype.every() expects a return value from function."] }, - { code: "foo.every(a ? function foo() {} : function bar() {})", errors: ["Array.prototype.every() expects a return value from function 'foo'.", "Array.prototype.every() expects a return value from function 'bar'."] }, - { code: "foo.every(function(){ return function() {}; }())", errors: [{ message: "Array.prototype.every() expects a return value from function.", column: 30 }] }, - { code: "foo.every(function(){ return function foo() {}; }())", errors: [{ message: "Array.prototype.every() expects a return value from function 'foo'.", column: 30 }] }, - { code: "foo.every(() => {})", options: [{ allowImplicit: false }], languageOptions: { ecmaVersion: 6 }, errors: [{ message: "Array.prototype.every() expects a return value from arrow function." }] }, - { code: "foo.every(() => {})", options: [{ allowImplicit: true }], languageOptions: { ecmaVersion: 6 }, errors: [{ message: "Array.prototype.every() expects a return value from arrow function." }] }, + // options: { checkForEach: true, allowVoid: true } - // options: { allowImplicit: true } - { code: "Array.from(x, function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.from" } }] }, - { code: "foo.every(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.filter(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] }, - { code: "foo.find(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.find" } }] }, - { code: "foo.map(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.map" } }] }, - { code: "foo.reduce(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.reduce" } }] }, - { code: "foo.reduceRight(function() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.reduceRight" } }] }, - { code: "foo.bar.baz.every(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.every(cb || function() {})", options: allowImplicitOptions, errors: ["Array.prototype.every() expects a return value from function."] }, - { code: "[\"foo\",\"bar\"].sort(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.sort" } }] }, - { code: "[\"foo\",\"bar\"].toSorted(function foo() {})", options: allowImplicitOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.toSorted" } }] }, - { code: "foo.forEach(x => x)", options: allowImplicitCheckForEach, languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "foo.forEach(function(x) { if (a == b) {return x;}})", options: allowImplicitCheckForEach, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "foo.forEach(function bar(x) { return x;})", options: allowImplicitCheckForEach, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] }, + { + code: "foo.forEach(x => x)", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: "foo.forEach(x => {x})", + messageId: "wrapBraces", + }, + { + output: "foo.forEach(x => void x)", + messageId: "prependVoid", + }, + ], + }, + ], + }, + { + code: "foo.forEach(x => !x)", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: "foo.forEach(x => {!x})", + messageId: "wrapBraces", + }, + { + output: "foo.forEach(x => void !x)", + messageId: "prependVoid", + }, + ], + }, + ], + }, + { + code: "foo.forEach(x => (x))", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: "foo.forEach(x => {(x)})", + messageId: "wrapBraces", + }, + { + output: "foo.forEach(x => void (x))", + messageId: "prependVoid", + }, + ], + }, + ], + }, + { + code: "foo.forEach((x) => { return x; })", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: "foo.forEach((x) => { return void x; })", + messageId: "prependVoid", + }, + ], + }, + ], + }, + { + code: "foo.forEach((x) => { return !x; })", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: "foo.forEach((x) => { return void !x; })", + messageId: "prependVoid", + }, + ], + }, + ], + }, + { + code: "foo.forEach((x) => { return(x); })", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: "foo.forEach((x) => { return void (x); })", + messageId: "prependVoid", + }, + ], + }, + ], + }, + { + code: "foo.forEach((x) => { return (x + 1); })", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: "foo.forEach((x) => { return void (x + 1); })", + messageId: "prependVoid", + }, + ], + }, + ], + }, + { + code: "foo.forEach((x) => { if (a === b) { return x; } })", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: "foo.forEach((x) => { if (a === b) { return void x; } })", + messageId: "prependVoid", + }, + ], + }, + ], + }, + { + code: "foo.forEach((x) => { if (a === b) { return !x; } })", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: "foo.forEach((x) => { if (a === b) { return void !x; } })", + messageId: "prependVoid", + }, + ], + }, + ], + }, + { + code: "foo.forEach((x) => { if (a === b) { return (x + a); } })", + options: checkForEachAllowVoid, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + suggestions: [ + { + output: "foo.forEach((x) => { if (a === b) { return void (x + a); } })", + messageId: "prependVoid", + }, + ], + }, + ], + }, - // // options: { checkForEach: true } - { - code: "foo.forEach(x => x)", - options: checkForEachOptions, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "foo.forEach(x => {x})", messageId: "wrapBraces" } - ] - }] - }, - { - code: "foo.forEach(x => (x))", - options: checkForEachOptions, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "foo.forEach(x => {(x)})", messageId: "wrapBraces" } - ] - }] - }, - { - code: "foo.forEach(val => y += val)", - options: checkForEachOptions, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "foo.forEach(val => {y += val})", messageId: "wrapBraces" } - ] - }] - }, - { - code: "[\"foo\",\"bar\"].forEach(x => ++x)", - options: checkForEachOptions, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "[\"foo\",\"bar\"].forEach(x => {++x})", messageId: "wrapBraces" } - ] - }] - }, - { - code: "foo.bar().forEach(x => x === y)", - options: checkForEachOptions, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "foo.bar().forEach(x => {x === y})", messageId: "wrapBraces" } - ] - }] - }, - { code: "foo.forEach(function() {return function() { if (a == b) { return a; }}}())", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "foo.forEach(function(x) { if (a == b) {return x;}})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "foo.forEach(function(x) { if (a == b) {return undefined;}})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "foo.forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "foo.forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: ["Array.prototype.forEach() expects no useless return value from function 'bar'."] }, - { code: "foo.bar().forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "[\"foo\",\"bar\"].forEach(function bar(x) { return x;})", options: checkForEachOptions, errors: [{ messageId: "expectedNoReturnValue", data: { name: "function 'bar'", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "foo.forEach((x) => { return x;})", options: checkForEachOptions, languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "Array.from(x, function() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.from" } }] }, - { code: "foo.every(function() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function", arrayMethodName: "Array.prototype.every" } }] }, - { code: "foo.filter(function foo() {})", options: checkForEachOptions, errors: [{ messageId: "expectedInside", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] }, - { code: "foo.filter(function foo() { return; })", options: checkForEachOptions, errors: [{ messageId: "expectedReturnValue", data: { name: "function 'foo'", arrayMethodName: "Array.prototype.filter" } }] }, - { code: "foo.every(cb || function() {})", options: checkForEachOptions, errors: ["Array.prototype.every() expects a return value from function."] }, - { code: "foo.forEach((x) => void x)", options: checkForEachOptions, languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "foo.forEach((x) => void bar(x))", options: checkForEachOptions, languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "foo.forEach((x) => { return void bar(x); })", options: checkForEachOptions, languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, - { code: "foo.forEach((x) => { if (a === b) { return void a; } bar(x) })", options: checkForEachOptions, languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expectedNoReturnValue", data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" } }] }, + // full location tests + { + code: "foo.filter(bar => { baz(); } )", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedInside", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.filter", + }, + type: "ArrowFunctionExpression", + line: 1, + column: 16, + endLine: 1, + endColumn: 18, + }, + ], + }, + { + code: "foo.filter(\n() => {} )", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedInside", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.filter", + }, + type: "ArrowFunctionExpression", + line: 2, + column: 4, + endLine: 2, + endColumn: 6, + }, + ], + }, + { + code: "foo.filter(bar || ((baz) => {}) )", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedInside", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.filter", + }, + type: "ArrowFunctionExpression", + line: 1, + column: 26, + endLine: 1, + endColumn: 28, + }, + ], + }, + { + code: "foo.filter(bar => { return; })", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.filter", + }, + type: "ReturnStatement", + line: 1, + column: 21, + endLine: 1, + endColumn: 28, + }, + ], + }, + { + code: "Array.from(foo, bar => { bar })", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedInside", + data: { + name: "arrow function", + arrayMethodName: "Array.from", + }, + type: "ArrowFunctionExpression", + line: 1, + column: 21, + endLine: 1, + endColumn: 23, + }, + ], + }, + { + code: "foo.forEach(bar => bar)", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + type: "ArrowFunctionExpression", + line: 1, + column: 17, + endLine: 1, + endColumn: 19, + suggestions: [ + { + messageId: "wrapBraces", + output: "foo.forEach(bar => {bar})", + }, + ], + }, + ], + }, + { + code: "foo.forEach((function () { return (bar) => bar; })())", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + type: "ArrowFunctionExpression", + line: 1, + column: 41, + endLine: 1, + endColumn: 43, + suggestions: [ + { + messageId: "wrapBraces", + output: "foo.forEach((function () { return (bar) => {bar}; })())", + }, + ], + }, + ], + }, + { + code: "foo.forEach((() => {\n return bar => bar; })())", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + type: "ArrowFunctionExpression", + line: 2, + column: 13, + endLine: 2, + endColumn: 15, + suggestions: [ + { + messageId: "wrapBraces", + output: "foo.forEach((() => {\n return bar => {bar}; })())", + }, + ], + }, + ], + }, + { + code: "foo.forEach((bar) => { if (bar) { return; } else { return bar ; } })", + options: checkForEachOptions, + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.forEach", + }, + type: "ReturnStatement", + line: 1, + column: 52, + endLine: 1, + endColumn: 64, + }, + ], + }, + { + code: "foo.filter(function(){})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.filter", + }, + type: "FunctionExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 20, + }, + ], + }, + { + code: "foo.filter(function (){})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.filter", + }, + type: "FunctionExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 21, + }, + ], + }, + { + code: "foo.filter(function\n(){})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function", + arrayMethodName: "Array.prototype.filter", + }, + type: "FunctionExpression", + line: 1, + column: 12, + endLine: 2, + endColumn: 1, + }, + ], + }, + { + code: "foo.filter(function bar(){})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'bar'", + arrayMethodName: "Array.prototype.filter", + }, + type: "FunctionExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 24, + }, + ], + }, + { + code: "foo.filter(function bar (){})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'bar'", + arrayMethodName: "Array.prototype.filter", + }, + type: "FunctionExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 26, + }, + ], + }, + { + code: "foo.filter(function\n bar() {})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'bar'", + arrayMethodName: "Array.prototype.filter", + }, + type: "FunctionExpression", + line: 1, + column: 12, + endLine: 2, + endColumn: 5, + }, + ], + }, + { + code: "Array.from(foo, function bar(){})", + errors: [ + { + messageId: "expectedInside", + data: { + name: "function 'bar'", + arrayMethodName: "Array.from", + }, + type: "FunctionExpression", + line: 1, + column: 17, + endLine: 1, + endColumn: 29, + }, + ], + }, + { + code: "Array.from(foo, bar ? function (){} : baz)", + errors: [ + { + messageId: "expectedInside", + data: { name: "function", arrayMethodName: "Array.from" }, + type: "FunctionExpression", + line: 1, + column: 23, + endLine: 1, + endColumn: 32, + }, + ], + }, + { + code: "foo.filter(function bar() { return \n })", + errors: [ + { + messageId: "expectedReturnValue", + data: { + name: "function 'bar'", + arrayMethodName: "Array.prototype.filter", + }, + type: "ReturnStatement", + line: 1, + column: 29, + endLine: 1, + endColumn: 35, + }, + ], + }, + { + code: "foo.forEach(function () { \nif (baz) return bar\nelse return\n })", + options: checkForEachOptions, + errors: [ + { + messageId: "expectedNoReturnValue", + data: { + name: "function", + arrayMethodName: "Array.prototype.forEach", + }, + type: "ReturnStatement", + line: 2, + column: 10, + endLine: 2, + endColumn: 20, + }, + ], + }, - // options: { checkForEach: true, allowVoid: true } - - { - code: "foo.forEach(x => x)", - options: checkForEachAllowVoid, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "foo.forEach(x => {x})", messageId: "wrapBraces" }, - { output: "foo.forEach(x => void x)", messageId: "prependVoid" } - ] - }] - }, - { - code: "foo.forEach(x => !x)", - options: checkForEachAllowVoid, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "foo.forEach(x => {!x})", messageId: "wrapBraces" }, - { output: "foo.forEach(x => void !x)", messageId: "prependVoid" } - ] - }] - }, - { - code: "foo.forEach(x => (x))", - options: checkForEachAllowVoid, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "foo.forEach(x => {(x)})", messageId: "wrapBraces" }, - { output: "foo.forEach(x => void (x))", messageId: "prependVoid" } - ] - }] - }, - { - code: "foo.forEach((x) => { return x; })", - options: checkForEachAllowVoid, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "foo.forEach((x) => { return void x; })", messageId: "prependVoid" } - ] - }] - }, - { - code: "foo.forEach((x) => { return !x; })", - options: checkForEachAllowVoid, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "foo.forEach((x) => { return void !x; })", messageId: "prependVoid" } - ] - }] - }, - { - code: "foo.forEach((x) => { return(x); })", - options: checkForEachAllowVoid, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "foo.forEach((x) => { return void (x); })", messageId: "prependVoid" } - ] - }] - }, - { - code: "foo.forEach((x) => { return (x + 1); })", - options: checkForEachAllowVoid, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "foo.forEach((x) => { return void (x + 1); })", messageId: "prependVoid" } - ] - }] - }, - { - code: "foo.forEach((x) => { if (a === b) { return x; } })", - options: checkForEachAllowVoid, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "foo.forEach((x) => { if (a === b) { return void x; } })", messageId: "prependVoid" } - ] - }] - }, - { - code: "foo.forEach((x) => { if (a === b) { return !x; } })", - options: checkForEachAllowVoid, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "foo.forEach((x) => { if (a === b) { return void !x; } })", messageId: "prependVoid" } - ] - }] - }, - { - code: "foo.forEach((x) => { if (a === b) { return (x + a); } })", - options: checkForEachAllowVoid, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - suggestions: [ - { output: "foo.forEach((x) => { if (a === b) { return void (x + a); } })", messageId: "prependVoid" } - ] - }] - }, - - // full location tests - { - code: "foo.filter(bar => { baz(); } )", - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedInside", - data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" }, - type: "ArrowFunctionExpression", - line: 1, - column: 16, - endLine: 1, - endColumn: 18 - }] - }, - { - code: "foo.filter(\n() => {} )", - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedInside", - data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" }, - type: "ArrowFunctionExpression", - line: 2, - column: 4, - endLine: 2, - endColumn: 6 - }] - }, - { - code: "foo.filter(bar || ((baz) => {}) )", - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedInside", - data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" }, - type: "ArrowFunctionExpression", - line: 1, - column: 26, - endLine: 1, - endColumn: 28 - }] - }, - { - code: "foo.filter(bar => { return; })", - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" }, - type: "ReturnStatement", - line: 1, - column: 21, - endLine: 1, - endColumn: 28 - }] - }, - { - code: "Array.from(foo, bar => { bar })", - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedInside", - data: { name: "arrow function", arrayMethodName: "Array.from" }, - type: "ArrowFunctionExpression", - line: 1, - column: 21, - endLine: 1, - endColumn: 23 - }] - }, - { - code: "foo.forEach(bar => bar)", - options: checkForEachOptions, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - type: "ArrowFunctionExpression", - line: 1, - column: 17, - endLine: 1, - endColumn: 19 - }] - }, - { - code: "foo.forEach((function () { return (bar) => bar; })())", - options: checkForEachOptions, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - type: "ArrowFunctionExpression", - line: 1, - column: 41, - endLine: 1, - endColumn: 43 - }] - }, - { - code: "foo.forEach((() => {\n return bar => bar; })())", - options: checkForEachOptions, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - type: "ArrowFunctionExpression", - line: 2, - column: 13, - endLine: 2, - endColumn: 15 - }] - }, - { - code: "foo.forEach((bar) => { if (bar) { return; } else { return bar ; } })", - options: checkForEachOptions, - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "arrow function", arrayMethodName: "Array.prototype.forEach" }, - type: "ReturnStatement", - line: 1, - column: 52, - endLine: 1, - endColumn: 64 - }] - }, - { - code: "foo.filter(function(){})", - errors: [{ - messageId: "expectedInside", - data: { name: "function", arrayMethodName: "Array.prototype.filter" }, - type: "FunctionExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 20 - }] - }, - { - code: "foo.filter(function (){})", - errors: [{ - messageId: "expectedInside", - data: { name: "function", arrayMethodName: "Array.prototype.filter" }, - type: "FunctionExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 21 - }] - }, - { - code: "foo.filter(function\n(){})", - errors: [{ - messageId: "expectedInside", - data: { name: "function", arrayMethodName: "Array.prototype.filter" }, - type: "FunctionExpression", - line: 1, - column: 12, - endLine: 2, - endColumn: 1 - }] - }, - { - code: "foo.filter(function bar(){})", - errors: [{ - messageId: "expectedInside", - data: { name: "function 'bar'", arrayMethodName: "Array.prototype.filter" }, - type: "FunctionExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 24 - }] - }, - { - code: "foo.filter(function bar (){})", - errors: [{ - messageId: "expectedInside", - data: { name: "function 'bar'", arrayMethodName: "Array.prototype.filter" }, - type: "FunctionExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 26 - }] - }, - { - code: "foo.filter(function\n bar() {})", - errors: [{ - messageId: "expectedInside", - data: { name: "function 'bar'", arrayMethodName: "Array.prototype.filter" }, - type: "FunctionExpression", - line: 1, - column: 12, - endLine: 2, - endColumn: 5 - }] - }, - { - code: "Array.from(foo, function bar(){})", - errors: [{ - messageId: "expectedInside", - data: { name: "function 'bar'", arrayMethodName: "Array.from" }, - type: "FunctionExpression", - line: 1, - column: 17, - endLine: 1, - endColumn: 29 - }] - }, - { - code: "Array.from(foo, bar ? function (){} : baz)", - errors: [{ - messageId: "expectedInside", - data: { name: "function", arrayMethodName: "Array.from" }, - type: "FunctionExpression", - line: 1, - column: 23, - endLine: 1, - endColumn: 32 - }] - }, - { - code: "foo.filter(function bar() { return \n })", - errors: [{ - messageId: "expectedReturnValue", - data: { name: "function 'bar'", arrayMethodName: "Array.prototype.filter" }, - type: "ReturnStatement", - line: 1, - column: 29, - endLine: 1, - endColumn: 35 - }] - }, - { - code: "foo.forEach(function () { \nif (baz) return bar\nelse return\n })", - options: checkForEachOptions, - errors: [{ - messageId: "expectedNoReturnValue", - data: { name: "function", arrayMethodName: "Array.prototype.forEach" }, - type: "ReturnStatement", - line: 2, - column: 10, - endLine: 2, - endColumn: 20 - }] - }, - - // Optional chaining - { - code: "foo?.filter(() => { console.log('hello') })", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" } }] - }, - { - code: "(foo?.filter)(() => { console.log('hello') })", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" } }] - }, - { - code: "Array?.from([], () => { console.log('hello') })", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.from" } }] - }, - { - code: "(Array?.from)([], () => { console.log('hello') })", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.from" } }] - }, - { - code: "foo?.filter((function() { return () => { console.log('hello') } })?.())", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "expectedInside", data: { name: "arrow function", arrayMethodName: "Array.prototype.filter" } }] - } - ] + // Optional chaining + { + code: "foo?.filter(() => { console.log('hello') })", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "expectedInside", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.filter", + }, + }, + ], + }, + { + code: "(foo?.filter)(() => { console.log('hello') })", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "expectedInside", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.filter", + }, + }, + ], + }, + { + code: "Array?.from([], () => { console.log('hello') })", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "expectedInside", + data: { + name: "arrow function", + arrayMethodName: "Array.from", + }, + }, + ], + }, + { + code: "(Array?.from)([], () => { console.log('hello') })", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "expectedInside", + data: { + name: "arrow function", + arrayMethodName: "Array.from", + }, + }, + ], + }, + { + code: "foo?.filter((function() { return () => { console.log('hello') } })?.())", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "expectedInside", + data: { + name: "arrow function", + arrayMethodName: "Array.prototype.filter", + }, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/array-element-newline.js b/tests/lib/rules/array-element-newline.js index a66ac2524991..f807e17ab039 100644 --- a/tests/lib/rules/array-element-newline.js +++ b/tests/lib/rules/array-element-newline.js @@ -10,8 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/array-element-newline"); -const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); - +const RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -20,954 +19,1045 @@ const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); const ruleTester = new RuleTester(); ruleTester.run("array-element-newline", rule, { + valid: [ + /* + * ArrayExpression + * "always" + */ + "var foo = [];", + "var foo = [1];", + "var foo = [1,\n2];", + "var foo = [1, // any comment\n2];", + "var foo = [// any comment \n1,\n2];", + "var foo = [1,\n2 // any comment\n];", + "var foo = [1,\n2,\n3];", + "var foo = [1\n, (2\n, 3)];", + "var foo = [1,\n( 2 ),\n3];", + "var foo = [1,\n((((2)))),\n3];", + "var foo = [1,\n(\n2\n),\n3];", + "var foo = [1,\n(2),\n3];", + "var foo = [1,\n(2)\n, 3];", + "var foo = [1\n, 2\n, 3];", + "var foo = [1,\n2,\n,\n3];", + "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\nosomething();\n}\n];", + "var foo = [1,\n[2,\n3],\n4]", + "var foo = [[],\n[\n[]]]", - valid: [ - - /* - * ArrayExpression - * "always" - */ - "var foo = [];", - "var foo = [1];", - "var foo = [1,\n2];", - "var foo = [1, // any comment\n2];", - "var foo = [// any comment \n1,\n2];", - "var foo = [1,\n2 // any comment\n];", - "var foo = [1,\n2,\n3];", - "var foo = [1\n, (2\n, 3)];", - "var foo = [1,\n( 2 ),\n3];", - "var foo = [1,\n((((2)))),\n3];", - "var foo = [1,\n(\n2\n),\n3];", - "var foo = [1,\n(2),\n3];", - "var foo = [1,\n(2)\n, 3];", - "var foo = [1\n, 2\n, 3];", - "var foo = [1,\n2,\n,\n3];", - "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\nosomething();\n}\n];", - "var foo = [1,\n[2,\n3],\n4]", - "var foo = [[],\n[\n[]]]", - - { code: "var foo = [];", options: ["always"] }, - { code: "var foo = [1];", options: ["always"] }, - { code: "var foo = [1,\n2];", options: ["always"] }, - { code: "var foo = [1,\n(2)];", options: ["always"] }, - { code: "var foo = [1\n, (2)];", options: ["always"] }, - { code: "var foo = [1, // any comment\n2];", options: ["always"] }, - { code: "var foo = [// any comment \n1,\n2];", options: ["always"] }, - { code: "var foo = [1,\n2 // any comment\n];", options: ["always"] }, - { code: "var foo = [1,\n2,\n3];", options: ["always"] }, - { code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", options: ["always"] }, - { code: "var foo = [\n[1,\n2],\n3,\n[\n4]]", options: ["always"] }, - - // "never" - { code: "var foo = [];", options: ["never"] }, - { code: "var foo = [1];", options: ["never"] }, - { code: "var foo = [1, 2];", options: ["never"] }, - { code: "var foo = [1, /* any comment */ 2];", options: ["never"] }, - { code: "var foo = [/* any comment */ 1, 2];", options: ["never"] }, - { code: "var foo = /* any comment */ [1, 2];", options: ["never"] }, - { code: "var foo = [1, 2, 3];", options: ["never"] }, - { code: "var foo = [1, (\n2\n), 3];", options: ["never"] }, - { code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", options: ["never"] }, - { code: "var foo = [\n[1,2],3,[4]\n]", options: ["never"] }, - { code: "var foo = [[1,2\n],3,[4\n]\n]", options: ["never"] }, + { code: "var foo = [];", options: ["always"] }, + { code: "var foo = [1];", options: ["always"] }, + { code: "var foo = [1,\n2];", options: ["always"] }, + { code: "var foo = [1,\n(2)];", options: ["always"] }, + { code: "var foo = [1\n, (2)];", options: ["always"] }, + { code: "var foo = [1, // any comment\n2];", options: ["always"] }, + { code: "var foo = [// any comment \n1,\n2];", options: ["always"] }, + { code: "var foo = [1,\n2 // any comment\n];", options: ["always"] }, + { code: "var foo = [1,\n2,\n3];", options: ["always"] }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", + options: ["always"], + }, + { code: "var foo = [\n[1,\n2],\n3,\n[\n4]]", options: ["always"] }, - // "consistent" - { code: "var foo = [];", options: ["consistent"] }, - { code: "var foo = [1];", options: ["consistent"] }, - { code: "var foo = [1, 2];", options: ["consistent"] }, - { code: "var foo = [1,\n2];", options: ["consistent"] }, - { code: "var foo = [1, 2, 3];", options: ["consistent"] }, - { code: "var foo = [1,\n2,\n3];", options: ["consistent"] }, - { code: "var foo = [1,\n2,\n,\n3];", options: ["consistent"] }, - { code: "var foo = [1, // any comment\n2];", options: ["consistent"] }, - { code: "var foo = [/* any comment */ 1, 2];", options: ["consistent"] }, - { code: "var foo = [1, (\n2\n), 3];", options: ["consistent"] }, - { code: "var foo = [1,\n(2)\n, 3];", options: ["consistent"] }, - { code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", options: ["consistent"] }, - { code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", options: ["consistent"] }, - { code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}];", options: ["consistent"] }, - { code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}, function bar() {\ndosomething();\n}];", options: ["consistent"] }, - { code: "var foo = [1,\n[\n2,3,\n]\n];", options: ["consistent"] }, - { code: "var foo = [\n1,\n[2\n,3\n,]\n];", options: ["consistent"] }, - { code: "var foo = [\n1,[2,\n3]];", options: ["consistent"] }, + // "never" + { code: "var foo = [];", options: ["never"] }, + { code: "var foo = [1];", options: ["never"] }, + { code: "var foo = [1, 2];", options: ["never"] }, + { code: "var foo = [1, /* any comment */ 2];", options: ["never"] }, + { code: "var foo = [/* any comment */ 1, 2];", options: ["never"] }, + { code: "var foo = /* any comment */ [1, 2];", options: ["never"] }, + { code: "var foo = [1, 2, 3];", options: ["never"] }, + { code: "var foo = [1, (\n2\n), 3];", options: ["never"] }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", + options: ["never"], + }, + { code: "var foo = [\n[1,2],3,[4]\n]", options: ["never"] }, + { code: "var foo = [[1,2\n],3,[4\n]\n]", options: ["never"] }, - // { multiline: true } - { code: "var foo = [];", options: [{ multiline: true }] }, - { code: "var foo = [1];", options: [{ multiline: true }] }, - { code: "var foo = [1, 2];", options: [{ multiline: true }] }, - { code: "var foo = [1, 2, 3];", options: [{ multiline: true }] }, - { code: "var f = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", options: [{ multiline: true }] }, - { code: "var foo = [\n1,\n2,\n3,\n[\n]\n];", options: [{ multiline: true }] }, + // "consistent" + { code: "var foo = [];", options: ["consistent"] }, + { code: "var foo = [1];", options: ["consistent"] }, + { code: "var foo = [1, 2];", options: ["consistent"] }, + { code: "var foo = [1,\n2];", options: ["consistent"] }, + { code: "var foo = [1, 2, 3];", options: ["consistent"] }, + { code: "var foo = [1,\n2,\n3];", options: ["consistent"] }, + { code: "var foo = [1,\n2,\n,\n3];", options: ["consistent"] }, + { code: "var foo = [1, // any comment\n2];", options: ["consistent"] }, + { + code: "var foo = [/* any comment */ 1, 2];", + options: ["consistent"], + }, + { code: "var foo = [1, (\n2\n), 3];", options: ["consistent"] }, + { code: "var foo = [1,\n(2)\n, 3];", options: ["consistent"] }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", + options: ["consistent"], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", + options: ["consistent"], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}];", + options: ["consistent"], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}, function bar() {\ndosomething();\n}];", + options: ["consistent"], + }, + { code: "var foo = [1,\n[\n2,3,\n]\n];", options: ["consistent"] }, + { code: "var foo = [\n1,\n[2\n,3\n,]\n];", options: ["consistent"] }, + { code: "var foo = [\n1,[2,\n3]];", options: ["consistent"] }, - // { minItems: null } - { code: "var foo = [];", options: [{ minItems: null }] }, - { code: "var foo = [1];", options: [{ minItems: null }] }, - { code: "var foo = [1, 2];", options: [{ minItems: null }] }, - { code: "var foo = [1, 2, 3];", options: [{ minItems: null }] }, - { code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", options: [{ minItems: null }] }, - { code: "var foo = [1, 2, 3, [[],1,[[]]]];", options: [{ minItems: null }] }, + // { multiline: true } + { code: "var foo = [];", options: [{ multiline: true }] }, + { code: "var foo = [1];", options: [{ multiline: true }] }, + { code: "var foo = [1, 2];", options: [{ multiline: true }] }, + { code: "var foo = [1, 2, 3];", options: [{ multiline: true }] }, + { + code: "var f = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", + options: [{ multiline: true }], + }, + { + code: "var foo = [\n1,\n2,\n3,\n[\n]\n];", + options: [{ multiline: true }], + }, - // { minItems: 0 } - { code: "var foo = [];", options: [{ minItems: 0 }] }, - { code: "var foo = [1];", options: [{ minItems: 0 }] }, - { code: "var foo = [1,\n2];", options: [{ minItems: 0 }] }, - { code: "var foo = [1,\n2,\n3];", options: [{ minItems: 0 }] }, - { code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", options: [{ minItems: 0 }] }, - { code: "var foo = [\n1, \n2, \n3,\n[\n[],\n[]],\n[]];", options: [{ minItems: 0 }] }, + // { minItems: null } + { code: "var foo = [];", options: [{ minItems: null }] }, + { code: "var foo = [1];", options: [{ minItems: null }] }, + { code: "var foo = [1, 2];", options: [{ minItems: null }] }, + { code: "var foo = [1, 2, 3];", options: [{ minItems: null }] }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", + options: [{ minItems: null }], + }, + { + code: "var foo = [1, 2, 3, [[],1,[[]]]];", + options: [{ minItems: null }], + }, - // { minItems: 3 } - { code: "var foo = [];", options: [{ minItems: 3 }] }, - { code: "var foo = [1];", options: [{ minItems: 3 }] }, - { code: "var foo = [1, 2];", options: [{ minItems: 3 }] }, - { code: "var foo = [1,\n2,\n3];", options: [{ minItems: 3 }] }, - { code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", options: [{ minItems: 3 }] }, - { code: "var foo = [[1,2],[[\n1,\n2,\n3]]];", options: [{ minItems: 3 }] }, + // { minItems: 0 } + { code: "var foo = [];", options: [{ minItems: 0 }] }, + { code: "var foo = [1];", options: [{ minItems: 0 }] }, + { code: "var foo = [1,\n2];", options: [{ minItems: 0 }] }, + { code: "var foo = [1,\n2,\n3];", options: [{ minItems: 0 }] }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", + options: [{ minItems: 0 }], + }, + { + code: "var foo = [\n1, \n2, \n3,\n[\n[],\n[]],\n[]];", + options: [{ minItems: 0 }], + }, - // { multiline: true, minItems: 3 } - { code: "var foo = [];", options: [{ multiline: true, minItems: 3 }] }, - { code: "var foo = [1];", options: [{ multiline: true, minItems: 3 }] }, - { code: "var foo = [1, 2];", options: [{ multiline: true, minItems: 3 }] }, - { code: "var foo = [1, // any comment\n2,\n, 3];", options: [{ multiline: true, minItems: 3 }] }, - { code: "var foo = [1,\n2,\n// any comment\n, 3];", options: [{ multiline: true, minItems: 3 }] }, - { code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", options: [{ multiline: true, minItems: 3 }] }, + // { minItems: 3 } + { code: "var foo = [];", options: [{ minItems: 3 }] }, + { code: "var foo = [1];", options: [{ minItems: 3 }] }, + { code: "var foo = [1, 2];", options: [{ minItems: 3 }] }, + { code: "var foo = [1,\n2,\n3];", options: [{ minItems: 3 }] }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", + options: [{ minItems: 3 }], + }, + { + code: "var foo = [[1,2],[[\n1,\n2,\n3]]];", + options: [{ minItems: 3 }], + }, - /* - * ArrayPattern - * "always" - */ - { code: "var [] = foo;", languageOptions: { ecmaVersion: 6 } }, - { code: "var [a] = foo;", languageOptions: { ecmaVersion: 6 } }, - { code: "var [a,\nb] = foo;", languageOptions: { ecmaVersion: 6 } }, - { code: "var [a, // any comment\nb] = foo;", languageOptions: { ecmaVersion: 6 } }, - { code: "var [// any comment \na,\nb] = foo;", languageOptions: { ecmaVersion: 6 } }, - { code: "var [a,\nb // any comment\n] = foo;", languageOptions: { ecmaVersion: 6 } }, - { code: "var [a,\nb,\nb] = foo;", languageOptions: { ecmaVersion: 6 } }, - { code: "var [\na,\n[\nb,\nc]] = foo;", languageOptions: { ecmaVersion: 6 } }, + // { multiline: true, minItems: 3 } + { code: "var foo = [];", options: [{ multiline: true, minItems: 3 }] }, + { code: "var foo = [1];", options: [{ multiline: true, minItems: 3 }] }, + { + code: "var foo = [1, 2];", + options: [{ multiline: true, minItems: 3 }], + }, + { + code: "var foo = [1, // any comment\n2,\n, 3];", + options: [{ multiline: true, minItems: 3 }], + }, + { + code: "var foo = [1,\n2,\n// any comment\n, 3];", + options: [{ multiline: true, minItems: 3 }], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", + options: [{ multiline: true, minItems: 3 }], + }, - // "never" - { code: "var [a,[b,c]] = foo;", options: ["never"], languageOptions: { ecmaVersion: 6 } }, + /* + * ArrayPattern + * "always" + */ + { code: "var [] = foo;", languageOptions: { ecmaVersion: 6 } }, + { code: "var [a] = foo;", languageOptions: { ecmaVersion: 6 } }, + { code: "var [a,\nb] = foo;", languageOptions: { ecmaVersion: 6 } }, + { + code: "var [a, // any comment\nb] = foo;", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [// any comment \na,\nb] = foo;", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [a,\nb // any comment\n] = foo;", + languageOptions: { ecmaVersion: 6 }, + }, + { code: "var [a,\nb,\nb] = foo;", languageOptions: { ecmaVersion: 6 } }, + { + code: "var [\na,\n[\nb,\nc]] = foo;", + languageOptions: { ecmaVersion: 6 }, + }, - // { minItems: 3 } - { code: "var [] = foo;", options: [{ minItems: 3 }], languageOptions: { ecmaVersion: 6 } }, - { code: "var [a] = foo;", options: [{ minItems: 3 }], languageOptions: { ecmaVersion: 6 } }, - { code: "var [a, b] = foo;", options: [{ minItems: 3 }], languageOptions: { ecmaVersion: 6 } }, - { code: "var [a,\nb,\nc] = foo;", options: [{ minItems: 3 }], languageOptions: { ecmaVersion: 6 } }, + // "never" + { + code: "var [a,[b,c]] = foo;", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, - /* - * ArrayExpression & ArrayPattern - * { ArrayExpression: "always", ArrayPattern: "never" } - */ - { code: "var [a, b] = [1,\n2]", options: [{ ArrayExpression: "always", ArrayPattern: "never" }], languageOptions: { ecmaVersion: 6 } }], + // { minItems: 3 } + { + code: "var [] = foo;", + options: [{ minItems: 3 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [a] = foo;", + options: [{ minItems: 3 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [a, b] = foo;", + options: [{ minItems: 3 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [a,\nb,\nc] = foo;", + options: [{ minItems: 3 }], + languageOptions: { ecmaVersion: 6 }, + }, - invalid: [ - { - code: "var foo = [\n1,[2,\n3]]", - output: "var foo = [\n1,\n[2,\n3]]", - errors: [ - { - line: 2, - column: 3, - messageId: "missingLineBreak", - endLine: 2, - endColumn: 3 - } - ] - }, + /* + * ArrayExpression & ArrayPattern + * { ArrayExpression: "always", ArrayPattern: "never" } + */ + { + code: "var [a, b] = [1,\n2]", + options: [{ ArrayExpression: "always", ArrayPattern: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + ], - /* - * ArrayExpression - * "always" - */ - { - code: "var foo = [1, 2];", - output: "var foo = [1,\n2];", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - } - ] - }, - { - code: "var foo = [1, 2, 3];", - output: "var foo = [1,\n2,\n3];", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 17, - endLine: 1, - endColumn: 18 - } - ] - }, - { - code: "var foo = [1,2, 3];", - output: "var foo = [1,\n2,\n3];", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14, - endLine: 1, - endColumn: 14 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 16, - endLine: 1, - endColumn: 17 - } - ] - }, - { - code: "var foo = [1, (2), 3];", - output: "var foo = [1,\n(2),\n3];", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 19, - endLine: 1, - endColumn: 20 - } - ] - }, - { - code: "var foo = [1,(\n2\n), 3];", - output: "var foo = [1,\n(\n2\n),\n3];", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14 - }, - { - messageId: "missingLineBreak", - line: 3, - column: 3 - } - ] - }, - { - code: "var foo = [1, \t (\n2\n),\n3];", - output: "var foo = [1,\n(\n2\n),\n3];", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14 - } - ] - }, - { - code: "var foo = [1, ((((2)))), 3];", - output: "var foo = [1,\n((((2)))),\n3];", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 25, - endLine: 1, - endColumn: 26 - } - ] - }, - { - code: "var foo = [1,/* any comment */(2), 3];", - output: "var foo = [1,/* any comment */\n(2),\n3];", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 31, - endLine: 1, - endColumn: 31 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 35, - endLine: 1, - endColumn: 36 - } - ] - }, - { - code: "var foo = [1,( 2), 3];", - output: "var foo = [1,\n( 2),\n3];", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14, - endLine: 1, - endColumn: 14 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 20, - endLine: 1, - endColumn: 21 - } - ] - }, - { - code: "var foo = [1, [2], 3];", - output: "var foo = [1,\n[2],\n3];", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 19, - endLine: 1, - endColumn: 20 - } - ] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", - output: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 4, - column: 3 - } - ] - }, - { - code: "var foo = [\n(function foo() {\ndosomething();\n}), function bar() {\ndosomething();\n}\n];", - output: "var foo = [\n(function foo() {\ndosomething();\n}),\nfunction bar() {\ndosomething();\n}\n];", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 4, - column: 4 - } - ] - }, - { - code: "var foo = [\n[1,\n2],\n3,[\n4]]", - output: "var foo = [\n[1,\n2],\n3,\n[\n4]]", - options: ["always"], - errors: [ - { - line: 4, - column: 3, - messageId: "missingLineBreak" - } - ] - }, + invalid: [ + { + code: "var foo = [\n1,[2,\n3]]", + output: "var foo = [\n1,\n[2,\n3]]", + errors: [ + { + line: 2, + column: 3, + messageId: "missingLineBreak", + endLine: 2, + endColumn: 3, + }, + ], + }, - // "never" - { - code: "var foo = [\n1,\n2\n];", - output: "var foo = [\n1, 2\n];", - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 2, - column: 3 - } - ] - }, - { - code: "var foo = [\n1\n, 2\n];", - output: "var foo = [\n1, 2\n];", - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 3, - column: 2 - } - ] - }, - { - code: "var foo = [\n1 // any comment\n, 2\n];", - output: null, - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 3, - column: 2 - } - ] - }, - { - code: "var foo = [\n1, // any comment\n2\n];", - output: null, - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 2, - column: 18 - } - ] - }, - { - code: "var foo = [\n1,\n2 // any comment\n];", - output: "var foo = [\n1, 2 // any comment\n];", - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 2, - column: 3 - } - ] - }, - { - code: "var foo = [\n1,\n2,\n3\n];", - output: "var foo = [\n1, 2, 3\n];", - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 2, - column: 3, - endLine: 3, - endColumn: 1 - }, - { - messageId: "unexpectedLineBreak", - line: 3, - column: 3, - endLine: 4, - endColumn: 1 - } - ] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", - output: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 4, - column: 3 - } - ] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n}, /* any comment */\nfunction bar() {\ndosomething();\n}\n];", - output: null, - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 4, - column: 21 - } - ] - }, + /* + * ArrayExpression + * "always" + */ + { + code: "var foo = [1, 2];", + output: "var foo = [1,\n2];", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "var foo = [1, 2, 3];", + output: "var foo = [1,\n2,\n3];", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 17, + endLine: 1, + endColumn: 18, + }, + ], + }, + { + code: "var foo = [1,2, 3];", + output: "var foo = [1,\n2,\n3];", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + endLine: 1, + endColumn: 14, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 16, + endLine: 1, + endColumn: 17, + }, + ], + }, + { + code: "var foo = [1, (2), 3];", + output: "var foo = [1,\n(2),\n3];", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 19, + endLine: 1, + endColumn: 20, + }, + ], + }, + { + code: "var foo = [1,(\n2\n), 3];", + output: "var foo = [1,\n(\n2\n),\n3];", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + }, + { + messageId: "missingLineBreak", + line: 3, + column: 3, + }, + ], + }, + { + code: "var foo = [1, \t (\n2\n),\n3];", + output: "var foo = [1,\n(\n2\n),\n3];", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + }, + ], + }, + { + code: "var foo = [1, ((((2)))), 3];", + output: "var foo = [1,\n((((2)))),\n3];", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 25, + endLine: 1, + endColumn: 26, + }, + ], + }, + { + code: "var foo = [1,/* any comment */(2), 3];", + output: "var foo = [1,/* any comment */\n(2),\n3];", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 31, + endLine: 1, + endColumn: 31, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 35, + endLine: 1, + endColumn: 36, + }, + ], + }, + { + code: "var foo = [1,( 2), 3];", + output: "var foo = [1,\n( 2),\n3];", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + endLine: 1, + endColumn: 14, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 20, + endLine: 1, + endColumn: 21, + }, + ], + }, + { + code: "var foo = [1, [2], 3];", + output: "var foo = [1,\n[2],\n3];", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 19, + endLine: 1, + endColumn: 20, + }, + ], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", + output: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 4, + column: 3, + }, + ], + }, + { + code: "var foo = [\n(function foo() {\ndosomething();\n}), function bar() {\ndosomething();\n}\n];", + output: "var foo = [\n(function foo() {\ndosomething();\n}),\nfunction bar() {\ndosomething();\n}\n];", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 4, + column: 4, + }, + ], + }, + { + code: "var foo = [\n[1,\n2],\n3,[\n4]]", + output: "var foo = [\n[1,\n2],\n3,\n[\n4]]", + options: ["always"], + errors: [ + { + line: 4, + column: 3, + messageId: "missingLineBreak", + }, + ], + }, - { - code: "var foo = [[1,\n2\n],3,[4\n]\n]", - output: "var foo = [[1, 2\n],3,[4\n]\n]", - options: ["never"], - errors: [ - { - line: 1, - column: 15, - messageId: "unexpectedLineBreak" - } - ] - }, + // "never" + { + code: "var foo = [\n1,\n2\n];", + output: "var foo = [\n1, 2\n];", + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 2, + column: 3, + }, + ], + }, + { + code: "var foo = [\n1\n, 2\n];", + output: "var foo = [\n1, 2\n];", + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 3, + column: 2, + }, + ], + }, + { + code: "var foo = [\n1 // any comment\n, 2\n];", + output: null, + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 3, + column: 2, + }, + ], + }, + { + code: "var foo = [\n1, // any comment\n2\n];", + output: null, + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 2, + column: 18, + }, + ], + }, + { + code: "var foo = [\n1,\n2 // any comment\n];", + output: "var foo = [\n1, 2 // any comment\n];", + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 2, + column: 3, + }, + ], + }, + { + code: "var foo = [\n1,\n2,\n3\n];", + output: "var foo = [\n1, 2, 3\n];", + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 2, + column: 3, + endLine: 3, + endColumn: 1, + }, + { + messageId: "unexpectedLineBreak", + line: 3, + column: 3, + endLine: 4, + endColumn: 1, + }, + ], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", + output: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 4, + column: 3, + }, + ], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}, /* any comment */\nfunction bar() {\ndosomething();\n}\n];", + output: null, + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 4, + column: 21, + }, + ], + }, - // "consistent" - { - code: "var foo = [1,\n2, 3];", - output: "var foo = [1,\n2,\n3];", - options: ["consistent"], - errors: [ - { - messageId: "missingLineBreak", - line: 2, - column: 3, - endLine: 2, - endColumn: 4 - } - ] - }, - { - code: "var foo = [1, 2,\n3];", - output: "var foo = [1,\n2,\n3];", - options: ["consistent"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - } - ] - }, - { - code: "var foo = [1,\n(\n2), 3];", - output: "var foo = [1,\n(\n2),\n3];", - options: ["consistent"], - errors: [ - { - messageId: "missingLineBreak", - line: 3, - column: 4, - endLine: 3, - endColumn: 5 - } - ] - }, - { - code: "var foo = [1, \t (\n2\n),\n3];", - output: "var foo = [1,\n(\n2\n),\n3];", - options: ["consistent"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14, - endLine: 1, - endColumn: 29 - } - ] - }, - { - code: "var foo = [1, /* any comment */(2),\n3];", - output: "var foo = [1, /* any comment */\n(2),\n3];", - options: ["consistent"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 32 - } - ] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n},function bar() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}];", - output: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}];", - options: ["consistent"], - errors: [ - { - messageId: "missingLineBreak", - line: 4, - column: 3 - } - ] - }, - { - code: "var foo = [\n1,[2,3,\n[]],\n[]\n];", - output: "var foo = [\n1,\n[2,\n3,\n[]],\n[]\n];", - options: ["consistent"], - errors: [ - { - line: 2, - column: 3, - messageId: "missingLineBreak" - }, - { - line: 2, - column: 6, - messageId: "missingLineBreak" - } - ] - }, + { + code: "var foo = [[1,\n2\n],3,[4\n]\n]", + output: "var foo = [[1, 2\n],3,[4\n]\n]", + options: ["never"], + errors: [ + { + line: 1, + column: 15, + messageId: "unexpectedLineBreak", + }, + ], + }, - // { multiline: true } - { - code: "var foo = [1,\n2, 3];", - output: "var foo = [1, 2, 3];", - options: [{ multiline: true }], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 14 - } - ] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", - output: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", - options: [{ multiline: true }], - errors: [ - { - messageId: "missingLineBreak", - line: 4, - column: 3 - } - ] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n}, /* any comment */ function bar() {\ndosomething();\n}\n];", - output: "var foo = [\nfunction foo() {\ndosomething();\n}, /* any comment */\nfunction bar() {\ndosomething();\n}\n];", - options: [{ multiline: true }], - errors: [ - { - messageId: "missingLineBreak", - line: 4, - column: 21 - } - ] - }, - { - code: "var foo = [\n1,2,3,\n[\n]\n];", - output: "var foo = [\n1,\n2,\n3,\n[\n]\n];", - options: [{ multiline: true }], - errors: [ - { - line: 2, - column: 3, - messageId: "missingLineBreak" - }, - { - line: 2, - column: 5, - messageId: "missingLineBreak" - } - ] - }, + // "consistent" + { + code: "var foo = [1,\n2, 3];", + output: "var foo = [1,\n2,\n3];", + options: ["consistent"], + errors: [ + { + messageId: "missingLineBreak", + line: 2, + column: 3, + endLine: 2, + endColumn: 4, + }, + ], + }, + { + code: "var foo = [1, 2,\n3];", + output: "var foo = [1,\n2,\n3];", + options: ["consistent"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "var foo = [1,\n(\n2), 3];", + output: "var foo = [1,\n(\n2),\n3];", + options: ["consistent"], + errors: [ + { + messageId: "missingLineBreak", + line: 3, + column: 4, + endLine: 3, + endColumn: 5, + }, + ], + }, + { + code: "var foo = [1, \t (\n2\n),\n3];", + output: "var foo = [1,\n(\n2\n),\n3];", + options: ["consistent"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + endLine: 1, + endColumn: 29, + }, + ], + }, + { + code: "var foo = [1, /* any comment */(2),\n3];", + output: "var foo = [1, /* any comment */\n(2),\n3];", + options: ["consistent"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 32, + }, + ], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n},function bar() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}];", + output: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}];", + options: ["consistent"], + errors: [ + { + messageId: "missingLineBreak", + line: 4, + column: 3, + }, + ], + }, + { + code: "var foo = [\n1,[2,3,\n[]],\n[]\n];", + output: "var foo = [\n1,\n[2,\n3,\n[]],\n[]\n];", + options: ["consistent"], + errors: [ + { + line: 2, + column: 3, + messageId: "missingLineBreak", + }, + { + line: 2, + column: 6, + messageId: "missingLineBreak", + }, + ], + }, - // { minItems: null } - { - code: "var foo = [1,\n2];", - output: "var foo = [1, 2];", - options: [{ minItems: null }], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 14 - } - ] - }, - { - code: "var foo = [1,\n2,\n3];", - output: "var foo = [1, 2, 3];", - options: [{ minItems: null }], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 14 - }, - { - messageId: "unexpectedLineBreak", - line: 2, - column: 3 - } - ] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", - output: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", - options: [{ minItems: null }], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 4, - column: 3 - } - ] - }, + // { multiline: true } + { + code: "var foo = [1,\n2, 3];", + output: "var foo = [1, 2, 3];", + options: [{ multiline: true }], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 14, + }, + ], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", + output: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", + options: [{ multiline: true }], + errors: [ + { + messageId: "missingLineBreak", + line: 4, + column: 3, + }, + ], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}, /* any comment */ function bar() {\ndosomething();\n}\n];", + output: "var foo = [\nfunction foo() {\ndosomething();\n}, /* any comment */\nfunction bar() {\ndosomething();\n}\n];", + options: [{ multiline: true }], + errors: [ + { + messageId: "missingLineBreak", + line: 4, + column: 21, + }, + ], + }, + { + code: "var foo = [\n1,2,3,\n[\n]\n];", + output: "var foo = [\n1,\n2,\n3,\n[\n]\n];", + options: [{ multiline: true }], + errors: [ + { + line: 2, + column: 3, + messageId: "missingLineBreak", + }, + { + line: 2, + column: 5, + messageId: "missingLineBreak", + }, + ], + }, - // { minItems: 0 } - { - code: "var foo = [1, 2];", - output: "var foo = [1,\n2];", - options: [{ minItems: 0 }], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14 - } - ] - }, - { - code: "var foo = [1, 2, 3];", - output: "var foo = [1,\n2,\n3];", - options: [{ minItems: 0 }], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 17 - } - ] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", - output: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", - options: [{ minItems: 0 }], - errors: [ - { - messageId: "missingLineBreak", - line: 4, - column: 3 - } - ] - }, + // { minItems: null } + { + code: "var foo = [1,\n2];", + output: "var foo = [1, 2];", + options: [{ minItems: null }], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 14, + }, + ], + }, + { + code: "var foo = [1,\n2,\n3];", + output: "var foo = [1, 2, 3];", + options: [{ minItems: null }], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 14, + }, + { + messageId: "unexpectedLineBreak", + line: 2, + column: 3, + }, + ], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", + output: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", + options: [{ minItems: null }], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 4, + column: 3, + }, + ], + }, - // { minItems: 3 } - { - code: "var foo = [1,\n2];", - output: "var foo = [1, 2];", - options: [{ minItems: 3 }], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 14 - } - ] - }, - { - code: "var foo = [1, 2, 3];", - output: "var foo = [1,\n2,\n3];", - options: [{ minItems: 3 }], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 17 - } - ] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", - output: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", - options: [{ minItems: 3 }], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 4, - column: 3 - } - ] - }, + // { minItems: 0 } + { + code: "var foo = [1, 2];", + output: "var foo = [1,\n2];", + options: [{ minItems: 0 }], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + }, + ], + }, + { + code: "var foo = [1, 2, 3];", + output: "var foo = [1,\n2,\n3];", + options: [{ minItems: 0 }], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 17, + }, + ], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", + output: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", + options: [{ minItems: 0 }], + errors: [ + { + messageId: "missingLineBreak", + line: 4, + column: 3, + }, + ], + }, - // { multiline: true, minItems: 3 } - { - code: "var foo = [1, 2, 3];", - output: "var foo = [1,\n2,\n3];", - options: [{ multiline: true, minItems: 3 }], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 14 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 17 - } - ] - }, - { - code: "var foo = [1,\n2];", - output: "var foo = [1, 2];", - options: [{ multiline: true, minItems: 3 }], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 14 - } - ] - }, - { - code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", - output: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", - options: [{ multiline: true, minItems: 3 }], - errors: [ - { - messageId: "missingLineBreak", - line: 4, - column: 3 - } - ] - }, + // { minItems: 3 } + { + code: "var foo = [1,\n2];", + output: "var foo = [1, 2];", + options: [{ minItems: 3 }], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 14, + }, + ], + }, + { + code: "var foo = [1, 2, 3];", + output: "var foo = [1,\n2,\n3];", + options: [{ minItems: 3 }], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 17, + }, + ], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", + output: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", + options: [{ minItems: 3 }], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 4, + column: 3, + }, + ], + }, - /* - * ArrayPattern - * "always" - */ - { - code: "var [a, b] = foo;", - output: "var [a,\nb] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 8 - } - ] - }, - { - code: "var [a, b, c] = foo;", - output: "var [a,\nb,\nc] = foo;", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 8 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 11 - } - ] - }, + // { multiline: true, minItems: 3 } + { + code: "var foo = [1, 2, 3];", + output: "var foo = [1,\n2,\n3];", + options: [{ multiline: true, minItems: 3 }], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 14, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 17, + }, + ], + }, + { + code: "var foo = [1,\n2];", + output: "var foo = [1, 2];", + options: [{ multiline: true, minItems: 3 }], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 14, + }, + ], + }, + { + code: "var foo = [\nfunction foo() {\ndosomething();\n}, function bar() {\ndosomething();\n}\n];", + output: "var foo = [\nfunction foo() {\ndosomething();\n},\nfunction bar() {\ndosomething();\n}\n];", + options: [{ multiline: true, minItems: 3 }], + errors: [ + { + messageId: "missingLineBreak", + line: 4, + column: 3, + }, + ], + }, - // { minItems: 3 } - { - code: "var [a,\nb] = foo;", - output: "var [a, b] = foo;", - options: [{ minItems: 3 }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 8 - } - ] - }, - { - code: "var [a, b, c] = foo;", - output: "var [a,\nb,\nc] = foo;", - options: [{ minItems: 3 }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 8 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 11 - } - ] - }, + /* + * ArrayPattern + * "always" + */ + { + code: "var [a, b] = foo;", + output: "var [a,\nb] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 8, + }, + ], + }, + { + code: "var [a, b, c] = foo;", + output: "var [a,\nb,\nc] = foo;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 8, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 11, + }, + ], + }, - /* - * ArrayExpression & ArrayPattern - * { ArrayExpression: "always", ArrayPattern: "never" } - */ - { - code: "var [a,\nb] = [1, 2]", - output: "var [a, b] = [1,\n2]", - options: [{ ArrayExpression: "always", ArrayPattern: "never" }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 8 - }, - { - messageId: "missingLineBreak", - line: 2, - column: 9 - } - ] - }, - { - code: "var [a, b] = [1, 2]", - output: "var [a, b] = [1,\n2]", - options: [{ ArrayExpression: "always", ArrayPattern: "never" }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 17 - } - ] - }, - { - code: "var [a,\nb] = [1,\n2]", - output: "var [a, b] = [1,\n2]", - options: [{ ArrayExpression: "always", ArrayPattern: "never" }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 8 - } - ] - } - ] + // { minItems: 3 } + { + code: "var [a,\nb] = foo;", + output: "var [a, b] = foo;", + options: [{ minItems: 3 }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 8, + }, + ], + }, + { + code: "var [a, b, c] = foo;", + output: "var [a,\nb,\nc] = foo;", + options: [{ minItems: 3 }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 8, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 11, + }, + ], + }, + /* + * ArrayExpression & ArrayPattern + * { ArrayExpression: "always", ArrayPattern: "never" } + */ + { + code: "var [a,\nb] = [1, 2]", + output: "var [a, b] = [1,\n2]", + options: [{ ArrayExpression: "always", ArrayPattern: "never" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 8, + }, + { + messageId: "missingLineBreak", + line: 2, + column: 9, + }, + ], + }, + { + code: "var [a, b] = [1, 2]", + output: "var [a, b] = [1,\n2]", + options: [{ ArrayExpression: "always", ArrayPattern: "never" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 17, + }, + ], + }, + { + code: "var [a,\nb] = [1,\n2]", + output: "var [a, b] = [1,\n2]", + options: [{ ArrayExpression: "always", ArrayPattern: "never" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 8, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/arrow-body-style.js b/tests/lib/rules/arrow-body-style.js index 444d0074a46e..e03185cb1937 100644 --- a/tests/lib/rules/arrow-body-style.js +++ b/tests/lib/rules/arrow-body-style.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/arrow-body-style"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,721 +18,772 @@ const rule = require("../../../lib/rules/arrow-body-style"), const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 6 } }); ruleTester.run("arrow-body-style", rule, { - valid: [ - "var foo = () => {};", - "var foo = () => 0;", - "var addToB = (a) => { b = b + a };", - "var foo = () => { /* do nothing */ };", - "var foo = () => {\n /* do nothing */ \n};", - "var foo = (retv, name) => {\nretv[name] = true;\nreturn retv;\n};", - "var foo = () => ({});", - "var foo = () => bar();", - "var foo = () => { bar(); };", - "var foo = () => { b = a };", - "var foo = () => { bar: 1 };", - { code: "var foo = () => { return 0; };", options: ["always"] }, - { code: "var foo = () => { return bar(); };", options: ["always"] }, - { code: "var foo = () => 0;", options: ["never"] }, - { code: "var foo = () => ({ foo: 0 });", options: ["never"] }, - { code: "var foo = () => {};", options: ["as-needed", { requireReturnForObjectLiteral: true }] }, - { code: "var foo = () => 0;", options: ["as-needed", { requireReturnForObjectLiteral: true }] }, - { code: "var addToB = (a) => { b = b + a };", options: ["as-needed", { requireReturnForObjectLiteral: true }] }, - { code: "var foo = () => { /* do nothing */ };", options: ["as-needed", { requireReturnForObjectLiteral: true }] }, - { code: "var foo = () => {\n /* do nothing */ \n};", options: ["as-needed", { requireReturnForObjectLiteral: true }] }, - { code: "var foo = (retv, name) => {\nretv[name] = true;\nreturn retv;\n};", options: ["as-needed", { requireReturnForObjectLiteral: true }] }, - { code: "var foo = () => bar();", options: ["as-needed", { requireReturnForObjectLiteral: true }] }, - { code: "var foo = () => { bar(); };", options: ["as-needed", { requireReturnForObjectLiteral: true }] }, - { code: "var foo = () => { return { bar: 0 }; };", options: ["as-needed", { requireReturnForObjectLiteral: true }] } - ], - invalid: [ - { - code: "for (var foo = () => { return a in b ? bar : () => {} } ;;);", - output: "for (var foo = () => (a in b ? bar : () => {}) ;;);", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 22, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "a in b; for (var f = () => { return c };;);", - output: "a in b; for (var f = () => c;;);", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 28, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "for (a = b => { return c in d ? e : f } ;;);", - output: "for (a = b => (c in d ? e : f) ;;);", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 15, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "for (var f = () => { return a };;);", - output: "for (var f = () => a;;);", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 20, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "for (var f;f = () => { return a };);", - output: "for (var f;f = () => a;);", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 22, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "for (var f = () => { return a in c };;);", - output: "for (var f = () => (a in c);;);", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 20, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "for (var f;f = () => { return a in c };);", - output: "for (var f;f = () => a in c;);", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 22, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "for (;;){var f = () => { return a in c }}", - output: "for (;;){var f = () => a in c}", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 24, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "for (a = b => { return c = d in e } ;;);", - output: "for (a = b => (c = d in e) ;;);", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 15, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "for (var a;;a = b => { return c = d in e } );", - output: "for (var a;;a = b => c = d in e );", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 22, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "for (let a = (b, c, d) => { return vb && c in d; }; ;);", - output: "for (let a = (b, c, d) => (vb && c in d); ;);", - errors: [ - { - line: 1, - column: 27, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "for (let a = (b, c, d) => { return v in b && c in d; }; ;);", - output: "for (let a = (b, c, d) => (v in b && c in d); ;);", - errors: [ - { - line: 1, - column: 27, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "function foo(){ for (let a = (b, c, d) => { return v in b && c in d; }; ;); }", - output: "function foo(){ for (let a = (b, c, d) => (v in b && c in d); ;); }", - errors: [ - { - line: 1, - column: 43, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "for ( a = (b, c, d) => { return v in b && c in d; }; ;);", - output: "for ( a = (b, c, d) => (v in b && c in d); ;);", - errors: [ - { - line: 1, - column: 24, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "for ( a = (b) => { return (c in d) }; ;);", - output: "for ( a = (b) => (c in d); ;);", - errors: [ - { - line: 1, - column: 18, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "for (let a = (b, c, d) => { return vb in dd ; }; ;);", - output: "for (let a = (b, c, d) => (vb in dd ); ;);", - errors: [ - { - line: 1, - column: 27, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "for (let a = (b, c, d) => { return vb in c in dd ; }; ;);", - output: "for (let a = (b, c, d) => (vb in c in dd ); ;);", - errors: [ - { - line: 1, - column: 27, - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "do{let a = () => {return f in ff}}while(true){}", - output: "do{let a = () => f in ff}while(true){}", - errors: [{ - line: 1, - column: 18, - messageId: "unexpectedSingleBlock" - }] - }, - { - code: "do{for (let a = (b, c, d) => { return vb in c in dd ; }; ;);}while(true){}", - output: "do{for (let a = (b, c, d) => (vb in c in dd ); ;);}while(true){}", - errors: [{ - line: 1, - column: 30, - messageId: "unexpectedSingleBlock" - }] - }, - { - code: "scores.map(score => { return x in +(score / maxScore).toFixed(2)});", - output: "scores.map(score => x in +(score / maxScore).toFixed(2));", - errors: [{ - line: 1, - column: 21, - messageId: "unexpectedSingleBlock" - }] - }, - { - code: "const fn = (a, b) => { return a + x in Number(b) };", - output: "const fn = (a, b) => a + x in Number(b);", - errors: [{ - line: 1, - column: 22, - messageId: "unexpectedSingleBlock" - }] - }, - { - code: "var foo = () => 0", - output: "var foo = () => {return 0}", - options: ["always"], - errors: [ - { - line: 1, - column: 17, - endLine: 1, - endColumn: 18, - type: "ArrowFunctionExpression", - messageId: "expectedBlock" - } - ] - }, - { - code: "var foo = () => 0;", - output: "var foo = () => {return 0};", - options: ["always"], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "expectedBlock" - } - ] - }, - { - code: "var foo = () => ({});", - output: "var foo = () => {return {}};", - options: ["always"], - errors: [ - { - line: 1, - column: 18, - type: "ArrowFunctionExpression", - messageId: "expectedBlock" - } - ] - }, - { - code: "var foo = () => ( {});", - output: "var foo = () => {return {}};", - options: ["always"], - errors: [ - { - line: 1, - column: 20, - type: "ArrowFunctionExpression", - messageId: "expectedBlock" - } - ] - }, - { - code: "(() => ({}))", - output: "(() => {return {}})", - options: ["always"], - errors: [ - { - line: 1, - column: 9, - type: "ArrowFunctionExpression", - messageId: "expectedBlock" - } - ] - }, - { - code: "(() => ( {}))", - output: "(() => {return {}})", - options: ["always"], - errors: [ - { - line: 1, - column: 10, - type: "ArrowFunctionExpression", - messageId: "expectedBlock" - } - ] - }, - { - code: "var foo = () => { return 0; };", - output: "var foo = () => 0;", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = () => { return 0 };", - output: "var foo = () => 0;", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = () => { return bar(); };", - output: "var foo = () => bar();", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = () => {};", - output: null, - options: ["never"], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedEmptyBlock" - } - ] - }, - { - code: "var foo = () => {\nreturn 0;\n};", - output: "var foo = () => 0;", - options: ["never"], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = () => { return { bar: 0 }; };", - output: "var foo = () => ({ bar: 0 });", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedObjectBlock" - } - ] - }, - { - code: "var foo = () => { return ({ bar: 0 }); };", - output: "var foo = () => ({ bar: 0 });", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = () => { return a, b }", - output: "var foo = () => (a, b)", - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = () => { return };", - output: null, // not fixed - options: ["as-needed", { requireReturnForObjectLiteral: true }], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = () => { return; };", - output: null, // not fixed - options: ["as-needed", { requireReturnForObjectLiteral: true }], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = () => { return ( /* a */ {ok: true} /* b */ ) };", - output: "var foo = () => ( /* a */ {ok: true} /* b */ );", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = () => { return '{' };", - output: "var foo = () => '{';", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = () => { return { bar: 0 }.bar; };", - output: "var foo = () => ({ bar: 0 }.bar);", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedObjectBlock" - } - ] - }, - { - code: "var foo = (retv, name) => {\nretv[name] = true;\nreturn retv;\n};", - output: null, // not fixed - options: ["never"], - errors: [ - { line: 1, column: 27, type: "ArrowFunctionExpression", messageId: "unexpectedOtherBlock" } - ] - }, - { - code: "var foo = () => { return 0; };", - output: "var foo = () => 0;", - options: ["as-needed", { requireReturnForObjectLiteral: true }], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = () => { return bar(); };", - output: "var foo = () => bar();", - options: ["as-needed", { requireReturnForObjectLiteral: true }], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = () => ({});", - output: "var foo = () => {return {}};", - options: ["as-needed", { requireReturnForObjectLiteral: true }], - errors: [ - { - line: 1, - column: 18, - type: "ArrowFunctionExpression", - messageId: "expectedBlock" - } - ] - }, - { - code: "var foo = () => ({ bar: 0 });", - output: "var foo = () => {return { bar: 0 }};", - options: ["as-needed", { requireReturnForObjectLiteral: true }], - errors: [ - { - line: 1, - column: 18, - type: "ArrowFunctionExpression", - messageId: "expectedBlock" - } - ] - }, - { - code: "var foo = () => (((((((5)))))));", - output: "var foo = () => {return (((((((5)))))))};", - options: ["always"], - errors: [ - { - line: 1, - column: 24, - type: "ArrowFunctionExpression", - messageId: "expectedBlock" - } - ] - }, - { - - // Not fixed; fixing would cause ASI issues. - code: - "var foo = () => { return bar }\n" + - "[1, 2, 3].map(foo)", - output: null, - options: ["never"], - errors: [ - { line: 1, column: 17, type: "ArrowFunctionExpression", messageId: "unexpectedSingleBlock" } - ] - }, - { - - - // Not fixed; fixing would cause ASI issues. - code: - "var foo = () => { return bar }\n" + - "(1).toString();", - output: null, - options: ["never"], - errors: [ - { line: 1, column: 17, type: "ArrowFunctionExpression", messageId: "unexpectedSingleBlock" } - ] - }, - { - - // Fixing here is ok because the arrow function has a semicolon afterwards. - code: - "var foo = () => { return bar };\n" + - "[1, 2, 3].map(foo)", - output: - "var foo = () => bar;\n" + - "[1, 2, 3].map(foo)", - options: ["never"], - errors: [ - { - line: 1, - column: 17, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = /* a */ ( /* b */ ) /* c */ => /* d */ { /* e */ return /* f */ 5 /* g */ ; /* h */ } /* i */ ;", - output: "var foo = /* a */ ( /* b */ ) /* c */ => /* d */ /* e */ /* f */ 5 /* g */ /* h */ /* i */ ;", - options: ["as-needed"], - errors: [ - { - line: 1, - column: 50, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = /* a */ ( /* b */ ) /* c */ => /* d */ ( /* e */ 5 /* f */ ) /* g */ ;", - output: "var foo = /* a */ ( /* b */ ) /* c */ => /* d */ {return ( /* e */ 5 /* f */ )} /* g */ ;", - options: ["always"], - errors: [ - { - line: 1, - column: 60, - type: "ArrowFunctionExpression", - messageId: "expectedBlock" - } - ] - }, - { - code: "var foo = () => {\nreturn bar;\n};", - output: "var foo = () => bar;", - errors: [ - { - line: 1, - column: 17, - endLine: 3, - endColumn: 2, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = () => {\nreturn bar;};", - output: "var foo = () => bar;", - errors: [ - { - line: 1, - column: 17, - endLine: 2, - endColumn: 13, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: "var foo = () => {return bar;\n};", - output: "var foo = () => bar;", - errors: [ - { - line: 1, - column: 17, - endLine: 2, - endColumn: 2, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: ` + valid: [ + "var foo = () => {};", + "var foo = () => 0;", + "var addToB = (a) => { b = b + a };", + "var foo = () => { /* do nothing */ };", + "var foo = () => {\n /* do nothing */ \n};", + "var foo = (retv, name) => {\nretv[name] = true;\nreturn retv;\n};", + "var foo = () => ({});", + "var foo = () => bar();", + "var foo = () => { bar(); };", + "var foo = () => { b = a };", + "var foo = () => { bar: 1 };", + { code: "var foo = () => { return 0; };", options: ["always"] }, + { code: "var foo = () => { return bar(); };", options: ["always"] }, + { code: "var foo = () => 0;", options: ["never"] }, + { code: "var foo = () => ({ foo: 0 });", options: ["never"] }, + { + code: "var foo = () => {};", + options: ["as-needed", { requireReturnForObjectLiteral: true }], + }, + { + code: "var foo = () => 0;", + options: ["as-needed", { requireReturnForObjectLiteral: true }], + }, + { + code: "var addToB = (a) => { b = b + a };", + options: ["as-needed", { requireReturnForObjectLiteral: true }], + }, + { + code: "var foo = () => { /* do nothing */ };", + options: ["as-needed", { requireReturnForObjectLiteral: true }], + }, + { + code: "var foo = () => {\n /* do nothing */ \n};", + options: ["as-needed", { requireReturnForObjectLiteral: true }], + }, + { + code: "var foo = (retv, name) => {\nretv[name] = true;\nreturn retv;\n};", + options: ["as-needed", { requireReturnForObjectLiteral: true }], + }, + { + code: "var foo = () => bar();", + options: ["as-needed", { requireReturnForObjectLiteral: true }], + }, + { + code: "var foo = () => { bar(); };", + options: ["as-needed", { requireReturnForObjectLiteral: true }], + }, + { + code: "var foo = () => { return { bar: 0 }; };", + options: ["as-needed", { requireReturnForObjectLiteral: true }], + }, + ], + invalid: [ + { + code: "for (var foo = () => { return a in b ? bar : () => {} } ;;);", + output: "for (var foo = () => (a in b ? bar : () => {}) ;;);", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 22, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "a in b; for (var f = () => { return c };;);", + output: "a in b; for (var f = () => c;;);", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 28, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "for (a = b => { return c in d ? e : f } ;;);", + output: "for (a = b => (c in d ? e : f) ;;);", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 15, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "for (var f = () => { return a };;);", + output: "for (var f = () => a;;);", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 20, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "for (var f;f = () => { return a };);", + output: "for (var f;f = () => a;);", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 22, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "for (var f = () => { return a in c };;);", + output: "for (var f = () => (a in c);;);", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 20, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "for (var f;f = () => { return a in c };);", + output: "for (var f;f = () => a in c;);", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 22, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "for (;;){var f = () => { return a in c }}", + output: "for (;;){var f = () => a in c}", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 24, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "for (a = b => { return c = d in e } ;;);", + output: "for (a = b => (c = d in e) ;;);", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 15, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "for (var a;;a = b => { return c = d in e } );", + output: "for (var a;;a = b => c = d in e );", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 22, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "for (let a = (b, c, d) => { return vb && c in d; }; ;);", + output: "for (let a = (b, c, d) => (vb && c in d); ;);", + errors: [ + { + line: 1, + column: 27, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "for (let a = (b, c, d) => { return v in b && c in d; }; ;);", + output: "for (let a = (b, c, d) => (v in b && c in d); ;);", + errors: [ + { + line: 1, + column: 27, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "function foo(){ for (let a = (b, c, d) => { return v in b && c in d; }; ;); }", + output: "function foo(){ for (let a = (b, c, d) => (v in b && c in d); ;); }", + errors: [ + { + line: 1, + column: 43, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "for ( a = (b, c, d) => { return v in b && c in d; }; ;);", + output: "for ( a = (b, c, d) => (v in b && c in d); ;);", + errors: [ + { + line: 1, + column: 24, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "for ( a = (b) => { return (c in d) }; ;);", + output: "for ( a = (b) => (c in d); ;);", + errors: [ + { + line: 1, + column: 18, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "for (let a = (b, c, d) => { return vb in dd ; }; ;);", + output: "for (let a = (b, c, d) => (vb in dd ); ;);", + errors: [ + { + line: 1, + column: 27, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "for (let a = (b, c, d) => { return vb in c in dd ; }; ;);", + output: "for (let a = (b, c, d) => (vb in c in dd ); ;);", + errors: [ + { + line: 1, + column: 27, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "do{let a = () => {return f in ff}}while(true){}", + output: "do{let a = () => f in ff}while(true){}", + errors: [ + { + line: 1, + column: 18, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "do{for (let a = (b, c, d) => { return vb in c in dd ; }; ;);}while(true){}", + output: "do{for (let a = (b, c, d) => (vb in c in dd ); ;);}while(true){}", + errors: [ + { + line: 1, + column: 30, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "scores.map(score => { return x in +(score / maxScore).toFixed(2)});", + output: "scores.map(score => x in +(score / maxScore).toFixed(2));", + errors: [ + { + line: 1, + column: 21, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "const fn = (a, b) => { return a + x in Number(b) };", + output: "const fn = (a, b) => a + x in Number(b);", + errors: [ + { + line: 1, + column: 22, + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => 0", + output: "var foo = () => {return 0}", + options: ["always"], + errors: [ + { + line: 1, + column: 17, + endLine: 1, + endColumn: 18, + type: "ArrowFunctionExpression", + messageId: "expectedBlock", + }, + ], + }, + { + code: "var foo = () => 0;", + output: "var foo = () => {return 0};", + options: ["always"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "expectedBlock", + }, + ], + }, + { + code: "var foo = () => ({});", + output: "var foo = () => {return {}};", + options: ["always"], + errors: [ + { + line: 1, + column: 18, + type: "ArrowFunctionExpression", + messageId: "expectedBlock", + }, + ], + }, + { + code: "var foo = () => ( {});", + output: "var foo = () => {return {}};", + options: ["always"], + errors: [ + { + line: 1, + column: 20, + type: "ArrowFunctionExpression", + messageId: "expectedBlock", + }, + ], + }, + { + code: "(() => ({}))", + output: "(() => {return {}})", + options: ["always"], + errors: [ + { + line: 1, + column: 9, + type: "ArrowFunctionExpression", + messageId: "expectedBlock", + }, + ], + }, + { + code: "(() => ( {}))", + output: "(() => {return {}})", + options: ["always"], + errors: [ + { + line: 1, + column: 10, + type: "ArrowFunctionExpression", + messageId: "expectedBlock", + }, + ], + }, + { + code: "var foo = () => { return 0; };", + output: "var foo = () => 0;", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => { return 0 };", + output: "var foo = () => 0;", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => { return bar(); };", + output: "var foo = () => bar();", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => {};", + output: null, + options: ["never"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedEmptyBlock", + }, + ], + }, + { + code: "var foo = () => {\nreturn 0;\n};", + output: "var foo = () => 0;", + options: ["never"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => { return { bar: 0 }; };", + output: "var foo = () => ({ bar: 0 });", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedObjectBlock", + }, + ], + }, + { + code: "var foo = () => { return ({ bar: 0 }); };", + output: "var foo = () => ({ bar: 0 });", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => { return a, b }", + output: "var foo = () => (a, b)", + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => { return };", + output: null, // not fixed + options: ["as-needed", { requireReturnForObjectLiteral: true }], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => { return; };", + output: null, // not fixed + options: ["as-needed", { requireReturnForObjectLiteral: true }], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => { return ( /* a */ {ok: true} /* b */ ) };", + output: "var foo = () => ( /* a */ {ok: true} /* b */ );", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => { return '{' };", + output: "var foo = () => '{';", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => { return { bar: 0 }.bar; };", + output: "var foo = () => ({ bar: 0 }.bar);", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedObjectBlock", + }, + ], + }, + { + code: "var foo = (retv, name) => {\nretv[name] = true;\nreturn retv;\n};", + output: null, // not fixed + options: ["never"], + errors: [ + { + line: 1, + column: 27, + type: "ArrowFunctionExpression", + messageId: "unexpectedOtherBlock", + }, + ], + }, + { + code: "var foo = () => { bar };", + output: null, // not fixed + options: ["never"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedOtherBlock", + }, + ], + }, + { + code: "var foo = () => { return 0; };", + output: "var foo = () => 0;", + options: ["as-needed", { requireReturnForObjectLiteral: true }], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => { return bar(); };", + output: "var foo = () => bar();", + options: ["as-needed", { requireReturnForObjectLiteral: true }], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => ({});", + output: "var foo = () => {return {}};", + options: ["as-needed", { requireReturnForObjectLiteral: true }], + errors: [ + { + line: 1, + column: 18, + type: "ArrowFunctionExpression", + messageId: "expectedBlock", + }, + ], + }, + { + code: "var foo = () => ({ bar: 0 });", + output: "var foo = () => {return { bar: 0 }};", + options: ["as-needed", { requireReturnForObjectLiteral: true }], + errors: [ + { + line: 1, + column: 18, + type: "ArrowFunctionExpression", + messageId: "expectedBlock", + }, + ], + }, + { + code: "var foo = () => (((((((5)))))));", + output: "var foo = () => {return (((((((5)))))))};", + options: ["always"], + errors: [ + { + line: 1, + column: 24, + type: "ArrowFunctionExpression", + messageId: "expectedBlock", + }, + ], + }, + { + // Not fixed; fixing would cause ASI issues. + code: "var foo = () => { return bar }\n" + "[1, 2, 3].map(foo)", + output: null, + options: ["never"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + // Not fixed; fixing would cause ASI issues. + code: "var foo = () => { return bar }\n" + "(1).toString();", + output: null, + options: ["never"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + // Fixing here is ok because the arrow function has a semicolon afterwards. + code: "var foo = () => { return bar };\n" + "[1, 2, 3].map(foo)", + output: "var foo = () => bar;\n" + "[1, 2, 3].map(foo)", + options: ["never"], + errors: [ + { + line: 1, + column: 17, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = /* a */ ( /* b */ ) /* c */ => /* d */ { /* e */ return /* f */ 5 /* g */ ; /* h */ } /* i */ ;", + output: "var foo = /* a */ ( /* b */ ) /* c */ => /* d */ /* e */ /* f */ 5 /* g */ /* h */ /* i */ ;", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 50, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = /* a */ ( /* b */ ) /* c */ => /* d */ ( /* e */ 5 /* f */ ) /* g */ ;", + output: "var foo = /* a */ ( /* b */ ) /* c */ => /* d */ {return ( /* e */ 5 /* f */ )} /* g */ ;", + options: ["always"], + errors: [ + { + line: 1, + column: 60, + type: "ArrowFunctionExpression", + messageId: "expectedBlock", + }, + ], + }, + { + code: "var foo = () => {\nreturn bar;\n};", + output: "var foo = () => bar;", + errors: [ + { + line: 1, + column: 17, + endLine: 3, + endColumn: 2, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => {\nreturn bar;};", + output: "var foo = () => bar;", + errors: [ + { + line: 1, + column: 17, + endLine: 2, + endColumn: 13, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: "var foo = () => {return bar;\n};", + output: "var foo = () => bar;", + errors: [ + { + line: 1, + column: 17, + endLine: 2, + endColumn: 2, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: ` var foo = () => { return foo .bar; }; `, - output: ` + output: ` var foo = () => foo .bar; `, - errors: [ - { - line: 2, - column: 31, - type: "ArrowFunctionExpression", - messageId: "unexpectedSingleBlock" - } - ] - }, - { - code: ` + errors: [ + { + line: 2, + column: 31, + type: "ArrowFunctionExpression", + messageId: "unexpectedSingleBlock", + }, + ], + }, + { + code: ` var foo = () => { return { bar: 1, @@ -740,59 +791,59 @@ ruleTester.run("arrow-body-style", rule, { }; }; `, - output: ` + output: ` var foo = () => ({ bar: 1, baz: 2 }); `, - errors: [ - { - line: 2, - column: 31, - endLine: 7, - endColumn: 16, - type: "ArrowFunctionExpression", - messageId: "unexpectedObjectBlock" - } - ] - }, - { - code: "var foo = () => ({foo: 1}).foo();", - output: "var foo = () => {return {foo: 1}.foo()};", - options: ["always"], - errors: [{ messageId: "expectedBlock" }] - }, - { - code: "var foo = () => ({foo: 1}.foo());", - output: "var foo = () => {return {foo: 1}.foo()};", - options: ["always"], - errors: [{ messageId: "expectedBlock" }] - }, - { - code: "var foo = () => ( {foo: 1} ).foo();", - output: "var foo = () => {return {foo: 1} .foo()};", - options: ["always"], - errors: [{ messageId: "expectedBlock" }] - }, - { - code: ` + errors: [ + { + line: 2, + column: 31, + endLine: 7, + endColumn: 16, + type: "ArrowFunctionExpression", + messageId: "unexpectedObjectBlock", + }, + ], + }, + { + code: "var foo = () => ({foo: 1}).foo();", + output: "var foo = () => {return {foo: 1}.foo()};", + options: ["always"], + errors: [{ messageId: "expectedBlock" }], + }, + { + code: "var foo = () => ({foo: 1}.foo());", + output: "var foo = () => {return {foo: 1}.foo()};", + options: ["always"], + errors: [{ messageId: "expectedBlock" }], + }, + { + code: "var foo = () => ( {foo: 1} ).foo();", + output: "var foo = () => {return {foo: 1} .foo()};", + options: ["always"], + errors: [{ messageId: "expectedBlock" }], + }, + { + code: ` var foo = () => ({ bar: 1, baz: 2 }); `, - output: ` + output: ` var foo = () => {return { bar: 1, baz: 2 }}; `, - options: ["always"], - errors: [{ messageId: "expectedBlock" }] - }, - { - code: ` + options: ["always"], + errors: [{ messageId: "expectedBlock" }], + }, + { + code: ` parsedYears = _map(years, (year) => ( { index : year, @@ -800,7 +851,7 @@ ruleTester.run("arrow-body-style", rule, { } )); `, - output: ` + output: ` parsedYears = _map(years, (year) => { return { index : year, @@ -808,16 +859,16 @@ ruleTester.run("arrow-body-style", rule, { } }); `, - options: ["always"], - errors: [{ messageId: "expectedBlock" }] - }, + options: ["always"], + errors: [{ messageId: "expectedBlock" }], + }, - // https://github.com/eslint/eslint/issues/14633 - { - code: "const createMarker = (color) => ({ latitude, longitude }, index) => {};", - output: "const createMarker = (color) => {return ({ latitude, longitude }, index) => {}};", - options: ["always"], - errors: [{ messageId: "expectedBlock" }] - } - ] + // https://github.com/eslint/eslint/issues/14633 + { + code: "const createMarker = (color) => ({ latitude, longitude }, index) => {};", + output: "const createMarker = (color) => {return ({ latitude, longitude }, index) => {}};", + options: ["always"], + errors: [{ messageId: "expectedBlock" }], + }, + ], }); diff --git a/tests/lib/rules/arrow-parens.js b/tests/lib/rules/arrow-parens.js index 9aa8a2939154..8111ac27a6f7 100644 --- a/tests/lib/rules/arrow-parens.js +++ b/tests/lib/rules/arrow-parens.js @@ -10,8 +10,8 @@ //------------------------------------------------------------------------------ const baseParser = require("../../fixtures/fixture-parser"), - rule = require("../../../lib/rules/arrow-parens"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + rule = require("../../../lib/rules/arrow-parens"), + RuleTester = require("../../../lib/rule-tester/rule-tester"); /** * Loads a parser. @@ -19,7 +19,7 @@ const baseParser = require("../../fixtures/fixture-parser"), * @returns {Object} The parser object. */ function parser(name) { - return require(baseParser("arrow-parens", name)); + return require(baseParser("arrow-parens", name)); } //------------------------------------------------------------------------------ @@ -29,533 +29,658 @@ function parser(name) { const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 6 } }); const valid = [ + // "always" (by default) + "() => {}", + "(a) => {}", + "(a) => a", + "(a) => {\n}", + "a.then((foo) => {});", + "a.then((foo) => { if (true) {}; });", + "const f = (/* */a) => a + a;", + "const f = (a/** */) => a + a;", + "const f = (a//\n) => a + a;", + "const f = (//\na) => a + a;", + "const f = (/*\n */a//\n) => a + a;", + "const f = (/** @type {number} */a/**hello*/) => a + a;", + { + code: "a.then(async (foo) => { if (true) {}; });", + languageOptions: { ecmaVersion: 8 }, + }, - // "always" (by default) - "() => {}", - "(a) => {}", - "(a) => a", - "(a) => {\n}", - "a.then((foo) => {});", - "a.then((foo) => { if (true) {}; });", - "const f = (/* */a) => a + a;", - "const f = (a/** */) => a + a;", - "const f = (a//\n) => a + a;", - "const f = (//\na) => a + a;", - "const f = (/*\n */a//\n) => a + a;", - "const f = (/** @type {number} */a/**hello*/) => a + a;", - { code: "a.then(async (foo) => { if (true) {}; });", languageOptions: { ecmaVersion: 8 } }, + // "always" (explicit) + { code: "() => {}", options: ["always"] }, + { code: "(a) => {}", options: ["always"] }, + { code: "(a) => a", options: ["always"] }, + { code: "(a) => {\n}", options: ["always"] }, + { code: "a.then((foo) => {});", options: ["always"] }, + { code: "a.then((foo) => { if (true) {}; });", options: ["always"] }, + { + code: "a.then(async (foo) => { if (true) {}; });", + options: ["always"], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "(a: T) => a", + options: ["always"], + languageOptions: { parser: parser("identifier-type") }, + }, + { + code: "(a): T => a", + options: ["always"], + languageOptions: { parser: parser("return-type") }, + }, - // "always" (explicit) - { code: "() => {}", options: ["always"] }, - { code: "(a) => {}", options: ["always"] }, - { code: "(a) => a", options: ["always"] }, - { code: "(a) => {\n}", options: ["always"] }, - { code: "a.then((foo) => {});", options: ["always"] }, - { code: "a.then((foo) => { if (true) {}; });", options: ["always"] }, - { code: "a.then(async (foo) => { if (true) {}; });", options: ["always"], languageOptions: { ecmaVersion: 8 } }, - { code: "(a: T) => a", options: ["always"], languageOptions: { parser: parser("identifier-type") } }, - { code: "(a): T => a", options: ["always"], languageOptions: { parser: parser("return-type") } }, + // "as-needed" + { code: "() => {}", options: ["as-needed"] }, + { code: "a => {}", options: ["as-needed"] }, + { code: "a => a", options: ["as-needed"] }, + { code: "a => (a)", options: ["as-needed"] }, + { code: "(a => a)", options: ["as-needed"] }, + { code: "((a => a))", options: ["as-needed"] }, + { code: "([a, b]) => {}", options: ["as-needed"] }, + { code: "({ a, b }) => {}", options: ["as-needed"] }, + { code: "(a = 10) => {}", options: ["as-needed"] }, + { code: "(...a) => a[0]", options: ["as-needed"] }, + { code: "(a, b) => {}", options: ["as-needed"] }, + { + code: "async a => a", + options: ["as-needed"], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "async ([a, b]) => {}", + options: ["as-needed"], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "async (a, b) => {}", + options: ["as-needed"], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "(a: T) => a", + options: ["as-needed"], + languageOptions: { parser: parser("identifier-type") }, + }, + { + code: "(a): T => a", + options: ["as-needed"], + languageOptions: { parser: parser("return-type") }, + }, - // "as-needed" - { code: "() => {}", options: ["as-needed"] }, - { code: "a => {}", options: ["as-needed"] }, - { code: "a => a", options: ["as-needed"] }, - { code: "a => (a)", options: ["as-needed"] }, - { code: "(a => a)", options: ["as-needed"] }, - { code: "((a => a))", options: ["as-needed"] }, - { code: "([a, b]) => {}", options: ["as-needed"] }, - { code: "({ a, b }) => {}", options: ["as-needed"] }, - { code: "(a = 10) => {}", options: ["as-needed"] }, - { code: "(...a) => a[0]", options: ["as-needed"] }, - { code: "(a, b) => {}", options: ["as-needed"] }, - { code: "async a => a", options: ["as-needed"], languageOptions: { ecmaVersion: 8 } }, - { code: "async ([a, b]) => {}", options: ["as-needed"], languageOptions: { ecmaVersion: 8 } }, - { code: "async (a, b) => {}", options: ["as-needed"], languageOptions: { ecmaVersion: 8 } }, - { code: "(a: T) => a", options: ["as-needed"], languageOptions: { parser: parser("identifier-type") } }, - { code: "(a): T => a", options: ["as-needed"], languageOptions: { parser: parser("return-type") } }, + // "as-needed", { "requireForBlockBody": true } + { code: "() => {}", options: ["as-needed", { requireForBlockBody: true }] }, + { code: "a => a", options: ["as-needed", { requireForBlockBody: true }] }, + { code: "a => (a)", options: ["as-needed", { requireForBlockBody: true }] }, + { code: "(a => a)", options: ["as-needed", { requireForBlockBody: true }] }, + { + code: "((a => a))", + options: ["as-needed", { requireForBlockBody: true }], + }, + { + code: "([a, b]) => {}", + options: ["as-needed", { requireForBlockBody: true }], + }, + { + code: "([a, b]) => a", + options: ["as-needed", { requireForBlockBody: true }], + }, + { + code: "({ a, b }) => {}", + options: ["as-needed", { requireForBlockBody: true }], + }, + { + code: "({ a, b }) => a + b", + options: ["as-needed", { requireForBlockBody: true }], + }, + { + code: "(a = 10) => {}", + options: ["as-needed", { requireForBlockBody: true }], + }, + { + code: "(...a) => a[0]", + options: ["as-needed", { requireForBlockBody: true }], + }, + { + code: "(a, b) => {}", + options: ["as-needed", { requireForBlockBody: true }], + }, + { + code: "a => ({})", + options: ["as-needed", { requireForBlockBody: true }], + }, + { + code: "async a => ({})", + options: ["as-needed", { requireForBlockBody: true }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "async a => a", + options: ["as-needed", { requireForBlockBody: true }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "(a: T) => a", + options: ["as-needed", { requireForBlockBody: true }], + languageOptions: { parser: parser("identifier-type") }, + }, + { + code: "(a): T => a", + options: ["as-needed", { requireForBlockBody: true }], + languageOptions: { parser: parser("return-type") }, + }, + { + code: "const f = (/** @type {number} */a/**hello*/) => a + a;", + options: ["as-needed"], + }, + { + code: "const f = (/* */a) => a + a;", + options: ["as-needed"], + }, + { + code: "const f = (a/** */) => a + a;", + options: ["as-needed"], + }, + { + code: "const f = (a//\n) => a + a;", + options: ["as-needed"], + }, + { + code: "const f = (//\na) => a + a;", + options: ["as-needed"], + }, + { + code: "const f = (/*\n */a//\n) => a + a;", + options: ["as-needed"], + }, + { + code: "var foo = (a,/**/) => b;", + languageOptions: { ecmaVersion: 2017 }, + options: ["as-needed"], + }, + { + code: "var foo = (a , /**/) => b;", + languageOptions: { ecmaVersion: 2017 }, + options: ["as-needed"], + }, + { + code: "var foo = (a\n,\n/**/) => b;", + languageOptions: { ecmaVersion: 2017 }, + options: ["as-needed"], + }, + { + code: "var foo = (a,//\n) => b;", + languageOptions: { ecmaVersion: 2017 }, + options: ["as-needed"], + }, + { + code: "const i = (a/**/,) => a + a;", + languageOptions: { ecmaVersion: 2017 }, + options: ["as-needed"], + }, + { + code: "const i = (a \n /**/,) => a + a;", + languageOptions: { ecmaVersion: 2017 }, + options: ["as-needed"], + }, + { + code: "var bar = ({/*comment here*/a}) => a", + options: ["as-needed"], + }, + { + code: "var bar = (/*comment here*/{a}) => a", + options: ["as-needed"], + }, - // "as-needed", { "requireForBlockBody": true } - { code: "() => {}", options: ["as-needed", { requireForBlockBody: true }] }, - { code: "a => a", options: ["as-needed", { requireForBlockBody: true }] }, - { code: "a => (a)", options: ["as-needed", { requireForBlockBody: true }] }, - { code: "(a => a)", options: ["as-needed", { requireForBlockBody: true }] }, - { code: "((a => a))", options: ["as-needed", { requireForBlockBody: true }] }, - { code: "([a, b]) => {}", options: ["as-needed", { requireForBlockBody: true }] }, - { code: "([a, b]) => a", options: ["as-needed", { requireForBlockBody: true }] }, - { code: "({ a, b }) => {}", options: ["as-needed", { requireForBlockBody: true }] }, - { code: "({ a, b }) => a + b", options: ["as-needed", { requireForBlockBody: true }] }, - { code: "(a = 10) => {}", options: ["as-needed", { requireForBlockBody: true }] }, - { code: "(...a) => a[0]", options: ["as-needed", { requireForBlockBody: true }] }, - { code: "(a, b) => {}", options: ["as-needed", { requireForBlockBody: true }] }, - { code: "a => ({})", options: ["as-needed", { requireForBlockBody: true }] }, - { code: "async a => ({})", options: ["as-needed", { requireForBlockBody: true }], languageOptions: { ecmaVersion: 8 } }, - { code: "async a => a", options: ["as-needed", { requireForBlockBody: true }], languageOptions: { ecmaVersion: 8 } }, - { code: "(a: T) => a", options: ["as-needed", { requireForBlockBody: true }], languageOptions: { parser: parser("identifier-type") } }, - { code: "(a): T => a", options: ["as-needed", { requireForBlockBody: true }], languageOptions: { parser: parser("return-type") } }, - { - code: "const f = (/** @type {number} */a/**hello*/) => a + a;", - options: ["as-needed"] - }, - { - code: "const f = (/* */a) => a + a;", - options: ["as-needed"] - }, - { - code: "const f = (a/** */) => a + a;", - options: ["as-needed"] - }, - { - code: "const f = (a//\n) => a + a;", - options: ["as-needed"] - }, - { - code: "const f = (//\na) => a + a;", - options: ["as-needed"] - }, - { - code: "const f = (/*\n */a//\n) => a + a;", - options: ["as-needed"] - }, - { - code: "var foo = (a,/**/) => b;", - languageOptions: { ecmaVersion: 2017 }, - options: ["as-needed"] - }, - { - code: "var foo = (a , /**/) => b;", - languageOptions: { ecmaVersion: 2017 }, - options: ["as-needed"] - }, - { - code: "var foo = (a\n,\n/**/) => b;", - languageOptions: { ecmaVersion: 2017 }, - options: ["as-needed"] - }, - { - code: "var foo = (a,//\n) => b;", - languageOptions: { ecmaVersion: 2017 }, - options: ["as-needed"] - }, - { - code: "const i = (a/**/,) => a + a;", - languageOptions: { ecmaVersion: 2017 }, - options: ["as-needed"] - }, - { - code: "const i = (a \n /**/,) => a + a;", - languageOptions: { ecmaVersion: 2017 }, - options: ["as-needed"] - }, - { - code: "var bar = ({/*comment here*/a}) => a", - options: ["as-needed"] - }, - { - code: "var bar = (/*comment here*/{a}) => a", - options: ["as-needed"] - }, - - // generics - { - code: "(a) => b", - options: ["always"], - languageOptions: { parser: parser("generics-simple") } - }, - { - code: "(a) => b", - options: ["as-needed"], - languageOptions: { parser: parser("generics-simple") } - }, - { - code: "(a) => b", - options: ["as-needed", { requireForBlockBody: true }], - languageOptions: { parser: parser("generics-simple") } - }, - { - code: "async (a) => b", - options: ["always"], - languageOptions: { parser: parser("generics-simple-async") } - }, - { - code: "async (a) => b", - options: ["as-needed"], - languageOptions: { parser: parser("generics-simple-async") } - }, - { - code: "async (a) => b", - options: ["as-needed", { requireForBlockBody: true }], - languageOptions: { parser: parser("generics-simple-async") } - }, - { - code: "() => b", - options: ["always"], - languageOptions: { parser: parser("generics-simple-no-params") } - }, - { - code: "() => b", - options: ["as-needed"], - languageOptions: { parser: parser("generics-simple-no-params") } - }, - { - code: "() => b", - options: ["as-needed", { requireForBlockBody: true }], - languageOptions: { parser: parser("generics-simple-no-params") } - }, - { - code: "(a) => b", - options: ["always"], - languageOptions: { parser: parser("generics-extends") } - }, - { - code: "(a) => b", - options: ["as-needed"], - languageOptions: { parser: parser("generics-extends") } - }, - { - code: "(a) => b", - options: ["as-needed", { requireForBlockBody: true }], - languageOptions: { parser: parser("generics-extends") } - }, - { - code: "(a) => b", - options: ["always"], - languageOptions: { parser: parser("generics-extends-complex") } - }, - { - code: "(a) => b", - options: ["as-needed"], - languageOptions: { parser: parser("generics-extends-complex") } - }, - { - code: "(a) => b", - options: ["as-needed", { requireForBlockBody: true }], - languageOptions: { parser: parser("generics-extends-complex") } - } + // generics + { + code: "(a) => b", + options: ["always"], + languageOptions: { parser: parser("generics-simple") }, + }, + { + code: "(a) => b", + options: ["as-needed"], + languageOptions: { parser: parser("generics-simple") }, + }, + { + code: "(a) => b", + options: ["as-needed", { requireForBlockBody: true }], + languageOptions: { parser: parser("generics-simple") }, + }, + { + code: "async (a) => b", + options: ["always"], + languageOptions: { parser: parser("generics-simple-async") }, + }, + { + code: "async (a) => b", + options: ["as-needed"], + languageOptions: { parser: parser("generics-simple-async") }, + }, + { + code: "async (a) => b", + options: ["as-needed", { requireForBlockBody: true }], + languageOptions: { parser: parser("generics-simple-async") }, + }, + { + code: "() => b", + options: ["always"], + languageOptions: { parser: parser("generics-simple-no-params") }, + }, + { + code: "() => b", + options: ["as-needed"], + languageOptions: { parser: parser("generics-simple-no-params") }, + }, + { + code: "() => b", + options: ["as-needed", { requireForBlockBody: true }], + languageOptions: { parser: parser("generics-simple-no-params") }, + }, + { + code: "(a) => b", + options: ["always"], + languageOptions: { parser: parser("generics-extends") }, + }, + { + code: "(a) => b", + options: ["as-needed"], + languageOptions: { parser: parser("generics-extends") }, + }, + { + code: "(a) => b", + options: ["as-needed", { requireForBlockBody: true }], + languageOptions: { parser: parser("generics-extends") }, + }, + { + code: "(a) => b", + options: ["always"], + languageOptions: { parser: parser("generics-extends-complex") }, + }, + { + code: "(a) => b", + options: ["as-needed"], + languageOptions: { parser: parser("generics-extends-complex") }, + }, + { + code: "(a) => b", + options: ["as-needed", { requireForBlockBody: true }], + languageOptions: { parser: parser("generics-extends-complex") }, + }, ]; const type = "ArrowFunctionExpression"; const invalid = [ + // "always" (by default) + { + code: "a => {}", + output: "(a) => {}", + errors: [ + { + line: 1, + column: 1, + endColumn: 2, + messageId: "expectedParens", + type, + }, + ], + }, + { + code: "a => a", + output: "(a) => a", + errors: [ + { + line: 1, + column: 1, + endColumn: 2, + messageId: "expectedParens", + type, + }, + ], + }, + { + code: "a => {\n}", + output: "(a) => {\n}", + errors: [ + { + line: 1, + column: 1, + endColumn: 2, + messageId: "expectedParens", + type, + }, + ], + }, + { + code: "a.then(foo => {});", + output: "a.then((foo) => {});", + errors: [ + { + line: 1, + column: 8, + endColumn: 11, + messageId: "expectedParens", + type, + }, + ], + }, + { + code: "a.then(foo => a);", + output: "a.then((foo) => a);", + errors: [ + { + line: 1, + column: 8, + endColumn: 11, + messageId: "expectedParens", + type, + }, + ], + }, + { + code: "a(foo => { if (true) {}; });", + output: "a((foo) => { if (true) {}; });", + errors: [ + { + line: 1, + column: 3, + endColumn: 6, + messageId: "expectedParens", + type, + }, + ], + }, + { + code: "a(async foo => { if (true) {}; });", + output: "a(async (foo) => { if (true) {}; });", + languageOptions: { ecmaVersion: 8 }, + errors: [ + { + line: 1, + column: 9, + endColumn: 12, + messageId: "expectedParens", + type, + }, + ], + }, - // "always" (by default) - { - code: "a => {}", - output: "(a) => {}", - errors: [{ - line: 1, - column: 1, - endColumn: 2, - messageId: "expectedParens", - type - }] - }, - { - code: "a => a", - output: "(a) => a", - errors: [{ - line: 1, - column: 1, - endColumn: 2, - messageId: "expectedParens", - type - }] - }, - { - code: "a => {\n}", - output: "(a) => {\n}", - errors: [{ - line: 1, - column: 1, - endColumn: 2, - messageId: "expectedParens", - type - }] - }, - { - code: "a.then(foo => {});", - output: "a.then((foo) => {});", - errors: [{ - line: 1, - column: 8, - endColumn: 11, - messageId: "expectedParens", - type - }] - }, - { - code: "a.then(foo => a);", - output: "a.then((foo) => a);", - errors: [{ - line: 1, - column: 8, - endColumn: 11, - messageId: "expectedParens", - type - }] - }, - { - code: "a(foo => { if (true) {}; });", - output: "a((foo) => { if (true) {}; });", - errors: [{ - line: 1, - column: 3, - endColumn: 6, - messageId: "expectedParens", - type - }] - }, - { - code: "a(async foo => { if (true) {}; });", - output: "a(async (foo) => { if (true) {}; });", - languageOptions: { ecmaVersion: 8 }, - errors: [{ - line: 1, - column: 9, - endColumn: 12, - messageId: "expectedParens", - type - }] - }, - - // "as-needed" - { - code: "(a) => a", - output: "a => a", - options: ["as-needed"], - errors: [{ - line: 1, - column: 2, - endColumn: 3, - messageId: "unexpectedParens", - type - }] - }, - { - code: "( a ) => b", - output: "a => b", - options: ["as-needed"], - errors: [{ - line: 1, - column: 4, - endColumn: 5, - messageId: "unexpectedParens", - type - }] - }, - { - code: "(\na\n) => b", - output: "a => b", - options: ["as-needed"], - errors: [{ - line: 2, - column: 1, - endColumn: 2, - messageId: "unexpectedParens", - type - }] - }, - { - code: "(a,) => a", - output: "a => a", - options: ["as-needed"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ - line: 1, - column: 2, - endColumn: 3, - messageId: "unexpectedParens", - type - }] - }, - { - code: "async (a) => a", - output: "async a => a", - options: ["as-needed"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ - line: 1, - column: 8, - endColumn: 9, - messageId: "unexpectedParens", - type - }] - }, - { - code: "async(a) => a", - output: "async a => a", - options: ["as-needed"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ - line: 1, - column: 7, - endColumn: 8, - messageId: "unexpectedParens", - type - }] - }, - { - code: "typeof((a) => {})", - output: "typeof(a => {})", - options: ["as-needed"], - errors: [{ - line: 1, - column: 9, - endColumn: 10, - messageId: "unexpectedParens", - type - }] - }, - { - code: "function *f() { yield(a) => a; }", - output: "function *f() { yield a => a; }", - options: ["as-needed"], - errors: [{ - line: 1, - column: 23, - endColumn: 24, - messageId: "unexpectedParens", - type - }] - }, + // "as-needed" + { + code: "(a) => a", + output: "a => a", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 2, + endColumn: 3, + messageId: "unexpectedParens", + type, + }, + ], + }, + { + code: "( a ) => b", + output: "a => b", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 4, + endColumn: 5, + messageId: "unexpectedParens", + type, + }, + ], + }, + { + code: "(\na\n) => b", + output: "a => b", + options: ["as-needed"], + errors: [ + { + line: 2, + column: 1, + endColumn: 2, + messageId: "unexpectedParens", + type, + }, + ], + }, + { + code: "(a,) => a", + output: "a => a", + options: ["as-needed"], + languageOptions: { ecmaVersion: 8 }, + errors: [ + { + line: 1, + column: 2, + endColumn: 3, + messageId: "unexpectedParens", + type, + }, + ], + }, + { + code: "async (a) => a", + output: "async a => a", + options: ["as-needed"], + languageOptions: { ecmaVersion: 8 }, + errors: [ + { + line: 1, + column: 8, + endColumn: 9, + messageId: "unexpectedParens", + type, + }, + ], + }, + { + code: "async(a) => a", + output: "async a => a", + options: ["as-needed"], + languageOptions: { ecmaVersion: 8 }, + errors: [ + { + line: 1, + column: 7, + endColumn: 8, + messageId: "unexpectedParens", + type, + }, + ], + }, + { + code: "typeof((a) => {})", + output: "typeof(a => {})", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 9, + endColumn: 10, + messageId: "unexpectedParens", + type, + }, + ], + }, + { + code: "function *f() { yield(a) => a; }", + output: "function *f() { yield a => a; }", + options: ["as-needed"], + errors: [ + { + line: 1, + column: 23, + endColumn: 24, + messageId: "unexpectedParens", + type, + }, + ], + }, - // "as-needed", { "requireForBlockBody": true } - { - code: "a => {}", - output: "(a) => {}", - options: ["as-needed", { requireForBlockBody: true }], - errors: [{ - line: 1, - column: 1, - endColumn: 2, - messageId: "expectedParensBlock", - type - }] - }, - { - code: "(a) => a", - output: "a => a", - options: ["as-needed", { requireForBlockBody: true }], - errors: [{ - line: 1, - column: 2, - endColumn: 3, - messageId: "unexpectedParensInline", - type - }] - }, - { - code: "async a => {}", - output: "async (a) => {}", - options: ["as-needed", { requireForBlockBody: true }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ - line: 1, - column: 7, - endColumn: 8, - messageId: "expectedParensBlock", - type - }] - }, - { - code: "async (a) => a", - output: "async a => a", - options: ["as-needed", { requireForBlockBody: true }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ - line: 1, - column: 8, - endColumn: 9, - messageId: "unexpectedParensInline", - type - }] - }, - { - code: "async(a) => a", - output: "async a => a", - options: ["as-needed", { requireForBlockBody: true }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ - line: 1, - column: 7, - endColumn: 8, - messageId: "unexpectedParensInline", - type - }] - }, - { - code: "const f = /** @type {number} */(a)/**hello*/ => a + a;", - options: ["as-needed"], - output: "const f = /** @type {number} */a/**hello*/ => a + a;", - errors: [{ - line: 1, - column: 33, - type, - messageId: "unexpectedParens", - endLine: 1, - endColumn: 34 - }] - }, - { - code: "const f = //\n(a) => a + a;", - output: "const f = //\na => a + a;", - options: ["as-needed"], - errors: [{ - line: 2, - column: 2, - type, - messageId: "unexpectedParens", - endLine: 2, - endColumn: 3 - }] - }, - { - code: "var foo = /**/ a => b;", - output: "var foo = /**/ (a) => b;", - errors: [{ - line: 1, - column: 16, - type: "ArrowFunctionExpression", - messageId: "expectedParens", - endLine: 1, - endColumn: 17 - }] - }, - { - code: "var bar = a /**/ => b;", - output: "var bar = (a) /**/ => b;", - errors: [{ - line: 1, - column: 11, - type: "ArrowFunctionExpression", - messageId: "expectedParens", - endLine: 1, - endColumn: 12 - }] - }, - { - code: `const foo = a => {}; + // "as-needed", { "requireForBlockBody": true } + { + code: "a => {}", + output: "(a) => {}", + options: ["as-needed", { requireForBlockBody: true }], + errors: [ + { + line: 1, + column: 1, + endColumn: 2, + messageId: "expectedParensBlock", + type, + }, + ], + }, + { + code: "(a) => a", + output: "a => a", + options: ["as-needed", { requireForBlockBody: true }], + errors: [ + { + line: 1, + column: 2, + endColumn: 3, + messageId: "unexpectedParensInline", + type, + }, + ], + }, + { + code: "async a => {}", + output: "async (a) => {}", + options: ["as-needed", { requireForBlockBody: true }], + languageOptions: { ecmaVersion: 8 }, + errors: [ + { + line: 1, + column: 7, + endColumn: 8, + messageId: "expectedParensBlock", + type, + }, + ], + }, + { + code: "async (a) => a", + output: "async a => a", + options: ["as-needed", { requireForBlockBody: true }], + languageOptions: { ecmaVersion: 8 }, + errors: [ + { + line: 1, + column: 8, + endColumn: 9, + messageId: "unexpectedParensInline", + type, + }, + ], + }, + { + code: "async(a) => a", + output: "async a => a", + options: ["as-needed", { requireForBlockBody: true }], + languageOptions: { ecmaVersion: 8 }, + errors: [ + { + line: 1, + column: 7, + endColumn: 8, + messageId: "unexpectedParensInline", + type, + }, + ], + }, + { + code: "const f = /** @type {number} */(a)/**hello*/ => a + a;", + options: ["as-needed"], + output: "const f = /** @type {number} */a/**hello*/ => a + a;", + errors: [ + { + line: 1, + column: 33, + type, + messageId: "unexpectedParens", + endLine: 1, + endColumn: 34, + }, + ], + }, + { + code: "const f = //\n(a) => a + a;", + output: "const f = //\na => a + a;", + options: ["as-needed"], + errors: [ + { + line: 2, + column: 2, + type, + messageId: "unexpectedParens", + endLine: 2, + endColumn: 3, + }, + ], + }, + { + code: "var foo = /**/ a => b;", + output: "var foo = /**/ (a) => b;", + errors: [ + { + line: 1, + column: 16, + type: "ArrowFunctionExpression", + messageId: "expectedParens", + endLine: 1, + endColumn: 17, + }, + ], + }, + { + code: "var bar = a /**/ => b;", + output: "var bar = (a) /**/ => b;", + errors: [ + { + line: 1, + column: 11, + type: "ArrowFunctionExpression", + messageId: "expectedParens", + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: `const foo = a => {}; // comment between 'a' and an unrelated closing paren bar();`, - output: `const foo = (a) => {}; + output: `const foo = (a) => {}; // comment between 'a' and an unrelated closing paren bar();`, - errors: [{ - line: 1, - column: 13, - type: "ArrowFunctionExpression", - messageId: "expectedParens", - endLine: 1, - endColumn: 14 - }] - } - + errors: [ + { + line: 1, + column: 13, + type: "ArrowFunctionExpression", + messageId: "expectedParens", + endLine: 1, + endColumn: 14, + }, + ], + }, ]; ruleTester.run("arrow-parens", rule, { - valid, - invalid + valid, + invalid, }); diff --git a/tests/lib/rules/arrow-spacing.js b/tests/lib/rules/arrow-spacing.js index 248c94d1ef87..886be7ffd638 100644 --- a/tests/lib/rules/arrow-spacing.js +++ b/tests/lib/rules/arrow-spacing.js @@ -10,7 +10,7 @@ // const rule = require("../../../lib/rules/arrow-spacing"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -19,295 +19,509 @@ const rule = require("../../../lib/rules/arrow-spacing"), const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 6 } }); const valid = [ - { - code: "a => a", - options: [{ after: true, before: true }] - }, - { - code: "() => {}", - options: [{ after: true, before: true }] - }, - { - code: "(a) => {}", - options: [{ after: true, before: true }] - }, - { - code: "a=> a", - options: [{ after: true, before: false }] - }, - { - code: "()=> {}", - options: [{ after: true, before: false }] - }, - { - code: "(a)=> {}", - options: [{ after: true, before: false }] - }, - { - code: "a =>a", - options: [{ after: false, before: true }] - }, - { - code: "() =>{}", - options: [{ after: false, before: true }] - }, - { - code: "(a) =>{}", - options: [{ after: false, before: true }] - }, - { - code: "a=>a", - options: [{ after: false, before: false }] - }, - { - code: "()=>{}", - options: [{ after: false, before: false }] - }, - { - code: "(a)=>{}", - options: [{ after: false, before: false }] - }, - { - code: "a => a", - options: [{}] - }, - { - code: "() => {}", - options: [{}] - }, - { - code: "(a) => {}", - options: [{}] - }, - "(a) =>\n{}", - "(a) =>\r\n{}", - "(a) =>\n 0" + { + code: "a => a", + options: [{ after: true, before: true }], + }, + { + code: "() => {}", + options: [{ after: true, before: true }], + }, + { + code: "(a) => {}", + options: [{ after: true, before: true }], + }, + { + code: "a=> a", + options: [{ after: true, before: false }], + }, + { + code: "()=> {}", + options: [{ after: true, before: false }], + }, + { + code: "(a)=> {}", + options: [{ after: true, before: false }], + }, + { + code: "a =>a", + options: [{ after: false, before: true }], + }, + { + code: "() =>{}", + options: [{ after: false, before: true }], + }, + { + code: "(a) =>{}", + options: [{ after: false, before: true }], + }, + { + code: "a=>a", + options: [{ after: false, before: false }], + }, + { + code: "()=>{}", + options: [{ after: false, before: false }], + }, + { + code: "(a)=>{}", + options: [{ after: false, before: false }], + }, + { + code: "a => a", + options: [{}], + }, + { + code: "() => {}", + options: [{}], + }, + { + code: "(a) => {}", + options: [{}], + }, + "(a) =>\n{}", + "(a) =>\r\n{}", + "(a) =>\n 0", ]; - const invalid = [ - { - code: "a=>a", - output: "a => a", - options: [{ after: true, before: true }], - errors: [ - { column: 1, line: 1, type: "Identifier", messageId: "expectedBefore" }, - { column: 4, line: 1, type: "Identifier", messageId: "expectedAfter" } - ] - }, - { - code: "()=>{}", - output: "() => {}", - options: [{ after: true, before: true }], - errors: [ - { column: 2, line: 1, type: "Punctuator", messageId: "expectedBefore" }, - { column: 5, line: 1, type: "Punctuator", messageId: "expectedAfter" } - ] - }, - { - code: "(a)=>{}", - output: "(a) => {}", - options: [{ after: true, before: true }], - errors: [ - { column: 3, line: 1, type: "Punctuator", messageId: "expectedBefore" }, - { column: 6, line: 1, type: "Punctuator", messageId: "expectedAfter" } - ] - }, - { - code: "a=> a", - output: "a =>a", - options: [{ after: false, before: true }], - errors: [ - { column: 1, line: 1, type: "Identifier", messageId: "expectedBefore" }, - { column: 5, line: 1, type: "Identifier", messageId: "unexpectedAfter" } - ] - }, - { - code: "()=> {}", - output: "() =>{}", - options: [{ after: false, before: true }], - errors: [ - { column: 2, line: 1, type: "Punctuator", messageId: "expectedBefore" }, - { column: 6, line: 1, type: "Punctuator", messageId: "unexpectedAfter" } - ] - }, - { - code: "(a)=> {}", - output: "(a) =>{}", - options: [{ after: false, before: true }], - errors: [ - { column: 3, line: 1, type: "Punctuator", messageId: "expectedBefore" }, - { column: 7, line: 1, type: "Punctuator", messageId: "unexpectedAfter" } - ] - }, - { - code: "a=> a", - output: "a =>a", - options: [{ after: false, before: true }], - errors: [ - { column: 1, line: 1, type: "Identifier", messageId: "expectedBefore" }, - { column: 6, line: 1, type: "Identifier", messageId: "unexpectedAfter" } - ] - }, - { - code: "()=> {}", - output: "() =>{}", - options: [{ after: false, before: true }], - errors: [ - { column: 2, line: 1, type: "Punctuator", messageId: "expectedBefore" }, - { column: 7, line: 1, type: "Punctuator", messageId: "unexpectedAfter" } - ] - }, - { - code: "(a)=> {}", - output: "(a) =>{}", - options: [{ after: false, before: true }], - errors: [ - { column: 3, line: 1, type: "Punctuator", messageId: "expectedBefore" }, - { column: 8, line: 1, type: "Punctuator", messageId: "unexpectedAfter" } - ] - }, - { - code: "a =>a", - output: "a=> a", - options: [{ after: true, before: false }], - errors: [ - { column: 1, line: 1, type: "Identifier", messageId: "unexpectedBefore" }, - { column: 5, line: 1, type: "Identifier", messageId: "expectedAfter" } - ] - }, - { - code: "() =>{}", - output: "()=> {}", - options: [{ after: true, before: false }], - errors: [ - { column: 2, line: 1, type: "Punctuator", messageId: "unexpectedBefore" }, - { column: 6, line: 1, type: "Punctuator", messageId: "expectedAfter" } - ] - }, - { - code: "(a) =>{}", - output: "(a)=> {}", - options: [{ after: true, before: false }], - errors: [ - { column: 3, line: 1, type: "Punctuator", messageId: "unexpectedBefore" }, - { column: 7, line: 1, type: "Punctuator", messageId: "expectedAfter" } - ] - }, - { - code: "a =>a", - output: "a=> a", - options: [{ after: true, before: false }], - errors: [ - { column: 1, line: 1, type: "Identifier", messageId: "unexpectedBefore" }, - { column: 6, line: 1, type: "Identifier", messageId: "expectedAfter" } - ] - }, - { - code: "() =>{}", - output: "()=> {}", - options: [{ after: true, before: false }], - errors: [ - { column: 2, line: 1, type: "Punctuator", messageId: "unexpectedBefore" }, - { column: 7, line: 1, type: "Punctuator", messageId: "expectedAfter" } - ] - }, - { - code: "(a) =>{}", - output: "(a)=> {}", - options: [{ after: true, before: false }], - errors: [ - { column: 3, line: 1, type: "Punctuator", messageId: "unexpectedBefore" }, - { column: 8, line: 1, type: "Punctuator", messageId: "expectedAfter" } - ] - }, - { - code: "a => a", - output: "a=>a", - options: [{ after: false, before: false }], - errors: [ - { column: 1, line: 1, type: "Identifier", messageId: "unexpectedBefore" }, - { column: 6, line: 1, type: "Identifier", messageId: "unexpectedAfter" } - ] - }, - { - code: "() => {}", - output: "()=>{}", - options: [{ after: false, before: false }], - errors: [ - { column: 2, line: 1, type: "Punctuator", messageId: "unexpectedBefore" }, - { column: 7, line: 1, type: "Punctuator", messageId: "unexpectedAfter" } - ] - }, - { - code: "(a) => {}", - output: "(a)=>{}", - options: [{ after: false, before: false }], - errors: [ - { column: 3, line: 1, type: "Punctuator", messageId: "unexpectedBefore" }, - { column: 8, line: 1, type: "Punctuator", messageId: "unexpectedAfter" } - ] - }, - { - code: "a => a", - output: "a=>a", - options: [{ after: false, before: false }], - errors: [ - { column: 1, line: 1, type: "Identifier", messageId: "unexpectedBefore" }, - { column: 8, line: 1, type: "Identifier", messageId: "unexpectedAfter" } - ] - }, - { - code: "() => {}", - output: "()=>{}", - options: [{ after: false, before: false }], - errors: [ - { column: 2, line: 1, type: "Punctuator", messageId: "unexpectedBefore" }, - { column: 9, line: 1, type: "Punctuator", messageId: "unexpectedAfter" } - ] - }, - { - code: "(a) => {}", - output: "(a)=>{}", - options: [{ after: false, before: false }], - errors: [ - { column: 3, line: 1, type: "Punctuator", messageId: "unexpectedBefore" }, - { column: 10, line: 1, type: "Punctuator", messageId: "unexpectedAfter" } - ] - }, - { - code: "(a) =>\n{}", - output: "(a) =>{}", - options: [{ after: false }], - errors: [ - { column: 1, line: 2, type: "Punctuator", messageId: "unexpectedAfter" } - ] - }, + { + code: "a=>a", + output: "a => a", + options: [{ after: true, before: true }], + errors: [ + { + column: 1, + line: 1, + type: "Identifier", + messageId: "expectedBefore", + }, + { + column: 4, + line: 1, + type: "Identifier", + messageId: "expectedAfter", + }, + ], + }, + { + code: "()=>{}", + output: "() => {}", + options: [{ after: true, before: true }], + errors: [ + { + column: 2, + line: 1, + type: "Punctuator", + messageId: "expectedBefore", + }, + { + column: 5, + line: 1, + type: "Punctuator", + messageId: "expectedAfter", + }, + ], + }, + { + code: "(a)=>{}", + output: "(a) => {}", + options: [{ after: true, before: true }], + errors: [ + { + column: 3, + line: 1, + type: "Punctuator", + messageId: "expectedBefore", + }, + { + column: 6, + line: 1, + type: "Punctuator", + messageId: "expectedAfter", + }, + ], + }, + { + code: "a=> a", + output: "a =>a", + options: [{ after: false, before: true }], + errors: [ + { + column: 1, + line: 1, + type: "Identifier", + messageId: "expectedBefore", + }, + { + column: 5, + line: 1, + type: "Identifier", + messageId: "unexpectedAfter", + }, + ], + }, + { + code: "()=> {}", + output: "() =>{}", + options: [{ after: false, before: true }], + errors: [ + { + column: 2, + line: 1, + type: "Punctuator", + messageId: "expectedBefore", + }, + { + column: 6, + line: 1, + type: "Punctuator", + messageId: "unexpectedAfter", + }, + ], + }, + { + code: "(a)=> {}", + output: "(a) =>{}", + options: [{ after: false, before: true }], + errors: [ + { + column: 3, + line: 1, + type: "Punctuator", + messageId: "expectedBefore", + }, + { + column: 7, + line: 1, + type: "Punctuator", + messageId: "unexpectedAfter", + }, + ], + }, + { + code: "a=> a", + output: "a =>a", + options: [{ after: false, before: true }], + errors: [ + { + column: 1, + line: 1, + type: "Identifier", + messageId: "expectedBefore", + }, + { + column: 6, + line: 1, + type: "Identifier", + messageId: "unexpectedAfter", + }, + ], + }, + { + code: "()=> {}", + output: "() =>{}", + options: [{ after: false, before: true }], + errors: [ + { + column: 2, + line: 1, + type: "Punctuator", + messageId: "expectedBefore", + }, + { + column: 7, + line: 1, + type: "Punctuator", + messageId: "unexpectedAfter", + }, + ], + }, + { + code: "(a)=> {}", + output: "(a) =>{}", + options: [{ after: false, before: true }], + errors: [ + { + column: 3, + line: 1, + type: "Punctuator", + messageId: "expectedBefore", + }, + { + column: 8, + line: 1, + type: "Punctuator", + messageId: "unexpectedAfter", + }, + ], + }, + { + code: "a =>a", + output: "a=> a", + options: [{ after: true, before: false }], + errors: [ + { + column: 1, + line: 1, + type: "Identifier", + messageId: "unexpectedBefore", + }, + { + column: 5, + line: 1, + type: "Identifier", + messageId: "expectedAfter", + }, + ], + }, + { + code: "() =>{}", + output: "()=> {}", + options: [{ after: true, before: false }], + errors: [ + { + column: 2, + line: 1, + type: "Punctuator", + messageId: "unexpectedBefore", + }, + { + column: 6, + line: 1, + type: "Punctuator", + messageId: "expectedAfter", + }, + ], + }, + { + code: "(a) =>{}", + output: "(a)=> {}", + options: [{ after: true, before: false }], + errors: [ + { + column: 3, + line: 1, + type: "Punctuator", + messageId: "unexpectedBefore", + }, + { + column: 7, + line: 1, + type: "Punctuator", + messageId: "expectedAfter", + }, + ], + }, + { + code: "a =>a", + output: "a=> a", + options: [{ after: true, before: false }], + errors: [ + { + column: 1, + line: 1, + type: "Identifier", + messageId: "unexpectedBefore", + }, + { + column: 6, + line: 1, + type: "Identifier", + messageId: "expectedAfter", + }, + ], + }, + { + code: "() =>{}", + output: "()=> {}", + options: [{ after: true, before: false }], + errors: [ + { + column: 2, + line: 1, + type: "Punctuator", + messageId: "unexpectedBefore", + }, + { + column: 7, + line: 1, + type: "Punctuator", + messageId: "expectedAfter", + }, + ], + }, + { + code: "(a) =>{}", + output: "(a)=> {}", + options: [{ after: true, before: false }], + errors: [ + { + column: 3, + line: 1, + type: "Punctuator", + messageId: "unexpectedBefore", + }, + { + column: 8, + line: 1, + type: "Punctuator", + messageId: "expectedAfter", + }, + ], + }, + { + code: "a => a", + output: "a=>a", + options: [{ after: false, before: false }], + errors: [ + { + column: 1, + line: 1, + type: "Identifier", + messageId: "unexpectedBefore", + }, + { + column: 6, + line: 1, + type: "Identifier", + messageId: "unexpectedAfter", + }, + ], + }, + { + code: "() => {}", + output: "()=>{}", + options: [{ after: false, before: false }], + errors: [ + { + column: 2, + line: 1, + type: "Punctuator", + messageId: "unexpectedBefore", + }, + { + column: 7, + line: 1, + type: "Punctuator", + messageId: "unexpectedAfter", + }, + ], + }, + { + code: "(a) => {}", + output: "(a)=>{}", + options: [{ after: false, before: false }], + errors: [ + { + column: 3, + line: 1, + type: "Punctuator", + messageId: "unexpectedBefore", + }, + { + column: 8, + line: 1, + type: "Punctuator", + messageId: "unexpectedAfter", + }, + ], + }, + { + code: "a => a", + output: "a=>a", + options: [{ after: false, before: false }], + errors: [ + { + column: 1, + line: 1, + type: "Identifier", + messageId: "unexpectedBefore", + }, + { + column: 8, + line: 1, + type: "Identifier", + messageId: "unexpectedAfter", + }, + ], + }, + { + code: "() => {}", + output: "()=>{}", + options: [{ after: false, before: false }], + errors: [ + { + column: 2, + line: 1, + type: "Punctuator", + messageId: "unexpectedBefore", + }, + { + column: 9, + line: 1, + type: "Punctuator", + messageId: "unexpectedAfter", + }, + ], + }, + { + code: "(a) => {}", + output: "(a)=>{}", + options: [{ after: false, before: false }], + errors: [ + { + column: 3, + line: 1, + type: "Punctuator", + messageId: "unexpectedBefore", + }, + { + column: 10, + line: 1, + type: "Punctuator", + messageId: "unexpectedAfter", + }, + ], + }, + { + code: "(a) =>\n{}", + output: "(a) =>{}", + options: [{ after: false }], + errors: [ + { + column: 1, + line: 2, + type: "Punctuator", + messageId: "unexpectedAfter", + }, + ], + }, - // https://github.com/eslint/eslint/issues/7079 - { - code: "(a = ()=>0)=>1", - output: "(a = () => 0) => 1", - errors: [ - { column: 7, line: 1, messageId: "expectedBefore" }, - { column: 10, line: 1, messageId: "expectedAfter" }, - { column: 11, line: 1, messageId: "expectedBefore" }, - { column: 14, line: 1, messageId: "expectedAfter" } - ] - }, - { - code: "(a = ()=>0)=>(1)", - output: "(a = () => 0) => (1)", - errors: [ - { column: 7, line: 1, messageId: "expectedBefore" }, - { column: 10, line: 1, messageId: "expectedAfter" }, - { column: 11, line: 1, messageId: "expectedBefore" }, - { column: 14, line: 1, messageId: "expectedAfter" } - ] - } + // https://github.com/eslint/eslint/issues/7079 + { + code: "(a = ()=>0)=>1", + output: "(a = () => 0) => 1", + errors: [ + { column: 7, line: 1, messageId: "expectedBefore" }, + { column: 10, line: 1, messageId: "expectedAfter" }, + { column: 11, line: 1, messageId: "expectedBefore" }, + { column: 14, line: 1, messageId: "expectedAfter" }, + ], + }, + { + code: "(a = ()=>0)=>(1)", + output: "(a = () => 0) => (1)", + errors: [ + { column: 7, line: 1, messageId: "expectedBefore" }, + { column: 10, line: 1, messageId: "expectedAfter" }, + { column: 11, line: 1, messageId: "expectedBefore" }, + { column: 14, line: 1, messageId: "expectedAfter" }, + ], + }, ]; ruleTester.run("arrow-spacing", rule, { - valid, - invalid + valid, + invalid, }); diff --git a/tests/lib/rules/block-scoped-var.js b/tests/lib/rules/block-scoped-var.js index 4fd456616603..d76554408d79 100644 --- a/tests/lib/rules/block-scoped-var.js +++ b/tests/lib/rules/block-scoped-var.js @@ -10,458 +10,596 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/block-scoped-var"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester({ - languageOptions: { ecmaVersion: 5, sourceType: "script" } + languageOptions: { ecmaVersion: 5, sourceType: "script" }, }); ruleTester.run("block-scoped-var", rule, { - valid: [ + valid: [ + // See issue https://github.com/eslint/eslint/issues/2242 + { + code: "function f() { } f(); var exports = { f: f };", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var f = () => {}; f(); var exports = { f: f };", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + "!function f(){ f; }", + "function f() { } f(); var exports = { f: f };", + "function f() { var a, b; { a = true; } b = a; }", + "var a; function f() { var b = a; }", + "function f(a) { }", + "!function(a) { };", + "!function f(a) { };", + "function f(a) { var b = a; }", + "!function f(a) { var b = a; };", + "function f() { var g = f; }", + "function f() { } function g() { var f = g; }", + "function f() { var hasOwnProperty; { hasOwnProperty; } }", + "function f(){ a; b; var a, b; }", + "function f(){ g(); function g(){} }", + "if (true) { var a = 1; a; }", + "var a; if (true) { a; }", + "for (var i = 0; i < 10; i++) { i; }", + "var i; for(i; i; i) { i; }", + { + code: 'function myFunc(foo) { "use strict"; var { bar } = foo; bar.hello();}', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'function myFunc(foo) { "use strict"; var [ bar ] = foo; bar.hello();}', + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function myFunc(...foo) { return foo;}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var f = () => { var g = f; }", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class Foo {}\nexport default Foo;", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { code: "new Date", languageOptions: { globals: { Date: false } } }, + { code: "new Date", languageOptions: { globals: {} } }, + { + code: "var eslint = require('eslint');", + languageOptions: { globals: { require: false } }, + }, + { + code: "var fun = function({x}) {return x;};", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var fun = function([,x]) {return x;};", + languageOptions: { ecmaVersion: 6 }, + }, + "function f(a) { return a.b; }", + 'var a = { "foo": 3 };', + "var a = { foo: 3 };", + "var a = { foo: 3, bar: 5 };", + "var a = { set foo(a){}, get bar(){} };", + "function f(a) { return arguments[0]; }", + "function f() { }; var a = f;", + "var a = f; function f() { };", + "function f(){ for(var i; i; i) i; }", + "function f(){ for(var a=0, b=1; a; b) a, b; }", + "function f(){ for(var a in {}) a; }", + "function f(){ switch(2) { case 1: var b = 2; b; break; default: b; break;} }", + "a:;", + "foo: while (true) { bar: for (var i = 0; i < 13; ++i) {if (i === 7) break foo; } }", + "foo: while (true) { bar: for (var i = 0; i < 13; ++i) {if (i === 7) continue foo; } }", + { + code: 'const React = require("react/addons");const cx = React.addons.classSet;', + languageOptions: { + ecmaVersion: 6, + sourceType: "module", + globals: { require: false }, + }, + }, + { + code: "var v = 1; function x() { return v; };", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'import * as y from "./other.js"; y();', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: 'import y from "./other.js"; y();', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: 'import {x as y} from "./other.js"; y();', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "var x; export {x};", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "var x; export {x as v};", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: 'export {x} from "./other.js";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: 'export {x as v} from "./other.js";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "class Test { myFunction() { return true; }}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class Test { get flag() { return true; }}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var Test = class { myFunction() { return true; }}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var doStuff; let {x: y} = {x: 1}; doStuff(y);", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({x: y}) { return y; }", + languageOptions: { ecmaVersion: 6 }, + }, - // See issue https://github.com/eslint/eslint/issues/2242 - { code: "function f() { } f(); var exports = { f: f };", languageOptions: { ecmaVersion: 6 } }, - { code: "var f = () => {}; f(); var exports = { f: f };", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, - "!function f(){ f; }", - "function f() { } f(); var exports = { f: f };", - "function f() { var a, b; { a = true; } b = a; }", - "var a; function f() { var b = a; }", - "function f(a) { }", - "!function(a) { };", - "!function f(a) { };", - "function f(a) { var b = a; }", - "!function f(a) { var b = a; };", - "function f() { var g = f; }", - "function f() { } function g() { var f = g; }", - "function f() { var hasOwnProperty; { hasOwnProperty; } }", - "function f(){ a; b; var a, b; }", - "function f(){ g(); function g(){} }", - "if (true) { var a = 1; a; }", - "var a; if (true) { a; }", - "for (var i = 0; i < 10; i++) { i; }", - "var i; for(i; i; i) { i; }", - { code: "function myFunc(foo) { \"use strict\"; var { bar } = foo; bar.hello();}", languageOptions: { ecmaVersion: 6 } }, - { code: "function myFunc(foo) { \"use strict\"; var [ bar ] = foo; bar.hello();}", languageOptions: { ecmaVersion: 6 } }, - { code: "function myFunc(...foo) { return foo;}", languageOptions: { ecmaVersion: 6 } }, - { code: "var f = () => { var g = f; }", languageOptions: { ecmaVersion: 6 } }, - { code: "class Foo {}\nexport default Foo;", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, - { code: "new Date", languageOptions: { globals: { Date: false } } }, - { code: "new Date", languageOptions: { globals: {} } }, - { code: "var eslint = require('eslint');", languageOptions: { globals: { require: false } } }, - { code: "var fun = function({x}) {return x;};", languageOptions: { ecmaVersion: 6 } }, - { code: "var fun = function([,x]) {return x;};", languageOptions: { ecmaVersion: 6 } }, - "function f(a) { return a.b; }", - "var a = { \"foo\": 3 };", - "var a = { foo: 3 };", - "var a = { foo: 3, bar: 5 };", - "var a = { set foo(a){}, get bar(){} };", - "function f(a) { return arguments[0]; }", - "function f() { }; var a = f;", - "var a = f; function f() { };", - "function f(){ for(var i; i; i) i; }", - "function f(){ for(var a=0, b=1; a; b) a, b; }", - "function f(){ for(var a in {}) a; }", - "function f(){ switch(2) { case 1: var b = 2; b; break; default: b; break;} }", - "a:;", - "foo: while (true) { bar: for (var i = 0; i < 13; ++i) {if (i === 7) break foo; } }", - "foo: while (true) { bar: for (var i = 0; i < 13; ++i) {if (i === 7) continue foo; } }", - { code: "const React = require(\"react/addons\");const cx = React.addons.classSet;", languageOptions: { ecmaVersion: 6, sourceType: "module", globals: { require: false } } }, - { code: "var v = 1; function x() { return v; };", languageOptions: { ecmaVersion: 6 } }, - { code: "import * as y from \"./other.js\"; y();", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, - { code: "import y from \"./other.js\"; y();", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, - { code: "import {x as y} from \"./other.js\"; y();", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, - { code: "var x; export {x};", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, - { code: "var x; export {x as v};", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, - { code: "export {x} from \"./other.js\";", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, - { code: "export {x as v} from \"./other.js\";", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, - { code: "class Test { myFunction() { return true; }}", languageOptions: { ecmaVersion: 6 } }, - { code: "class Test { get flag() { return true; }}", languageOptions: { ecmaVersion: 6 } }, - { code: "var Test = class { myFunction() { return true; }}", languageOptions: { ecmaVersion: 6 } }, - { code: "var doStuff; let {x: y} = {x: 1}; doStuff(y);", languageOptions: { ecmaVersion: 6 } }, - { code: "function foo({x: y}) { return y; }", languageOptions: { ecmaVersion: 6 } }, + // those are the same as `no-undef`. + "!function f(){}; f", + "var f = function foo() { }; foo(); var exports = { f: foo };", + { code: "var f = () => { x; }", languageOptions: { ecmaVersion: 6 } }, + "function f(){ x; }", + "var eslint = require('eslint');", + "function f(a) { return a[b]; }", + "function f() { return b.a; }", + "var a = { foo: bar };", + "var a = { foo: foo };", + "var a = { bar: 7, foo: bar };", + "var a = arguments;", + "function x(){}; var a = arguments;", + "function z(b){}; var a = b;", + "function z(){var b;}; var a = b;", + "function f(){ try{}catch(e){} e }", + "a:b;", - // those are the same as `no-undef`. - "!function f(){}; f", - "var f = function foo() { }; foo(); var exports = { f: foo };", - { code: "var f = () => { x; }", languageOptions: { ecmaVersion: 6 } }, - "function f(){ x; }", - "var eslint = require('eslint');", - "function f(a) { return a[b]; }", - "function f() { return b.a; }", - "var a = { foo: bar };", - "var a = { foo: foo };", - "var a = { bar: 7, foo: bar };", - "var a = arguments;", - "function x(){}; var a = arguments;", - "function z(b){}; var a = b;", - "function z(){var b;}; var a = b;", - "function f(){ try{}catch(e){} e }", - "a:b;", + // https://github.com/eslint/eslint/issues/2253 + { + code: "/*global React*/ let {PropTypes, addons: {PureRenderMixin}} = React; let Test = React.createClass({mixins: [PureRenderMixin]});", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "/*global prevState*/ const { virtualSize: prevVirtualSize = 0 } = prevState;", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "const { dummy: { data, isLoading }, auth: { isLoggedIn } } = this.props;", + languageOptions: { ecmaVersion: 6 }, + }, - // https://github.com/eslint/eslint/issues/2253 - { code: "/*global React*/ let {PropTypes, addons: {PureRenderMixin}} = React; let Test = React.createClass({mixins: [PureRenderMixin]});", languageOptions: { ecmaVersion: 6 } }, - { code: "/*global prevState*/ const { virtualSize: prevVirtualSize = 0 } = prevState;", languageOptions: { ecmaVersion: 6 } }, - { code: "const { dummy: { data, isLoading }, auth: { isLoggedIn } } = this.props;", languageOptions: { ecmaVersion: 6 } }, + // https://github.com/eslint/eslint/issues/2747 + 'function a(n) { return n > 0 ? b(n - 1) : "a"; } function b(n) { return n > 0 ? a(n - 1) : "b"; }', - // https://github.com/eslint/eslint/issues/2747 - "function a(n) { return n > 0 ? b(n - 1) : \"a\"; } function b(n) { return n > 0 ? a(n - 1) : \"b\"; }", + // https://github.com/eslint/eslint/issues/2967 + "(function () { foo(); })(); function foo() {}", + { + code: "(function () { foo(); })(); function foo() {}", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, - // https://github.com/eslint/eslint/issues/2967 - "(function () { foo(); })(); function foo() {}", - { code: "(function () { foo(); })(); function foo() {}", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, - - { code: "class C { static { var foo; foo; } }", languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { foo; var foo; } }", languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { if (bar) { foo; } var foo; } }", languageOptions: { ecmaVersion: 2022 } }, - { code: "var foo; class C { static { foo; } } ", languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { foo; } } var foo;", languageOptions: { ecmaVersion: 2022 } }, - { code: "var foo; class C { static {} [foo]; } ", languageOptions: { ecmaVersion: 2022 } }, - { code: "foo; class C { static {} } var foo; ", languageOptions: { ecmaVersion: 2022 } } - ], - invalid: [ - { - code: "function f(){ x; { var x; } }", - errors: [{ - messageId: "outOfScope", - data: { - name: "x", - definitionLine: 1, - definitionColumn: 24 - }, - line: 1, - column: 15, - type: "Identifier" - }] - }, - { - code: "function f(){ { var x; } x; }", - errors: [{ - messageId: "outOfScope", - data: { - name: "x", - definitionLine: 1, - definitionColumn: 21 - }, - line: 1, - column: 26, - type: "Identifier" - }] - }, - { - code: "function f() { var a; { var b = 0; } a = b; }", - errors: [{ - messageId: "outOfScope", - data: { - name: "b", - definitionLine: 1, - definitionColumn: 29 - }, - line: 1, - column: 42, - type: "Identifier" - }] - }, - { - code: "function f() { try { var a = 0; } catch (e) { var b = a; } }", - errors: [{ - messageId: "outOfScope", - data: { - name: "a", - definitionLine: 1, - definitionColumn: 26 - }, - line: 1, - column: 55, - type: "Identifier" - }] - }, - { - code: "function a() { for(var b in {}) { var c = b; } c; }", - errors: [{ - messageId: "outOfScope", - data: { - name: "c", - definitionLine: 1, - definitionColumn: 39 - }, - line: 1, - column: 48, - type: "Identifier" - }] - }, - { - code: "function a() { for(var b of {}) { var c = b; } c; }", - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "outOfScope", - data: { - name: "c", - definitionLine: 1, - definitionColumn: 39 - }, - line: 1, - column: 48, - type: "Identifier" - }] - }, - { - code: "function f(){ switch(2) { case 1: var b = 2; b; break; default: b; break;} b; }", - errors: [{ - messageId: "outOfScope", - data: { - name: "b", - definitionLine: 1, - definitionColumn: 39 - }, - line: 1, - column: 76, - type: "Identifier" - }] - }, - { - code: "for (var a = 0;;) {} a;", - errors: [{ - messageId: "outOfScope", - data: { - name: "a", - definitionLine: 1, - definitionColumn: 10 - }, - line: 1, - column: 22, - type: "Identifier" - }] - }, - { - code: "for (var a in []) {} a;", - errors: [{ - messageId: "outOfScope", - data: { - name: "a", - definitionLine: 1, - definitionColumn: 10 - }, - line: 1, - column: 22, - type: "Identifier" - }] - }, - { - code: "for (var a of []) {} a;", - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "outOfScope", - data: { - name: "a", - definitionLine: 1, - definitionColumn: 10 - }, - line: 1, - column: 22, - type: "Identifier" - }] - }, - { - code: "{ var a = 0; } a;", - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "outOfScope", - data: { - name: "a", - definitionLine: 1, - definitionColumn: 7 - }, - line: 1, - column: 16, - type: "Identifier" - }] - }, - { - code: "if (true) { var a; } a;", - errors: [{ - messageId: "outOfScope", - data: { - name: "a", - definitionLine: 1, - definitionColumn: 17 - }, - line: 1, - column: 22, - type: "Identifier" - }] - }, - { - code: "if (true) { var a = 1; } else { var a = 2; }", - errors: [ - { - messageId: "outOfScope", - data: { - name: "a", - definitionLine: 1, - definitionColumn: 37 - }, - line: 1, - column: 17, - type: "Identifier" - }, - { - messageId: "outOfScope", - data: { - name: "a", - definitionLine: 1, - definitionColumn: 17 - }, - line: 1, - column: 37, - type: "Identifier" - } - ] - }, - { - code: "for (var i = 0;;) {} for(var i = 0;;) {}", - errors: [ - { - messageId: "outOfScope", - data: { - name: "i", - definitionLine: 1, - definitionColumn: 30 - }, - line: 1, - column: 10, - type: "Identifier" - }, - { - messageId: "outOfScope", - data: { - name: "i", - definitionLine: 1, - definitionColumn: 10 - }, - line: 1, - column: 30, - type: "Identifier" - } - ] - }, - { - code: "class C { static { if (bar) { var foo; } foo; } }", - languageOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "outOfScope", - data: { - name: "foo", - definitionLine: 1, - definitionColumn: 35 - }, - line: 1, - column: 42, - type: "Identifier" - }] - }, - { - code: "{ var foo,\n bar; } bar;", - errors: [{ - messageId: "outOfScope", - data: { - name: "bar", - definitionLine: 2, - definitionColumn: 3 - }, - line: 2, - column: 10, - type: "Identifier" - }] - }, - { - code: "{ var { foo,\n bar } = baz; } bar;", - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "outOfScope", - data: { - name: "bar", - definitionLine: 2, - definitionColumn: 3 - }, - line: 2, - column: 18, - type: "Identifier" - }] - }, - { - code: "if (foo) { var a = 1; } else if (bar) { var a = 2; } else { var a = 3; }", - errors: [ - { - messageId: "outOfScope", - data: { - name: "a", - definitionLine: 1, - definitionColumn: 45 - }, - line: 1, - column: 16, - type: "Identifier" - }, - { - messageId: "outOfScope", - data: { - name: "a", - definitionLine: 1, - definitionColumn: 65 - }, - line: 1, - column: 16, - type: "Identifier" - }, - { - messageId: "outOfScope", - data: { - name: "a", - definitionLine: 1, - definitionColumn: 16 - }, - line: 1, - column: 45, - type: "Identifier" - }, - { - messageId: "outOfScope", - data: { - name: "a", - definitionLine: 1, - definitionColumn: 65 - }, - line: 1, - column: 45, - type: "Identifier" - }, - { - messageId: "outOfScope", - data: { - name: "a", - definitionLine: 1, - definitionColumn: 16 - }, - line: 1, - column: 65, - type: "Identifier" - }, - { - messageId: "outOfScope", - data: { - name: "a", - definitionLine: 1, - definitionColumn: 45 - }, - line: 1, - column: 65, - type: "Identifier" - } - ] - } - ] + { + code: "class C { static { var foo; foo; } }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { foo; var foo; } }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { if (bar) { foo; } var foo; } }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "var foo; class C { static { foo; } } ", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { foo; } } var foo;", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "var foo; class C { static {} [foo]; } ", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "foo; class C { static {} } var foo; ", + languageOptions: { ecmaVersion: 2022 }, + }, + ], + invalid: [ + { + code: "function f(){ x; { var x; } }", + errors: [ + { + messageId: "outOfScope", + data: { + name: "x", + definitionLine: 1, + definitionColumn: 24, + }, + line: 1, + column: 15, + type: "Identifier", + }, + ], + }, + { + code: "function f(){ { var x; } x; }", + errors: [ + { + messageId: "outOfScope", + data: { + name: "x", + definitionLine: 1, + definitionColumn: 21, + }, + line: 1, + column: 26, + type: "Identifier", + }, + ], + }, + { + code: "function f() { var a; { var b = 0; } a = b; }", + errors: [ + { + messageId: "outOfScope", + data: { + name: "b", + definitionLine: 1, + definitionColumn: 29, + }, + line: 1, + column: 42, + type: "Identifier", + }, + ], + }, + { + code: "function f() { try { var a = 0; } catch (e) { var b = a; } }", + errors: [ + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 26, + }, + line: 1, + column: 55, + type: "Identifier", + }, + ], + }, + { + code: "function a() { for(var b in {}) { var c = b; } c; }", + errors: [ + { + messageId: "outOfScope", + data: { + name: "c", + definitionLine: 1, + definitionColumn: 39, + }, + line: 1, + column: 48, + type: "Identifier", + }, + ], + }, + { + code: "function a() { for(var b of {}) { var c = b; } c; }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "outOfScope", + data: { + name: "c", + definitionLine: 1, + definitionColumn: 39, + }, + line: 1, + column: 48, + type: "Identifier", + }, + ], + }, + { + code: "function f(){ switch(2) { case 1: var b = 2; b; break; default: b; break;} b; }", + errors: [ + { + messageId: "outOfScope", + data: { + name: "b", + definitionLine: 1, + definitionColumn: 39, + }, + line: 1, + column: 76, + type: "Identifier", + }, + ], + }, + { + code: "for (var a = 0;;) {} a;", + errors: [ + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 10, + }, + line: 1, + column: 22, + type: "Identifier", + }, + ], + }, + { + code: "for (var a in []) {} a;", + errors: [ + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 10, + }, + line: 1, + column: 22, + type: "Identifier", + }, + ], + }, + { + code: "for (var a of []) {} a;", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 10, + }, + line: 1, + column: 22, + type: "Identifier", + }, + ], + }, + { + code: "{ var a = 0; } a;", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 7, + }, + line: 1, + column: 16, + type: "Identifier", + }, + ], + }, + { + code: "if (true) { var a; } a;", + errors: [ + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 17, + }, + line: 1, + column: 22, + type: "Identifier", + }, + ], + }, + { + code: "if (true) { var a = 1; } else { var a = 2; }", + errors: [ + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 37, + }, + line: 1, + column: 17, + type: "Identifier", + }, + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 17, + }, + line: 1, + column: 37, + type: "Identifier", + }, + ], + }, + { + code: "for (var i = 0;;) {} for(var i = 0;;) {}", + errors: [ + { + messageId: "outOfScope", + data: { + name: "i", + definitionLine: 1, + definitionColumn: 30, + }, + line: 1, + column: 10, + type: "Identifier", + }, + { + messageId: "outOfScope", + data: { + name: "i", + definitionLine: 1, + definitionColumn: 10, + }, + line: 1, + column: 30, + type: "Identifier", + }, + ], + }, + { + code: "class C { static { if (bar) { var foo; } foo; } }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "outOfScope", + data: { + name: "foo", + definitionLine: 1, + definitionColumn: 35, + }, + line: 1, + column: 42, + type: "Identifier", + }, + ], + }, + { + code: "{ var foo,\n bar; } bar;", + errors: [ + { + messageId: "outOfScope", + data: { + name: "bar", + definitionLine: 2, + definitionColumn: 3, + }, + line: 2, + column: 10, + type: "Identifier", + }, + ], + }, + { + code: "{ var { foo,\n bar } = baz; } bar;", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "outOfScope", + data: { + name: "bar", + definitionLine: 2, + definitionColumn: 3, + }, + line: 2, + column: 18, + type: "Identifier", + }, + ], + }, + { + code: "if (foo) { var a = 1; } else if (bar) { var a = 2; } else { var a = 3; }", + errors: [ + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 45, + }, + line: 1, + column: 16, + type: "Identifier", + }, + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 65, + }, + line: 1, + column: 16, + type: "Identifier", + }, + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 16, + }, + line: 1, + column: 45, + type: "Identifier", + }, + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 65, + }, + line: 1, + column: 45, + type: "Identifier", + }, + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 16, + }, + line: 1, + column: 65, + type: "Identifier", + }, + { + messageId: "outOfScope", + data: { + name: "a", + definitionLine: 1, + definitionColumn: 45, + }, + line: 1, + column: 65, + type: "Identifier", + }, + ], + }, + ], }); diff --git a/tests/lib/rules/block-spacing.js b/tests/lib/rules/block-spacing.js index 4ec40f87bec7..db87cbc9c78f 100644 --- a/tests/lib/rules/block-spacing.js +++ b/tests/lib/rules/block-spacing.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/block-spacing"); -const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); +const RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -19,1147 +19,1384 @@ const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); const ruleTester = new RuleTester(); ruleTester.run("block-spacing", rule, { - valid: [ + valid: [ + // default/always + { code: "{ foo(); }", options: ["always"] }, + "{ foo(); }", + "{ foo();\n}", + "{\nfoo(); }", + "{\r\nfoo();\r\n}", + "if (a) { foo(); }", + "if (a) {} else { foo(); }", + "switch (a) {}", + "switch (a) { case 0: foo(); }", + "while (a) { foo(); }", + "do { foo(); } while (a);", + "for (;;) { foo(); }", + "for (var a in b) { foo(); }", + { + code: "for (var a of b) { foo(); }", + languageOptions: { ecmaVersion: 6 }, + }, + "try { foo(); } catch (e) { foo(); }", + "function foo() { bar(); }", + "(function() { bar(); });", + { code: "(() => { bar(); });", languageOptions: { ecmaVersion: 6 } }, + "if (a) { /* comment */ foo(); /* comment */ }", + "if (a) { //comment\n foo(); }", + { + code: "class C { static {} }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { foo; } }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { /* comment */foo;/* comment */ } }", + languageOptions: { ecmaVersion: 2022 }, + }, - // default/always - { code: "{ foo(); }", options: ["always"] }, - "{ foo(); }", - "{ foo();\n}", - "{\nfoo(); }", - "{\r\nfoo();\r\n}", - "if (a) { foo(); }", - "if (a) {} else { foo(); }", - "switch (a) {}", - "switch (a) { case 0: foo(); }", - "while (a) { foo(); }", - "do { foo(); } while (a);", - "for (;;) { foo(); }", - "for (var a in b) { foo(); }", - { code: "for (var a of b) { foo(); }", languageOptions: { ecmaVersion: 6 } }, - "try { foo(); } catch (e) { foo(); }", - "function foo() { bar(); }", - "(function() { bar(); });", - { code: "(() => { bar(); });", languageOptions: { ecmaVersion: 6 } }, - "if (a) { /* comment */ foo(); /* comment */ }", - "if (a) { //comment\n foo(); }", - { code: "class C { static {} }", languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { foo; } }", languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { /* comment */foo;/* comment */ } }", languageOptions: { ecmaVersion: 2022 } }, + // never + { code: "{foo();}", options: ["never"] }, + { code: "{foo();\n}", options: ["never"] }, + { code: "{\nfoo();}", options: ["never"] }, + { code: "{\r\nfoo();\r\n}", options: ["never"] }, + { code: "if (a) {foo();}", options: ["never"] }, + { code: "if (a) {} else {foo();}", options: ["never"] }, + { code: "switch (a) {}", options: ["never"] }, + { code: "switch (a) {case 0: foo();}", options: ["never"] }, + { code: "while (a) {foo();}", options: ["never"] }, + { code: "do {foo();} while (a);", options: ["never"] }, + { code: "for (;;) {foo();}", options: ["never"] }, + { code: "for (var a in b) {foo();}", options: ["never"] }, + { + code: "for (var a of b) {foo();}", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { code: "try {foo();} catch (e) {foo();}", options: ["never"] }, + { code: "function foo() {bar();}", options: ["never"] }, + { code: "(function() {bar();});", options: ["never"] }, + { + code: "(() => {bar();});", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "if (a) {/* comment */ foo(); /* comment */}", + options: ["never"], + }, + { code: "if (a) { //comment\n foo();}", options: ["never"] }, + { + code: "class C { static { } }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static {foo;} }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static {/* comment */ foo; /* comment */} }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { // line comment is allowed\n foo;\n} }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static {\nfoo;\n} }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { \n foo; \n } }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + ], - // never - { code: "{foo();}", options: ["never"] }, - { code: "{foo();\n}", options: ["never"] }, - { code: "{\nfoo();}", options: ["never"] }, - { code: "{\r\nfoo();\r\n}", options: ["never"] }, - { code: "if (a) {foo();}", options: ["never"] }, - { code: "if (a) {} else {foo();}", options: ["never"] }, - { code: "switch (a) {}", options: ["never"] }, - { code: "switch (a) {case 0: foo();}", options: ["never"] }, - { code: "while (a) {foo();}", options: ["never"] }, - { code: "do {foo();} while (a);", options: ["never"] }, - { code: "for (;;) {foo();}", options: ["never"] }, - { code: "for (var a in b) {foo();}", options: ["never"] }, - { code: "for (var a of b) {foo();}", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "try {foo();} catch (e) {foo();}", options: ["never"] }, - { code: "function foo() {bar();}", options: ["never"] }, - { code: "(function() {bar();});", options: ["never"] }, - { code: "(() => {bar();});", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "if (a) {/* comment */ foo(); /* comment */}", options: ["never"] }, - { code: "if (a) { //comment\n foo();}", options: ["never"] }, - { code: "class C { static { } }", options: ["never"], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static {foo;} }", options: ["never"], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static {/* comment */ foo; /* comment */} }", options: ["never"], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { // line comment is allowed\n foo;\n} }", options: ["never"], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static {\nfoo;\n} }", options: ["never"], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { \n foo; \n } }", options: ["never"], languageOptions: { ecmaVersion: 2022 } } - ], + invalid: [ + // default/always + { + code: "{foo();}", + output: "{ foo(); }", + options: ["always"], + errors: [ + { + type: "BlockStatement", + line: 1, + column: 1, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + { + type: "BlockStatement", + line: 1, + column: 8, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "{foo();}", + output: "{ foo(); }", + errors: [ + { + type: "BlockStatement", + line: 1, + column: 1, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + { + type: "BlockStatement", + line: 1, + column: 8, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "{ foo();}", + output: "{ foo(); }", + errors: [ + { + type: "BlockStatement", + line: 1, + column: 9, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "{foo(); }", + output: "{ foo(); }", + errors: [ + { + type: "BlockStatement", + line: 1, + column: 1, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + ], + }, + { + code: "{\nfoo();}", + output: "{\nfoo(); }", + errors: [ + { + type: "BlockStatement", + line: 2, + column: 7, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "{foo();\n}", + output: "{ foo();\n}", + errors: [ + { + type: "BlockStatement", + line: 1, + column: 1, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + ], + }, + { + code: "if (a) {foo();}", + output: "if (a) { foo(); }", + errors: [ + { + type: "BlockStatement", + line: 1, + column: 8, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + { + type: "BlockStatement", + line: 1, + column: 15, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "if (a) {} else {foo();}", + output: "if (a) {} else { foo(); }", + errors: [ + { + type: "BlockStatement", + line: 1, + column: 16, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + { + type: "BlockStatement", + line: 1, + column: 23, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "switch (a) {case 0: foo();}", + output: "switch (a) { case 0: foo(); }", + errors: [ + { + type: "SwitchStatement", + line: 1, + column: 12, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + { + type: "SwitchStatement", + line: 1, + column: 27, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "while (a) {foo();}", + output: "while (a) { foo(); }", + errors: [ + { + type: "BlockStatement", + line: 1, + column: 11, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + { + type: "BlockStatement", + line: 1, + column: 18, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "do {foo();} while (a);", + output: "do { foo(); } while (a);", + errors: [ + { + type: "BlockStatement", + line: 1, + column: 4, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + { + type: "BlockStatement", + line: 1, + column: 11, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "for (;;) {foo();}", + output: "for (;;) { foo(); }", + errors: [ + { + type: "BlockStatement", + line: 1, + column: 10, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + { + type: "BlockStatement", + line: 1, + column: 17, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "for (var a in b) {foo();}", + output: "for (var a in b) { foo(); }", + errors: [ + { + type: "BlockStatement", + line: 1, + column: 18, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + { + type: "BlockStatement", + line: 1, + column: 25, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "for (var a of b) {foo();}", + output: "for (var a of b) { foo(); }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "BlockStatement", + line: 1, + column: 18, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + { + type: "BlockStatement", + line: 1, + column: 25, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "try {foo();} catch (e) {foo();} finally {foo();}", + output: "try { foo(); } catch (e) { foo(); } finally { foo(); }", + errors: [ + { + type: "BlockStatement", + messageId: "missing", + data: { location: "after", token: "{" }, + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + { + type: "BlockStatement", + messageId: "missing", + data: { location: "before", token: "}" }, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + type: "BlockStatement", + messageId: "missing", + data: { location: "after", token: "{" }, + line: 1, + column: 24, + endLine: 1, + endColumn: 25, + }, + { + type: "BlockStatement", + messageId: "missing", + data: { location: "before", token: "}" }, + line: 1, + column: 31, + endLine: 1, + endColumn: 32, + }, + { + type: "BlockStatement", + messageId: "missing", + data: { location: "after", token: "{" }, + line: 1, + column: 41, + endLine: 1, + endColumn: 42, + }, + { + type: "BlockStatement", + messageId: "missing", + data: { location: "before", token: "}" }, + line: 1, + column: 48, + endLine: 1, + endColumn: 49, + }, + ], + }, + { + code: "function foo() {bar();}", + output: "function foo() { bar(); }", + errors: [ + { + type: "BlockStatement", + line: 1, + column: 16, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + { + type: "BlockStatement", + line: 1, + column: 23, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "(function() {bar();});", + output: "(function() { bar(); });", + errors: [ + { + type: "BlockStatement", + line: 1, + column: 13, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + { + type: "BlockStatement", + line: 1, + column: 20, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "(() => {bar();});", + output: "(() => { bar(); });", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "BlockStatement", + line: 1, + column: 8, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + { + type: "BlockStatement", + line: 1, + column: 15, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "if (a) {/* comment */ foo(); /* comment */}", + output: "if (a) { /* comment */ foo(); /* comment */ }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "BlockStatement", + line: 1, + column: 8, + messageId: "missing", + data: { location: "after", token: "{" }, + }, + { + type: "BlockStatement", + line: 1, + column: 43, + messageId: "missing", + data: { location: "before", token: "}" }, + }, + ], + }, + { + code: "if (a) {//comment\n foo(); }", + output: "if (a) { //comment\n foo(); }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "BlockStatement", + messageId: "missing", + data: { + location: "after", + token: "{", + }, + line: 1, + column: 8, + endLine: 1, + endColumn: 9, + }, + ], + }, - invalid: [ + // class static blocks + { + code: "class C { static {foo; } }", + output: "class C { static { foo; } }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "after", + token: "{", + }, + line: 1, + column: 18, + endLine: 1, + endColumn: 19, + }, + ], + }, + { + code: "class C { static { foo;} }", + output: "class C { static { foo; } }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "before", + token: "}", + }, + line: 1, + column: 24, + endLine: 1, + endColumn: 25, + }, + ], + }, + { + code: "class C { static {foo;} }", + output: "class C { static { foo; } }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "after", + token: "{", + }, + line: 1, + column: 18, + endLine: 1, + endColumn: 19, + }, + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "before", + token: "}", + }, + line: 1, + column: 23, + endLine: 1, + endColumn: 24, + }, + ], + }, + { + code: "class C { static {/* comment */} }", + output: "class C { static { /* comment */ } }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "after", + token: "{", + }, + line: 1, + column: 18, + endLine: 1, + endColumn: 19, + }, + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "before", + token: "}", + }, + line: 1, + column: 32, + endLine: 1, + endColumn: 33, + }, + ], + }, + { + code: "class C { static {/* comment 1 */ foo; /* comment 2 */} }", + output: "class C { static { /* comment 1 */ foo; /* comment 2 */ } }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "after", + token: "{", + }, + line: 1, + column: 18, + endLine: 1, + endColumn: 19, + }, + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "before", + token: "}", + }, + line: 1, + column: 55, + endLine: 1, + endColumn: 56, + }, + ], + }, + { + code: "class C {\n static {foo()\nbar()} }", + output: "class C {\n static { foo()\nbar() } }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "after", + token: "{", + }, + line: 2, + column: 9, + endLine: 2, + endColumn: 10, + }, + { + type: "StaticBlock", + messageId: "missing", + data: { + location: "before", + token: "}", + }, + line: 3, + column: 6, + endLine: 3, + endColumn: 7, + }, + ], + }, - // default/always - { - code: "{foo();}", - output: "{ foo(); }", - options: ["always"], - errors: [ - { type: "BlockStatement", line: 1, column: 1, messageId: "missing", data: { location: "after", token: "{" } }, - { type: "BlockStatement", line: 1, column: 8, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "{foo();}", - output: "{ foo(); }", - errors: [ - { type: "BlockStatement", line: 1, column: 1, messageId: "missing", data: { location: "after", token: "{" } }, - { type: "BlockStatement", line: 1, column: 8, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "{ foo();}", - output: "{ foo(); }", - errors: [ - { type: "BlockStatement", line: 1, column: 9, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "{foo(); }", - output: "{ foo(); }", - errors: [ - { type: "BlockStatement", line: 1, column: 1, messageId: "missing", data: { location: "after", token: "{" } } - ] - }, - { - code: "{\nfoo();}", - output: "{\nfoo(); }", - errors: [ - { type: "BlockStatement", line: 2, column: 7, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "{foo();\n}", - output: "{ foo();\n}", - errors: [ - { type: "BlockStatement", line: 1, column: 1, messageId: "missing", data: { location: "after", token: "{" } } - ] - }, - { - code: "if (a) {foo();}", - output: "if (a) { foo(); }", - errors: [ - { type: "BlockStatement", line: 1, column: 8, messageId: "missing", data: { location: "after", token: "{" } }, - { type: "BlockStatement", line: 1, column: 15, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "if (a) {} else {foo();}", - output: "if (a) {} else { foo(); }", - errors: [ - { type: "BlockStatement", line: 1, column: 16, messageId: "missing", data: { location: "after", token: "{" } }, - { type: "BlockStatement", line: 1, column: 23, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "switch (a) {case 0: foo();}", - output: "switch (a) { case 0: foo(); }", - errors: [ - { type: "SwitchStatement", line: 1, column: 12, messageId: "missing", data: { location: "after", token: "{" } }, - { type: "SwitchStatement", line: 1, column: 27, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "while (a) {foo();}", - output: "while (a) { foo(); }", - errors: [ - { type: "BlockStatement", line: 1, column: 11, messageId: "missing", data: { location: "after", token: "{" } }, - { type: "BlockStatement", line: 1, column: 18, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "do {foo();} while (a);", - output: "do { foo(); } while (a);", - errors: [ - { type: "BlockStatement", line: 1, column: 4, messageId: "missing", data: { location: "after", token: "{" } }, - { type: "BlockStatement", line: 1, column: 11, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "for (;;) {foo();}", - output: "for (;;) { foo(); }", - errors: [ - { type: "BlockStatement", line: 1, column: 10, messageId: "missing", data: { location: "after", token: "{" } }, - { type: "BlockStatement", line: 1, column: 17, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "for (var a in b) {foo();}", - output: "for (var a in b) { foo(); }", - errors: [ - { type: "BlockStatement", line: 1, column: 18, messageId: "missing", data: { location: "after", token: "{" } }, - { type: "BlockStatement", line: 1, column: 25, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "for (var a of b) {foo();}", - output: "for (var a of b) { foo(); }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { type: "BlockStatement", line: 1, column: 18, messageId: "missing", data: { location: "after", token: "{" } }, - { type: "BlockStatement", line: 1, column: 25, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "try {foo();} catch (e) {foo();} finally {foo();}", - output: "try { foo(); } catch (e) { foo(); } finally { foo(); }", - errors: [ - { - type: "BlockStatement", - messageId: "missing", - data: { location: "after", token: "{" }, - line: 1, - column: 5, - endLine: 1, - endColumn: 6 - }, - { - type: "BlockStatement", - messageId: "missing", - data: { location: "before", token: "}" }, - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - }, - { - type: "BlockStatement", - messageId: "missing", - data: { location: "after", token: "{" }, - line: 1, - column: 24, - endLine: 1, - endColumn: 25 - }, - { - type: "BlockStatement", - messageId: "missing", - data: { location: "before", token: "}" }, - line: 1, - column: 31, - endLine: 1, - endColumn: 32 - }, - { - type: "BlockStatement", - messageId: "missing", - data: { location: "after", token: "{" }, - line: 1, - column: 41, - endLine: 1, - endColumn: 42 - }, - { - type: "BlockStatement", - messageId: "missing", - data: { location: "before", token: "}" }, - line: 1, - column: 48, - endLine: 1, - endColumn: 49 - } - ] - }, - { - code: "function foo() {bar();}", - output: "function foo() { bar(); }", - errors: [ - { type: "BlockStatement", line: 1, column: 16, messageId: "missing", data: { location: "after", token: "{" } }, - { type: "BlockStatement", line: 1, column: 23, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "(function() {bar();});", - output: "(function() { bar(); });", - errors: [ - { type: "BlockStatement", line: 1, column: 13, messageId: "missing", data: { location: "after", token: "{" } }, - { type: "BlockStatement", line: 1, column: 20, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "(() => {bar();});", - output: "(() => { bar(); });", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { type: "BlockStatement", line: 1, column: 8, messageId: "missing", data: { location: "after", token: "{" } }, - { type: "BlockStatement", line: 1, column: 15, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "if (a) {/* comment */ foo(); /* comment */}", - output: "if (a) { /* comment */ foo(); /* comment */ }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { type: "BlockStatement", line: 1, column: 8, messageId: "missing", data: { location: "after", token: "{" } }, - { type: "BlockStatement", line: 1, column: 43, messageId: "missing", data: { location: "before", token: "}" } } - ] - }, - { - code: "if (a) {//comment\n foo(); }", - output: "if (a) { //comment\n foo(); }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - type: "BlockStatement", - messageId: "missing", - data: { - location: "after", - token: "{" - }, - line: 1, - column: 8, - endLine: 1, - endColumn: 9 - } - ] - }, + //---------------------------------------------------------------------- + // never + { + code: "{ foo(); }", + output: "{foo();}", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 2, + endLine: 1, + endColumn: 3, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + ], + }, + { + code: "{ foo();}", + output: "{foo();}", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { + location: "after", + token: "{", + }, + line: 1, + column: 2, + endLine: 1, + endColumn: 3, + }, + ], + }, + { + code: "{foo(); }", + output: "{foo();}", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { + location: "before", + token: "}", + }, + line: 1, + column: 8, + endLine: 1, + endColumn: 9, + }, + ], + }, + { + code: "{\nfoo(); }", + output: "{\nfoo();}", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { + location: "before", + token: "}", + }, + line: 2, + column: 7, + endLine: 2, + endColumn: 8, + }, + ], + }, + { + code: "{ foo();\n}", + output: "{foo();\n}", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { + location: "after", + token: "{", + }, + line: 1, + column: 2, + endLine: 1, + endColumn: 3, + }, + ], + }, + { + code: "if (a) { foo(); }", + output: "if (a) {foo();}", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 16, + endLine: 1, + endColumn: 17, + }, + ], + }, + { + code: "if (a) {} else { foo(); }", + output: "if (a) {} else {foo();}", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 17, + endLine: 1, + endColumn: 18, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 24, + endLine: 1, + endColumn: 25, + }, + ], + }, + { + code: "switch (a) { case 0: foo(); }", + output: "switch (a) {case 0: foo();}", + options: ["never"], + errors: [ + { + type: "SwitchStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 13, + endLine: 1, + endColumn: 14, + }, + { + type: "SwitchStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 28, + endLine: 1, + endColumn: 29, + }, + ], + }, + { + code: "while (a) { foo(); }", + output: "while (a) {foo();}", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 19, + endLine: 1, + endColumn: 20, + }, + ], + }, + { + code: "do { foo(); } while (a);", + output: "do {foo();} while (a);", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "for (;;) { foo(); }", + output: "for (;;) {foo();}", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 18, + endLine: 1, + endColumn: 19, + }, + ], + }, + { + code: "for (var a in b) { foo(); }", + output: "for (var a in b) {foo();}", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 19, + endLine: 1, + endColumn: 20, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 26, + endLine: 1, + endColumn: 27, + }, + ], + }, + { + code: "for (var a of b) { foo(); }", + output: "for (var a of b) {foo();}", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 19, + endLine: 1, + endColumn: 20, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 26, + endLine: 1, + endColumn: 27, + }, + ], + }, + { + code: "try { foo(); } catch (e) { foo(); } finally { foo(); }", + output: "try {foo();} catch (e) {foo();} finally {foo();}", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 13, + endLine: 1, + endColumn: 14, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 27, + endLine: 1, + endColumn: 28, + }, + { + type: "BlockStatement", + line: 1, + column: 34, + messageId: "extra", + data: { location: "before", token: "}" }, + endLine: 1, + endColumn: 35, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 46, + endLine: 1, + endColumn: 47, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 53, + endLine: 1, + endColumn: 54, + }, + ], + }, + { + code: "function foo() { bar(); }", + output: "function foo() {bar();}", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 17, + endLine: 1, + endColumn: 18, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 24, + endLine: 1, + endColumn: 25, + }, + ], + }, + { + code: "(function() { bar(); });", + output: "(function() {bar();});", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 21, + endLine: 1, + endColumn: 22, + }, + ], + }, + { + code: "(() => { bar(); });", + output: "(() => {bar();});", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 16, + endLine: 1, + endColumn: 17, + }, + ], + }, + { + code: "if (a) { /* comment */ foo(); /* comment */ }", + output: "if (a) {/* comment */ foo(); /* comment */}", + options: ["never"], + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 44, + endLine: 1, + endColumn: 45, + }, + ], + }, + { + code: "(() => { bar();});", + output: "(() => {bar();});", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 9, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: "(() => {bar(); });", + output: "(() => {bar();});", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 15, + endLine: 1, + endColumn: 18, + }, + ], + }, + { + code: "(() => { bar(); });", + output: "(() => {bar();});", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "BlockStatement", + messageId: "extra", + data: { location: "after", token: "{" }, + line: 1, + column: 9, + endLine: 1, + endColumn: 12, + }, + { + type: "BlockStatement", + messageId: "extra", + data: { location: "before", token: "}" }, + line: 1, + column: 18, + endLine: 1, + endColumn: 21, + }, + ], + }, - // class static blocks - { - code: "class C { static {foo; } }", - output: "class C { static { foo; } }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - type: "StaticBlock", - messageId: "missing", - data: { - location: "after", - token: "{" - }, - line: 1, - column: 18, - endLine: 1, - endColumn: 19 - } - ] - }, - { - code: "class C { static { foo;} }", - output: "class C { static { foo; } }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - type: "StaticBlock", - messageId: "missing", - data: { - location: "before", - token: "}" - }, - line: 1, - column: 24, - endLine: 1, - endColumn: 25 - } - ] - }, - { - code: "class C { static {foo;} }", - output: "class C { static { foo; } }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - type: "StaticBlock", - messageId: "missing", - data: { - location: "after", - token: "{" - }, - line: 1, - column: 18, - endLine: 1, - endColumn: 19 - }, - { - type: "StaticBlock", - messageId: "missing", - data: { - location: "before", - token: "}" - }, - line: 1, - column: 23, - endLine: 1, - endColumn: 24 - } - ] - }, - { - code: "class C { static {/* comment */} }", - output: "class C { static { /* comment */ } }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - type: "StaticBlock", - messageId: "missing", - data: { - location: "after", - token: "{" - }, - line: 1, - column: 18, - endLine: 1, - endColumn: 19 - }, - { - type: "StaticBlock", - messageId: "missing", - data: { - location: "before", - token: "}" - }, - line: 1, - column: 32, - endLine: 1, - endColumn: 33 - } - ] - }, - { - code: "class C { static {/* comment 1 */ foo; /* comment 2 */} }", - output: "class C { static { /* comment 1 */ foo; /* comment 2 */ } }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - type: "StaticBlock", - messageId: "missing", - data: { - location: "after", - token: "{" - }, - line: 1, - column: 18, - endLine: 1, - endColumn: 19 - }, - { - type: "StaticBlock", - messageId: "missing", - data: { - location: "before", - token: "}" - }, - line: 1, - column: 55, - endLine: 1, - endColumn: 56 - } - ] - }, - { - code: "class C {\n static {foo()\nbar()} }", - output: "class C {\n static { foo()\nbar() } }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - type: "StaticBlock", - messageId: "missing", - data: { - location: "after", - token: "{" - }, - line: 2, - column: 9, - endLine: 2, - endColumn: 10 - }, - { - type: "StaticBlock", - messageId: "missing", - data: { - location: "before", - token: "}" - }, - line: 3, - column: 6, - endLine: 3, - endColumn: 7 - } - ] - }, - - //---------------------------------------------------------------------- - // never - { - code: "{ foo(); }", - output: "{foo();}", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 2, - endLine: 1, - endColumn: 3 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 9, - endLine: 1, - endColumn: 10 - } - ] - }, - { - code: "{ foo();}", - output: "{foo();}", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { - location: "after", - token: "{" - }, - line: 1, - column: 2, - endLine: 1, - endColumn: 3 - } - ] - }, - { - code: "{foo(); }", - output: "{foo();}", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { - location: "before", - token: "}" - }, - line: 1, - column: 8, - endLine: 1, - endColumn: 9 - } - ] - }, - { - code: "{\nfoo(); }", - output: "{\nfoo();}", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { - location: "before", - token: "}" - }, - line: 2, - column: 7, - endLine: 2, - endColumn: 8 - } - ] - }, - { - code: "{ foo();\n}", - output: "{foo();\n}", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { - location: "after", - token: "{" - }, - line: 1, - column: 2, - endLine: 1, - endColumn: 3 - } - ] - }, - { - code: "if (a) { foo(); }", - output: "if (a) {foo();}", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 9, - endLine: 1, - endColumn: 10 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 16, - endLine: 1, - endColumn: 17 - } - ] - }, - { - code: "if (a) {} else { foo(); }", - output: "if (a) {} else {foo();}", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 17, - endLine: 1, - endColumn: 18 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 24, - endLine: 1, - endColumn: 25 - } - ] - }, - { - code: "switch (a) { case 0: foo(); }", - output: "switch (a) {case 0: foo();}", - options: ["never"], - errors: [ - { - type: "SwitchStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 13, - endLine: 1, - endColumn: 14 - }, - { - type: "SwitchStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 28, - endLine: 1, - endColumn: 29 - } - ] - }, - { - code: "while (a) { foo(); }", - output: "while (a) {foo();}", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 19, - endLine: 1, - endColumn: 20 - } - ] - }, - { - code: "do { foo(); } while (a);", - output: "do {foo();} while (a);", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 5, - endLine: 1, - endColumn: 6 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - } - ] - }, - { - code: "for (;;) { foo(); }", - output: "for (;;) {foo();}", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 18, - endLine: 1, - endColumn: 19 - } - ] - }, - { - code: "for (var a in b) { foo(); }", - output: "for (var a in b) {foo();}", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 19, - endLine: 1, - endColumn: 20 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 26, - endLine: 1, - endColumn: 27 - } - ] - }, - { - code: "for (var a of b) { foo(); }", - output: "for (var a of b) {foo();}", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 19, - endLine: 1, - endColumn: 20 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 26, - endLine: 1, - endColumn: 27 - } - ] - }, - { - code: "try { foo(); } catch (e) { foo(); } finally { foo(); }", - output: "try {foo();} catch (e) {foo();} finally {foo();}", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 6, - endLine: 1, - endColumn: 7 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 13, - endLine: 1, - endColumn: 14 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 27, - endLine: 1, - endColumn: 28 - }, - { - type: "BlockStatement", - line: 1, - column: 34, - messageId: "extra", - data: { location: "before", token: "}" }, - endLine: 1, - endColumn: 35 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 46, - endLine: 1, - endColumn: 47 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 53, - endLine: 1, - endColumn: 54 - } - ] - }, - { - code: "function foo() { bar(); }", - output: "function foo() {bar();}", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 17, - endLine: 1, - endColumn: 18 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 24, - endLine: 1, - endColumn: 25 - } - ] - }, - { - code: "(function() { bar(); });", - output: "(function() {bar();});", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 21, - endLine: 1, - endColumn: 22 - } - ] - }, - { - code: "(() => { bar(); });", - output: "(() => {bar();});", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 9, - endLine: 1, - endColumn: 10 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 16, - endLine: 1, - endColumn: 17 - } - ] - }, - { - code: "if (a) { /* comment */ foo(); /* comment */ }", - output: "if (a) {/* comment */ foo(); /* comment */}", - options: ["never"], - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 9, - endLine: 1, - endColumn: 10 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 44, - endLine: 1, - endColumn: 45 - } - ] - }, - { - code: "(() => { bar();});", - output: "(() => {bar();});", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 9, - endLine: 1, - endColumn: 12 - } - ] - }, - { - code: "(() => {bar(); });", - output: "(() => {bar();});", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 15, - endLine: 1, - endColumn: 18 - } - ] - }, - { - code: "(() => { bar(); });", - output: "(() => {bar();});", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - type: "BlockStatement", - messageId: "extra", - data: { location: "after", token: "{" }, - line: 1, - column: 9, - endLine: 1, - endColumn: 12 - }, - { - type: "BlockStatement", - messageId: "extra", - data: { location: "before", token: "}" }, - line: 1, - column: 18, - endLine: 1, - endColumn: 21 - } - ] - }, - - // class static blocks - { - code: "class C { static { foo;} }", - output: "class C { static {foo;} }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - type: "StaticBlock", - messageId: "extra", - data: { - location: "after", - token: "{" - }, - line: 1, - column: 19, - endLine: 1, - endColumn: 20 - } - ] - }, - { - code: "class C { static {foo; } }", - output: "class C { static {foo;} }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - type: "StaticBlock", - messageId: "extra", - data: { - location: "before", - token: "}" - }, - line: 1, - column: 23, - endLine: 1, - endColumn: 24 - } - ] - }, - { - code: "class C { static { foo; } }", - output: "class C { static {foo;} }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - type: "StaticBlock", - messageId: "extra", - data: { - location: "after", - token: "{" - }, - line: 1, - column: 19, - endLine: 1, - endColumn: 20 - }, - { - type: "StaticBlock", - messageId: "extra", - data: { - location: "before", - token: "}" - }, - line: 1, - column: 24, - endLine: 1, - endColumn: 25 - } - ] - }, - { - code: "class C { static { /* comment */ } }", - output: "class C { static {/* comment */} }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - type: "StaticBlock", - messageId: "extra", - data: { - location: "after", - token: "{" - }, - line: 1, - column: 19, - endLine: 1, - endColumn: 20 - }, - { - type: "StaticBlock", - messageId: "extra", - data: { - location: "before", - token: "}" - }, - line: 1, - column: 33, - endLine: 1, - endColumn: 34 - } - ] - }, - { - code: "class C { static { /* comment 1 */ foo; /* comment 2 */ } }", - output: "class C { static {/* comment 1 */ foo; /* comment 2 */} }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - type: "StaticBlock", - messageId: "extra", - data: { - location: "after", - token: "{" - }, - line: 1, - column: 19, - endLine: 1, - endColumn: 20 - }, - { - type: "StaticBlock", - messageId: "extra", - data: { - location: "before", - token: "}" - }, - line: 1, - column: 56, - endLine: 1, - endColumn: 57 - } - ] - }, - { - code: "class C { static\n{ foo()\nbar() } }", - output: "class C { static\n{foo()\nbar()} }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - type: "StaticBlock", - messageId: "extra", - data: { - location: "after", - token: "{" - }, - line: 2, - column: 2, - endLine: 2, - endColumn: 5 - }, - { - type: "StaticBlock", - messageId: "extra", - data: { - location: "before", - token: "}" - }, - line: 3, - column: 6, - endLine: 3, - endColumn: 8 - } - ] - } - ] + // class static blocks + { + code: "class C { static { foo;} }", + output: "class C { static {foo;} }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "after", + token: "{", + }, + line: 1, + column: 19, + endLine: 1, + endColumn: 20, + }, + ], + }, + { + code: "class C { static {foo; } }", + output: "class C { static {foo;} }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "before", + token: "}", + }, + line: 1, + column: 23, + endLine: 1, + endColumn: 24, + }, + ], + }, + { + code: "class C { static { foo; } }", + output: "class C { static {foo;} }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "after", + token: "{", + }, + line: 1, + column: 19, + endLine: 1, + endColumn: 20, + }, + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "before", + token: "}", + }, + line: 1, + column: 24, + endLine: 1, + endColumn: 25, + }, + ], + }, + { + code: "class C { static { /* comment */ } }", + output: "class C { static {/* comment */} }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "after", + token: "{", + }, + line: 1, + column: 19, + endLine: 1, + endColumn: 20, + }, + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "before", + token: "}", + }, + line: 1, + column: 33, + endLine: 1, + endColumn: 34, + }, + ], + }, + { + code: "class C { static { /* comment 1 */ foo; /* comment 2 */ } }", + output: "class C { static {/* comment 1 */ foo; /* comment 2 */} }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "after", + token: "{", + }, + line: 1, + column: 19, + endLine: 1, + endColumn: 20, + }, + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "before", + token: "}", + }, + line: 1, + column: 56, + endLine: 1, + endColumn: 57, + }, + ], + }, + { + code: "class C { static\n{ foo()\nbar() } }", + output: "class C { static\n{foo()\nbar()} }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "after", + token: "{", + }, + line: 2, + column: 2, + endLine: 2, + endColumn: 5, + }, + { + type: "StaticBlock", + messageId: "extra", + data: { + location: "before", + token: "}", + }, + line: 3, + column: 6, + endLine: 3, + endColumn: 8, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/brace-style.js b/tests/lib/rules/brace-style.js index e2b47c547058..8e5e39c4ac0f 100644 --- a/tests/lib/rules/brace-style.js +++ b/tests/lib/rules/brace-style.js @@ -10,151 +10,224 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/brace-style"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"), - { unIndent } = require("../../_utils"); + RuleTester = require("../../../lib/rule-tester/rule-tester"), + { unIndent } = require("../../_utils"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 6, sourceType: "script" } }); +const ruleTester = new RuleTester({ + languageOptions: { ecmaVersion: 6, sourceType: "script" }, +}); ruleTester.run("brace-style", rule, { - valid: [ - "function f() {\n" + - " if (true)\n" + - " return {x: 1}\n" + - " else {\n" + - " var y = 2\n" + - " return y\n" + - " }\n" + - "}", - "if (tag === 1) glyph.id = pbf.readVarint();\nelse if (tag === 2) glyph.bitmap = pbf.readBytes();", - "function foo () { \nreturn; \n}", - "function a(b,\nc,\nd) { }", - "!function foo () { \nreturn;\n }", - "!function a(b,\nc,\nd) { }", - "if (foo) { \n bar(); \n}", - "if (a) { \nb();\n } else { \nc();\n }", - "while (foo) { \n bar();\n }", - "for (;;) { \n bar(); \n}", - "with (foo) { \n bar(); \n}", - "switch (foo) { \n case \"bar\": break;\n }", - "try { \n bar();\n } catch (e) {\n baz(); \n }", - "do { \n bar();\n } while (true)", - "for (foo in bar) { \n baz(); \n }", - "if (a &&\n b &&\n c) { \n }", - "switch(0) {\n}", - "class Foo {\n}", - "(class {\n})", - "class\nFoo {\n}", - ` + valid: [ + "function f() {\n" + + " if (true)\n" + + " return {x: 1}\n" + + " else {\n" + + " var y = 2\n" + + " return y\n" + + " }\n" + + "}", + "if (tag === 1) glyph.id = pbf.readVarint();\nelse if (tag === 2) glyph.bitmap = pbf.readBytes();", + "function foo () { \nreturn; \n}", + "function a(b,\nc,\nd) { }", + "!function foo () { \nreturn;\n }", + "!function a(b,\nc,\nd) { }", + "if (foo) { \n bar(); \n}", + "if (a) { \nb();\n } else { \nc();\n }", + "while (foo) { \n bar();\n }", + "for (;;) { \n bar(); \n}", + "with (foo) { \n bar(); \n}", + 'switch (foo) { \n case "bar": break;\n }', + "try { \n bar();\n } catch (e) {\n baz(); \n }", + "do { \n bar();\n } while (true)", + "for (foo in bar) { \n baz(); \n }", + "if (a &&\n b &&\n c) { \n }", + "switch(0) {\n}", + "class Foo {\n}", + "(class {\n})", + "class\nFoo {\n}", + ` class Foo { bar() { } } `, - { code: "if (foo) {\n}\nelse {\n}", options: ["stroustrup"] }, - { code: "if (foo)\n{\n}\nelse\n{\n}", options: ["allman"] }, - { code: "try { \n bar();\n }\ncatch (e) {\n baz(); \n }", options: ["stroustrup"] }, - { code: "try\n{\n bar();\n}\ncatch (e)\n{\n baz(); \n}", options: ["allman"] }, + { code: "if (foo) {\n}\nelse {\n}", options: ["stroustrup"] }, + { code: "if (foo)\n{\n}\nelse\n{\n}", options: ["allman"] }, + { + code: "try { \n bar();\n }\ncatch (e) {\n baz(); \n }", + options: ["stroustrup"], + }, + { + code: "try\n{\n bar();\n}\ncatch (e)\n{\n baz(); \n}", + options: ["allman"], + }, - // allowSingleLine: true - { code: "function foo () { return; }", options: ["1tbs", { allowSingleLine: true }] }, - { code: "function foo () { a(); b(); return; }", options: ["1tbs", { allowSingleLine: true }] }, - { code: "function a(b,c,d) { }", options: ["1tbs", { allowSingleLine: true }] }, - { code: "!function foo () { return; }", options: ["1tbs", { allowSingleLine: true }] }, - { code: "!function a(b,c,d) { }", options: ["1tbs", { allowSingleLine: true }] }, - { code: "if (foo) { bar(); }", options: ["1tbs", { allowSingleLine: true }] }, - { code: "if (a) { b(); } else { c(); }", options: ["1tbs", { allowSingleLine: true }] }, - { code: "while (foo) { bar(); }", options: ["1tbs", { allowSingleLine: true }] }, - { code: "for (;;) { bar(); }", options: ["1tbs", { allowSingleLine: true }] }, - { code: "with (foo) { bar(); }", options: ["1tbs", { allowSingleLine: true }] }, - { code: "switch (foo) { case \"bar\": break; }", options: ["1tbs", { allowSingleLine: true }] }, - { code: "try { bar(); } catch (e) { baz(); }", options: ["1tbs", { allowSingleLine: true }] }, - { code: "do { bar(); } while (true)", options: ["1tbs", { allowSingleLine: true }] }, - { code: "for (foo in bar) { baz(); }", options: ["1tbs", { allowSingleLine: true }] }, - { code: "if (a && b && c) { }", options: ["1tbs", { allowSingleLine: true }] }, - { code: "switch(0) {}", options: ["1tbs", { allowSingleLine: true }] }, - { code: "if (foo) {}\nelse {}", options: ["stroustrup", { allowSingleLine: true }] }, - { code: "try { bar(); }\ncatch (e) { baz(); }", options: ["stroustrup", { allowSingleLine: true }] }, - { code: "var foo = () => { return; }", options: ["stroustrup", { allowSingleLine: true }], languageOptions: { ecmaVersion: 6 } }, - { code: "if (foo) {}\nelse {}", options: ["allman", { allowSingleLine: true }] }, - { code: "try { bar(); }\ncatch (e) { baz(); }", options: ["allman", { allowSingleLine: true }] }, - { code: "var foo = () => { return; }", options: ["allman", { allowSingleLine: true }], languageOptions: { ecmaVersion: 6 } }, - { - code: "if (foo) { baz(); } else {\n boom();\n}", - options: ["1tbs", { allowSingleLine: true }] - }, - { - code: "if (foo) { baz(); } else if (bar) {\n boom();\n}", - options: ["1tbs", { allowSingleLine: true }] - }, - { - code: "if (foo) { baz(); } else\nif (bar) {\n boom();\n}", - options: ["1tbs", { allowSingleLine: true }] - }, - { - code: "try { somethingRisky(); } catch(e) {\n handleError();\n}", - options: ["1tbs", { allowSingleLine: true }] - }, - { - code: "if (tag === 1) fontstack.name = pbf.readString(); \nelse if (tag === 2) fontstack.range = pbf.readString(); \nelse if (tag === 3) {\n var glyph = pbf.readMessage(readGlyph, {});\n fontstack.glyphs[glyph.id] = glyph; \n}", - options: ["1tbs"] - }, - { - code: "if (tag === 1) fontstack.name = pbf.readString(); \nelse if (tag === 2) fontstack.range = pbf.readString(); \nelse if (tag === 3) {\n var glyph = pbf.readMessage(readGlyph, {});\n fontstack.glyphs[glyph.id] = glyph; \n}", - options: ["stroustrup"] - }, - { - code: "switch(x) \n{ \n case 1: \nbar(); \n }\n ", - options: ["allman"] - }, - { - code: "switch(x) {}", - options: ["allman", { allowSingleLine: true }] - }, - { - code: "class Foo {\n}", - options: ["stroustrup"] - }, - { - code: "(class {\n})", - options: ["stroustrup"] - }, - { - code: "class Foo\n{\n}", - options: ["allman"] - }, - { - code: "(class\n{\n})", - options: ["allman"] - }, - { - code: "class\nFoo\n{\n}", - options: ["allman"] - }, - { - code: "class Foo {}", - options: ["1tbs", { allowSingleLine: true }] - }, - { - code: "class Foo {}", - options: ["allman", { allowSingleLine: true }] - }, - { - code: "(class {})", - options: ["1tbs", { allowSingleLine: true }] - }, - { - code: "(class {})", - options: ["allman", { allowSingleLine: true }] - }, + // allowSingleLine: true + { + code: "function foo () { return; }", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "function foo () { a(); b(); return; }", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "function a(b,c,d) { }", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "!function foo () { return; }", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "!function a(b,c,d) { }", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "if (foo) { bar(); }", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "if (a) { b(); } else { c(); }", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "while (foo) { bar(); }", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "for (;;) { bar(); }", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "with (foo) { bar(); }", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: 'switch (foo) { case "bar": break; }', + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "try { bar(); } catch (e) { baz(); }", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "do { bar(); } while (true)", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "for (foo in bar) { baz(); }", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "if (a && b && c) { }", + options: ["1tbs", { allowSingleLine: true }], + }, + { code: "switch(0) {}", options: ["1tbs", { allowSingleLine: true }] }, + { + code: "if (foo) {}\nelse {}", + options: ["stroustrup", { allowSingleLine: true }], + }, + { + code: "try { bar(); }\ncatch (e) { baz(); }", + options: ["stroustrup", { allowSingleLine: true }], + }, + { + code: "var foo = () => { return; }", + options: ["stroustrup", { allowSingleLine: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "if (foo) {}\nelse {}", + options: ["allman", { allowSingleLine: true }], + }, + { + code: "try { bar(); }\ncatch (e) { baz(); }", + options: ["allman", { allowSingleLine: true }], + }, + { + code: "var foo = () => { return; }", + options: ["allman", { allowSingleLine: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "if (foo) { baz(); } else {\n boom();\n}", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "if (foo) { baz(); } else if (bar) {\n boom();\n}", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "if (foo) { baz(); } else\nif (bar) {\n boom();\n}", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "try { somethingRisky(); } catch(e) {\n handleError();\n}", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "if (tag === 1) fontstack.name = pbf.readString(); \nelse if (tag === 2) fontstack.range = pbf.readString(); \nelse if (tag === 3) {\n var glyph = pbf.readMessage(readGlyph, {});\n fontstack.glyphs[glyph.id] = glyph; \n}", + options: ["1tbs"], + }, + { + code: "if (tag === 1) fontstack.name = pbf.readString(); \nelse if (tag === 2) fontstack.range = pbf.readString(); \nelse if (tag === 3) {\n var glyph = pbf.readMessage(readGlyph, {});\n fontstack.glyphs[glyph.id] = glyph; \n}", + options: ["stroustrup"], + }, + { + code: "switch(x) \n{ \n case 1: \nbar(); \n }\n ", + options: ["allman"], + }, + { + code: "switch(x) {}", + options: ["allman", { allowSingleLine: true }], + }, + { + code: "class Foo {\n}", + options: ["stroustrup"], + }, + { + code: "(class {\n})", + options: ["stroustrup"], + }, + { + code: "class Foo\n{\n}", + options: ["allman"], + }, + { + code: "(class\n{\n})", + options: ["allman"], + }, + { + code: "class\nFoo\n{\n}", + options: ["allman"], + }, + { + code: "class Foo {}", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "class Foo {}", + options: ["allman", { allowSingleLine: true }], + }, + { + code: "(class {})", + options: ["1tbs", { allowSingleLine: true }], + }, + { + code: "(class {})", + options: ["allman", { allowSingleLine: true }], + }, - // https://github.com/eslint/eslint/issues/7908 - "{}", - ` + // https://github.com/eslint/eslint/issues/7908 + "{}", + ` if (foo) { } @@ -163,7 +236,7 @@ ruleTester.run("brace-style", rule, { } `, - ` + ` switch (foo) { case bar: baz(); @@ -172,55 +245,55 @@ ruleTester.run("brace-style", rule, { } } `, - ` + ` { } `, - ` + ` { { } } `, - // https://github.com/eslint/eslint/issues/7974 - ` + // https://github.com/eslint/eslint/issues/7974 + ` class Ball { throw() {} catch() {} } `, - ` + ` ({ and() {}, finally() {} }) `, - ` + ` (class { or() {} else() {} }) `, - ` + ` if (foo) bar = function() {} else baz() `, - // class static blocks - { - code: unIndent` + // class static blocks + { + code: unIndent` class C { static { foo; } } `, - options: ["1tbs"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: ["1tbs"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static {} @@ -228,31 +301,31 @@ ruleTester.run("brace-style", rule, { } } `, - options: ["1tbs"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: ["1tbs"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { foo; } } `, - options: ["1tbs", { allowSingleLine: true }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: ["1tbs", { allowSingleLine: true }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { foo; } } `, - options: ["stroustrup"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: ["stroustrup"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static {} @@ -260,20 +333,20 @@ ruleTester.run("brace-style", rule, { } } `, - options: ["stroustrup"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: ["stroustrup"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { foo; } } `, - options: ["stroustrup", { allowSingleLine: true }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: ["stroustrup", { allowSingleLine: true }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static @@ -282,22 +355,22 @@ ruleTester.run("brace-style", rule, { } } `, - options: ["allman"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: ["allman"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static {} } `, - options: ["allman"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: ["allman"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static {} @@ -308,11 +381,11 @@ ruleTester.run("brace-style", rule, { { foo; } } `, - options: ["allman", { allowSingleLine: true }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: ["allman", { allowSingleLine: true }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { { @@ -321,449 +394,482 @@ ruleTester.run("brace-style", rule, { } } `, - options: ["1tbs"], - languageOptions: { ecmaVersion: 2022 } - } - ], + options: ["1tbs"], + languageOptions: { ecmaVersion: 2022 }, + }, + ], - invalid: [ - { - code: "if (f) {\nbar;\n}\nelse\nbaz;", - output: "if (f) {\nbar;\n} else\nbaz;", - errors: [{ messageId: "nextLineClose", type: "Punctuator" }] - }, - { - code: "var foo = () => { return; }", - output: "var foo = () => {\n return; \n}", - languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "blockSameLine", type: "Punctuator" }, { messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "function foo() { return; }", - output: "function foo() {\n return; \n}", - errors: [{ messageId: "blockSameLine", type: "Punctuator" }, { messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "function foo() \n { \n return; }", - output: "function foo() { \n return; \n}", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }, { messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "!function foo() \n { \n return; }", - output: "!function foo() { \n return; \n}", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }, { messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "if (foo) \n { \n bar(); }", - output: "if (foo) { \n bar(); \n}", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }, { messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "if (a) { \nb();\n } else \n { c(); }", - output: "if (a) { \nb();\n } else {\n c(); \n}", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }, { messageId: "blockSameLine", type: "Punctuator" }, { messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "while (foo) \n { \n bar(); }", - output: "while (foo) { \n bar(); \n}", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }, { messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "for (;;) \n { \n bar(); }", - output: "for (;;) { \n bar(); \n}", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }, { messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "with (foo) \n { \n bar(); }", - output: "with (foo) { \n bar(); \n}", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }, { messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "switch (foo) \n { \n case \"bar\": break; }", - output: "switch (foo) { \n case \"bar\": break; \n}", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }, { messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "switch (foo) \n { }", - output: "switch (foo) { }", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }] - }, - { - code: "try \n { \n bar(); \n } catch (e) {}", - output: "try { \n bar(); \n } catch (e) {}", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }] - }, - { - code: "try { \n bar(); \n } catch (e) \n {}", - output: "try { \n bar(); \n } catch (e) {}", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }] - }, - { - code: "do \n { \n bar(); \n} while (true)", - output: "do { \n bar(); \n} while (true)", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }] - }, - { - code: "for (foo in bar) \n { \n baz(); \n }", - output: "for (foo in bar) { \n baz(); \n }", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }] - }, - { - code: "for (foo of bar) \n { \n baz(); \n }", - output: "for (foo of bar) { \n baz(); \n }", - languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }] - }, - { - code: "try { \n bar(); \n }\ncatch (e) {\n}", - output: "try { \n bar(); \n } catch (e) {\n}", - errors: [{ messageId: "nextLineClose", type: "Punctuator" }] - }, - { - code: "try { \n bar(); \n } catch (e) {\n}\n finally {\n}", - output: "try { \n bar(); \n } catch (e) {\n} finally {\n}", - errors: [{ messageId: "nextLineClose", type: "Punctuator" }] - }, - { - code: "if (a) { \nb();\n } \n else { \nc();\n }", - output: "if (a) { \nb();\n } else { \nc();\n }", - errors: [{ messageId: "nextLineClose", type: "Punctuator" }] - }, - { - code: "try { \n bar(); \n }\ncatch (e) {\n} finally {\n}", - output: "try { \n bar(); \n }\ncatch (e) {\n}\n finally {\n}", - options: ["stroustrup"], - errors: [{ messageId: "sameLineClose", type: "Punctuator" }] - }, - { - code: "try { \n bar(); \n } catch (e) {\n}\n finally {\n}", - output: "try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}", - options: ["stroustrup"], - errors: [{ messageId: "sameLineClose", type: "Punctuator" }] - }, - { - code: "if (a) { \nb();\n } else { \nc();\n }", - output: "if (a) { \nb();\n }\n else { \nc();\n }", - options: ["stroustrup"], - errors: [{ messageId: "sameLineClose", type: "Punctuator" }] - }, - { - code: "if (foo) {\nbaz();\n} else if (bar) {\nbaz();\n}\nelse {\nqux();\n}", - output: "if (foo) {\nbaz();\n}\n else if (bar) {\nbaz();\n}\nelse {\nqux();\n}", - options: ["stroustrup"], - errors: [{ messageId: "sameLineClose", type: "Punctuator" }] - }, - { - code: "if (foo) {\npoop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}", - output: "if (foo) {\npoop();\n} \nelse if (bar) {\nbaz();\n}\n else if (thing) {\nboom();\n}\nelse {\nqux();\n}", - options: ["stroustrup"], - errors: [{ messageId: "sameLineClose", type: "Punctuator" }] - }, - { - code: "try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}", - output: "try \n{ \n bar(); \n }\n catch (e) \n{\n}\n finally \n{\n}", - options: ["allman"], - errors: [ - { messageId: "sameLineOpen", type: "Punctuator", line: 1 }, - { messageId: "sameLineOpen", type: "Punctuator", line: 4 }, - { messageId: "sameLineOpen", type: "Punctuator", line: 6 } - ] - }, - { - code: "switch(x) { case 1: \nbar(); }\n ", - output: "switch(x) \n{\n case 1: \nbar(); \n}\n ", - options: ["allman"], - errors: [ - { messageId: "sameLineOpen", type: "Punctuator", line: 1 }, - { messageId: "blockSameLine", type: "Punctuator", line: 1 }, - { messageId: "singleLineClose", type: "Punctuator", line: 2 } - ] - }, - { - code: "if (a) { \nb();\n } else { \nc();\n }", - output: "if (a) \n{ \nb();\n }\n else \n{ \nc();\n }", - options: ["allman"], - errors: [ - { messageId: "sameLineOpen", type: "Punctuator" }, - { messageId: "sameLineClose", type: "Punctuator" }, - { messageId: "sameLineOpen", type: "Punctuator" } - ] - }, - { - code: "if (foo) {\nbaz();\n} else if (bar) {\nbaz();\n}\nelse {\nqux();\n}", - output: "if (foo) \n{\nbaz();\n}\n else if (bar) \n{\nbaz();\n}\nelse \n{\nqux();\n}", - options: ["allman"], - errors: [ - { messageId: "sameLineOpen", type: "Punctuator" }, - { messageId: "sameLineClose", type: "Punctuator" }, - { messageId: "sameLineOpen", type: "Punctuator" }, - { messageId: "sameLineOpen", type: "Punctuator" } - ] - }, - { - code: "if (foo)\n{ poop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}", - output: "if (foo)\n{\n poop();\n} \nelse if (bar) \n{\nbaz();\n}\n else if (thing) \n{\nboom();\n}\nelse \n{\nqux();\n}", - options: ["allman"], - errors: [ - { messageId: "blockSameLine", type: "Punctuator" }, - { messageId: "sameLineOpen", type: "Punctuator" }, - { messageId: "sameLineClose", type: "Punctuator" }, - { messageId: "sameLineOpen", type: "Punctuator" }, - { messageId: "sameLineOpen", type: "Punctuator" } - ] - }, - { - code: "if (foo)\n{\n bar(); }", - output: "if (foo)\n{\n bar(); \n}", - options: ["allman"], - errors: [ - { messageId: "singleLineClose", type: "Punctuator" } - ] - }, - { - code: "try\n{\n somethingRisky();\n} catch (e)\n{\n handleError()\n}", - output: "try\n{\n somethingRisky();\n}\n catch (e)\n{\n handleError()\n}", - options: ["allman"], - errors: [ - { messageId: "sameLineClose", type: "Punctuator" } - ] - }, + invalid: [ + { + code: "if (f) {\nbar;\n}\nelse\nbaz;", + output: "if (f) {\nbar;\n} else\nbaz;", + errors: [{ messageId: "nextLineClose", type: "Punctuator" }], + }, + { + code: "var foo = () => { return; }", + output: "var foo = () => {\n return; \n}", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { messageId: "blockSameLine", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: "function foo() { return; }", + output: "function foo() {\n return; \n}", + errors: [ + { messageId: "blockSameLine", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: "function foo() \n { \n return; }", + output: "function foo() { \n return; \n}", + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: "!function foo() \n { \n return; }", + output: "!function foo() { \n return; \n}", + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: "if (foo) \n { \n bar(); }", + output: "if (foo) { \n bar(); \n}", + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: "if (a) { \nb();\n } else \n { c(); }", + output: "if (a) { \nb();\n } else {\n c(); \n}", + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" }, + { messageId: "blockSameLine", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: "while (foo) \n { \n bar(); }", + output: "while (foo) { \n bar(); \n}", + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: "for (;;) \n { \n bar(); }", + output: "for (;;) { \n bar(); \n}", + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: "with (foo) \n { \n bar(); }", + output: "with (foo) { \n bar(); \n}", + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: 'switch (foo) \n { \n case "bar": break; }', + output: 'switch (foo) { \n case "bar": break; \n}', + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: "switch (foo) \n { }", + output: "switch (foo) { }", + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: "try \n { \n bar(); \n } catch (e) {}", + output: "try { \n bar(); \n } catch (e) {}", + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: "try { \n bar(); \n } catch (e) \n {}", + output: "try { \n bar(); \n } catch (e) {}", + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: "do \n { \n bar(); \n} while (true)", + output: "do { \n bar(); \n} while (true)", + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: "for (foo in bar) \n { \n baz(); \n }", + output: "for (foo in bar) { \n baz(); \n }", + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: "for (foo of bar) \n { \n baz(); \n }", + output: "for (foo of bar) { \n baz(); \n }", + languageOptions: { ecmaVersion: 6 }, + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: "try { \n bar(); \n }\ncatch (e) {\n}", + output: "try { \n bar(); \n } catch (e) {\n}", + errors: [{ messageId: "nextLineClose", type: "Punctuator" }], + }, + { + code: "try { \n bar(); \n } catch (e) {\n}\n finally {\n}", + output: "try { \n bar(); \n } catch (e) {\n} finally {\n}", + errors: [{ messageId: "nextLineClose", type: "Punctuator" }], + }, + { + code: "if (a) { \nb();\n } \n else { \nc();\n }", + output: "if (a) { \nb();\n } else { \nc();\n }", + errors: [{ messageId: "nextLineClose", type: "Punctuator" }], + }, + { + code: "try { \n bar(); \n }\ncatch (e) {\n} finally {\n}", + output: "try { \n bar(); \n }\ncatch (e) {\n}\n finally {\n}", + options: ["stroustrup"], + errors: [{ messageId: "sameLineClose", type: "Punctuator" }], + }, + { + code: "try { \n bar(); \n } catch (e) {\n}\n finally {\n}", + output: "try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}", + options: ["stroustrup"], + errors: [{ messageId: "sameLineClose", type: "Punctuator" }], + }, + { + code: "if (a) { \nb();\n } else { \nc();\n }", + output: "if (a) { \nb();\n }\n else { \nc();\n }", + options: ["stroustrup"], + errors: [{ messageId: "sameLineClose", type: "Punctuator" }], + }, + { + code: "if (foo) {\nbaz();\n} else if (bar) {\nbaz();\n}\nelse {\nqux();\n}", + output: "if (foo) {\nbaz();\n}\n else if (bar) {\nbaz();\n}\nelse {\nqux();\n}", + options: ["stroustrup"], + errors: [{ messageId: "sameLineClose", type: "Punctuator" }], + }, + { + code: "if (foo) {\npoop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}", + output: "if (foo) {\npoop();\n} \nelse if (bar) {\nbaz();\n}\n else if (thing) {\nboom();\n}\nelse {\nqux();\n}", + options: ["stroustrup"], + errors: [{ messageId: "sameLineClose", type: "Punctuator" }], + }, + { + code: "try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}", + output: "try \n{ \n bar(); \n }\n catch (e) \n{\n}\n finally \n{\n}", + options: ["allman"], + errors: [ + { messageId: "sameLineOpen", type: "Punctuator", line: 1 }, + { messageId: "sameLineOpen", type: "Punctuator", line: 4 }, + { messageId: "sameLineOpen", type: "Punctuator", line: 6 }, + ], + }, + { + code: "switch(x) { case 1: \nbar(); }\n ", + output: "switch(x) \n{\n case 1: \nbar(); \n}\n ", + options: ["allman"], + errors: [ + { messageId: "sameLineOpen", type: "Punctuator", line: 1 }, + { messageId: "blockSameLine", type: "Punctuator", line: 1 }, + { messageId: "singleLineClose", type: "Punctuator", line: 2 }, + ], + }, + { + code: "if (a) { \nb();\n } else { \nc();\n }", + output: "if (a) \n{ \nb();\n }\n else \n{ \nc();\n }", + options: ["allman"], + errors: [ + { messageId: "sameLineOpen", type: "Punctuator" }, + { messageId: "sameLineClose", type: "Punctuator" }, + { messageId: "sameLineOpen", type: "Punctuator" }, + ], + }, + { + code: "if (foo) {\nbaz();\n} else if (bar) {\nbaz();\n}\nelse {\nqux();\n}", + output: "if (foo) \n{\nbaz();\n}\n else if (bar) \n{\nbaz();\n}\nelse \n{\nqux();\n}", + options: ["allman"], + errors: [ + { messageId: "sameLineOpen", type: "Punctuator" }, + { messageId: "sameLineClose", type: "Punctuator" }, + { messageId: "sameLineOpen", type: "Punctuator" }, + { messageId: "sameLineOpen", type: "Punctuator" }, + ], + }, + { + code: "if (foo)\n{ poop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}", + output: "if (foo)\n{\n poop();\n} \nelse if (bar) \n{\nbaz();\n}\n else if (thing) \n{\nboom();\n}\nelse \n{\nqux();\n}", + options: ["allman"], + errors: [ + { messageId: "blockSameLine", type: "Punctuator" }, + { messageId: "sameLineOpen", type: "Punctuator" }, + { messageId: "sameLineClose", type: "Punctuator" }, + { messageId: "sameLineOpen", type: "Punctuator" }, + { messageId: "sameLineOpen", type: "Punctuator" }, + ], + }, + { + code: "if (foo)\n{\n bar(); }", + output: "if (foo)\n{\n bar(); \n}", + options: ["allman"], + errors: [{ messageId: "singleLineClose", type: "Punctuator" }], + }, + { + code: "try\n{\n somethingRisky();\n} catch (e)\n{\n handleError()\n}", + output: "try\n{\n somethingRisky();\n}\n catch (e)\n{\n handleError()\n}", + options: ["allman"], + errors: [{ messageId: "sameLineClose", type: "Punctuator" }], + }, - // allowSingleLine: true - { - code: "function foo() { return; \n}", - output: "function foo() {\n return; \n}", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "blockSameLine", type: "Punctuator" }] - }, - { - code: "function foo() { a(); b(); return; \n}", - output: "function foo() {\n a(); b(); return; \n}", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "blockSameLine", type: "Punctuator" }] - }, - { - code: "function foo() { \n return; }", - output: "function foo() { \n return; \n}", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "function foo() {\na();\nb();\nreturn; }", - output: "function foo() {\na();\nb();\nreturn; \n}", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "!function foo() { \n return; }", - output: "!function foo() { \n return; \n}", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "if (a) { b();\n } else { c(); }", - output: "if (a) {\n b();\n } else { c(); }", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "blockSameLine", type: "Punctuator" }] - }, - { - code: "if (a) { b(); }\nelse { c(); }", - output: "if (a) { b(); } else { c(); }", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "nextLineClose", type: "Punctuator" }] - }, - { - code: "while (foo) { \n bar(); }", - output: "while (foo) { \n bar(); \n}", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "for (;;) { bar(); \n }", - output: "for (;;) {\n bar(); \n }", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "blockSameLine", type: "Punctuator" }] - }, - { - code: "with (foo) { bar(); \n }", - output: "with (foo) {\n bar(); \n }", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "blockSameLine", type: "Punctuator" }] - }, - { - code: "switch (foo) \n { \n case \"bar\": break; }", - output: "switch (foo) { \n case \"bar\": break; \n}", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }, { messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "switch (foo) \n { }", - output: "switch (foo) { }", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }] - }, - { - code: "try { bar(); }\ncatch (e) { baz(); }", - output: "try { bar(); } catch (e) { baz(); }", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "nextLineClose", type: "Punctuator" }] - }, - { - code: "try \n { \n bar(); \n } catch (e) {}", - output: "try { \n bar(); \n } catch (e) {}", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }] - }, - { - code: "try { \n bar(); \n } catch (e) \n {}", - output: "try { \n bar(); \n } catch (e) {}", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }] - }, - { - code: "do \n { \n bar(); \n} while (true)", - output: "do { \n bar(); \n} while (true)", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }] - }, - { - code: "for (foo in bar) \n { \n baz(); \n }", - output: "for (foo in bar) { \n baz(); \n }", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }] - }, - { - code: "try { \n bar(); \n }\ncatch (e) {\n}", - output: "try { \n bar(); \n } catch (e) {\n}", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "nextLineClose", type: "Punctuator" }] - }, - { - code: "try { \n bar(); \n } catch (e) {\n}\n finally {\n}", - output: "try { \n bar(); \n } catch (e) {\n} finally {\n}", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "nextLineClose", type: "Punctuator" }] - }, - { - code: "if (a) { \nb();\n } \n else { \nc();\n }", - output: "if (a) { \nb();\n } else { \nc();\n }", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "nextLineClose", type: "Punctuator" }] - }, - { - code: "try { \n bar(); \n }\ncatch (e) {\n} finally {\n}", - output: "try { \n bar(); \n }\ncatch (e) {\n}\n finally {\n}", - options: ["stroustrup", { allowSingleLine: true }], - errors: [{ messageId: "sameLineClose", type: "Punctuator" }] - }, - { - code: "try { \n bar(); \n } catch (e) {\n}\n finally {\n}", - output: "try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}", - options: ["stroustrup", { allowSingleLine: true }], - errors: [{ messageId: "sameLineClose", type: "Punctuator" }] - }, - { - code: "if (a) { \nb();\n } else { \nc();\n }", - output: "if (a) { \nb();\n }\n else { \nc();\n }", - options: ["stroustrup", { allowSingleLine: true }], - errors: [{ messageId: "sameLineClose", type: "Punctuator" }] - }, - { - code: "if (foo)\n{ poop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}", - output: "if (foo)\n{\n poop();\n} \nelse if (bar) \n{\nbaz();\n}\n else if (thing) \n{\nboom();\n}\nelse \n{\nqux();\n}", - options: ["allman", { allowSingleLine: true }], - errors: [ - { messageId: "blockSameLine", type: "Punctuator" }, - { messageId: "sameLineOpen", type: "Punctuator" }, - { messageId: "sameLineClose", type: "Punctuator" }, - { messageId: "sameLineOpen", type: "Punctuator" }, - { messageId: "sameLineOpen", type: "Punctuator" } - ] - }, + // allowSingleLine: true + { + code: "function foo() { return; \n}", + output: "function foo() {\n return; \n}", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "blockSameLine", type: "Punctuator" }], + }, + { + code: "function foo() { a(); b(); return; \n}", + output: "function foo() {\n a(); b(); return; \n}", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "blockSameLine", type: "Punctuator" }], + }, + { + code: "function foo() { \n return; }", + output: "function foo() { \n return; \n}", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "singleLineClose", type: "Punctuator" }], + }, + { + code: "function foo() {\na();\nb();\nreturn; }", + output: "function foo() {\na();\nb();\nreturn; \n}", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "singleLineClose", type: "Punctuator" }], + }, + { + code: "!function foo() { \n return; }", + output: "!function foo() { \n return; \n}", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "singleLineClose", type: "Punctuator" }], + }, + { + code: "if (a) { b();\n } else { c(); }", + output: "if (a) {\n b();\n } else { c(); }", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "blockSameLine", type: "Punctuator" }], + }, + { + code: "if (a) { b(); }\nelse { c(); }", + output: "if (a) { b(); } else { c(); }", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "nextLineClose", type: "Punctuator" }], + }, + { + code: "while (foo) { \n bar(); }", + output: "while (foo) { \n bar(); \n}", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "singleLineClose", type: "Punctuator" }], + }, + { + code: "for (;;) { bar(); \n }", + output: "for (;;) {\n bar(); \n }", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "blockSameLine", type: "Punctuator" }], + }, + { + code: "with (foo) { bar(); \n }", + output: "with (foo) {\n bar(); \n }", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "blockSameLine", type: "Punctuator" }], + }, + { + code: 'switch (foo) \n { \n case "bar": break; }', + output: 'switch (foo) { \n case "bar": break; \n}', + options: ["1tbs", { allowSingleLine: true }], + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: "switch (foo) \n { }", + output: "switch (foo) { }", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: "try { bar(); }\ncatch (e) { baz(); }", + output: "try { bar(); } catch (e) { baz(); }", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "nextLineClose", type: "Punctuator" }], + }, + { + code: "try \n { \n bar(); \n } catch (e) {}", + output: "try { \n bar(); \n } catch (e) {}", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: "try { \n bar(); \n } catch (e) \n {}", + output: "try { \n bar(); \n } catch (e) {}", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: "do \n { \n bar(); \n} while (true)", + output: "do { \n bar(); \n} while (true)", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: "for (foo in bar) \n { \n baz(); \n }", + output: "for (foo in bar) { \n baz(); \n }", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: "try { \n bar(); \n }\ncatch (e) {\n}", + output: "try { \n bar(); \n } catch (e) {\n}", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "nextLineClose", type: "Punctuator" }], + }, + { + code: "try { \n bar(); \n } catch (e) {\n}\n finally {\n}", + output: "try { \n bar(); \n } catch (e) {\n} finally {\n}", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "nextLineClose", type: "Punctuator" }], + }, + { + code: "if (a) { \nb();\n } \n else { \nc();\n }", + output: "if (a) { \nb();\n } else { \nc();\n }", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "nextLineClose", type: "Punctuator" }], + }, + { + code: "try { \n bar(); \n }\ncatch (e) {\n} finally {\n}", + output: "try { \n bar(); \n }\ncatch (e) {\n}\n finally {\n}", + options: ["stroustrup", { allowSingleLine: true }], + errors: [{ messageId: "sameLineClose", type: "Punctuator" }], + }, + { + code: "try { \n bar(); \n } catch (e) {\n}\n finally {\n}", + output: "try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}", + options: ["stroustrup", { allowSingleLine: true }], + errors: [{ messageId: "sameLineClose", type: "Punctuator" }], + }, + { + code: "if (a) { \nb();\n } else { \nc();\n }", + output: "if (a) { \nb();\n }\n else { \nc();\n }", + options: ["stroustrup", { allowSingleLine: true }], + errors: [{ messageId: "sameLineClose", type: "Punctuator" }], + }, + { + code: "if (foo)\n{ poop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}", + output: "if (foo)\n{\n poop();\n} \nelse if (bar) \n{\nbaz();\n}\n else if (thing) \n{\nboom();\n}\nelse \n{\nqux();\n}", + options: ["allman", { allowSingleLine: true }], + errors: [ + { messageId: "blockSameLine", type: "Punctuator" }, + { messageId: "sameLineOpen", type: "Punctuator" }, + { messageId: "sameLineClose", type: "Punctuator" }, + { messageId: "sameLineOpen", type: "Punctuator" }, + { messageId: "sameLineOpen", type: "Punctuator" }, + ], + }, - // Comment interferes with fix - { - code: "if (foo) // comment \n{\nbar();\n}", - output: null, - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }] - }, + // Comment interferes with fix + { + code: "if (foo) // comment \n{\nbar();\n}", + output: null, + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, - // https://github.com/eslint/eslint/issues/7493 - { - code: "if (foo) {\n bar\n.baz }", - output: "if (foo) {\n bar\n.baz \n}", - errors: [{ messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "if (foo)\n{\n bar\n.baz }", - output: "if (foo)\n{\n bar\n.baz \n}", - options: ["allman"], - errors: [{ messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "if (foo) { bar\n.baz }", - output: "if (foo) {\n bar\n.baz \n}", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "blockSameLine", type: "Punctuator" }, { messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "if (foo) { bar\n.baz }", - output: "if (foo) \n{\n bar\n.baz \n}", - options: ["allman", { allowSingleLine: true }], - errors: [ - { messageId: "sameLineOpen", type: "Punctuator" }, - { messageId: "blockSameLine", type: "Punctuator" }, - { messageId: "singleLineClose", type: "Punctuator" } - ] - }, - { - code: "switch (x) {\n case 1: foo() }", - output: "switch (x) {\n case 1: foo() \n}", - options: ["1tbs", { allowSingleLine: true }], - errors: [{ messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "class Foo\n{\n}", - output: "class Foo {\n}", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }] - }, - { - code: "(class\n{\n})", - output: "(class {\n})", - errors: [{ messageId: "nextLineOpen", type: "Punctuator" }] - }, - { - code: "class Foo{\n}", - output: "class Foo\n{\n}", - options: ["allman"], - errors: [{ messageId: "sameLineOpen", type: "Punctuator" }] - }, - { - code: "(class {\n})", - output: "(class \n{\n})", - options: ["allman"], - errors: [{ messageId: "sameLineOpen", type: "Punctuator" }] - }, - { - code: "class Foo {\nbar() {\n}}", - output: "class Foo {\nbar() {\n}\n}", - errors: [{ messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "(class Foo {\nbar() {\n}})", - output: "(class Foo {\nbar() {\n}\n})", - errors: [{ messageId: "singleLineClose", type: "Punctuator" }] - }, - { - code: "class\nFoo{}", - output: "class\nFoo\n{}", - options: ["allman"], - errors: [{ messageId: "sameLineOpen", type: "Punctuator" }] - }, + // https://github.com/eslint/eslint/issues/7493 + { + code: "if (foo) {\n bar\n.baz }", + output: "if (foo) {\n bar\n.baz \n}", + errors: [{ messageId: "singleLineClose", type: "Punctuator" }], + }, + { + code: "if (foo)\n{\n bar\n.baz }", + output: "if (foo)\n{\n bar\n.baz \n}", + options: ["allman"], + errors: [{ messageId: "singleLineClose", type: "Punctuator" }], + }, + { + code: "if (foo) { bar\n.baz }", + output: "if (foo) {\n bar\n.baz \n}", + options: ["1tbs", { allowSingleLine: true }], + errors: [ + { messageId: "blockSameLine", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: "if (foo) { bar\n.baz }", + output: "if (foo) \n{\n bar\n.baz \n}", + options: ["allman", { allowSingleLine: true }], + errors: [ + { messageId: "sameLineOpen", type: "Punctuator" }, + { messageId: "blockSameLine", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: "switch (x) {\n case 1: foo() }", + output: "switch (x) {\n case 1: foo() \n}", + options: ["1tbs", { allowSingleLine: true }], + errors: [{ messageId: "singleLineClose", type: "Punctuator" }], + }, + { + code: "class Foo\n{\n}", + output: "class Foo {\n}", + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: "(class\n{\n})", + output: "(class {\n})", + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: "class Foo{\n}", + output: "class Foo\n{\n}", + options: ["allman"], + errors: [{ messageId: "sameLineOpen", type: "Punctuator" }], + }, + { + code: "(class {\n})", + output: "(class \n{\n})", + options: ["allman"], + errors: [{ messageId: "sameLineOpen", type: "Punctuator" }], + }, + { + code: "class Foo {\nbar() {\n}}", + output: "class Foo {\nbar() {\n}\n}", + errors: [{ messageId: "singleLineClose", type: "Punctuator" }], + }, + { + code: "(class Foo {\nbar() {\n}})", + output: "(class Foo {\nbar() {\n}\n})", + errors: [{ messageId: "singleLineClose", type: "Punctuator" }], + }, + { + code: "class\nFoo{}", + output: "class\nFoo\n{}", + options: ["allman"], + errors: [{ messageId: "sameLineOpen", type: "Punctuator" }], + }, - // https://github.com/eslint/eslint/issues/7621 - { - code: ` + // https://github.com/eslint/eslint/issues/7621 + { + code: ` if (foo) { bar @@ -772,28 +878,28 @@ ruleTester.run("brace-style", rule, { baz } `, - output: ` + output: ` if (foo) { bar } else { baz } `, - errors: [ - { messageId: "nextLineOpen", type: "Punctuator" }, - { messageId: "nextLineClose", type: "Punctuator" } - ] - }, + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" }, + { messageId: "nextLineClose", type: "Punctuator" }, + ], + }, - /* - * class static blocks - * - * Note about the autofix: this rule only inserts linebreaks and removes linebreaks. - * It does not aim to produce code with a valid indentation. Indentation and other formatting issues - * are expected to be fixed by `indent` and other rules in subsequent iterations. - */ - { - code: unIndent` + /* + * class static blocks + * + * Note about the autofix: this rule only inserts linebreaks and removes linebreaks. + * It does not aim to produce code with a valid indentation. Indentation and other formatting issues + * are expected to be fixed by `indent` and other rules in subsequent iterations. + */ + { + code: unIndent` class C { static { @@ -801,101 +907,93 @@ ruleTester.run("brace-style", rule, { } } `, - output: unIndent` + output: unIndent` class C { static { foo; } } `, - options: ["1tbs"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "nextLineOpen", type: "Punctuator" } - ] - }, - { - code: unIndent` + options: ["1tbs"], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: unIndent` class C { static {foo; } } `, - output: unIndent` + output: unIndent` class C { static { foo; } } `, - options: ["1tbs"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "blockSameLine", type: "Punctuator" } - ] - }, - { - code: unIndent` + options: ["1tbs"], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "blockSameLine", type: "Punctuator" }], + }, + { + code: unIndent` class C { static { foo;} } `, - output: unIndent` + output: unIndent` class C { static { foo; } } `, - options: ["1tbs"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "singleLineClose", type: "Punctuator" } - ] - }, - { - code: unIndent` + options: ["1tbs"], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "singleLineClose", type: "Punctuator" }], + }, + { + code: unIndent` class C { static {foo;} } `, - output: unIndent` + output: unIndent` class C { static { foo; } } `, - options: ["1tbs"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "nextLineOpen", type: "Punctuator" }, - { messageId: "blockSameLine", type: "Punctuator" }, - { messageId: "singleLineClose", type: "Punctuator" } - ] - }, - { - code: unIndent` + options: ["1tbs"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" }, + { messageId: "blockSameLine", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: unIndent` class C { static {} } `, - output: unIndent` + output: unIndent` class C { static {} } `, - options: ["1tbs"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "nextLineOpen", type: "Punctuator" } - ] - }, - { - code: unIndent` + options: ["1tbs"], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: unIndent` class C { static { @@ -903,101 +1001,93 @@ ruleTester.run("brace-style", rule, { } } `, - output: unIndent` + output: unIndent` class C { static { foo; } } `, - options: ["stroustrup"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "nextLineOpen", type: "Punctuator" } - ] - }, - { - code: unIndent` + options: ["stroustrup"], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: unIndent` class C { static {foo; } } `, - output: unIndent` + output: unIndent` class C { static { foo; } } `, - options: ["stroustrup"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "blockSameLine", type: "Punctuator" } - ] - }, - { - code: unIndent` + options: ["stroustrup"], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "blockSameLine", type: "Punctuator" }], + }, + { + code: unIndent` class C { static { foo;} } `, - output: unIndent` + output: unIndent` class C { static { foo; } } `, - options: ["stroustrup"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "singleLineClose", type: "Punctuator" } - ] - }, - { - code: unIndent` + options: ["stroustrup"], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "singleLineClose", type: "Punctuator" }], + }, + { + code: unIndent` class C { static {foo;} } `, - output: unIndent` + output: unIndent` class C { static { foo; } } `, - options: ["stroustrup"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "nextLineOpen", type: "Punctuator" }, - { messageId: "blockSameLine", type: "Punctuator" }, - { messageId: "singleLineClose", type: "Punctuator" } - ] - }, - { - code: unIndent` + options: ["stroustrup"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "nextLineOpen", type: "Punctuator" }, + { messageId: "blockSameLine", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: unIndent` class C { static {} } `, - output: unIndent` + output: unIndent` class C { static {} } `, - options: ["stroustrup"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "nextLineOpen", type: "Punctuator" } - ] - }, - { - code: unIndent` + options: ["stroustrup"], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "nextLineOpen", type: "Punctuator" }], + }, + { + code: unIndent` class C { static{ @@ -1005,7 +1095,7 @@ ruleTester.run("brace-style", rule, { } } `, - output: unIndent` + output: unIndent` class C { static @@ -1014,14 +1104,12 @@ ruleTester.run("brace-style", rule, { } } `, - options: ["allman"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "sameLineOpen", type: "Punctuator" } - ] - }, - { - code: unIndent` + options: ["allman"], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "sameLineOpen", type: "Punctuator" }], + }, + { + code: unIndent` class C { static @@ -1029,7 +1117,7 @@ ruleTester.run("brace-style", rule, { } } `, - output: unIndent` + output: unIndent` class C { static @@ -1038,14 +1126,12 @@ ruleTester.run("brace-style", rule, { } } `, - options: ["allman"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "blockSameLine", type: "Punctuator" } - ] - }, - { - code: unIndent` + options: ["allman"], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "blockSameLine", type: "Punctuator" }], + }, + { + code: unIndent` class C { static @@ -1053,7 +1139,7 @@ ruleTester.run("brace-style", rule, { foo;} } `, - output: unIndent` + output: unIndent` class C { static @@ -1062,20 +1148,18 @@ ruleTester.run("brace-style", rule, { } } `, - options: ["allman"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "singleLineClose", type: "Punctuator" } - ] - }, - { - code: unIndent` + options: ["allman"], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "singleLineClose", type: "Punctuator" }], + }, + { + code: unIndent` class C { static{foo;} } `, - output: unIndent` + output: unIndent` class C { static @@ -1084,33 +1168,31 @@ ruleTester.run("brace-style", rule, { } } `, - options: ["allman"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "sameLineOpen", type: "Punctuator" }, - { messageId: "blockSameLine", type: "Punctuator" }, - { messageId: "singleLineClose", type: "Punctuator" } - ] - }, - { - code: unIndent` + options: ["allman"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "sameLineOpen", type: "Punctuator" }, + { messageId: "blockSameLine", type: "Punctuator" }, + { messageId: "singleLineClose", type: "Punctuator" }, + ], + }, + { + code: unIndent` class C { static{} } `, - output: unIndent` + output: unIndent` class C { static {} } `, - options: ["allman"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "sameLineOpen", type: "Punctuator" } - ] - } - ] + options: ["allman"], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "sameLineOpen", type: "Punctuator" }], + }, + ], }); diff --git a/tests/lib/rules/callback-return.js b/tests/lib/rules/callback-return.js index 32d136e818df..fc18a2a0b8ca 100644 --- a/tests/lib/rules/callback-return.js +++ b/tests/lib/rules/callback-return.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/callback-return"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,466 +18,496 @@ const rule = require("../../../lib/rules/callback-return"), const ruleTester = new RuleTester(); ruleTester.run("callback-return", rule, { - valid: [ + valid: [ + // callbacks inside of functions should return + "function a(err) { if (err) return callback (err); }", + "function a(err) { if (err) return callback (err); callback(); }", + "function a(err) { if (err) { return callback (err); } callback(); }", + "function a(err) { if (err) { return /* confusing comment */ callback (err); } callback(); }", + "function x(err) { if (err) { callback(); return; } }", + "function x(err) { if (err) { \n log();\n callback(); return; } }", + "function x(err) { if (err) { callback(); return; } return callback(); }", + "function x(err) { if (err) { return callback(); } else { return callback(); } }", + "function x(err) { if (err) { return callback(); } else if (x) { return callback(); } }", + "function x(err) { if (err) return callback(); else return callback(); }", + "function x(cb) { cb && cb(); }", + "function x(next) { typeof next !== 'undefined' && next(); }", + "function x(next) { if (typeof next === 'function') { return next() } }", + "function x() { switch(x) { case 'a': return next(); } }", + "function x() { for(x = 0; x < 10; x++) { return next(); } }", + "function x() { while(x) { return next(); } }", + "function a(err) { if (err) { obj.method (err); } }", - // callbacks inside of functions should return - "function a(err) { if (err) return callback (err); }", - "function a(err) { if (err) return callback (err); callback(); }", - "function a(err) { if (err) { return callback (err); } callback(); }", - "function a(err) { if (err) { return /* confusing comment */ callback (err); } callback(); }", - "function x(err) { if (err) { callback(); return; } }", - "function x(err) { if (err) { \n log();\n callback(); return; } }", - "function x(err) { if (err) { callback(); return; } return callback(); }", - "function x(err) { if (err) { return callback(); } else { return callback(); } }", - "function x(err) { if (err) { return callback(); } else if (x) { return callback(); } }", - "function x(err) { if (err) return callback(); else return callback(); }", - "function x(cb) { cb && cb(); }", - "function x(next) { typeof next !== 'undefined' && next(); }", - "function x(next) { if (typeof next === 'function') { return next() } }", - "function x() { switch(x) { case 'a': return next(); } }", - "function x() { for(x = 0; x < 10; x++) { return next(); } }", - "function x() { while(x) { return next(); } }", - "function a(err) { if (err) { obj.method (err); } }", + // callback() all you want outside of a function + "callback()", + "callback(); callback();", + "while(x) { move(); }", + "for (var i = 0; i < 10; i++) { move(); }", + "for (var i = 0; i < 10; i++) move();", + "if (x) callback();", + "if (x) { callback(); }", - // callback() all you want outside of a function - "callback()", - "callback(); callback();", - "while(x) { move(); }", - "for (var i = 0; i < 10; i++) { move(); }", - "for (var i = 0; i < 10; i++) move();", - "if (x) callback();", - "if (x) { callback(); }", + // arrow functions + { + code: "var x = err => { if (err) { callback(); return; } }", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var x = err => callback(err)", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var x = err => { setTimeout( () => { callback(); }); }", + languageOptions: { ecmaVersion: 6 }, + }, - // arrow functions - { - code: "var x = err => { if (err) { callback(); return; } }", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var x = err => callback(err)", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var x = err => { setTimeout( () => { callback(); }); }", - languageOptions: { ecmaVersion: 6 } - }, + // classes + { + code: "class x { horse() { callback(); } } ", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class x { horse() { if (err) { return callback(); } callback(); } } ", + languageOptions: { ecmaVersion: 6 }, + }, - // classes - { - code: "class x { horse() { callback(); } } ", - languageOptions: { ecmaVersion: 6 } - }, { - code: "class x { horse() { if (err) { return callback(); } callback(); } } ", - languageOptions: { ecmaVersion: 6 } - }, + // options (only warns with the correct callback name) + { + code: "function a(err) { if (err) { callback(err) } }", + options: [["cb"]], + }, + { + code: "function a(err) { if (err) { callback(err) } next(); }", + options: [["cb", "next"]], + }, + { + code: "function a(err) { if (err) { return next(err) } else { callback(); } }", + options: [["cb", "next"]], + }, - // options (only warns with the correct callback name) - { - code: "function a(err) { if (err) { callback(err) } }", - options: [["cb"]] - }, - { - code: "function a(err) { if (err) { callback(err) } next(); }", - options: [["cb", "next"]] - }, - { - code: "function a(err) { if (err) { return next(err) } else { callback(); } }", - options: [["cb", "next"]] - }, + // allow object methods (https://github.com/eslint/eslint/issues/4711) + { + code: "function a(err) { if (err) { return obj.method(err); } }", + options: [["obj.method"]], + }, + { + code: "function a(err) { if (err) { return obj.prop.method(err); } }", + options: [["obj.prop.method"]], + }, + { + code: "function a(err) { if (err) { return obj.prop.method(err); } otherObj.prop.method() }", + options: [["obj.prop.method", "otherObj.prop.method"]], + }, + { + code: "function a(err) { if (err) { callback(err); } }", + options: [["obj.method"]], + }, + { + code: "function a(err) { if (err) { otherObj.method(err); } }", + options: [["obj.method"]], + }, + { + code: "function a(err) { if (err) { //comment\nreturn obj.method(err); } }", + options: [["obj.method"]], + }, + { + code: "function a(err) { if (err) { /*comment*/return obj.method(err); } }", + options: [["obj.method"]], + }, + { + code: "function a(err) { if (err) { return obj.method(err); //comment\n } }", + options: [["obj.method"]], + }, + { + code: "function a(err) { if (err) { return obj.method(err); /*comment*/ } }", + options: [["obj.method"]], + }, - // allow object methods (https://github.com/eslint/eslint/issues/4711) - { - code: "function a(err) { if (err) { return obj.method(err); } }", - options: [["obj.method"]] - }, - { - code: "function a(err) { if (err) { return obj.prop.method(err); } }", - options: [["obj.prop.method"]] - }, - { - code: "function a(err) { if (err) { return obj.prop.method(err); } otherObj.prop.method() }", - options: [["obj.prop.method", "otherObj.prop.method"]] - }, - { - code: "function a(err) { if (err) { callback(err); } }", - options: [["obj.method"]] - }, - { - code: "function a(err) { if (err) { otherObj.method(err); } }", - options: [["obj.method"]] - }, - { - code: "function a(err) { if (err) { //comment\nreturn obj.method(err); } }", - options: [["obj.method"]] - }, - { - code: "function a(err) { if (err) { /*comment*/return obj.method(err); } }", - options: [["obj.method"]] - }, - { - code: "function a(err) { if (err) { return obj.method(err); //comment\n } }", - options: [["obj.method"]] - }, - { - code: "function a(err) { if (err) { return obj.method(err); /*comment*/ } }", - options: [["obj.method"]] - }, + // only warns if object of MemberExpression is an Identifier + { + code: "function a(err) { if (err) { obj().method(err); } }", + options: [["obj().method"]], + }, + { + code: "function a(err) { if (err) { obj.prop().method(err); } }", + options: [["obj.prop().method"]], + }, + { + code: "function a(err) { if (err) { obj().prop.method(err); } }", + options: [["obj().prop.method"]], + }, - // only warns if object of MemberExpression is an Identifier - { - code: "function a(err) { if (err) { obj().method(err); } }", - options: [["obj().method"]] - }, - { - code: "function a(err) { if (err) { obj.prop().method(err); } }", - options: [["obj.prop().method"]] - }, - { - code: "function a(err) { if (err) { obj().prop.method(err); } }", - options: [["obj().prop.method"]] - }, + // does not warn if object of MemberExpression is invoked + { + code: "function a(err) { if (err) { obj().method(err); } }", + options: [["obj.method"]], + }, + { + code: "function a(err) { if (err) { obj().method(err); } obj.method(); }", + options: [["obj.method"]], + }, - // does not warn if object of MemberExpression is invoked - { - code: "function a(err) { if (err) { obj().method(err); } }", - options: [["obj.method"]] - }, - { - code: "function a(err) { if (err) { obj().method(err); } obj.method(); }", - options: [["obj.method"]] - }, + // known bad examples that we know we are ignoring + "function x(err) { if (err) { setTimeout(callback, 0); } callback(); }", // callback() called twice + "function x(err) { if (err) { process.nextTick(function(err) { callback(); }); } callback(); }", // callback() called twice + ], + invalid: [ + { + code: "function a(err) { if (err) { callback (err); } }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + { + code: "function a(callback) { if (typeof callback !== 'undefined') { callback(); } }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 63, + type: "CallExpression", + }, + ], + }, + { + code: "function a(callback) { if (typeof callback !== 'undefined') callback(); }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 61, + type: "CallExpression", + }, + ], + }, + { + code: "function a(callback) { if (err) { callback(); horse && horse(); } }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 35, + type: "CallExpression", + }, + ], + }, + { + code: "var x = (err) => { if (err) { callback (err); } }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 31, + type: "CallExpression", + }, + ], + }, + { + code: "var x = { x(err) { if (err) { callback (err); } } }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 31, + type: "CallExpression", + }, + ], + }, + { + code: "function x(err) { if (err) {\n log();\n callback(err); } }", + errors: [ + { + messageId: "missingReturn", + line: 3, + column: 2, + type: "CallExpression", + }, + ], + }, + { + code: "var x = { x(err) { if (err) { callback && callback (err); } } }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 43, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { callback (err); callback(); }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 19, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { callback (err); horse(); }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 19, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { if (err) { callback (err); horse(); return; } }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + { + code: "var a = (err) => { callback (err); callback(); }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 20, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { if (err) { callback (err); } else if (x) { callback(err); return; } }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + { + code: "function x(err) { if (err) { return callback(); }\nelse if (abc) {\ncallback(); }\nelse {\nreturn callback(); } }", + errors: [ + { + messageId: "missingReturn", + line: 3, + column: 1, + type: "CallExpression", + }, + ], + }, + { + code: "class x { horse() { if (err) { callback(); } callback(); } } ", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 32, + type: "CallExpression", + }, + ], + }, - // known bad examples that we know we are ignoring - "function x(err) { if (err) { setTimeout(callback, 0); } callback(); }", // callback() called twice - "function x(err) { if (err) { process.nextTick(function(err) { callback(); }); } callback(); }" // callback() called twice + // generally good behavior which we must not allow to keep the rule simple + { + code: "function x(err) { if (err) { callback() } else { callback() } }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + { + messageId: "missingReturn", + line: 1, + column: 50, + type: "CallExpression", + }, + ], + }, + { + code: "function x(err) { if (err) return callback(); else callback(); }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 52, + type: "CallExpression", + }, + ], + }, + { + code: "() => { if (x) { callback(); } }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 18, + type: "CallExpression", + }, + ], + }, + { + code: "function b() { switch(x) { case 'horse': callback(); } }", + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 42, + type: "CallExpression", + }, + ], + }, + { + code: "function a() { switch(x) { case 'horse': move(); } }", + options: [["move"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 42, + type: "CallExpression", + }, + ], + }, - ], - invalid: [ - { - code: "function a(err) { if (err) { callback (err); } }", - errors: [{ - messageId: "missingReturn", - line: 1, - column: 30, - type: "CallExpression" - }] - }, - { - code: "function a(callback) { if (typeof callback !== 'undefined') { callback(); } }", - errors: [{ - messageId: "missingReturn", - line: 1, - column: 63, - type: "CallExpression" - }] - }, - { - code: "function a(callback) { if (typeof callback !== 'undefined') callback(); }", - errors: [{ - messageId: "missingReturn", - line: 1, - column: 61, - type: "CallExpression" - }] - }, - { - code: "function a(callback) { if (err) { callback(); horse && horse(); } }", - errors: [{ - messageId: "missingReturn", - line: 1, - column: 35, - type: "CallExpression" - }] - }, - { - code: "var x = (err) => { if (err) { callback (err); } }", - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "missingReturn", - line: 1, - column: 31, - type: "CallExpression" - }] - }, - { - code: "var x = { x(err) { if (err) { callback (err); } } }", - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "missingReturn", - line: 1, - column: 31, - type: "CallExpression" - }] - }, - { - code: "function x(err) { if (err) {\n log();\n callback(err); } }", - errors: [{ - messageId: "missingReturn", - line: 3, - column: 2, - type: "CallExpression" - }] - }, - { - code: "var x = { x(err) { if (err) { callback && callback (err); } } }", - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "missingReturn", - line: 1, - column: 43, - type: "CallExpression" - }] - }, - { - code: "function a(err) { callback (err); callback(); }", - errors: [{ - messageId: "missingReturn", - line: 1, - column: 19, - type: "CallExpression" - }] - }, - { - code: "function a(err) { callback (err); horse(); }", - errors: [{ - messageId: "missingReturn", - line: 1, - column: 19, - type: "CallExpression" - }] - }, - { - code: "function a(err) { if (err) { callback (err); horse(); return; } }", - errors: [{ - messageId: "missingReturn", - line: 1, - column: 30, - type: "CallExpression" - }] - }, - { - code: "var a = (err) => { callback (err); callback(); }", - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "missingReturn", - line: 1, - column: 20, - type: "CallExpression" - }] - }, - { - code: "function a(err) { if (err) { callback (err); } else if (x) { callback(err); return; } }", - errors: [{ - messageId: "missingReturn", - line: 1, - column: 30, - type: "CallExpression" - }] - }, - { - code: "function x(err) { if (err) { return callback(); }\nelse if (abc) {\ncallback(); }\nelse {\nreturn callback(); } }", - errors: [{ - messageId: "missingReturn", - line: 3, - column: 1, - type: "CallExpression" - - }] - }, - { - code: "class x { horse() { if (err) { callback(); } callback(); } } ", - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "missingReturn", - line: 1, - column: 32, - type: "CallExpression" - }] - }, - - - // generally good behavior which we must not allow to keep the rule simple - { - code: "function x(err) { if (err) { callback() } else { callback() } }", - errors: [{ - messageId: "missingReturn", - line: 1, - column: 30, - type: "CallExpression" - }, { - messageId: "missingReturn", - line: 1, - column: 50, - type: "CallExpression" - }] - }, - { - code: "function x(err) { if (err) return callback(); else callback(); }", - errors: [ - { - messageId: "missingReturn", - line: 1, - column: 52, - type: "CallExpression" - } - ] - }, - { - code: "() => { if (x) { callback(); } }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingReturn", - line: 1, - column: 18, - type: "CallExpression" - } - ] - }, - { - code: "function b() { switch(x) { case 'horse': callback(); } }", - errors: [ - { - messageId: "missingReturn", - line: 1, - column: 42, - type: "CallExpression" - } - ] - }, - { - code: "function a() { switch(x) { case 'horse': move(); } }", - options: [["move"]], - errors: [ - { - messageId: "missingReturn", - line: 1, - column: 42, - type: "CallExpression" - } - ] - }, - - // loops - { - code: "var x = function() { while(x) { move(); } }", - options: [["move"]], - errors: [ - { - messageId: "missingReturn", - line: 1, - column: 33, - type: "CallExpression" - } - ] - }, - { - code: "function x() { for (var i = 0; i < 10; i++) { move(); } }", - options: [["move"]], - errors: [ - { - messageId: "missingReturn", - line: 1, - column: 47, - type: "CallExpression" - } - ] - }, - { - code: "var x = function() { for (var i = 0; i < 10; i++) move(); }", - options: [["move"]], - errors: [ - { - messageId: "missingReturn", - line: 1, - column: 51, - type: "CallExpression" - } - ] - }, - { - code: "function a(err) { if (err) { obj.method(err); } }", - options: [["obj.method"]], - errors: [ - { - messageId: "missingReturn", - line: 1, - column: 30, - type: "CallExpression" - } - ] - }, - { - code: "function a(err) { if (err) { obj.prop.method(err); } }", - options: [["obj.prop.method"]], - errors: [ - { - messageId: "missingReturn", - line: 1, - column: 30, - type: "CallExpression" - } - ] - }, - { - code: "function a(err) { if (err) { obj.prop.method(err); } otherObj.prop.method() }", - options: [["obj.prop.method", "otherObj.prop.method"]], - errors: [ - { - messageId: "missingReturn", - line: 1, - column: 30, - type: "CallExpression" - } - ] - }, - { - code: "function a(err) { if (err) { /*comment*/obj.method(err); } }", - options: [["obj.method"]], - errors: [ - { - messageId: "missingReturn", - line: 1, - column: 41, - type: "CallExpression" - } - ] - }, - { - code: "function a(err) { if (err) { //comment\nobj.method(err); } }", - options: [["obj.method"]], - errors: [ - { - messageId: "missingReturn", - line: 2, - column: 1, - type: "CallExpression" - } - ] - }, - { - code: "function a(err) { if (err) { obj.method(err); /*comment*/ } }", - options: [["obj.method"]], - errors: [ - { - messageId: "missingReturn", - line: 1, - column: 30, - type: "CallExpression" - } - ] - }, - { - code: "function a(err) { if (err) { obj.method(err); //comment\n } }", - options: [["obj.method"]], - errors: [ - { - messageId: "missingReturn", - line: 1, - column: 30, - type: "CallExpression" - } - ] - } - ] + // loops + { + code: "var x = function() { while(x) { move(); } }", + options: [["move"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 33, + type: "CallExpression", + }, + ], + }, + { + code: "function x() { for (var i = 0; i < 10; i++) { move(); } }", + options: [["move"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 47, + type: "CallExpression", + }, + ], + }, + { + code: "var x = function() { for (var i = 0; i < 10; i++) move(); }", + options: [["move"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 51, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { if (err) { obj.method(err); } }", + options: [["obj.method"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { if (err) { obj.prop.method(err); } }", + options: [["obj.prop.method"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { if (err) { obj.prop.method(err); } otherObj.prop.method() }", + options: [["obj.prop.method", "otherObj.prop.method"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { if (err) { /*comment*/obj.method(err); } }", + options: [["obj.method"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 41, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { if (err) { //comment\nobj.method(err); } }", + options: [["obj.method"]], + errors: [ + { + messageId: "missingReturn", + line: 2, + column: 1, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { if (err) { obj.method(err); /*comment*/ } }", + options: [["obj.method"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + { + code: "function a(err) { if (err) { obj.method(err); //comment\n } }", + options: [["obj.method"]], + errors: [ + { + messageId: "missingReturn", + line: 1, + column: 30, + type: "CallExpression", + }, + ], + }, + ], }); diff --git a/tests/lib/rules/camelcase.js b/tests/lib/rules/camelcase.js index 10bc1f613569..ba0edbceb66b 100644 --- a/tests/lib/rules/camelcase.js +++ b/tests/lib/rules/camelcase.js @@ -10,405 +10,470 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/camelcase"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, }); ruleTester.run("camelcase", rule, { - valid: [ - "firstName = \"Nicholas\"", - "FIRST_NAME = \"Nicholas\"", - "__myPrivateVariable = \"Patrick\"", - "myPrivateVariable_ = \"Patrick\"", - "function doSomething(){}", - "do_something()", - "new do_something", - "new do_something()", - "foo.do_something()", - "var foo = bar.baz_boom;", - "var foo = bar.baz_boom.something;", - "foo.boom_pow.qux = bar.baz_boom.something;", - "if (bar.baz_boom) {}", - "var obj = { key: foo.bar_baz };", - "var arr = [foo.bar_baz];", - "[foo.bar_baz]", - "var arr = [foo.bar_baz.qux];", - "[foo.bar_baz.nesting]", - "if (foo.bar_baz === boom.bam_pow) { [foo.baz_boom] }", - { - code: "var o = {key: 1}", - options: [{ properties: "always" }] - }, - { - code: "var o = {_leading: 1}", - options: [{ properties: "always" }] - }, - { - code: "var o = {trailing_: 1}", - options: [{ properties: "always" }] - }, - { - code: "var o = {bar_baz: 1}", - options: [{ properties: "never" }] - }, - { - code: "var o = {_leading: 1}", - options: [{ properties: "never" }] - }, - { - code: "var o = {trailing_: 1}", - options: [{ properties: "never" }] - }, - { - code: "obj.a_b = 2;", - options: [{ properties: "never" }] - }, - { - code: "obj._a = 2;", - options: [{ properties: "always" }] - }, - { - code: "obj.a_ = 2;", - options: [{ properties: "always" }] - }, - { - code: "obj._a = 2;", - options: [{ properties: "never" }] - }, - { - code: "obj.a_ = 2;", - options: [{ properties: "never" }] - }, - { - code: "var obj = {\n a_a: 1 \n};\n obj.a_b = 2;", - options: [{ properties: "never" }] - }, - { - code: "obj.foo_bar = function(){};", - options: [{ properties: "never" }] - }, - { - code: "const { ['foo']: _foo } = obj;", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "const { [_foo_]: foo } = obj;", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var { category_id } = query;", - options: [{ ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var { category_id: category_id } = query;", - options: [{ ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var { category_id = 1 } = query;", - options: [{ ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var { [{category_id} = query]: categoryId } = query;", - options: [{ ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var { category_id: category } = query;", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var { _leading } = query;", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var { trailing_ } = query;", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "import { camelCased } from \"external module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import { _leading } from \"external module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import { trailing_ } from \"external module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import { no_camelcased as camelCased } from \"external-module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import { no_camelcased as _leading } from \"external-module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import { no_camelcased as trailing_ } from \"external-module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import { no_camelcased as camelCased, anotherCamelCased } from \"external-module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import { snake_cased } from 'mod'", - options: [{ ignoreImports: true }], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import { snake_cased as snake_cased } from 'mod'", - options: [{ ignoreImports: true }], - languageOptions: { ecmaVersion: 2022, sourceType: "module" } - }, - { - code: "import { 'snake_cased' as snake_cased } from 'mod'", - options: [{ ignoreImports: true }], - languageOptions: { ecmaVersion: 2022, sourceType: "module" } - }, - { - code: "import { camelCased } from 'mod'", - options: [{ ignoreImports: false }], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, + valid: [ + 'firstName = "Nicholas"', + 'FIRST_NAME = "Nicholas"', + '__myPrivateVariable = "Patrick"', + 'myPrivateVariable_ = "Patrick"', + "function doSomething(){}", + "do_something()", + "new do_something", + "new do_something()", + "foo.do_something()", + "var foo = bar.baz_boom;", + "var foo = bar.baz_boom.something;", + "foo.boom_pow.qux = bar.baz_boom.something;", + "if (bar.baz_boom) {}", + "var obj = { key: foo.bar_baz };", + "var arr = [foo.bar_baz];", + "[foo.bar_baz]", + "var arr = [foo.bar_baz.qux];", + "[foo.bar_baz.nesting]", + "if (foo.bar_baz === boom.bam_pow) { [foo.baz_boom] }", + { + code: "var o = {key: 1}", + options: [{ properties: "always" }], + }, + { + code: "var o = {_leading: 1}", + options: [{ properties: "always" }], + }, + { + code: "var o = {trailing_: 1}", + options: [{ properties: "always" }], + }, + { + code: "var o = {bar_baz: 1}", + options: [{ properties: "never" }], + }, + { + code: "var o = {_leading: 1}", + options: [{ properties: "never" }], + }, + { + code: "var o = {trailing_: 1}", + options: [{ properties: "never" }], + }, + { + code: "obj.a_b = 2;", + options: [{ properties: "never" }], + }, + { + code: "obj._a = 2;", + options: [{ properties: "always" }], + }, + { + code: "obj.a_ = 2;", + options: [{ properties: "always" }], + }, + { + code: "obj._a = 2;", + options: [{ properties: "never" }], + }, + { + code: "obj.a_ = 2;", + options: [{ properties: "never" }], + }, + { + code: "var obj = {\n a_a: 1 \n};\n obj.a_b = 2;", + options: [{ properties: "never" }], + }, + { + code: "obj.foo_bar = function(){};", + options: [{ properties: "never" }], + }, + { + code: "const { ['foo']: _foo } = obj;", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "const { [_foo_]: foo } = obj;", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { category_id } = query;", + options: [{ ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { category_id: category_id } = query;", + options: [{ ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { category_id = 1 } = query;", + options: [{ ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { [{category_id} = query]: categoryId } = query;", + options: [{ ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { category_id: category } = query;", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { _leading } = query;", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { trailing_ } = query;", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'import { camelCased } from "external module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: 'import { _leading } from "external module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: 'import { trailing_ } from "external module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: 'import { no_camelcased as camelCased } from "external-module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: 'import { no_camelcased as _leading } from "external-module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: 'import { no_camelcased as trailing_ } from "external-module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: 'import { no_camelcased as camelCased, anotherCamelCased } from "external-module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import { snake_cased } from 'mod'", + options: [{ ignoreImports: true }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import { snake_cased as snake_cased } from 'mod'", + options: [{ ignoreImports: true }], + languageOptions: { ecmaVersion: 2022, sourceType: "module" }, + }, + { + code: "import { 'snake_cased' as snake_cased } from 'mod'", + options: [{ ignoreImports: true }], + languageOptions: { ecmaVersion: 2022, sourceType: "module" }, + }, + { + code: "import { camelCased } from 'mod'", + options: [{ ignoreImports: false }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, - // this rule doesn't apply to quoted module export names, as it doesn't apply to quoted property names. - { - code: "export { a as 'snake_cased' } from 'mod'", - languageOptions: { ecmaVersion: 2022, sourceType: "module" } - }, - { - code: "export * as 'snake_cased' from 'mod'", - languageOptions: { ecmaVersion: 2022, sourceType: "module" } - }, + // this rule doesn't apply to quoted module export names, as it doesn't apply to quoted property names. + { + code: "export { a as 'snake_cased' } from 'mod'", + languageOptions: { ecmaVersion: 2022, sourceType: "module" }, + }, + { + code: "export * as 'snake_cased' from 'mod'", + languageOptions: { ecmaVersion: 2022, sourceType: "module" }, + }, - { - code: "var _camelCased = aGlobalVariable", - options: [{ ignoreGlobals: false }], - languageOptions: { globals: { aGlobalVariable: "readonly" } } - }, - { - code: "var camelCased = _aGlobalVariable", - options: [{ ignoreGlobals: false }], - languageOptions: { globals: { _aGlobalVariable: "readonly" } } - }, - { - code: "var camelCased = a_global_variable", - options: [{ ignoreGlobals: true }], - languageOptions: { globals: { a_global_variable: "readonly" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "a_global_variable.foo()", - options: [{ ignoreGlobals: true }], - languageOptions: { globals: { a_global_variable: "readonly" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "a_global_variable[undefined]", - options: [{ ignoreGlobals: true }], - languageOptions: { globals: { a_global_variable: "readonly" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "var foo = a_global_variable.bar", - options: [{ ignoreGlobals: true }], - languageOptions: { globals: { a_global_variable: "readonly" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "a_global_variable.foo = bar", - options: [{ ignoreGlobals: true }], - languageOptions: { globals: { a_global_variable: "readonly" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "( { foo: a_global_variable.bar } = baz )", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "readonly" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "a_global_variable = foo", - options: [{ ignoreGlobals: true }], - languageOptions: { globals: { a_global_variable: "writable" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "a_global_variable = foo", - options: [{ ignoreGlobals: true }], - languageOptions: { globals: { a_global_variable: "readonly" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "({ a_global_variable } = foo)", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "writable" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "({ snake_cased: a_global_variable } = foo)", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "writable" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "({ snake_cased: a_global_variable = foo } = bar)", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "writable" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "[a_global_variable] = bar", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "writable" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "[a_global_variable = foo] = bar", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "writable" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "foo[a_global_variable] = bar", - options: [{ ignoreGlobals: true }], - languageOptions: { globals: { a_global_variable: "readonly" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "var foo = { [a_global_variable]: bar }", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "readonly" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "var { [a_global_variable]: foo } = bar", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "readonly" } } // eslint-disable-line camelcase -- Testing non-CamelCase - }, - { - code: "function foo({ no_camelcased: camelCased }) {};", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({ no_camelcased: _leading }) {};", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({ no_camelcased: trailing_ }) {};", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({ camelCased = 'default value' }) {};", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({ _leading = 'default value' }) {};", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({ trailing_ = 'default value' }) {};", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({ camelCased }) {};", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({ _leading }) {}", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({ trailing_ }) {}", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "ignored_foo = 0;", - options: [{ allow: ["ignored_foo"] }] - }, - { - code: "ignored_foo = 0; ignored_bar = 1;", - options: [{ allow: ["ignored_foo", "ignored_bar"] }] - }, - { - code: "user_id = 0;", - options: [{ allow: ["_id$"] }] - }, - { - code: "__option_foo__ = 0;", - options: [{ allow: ["__option_foo__"] }] - }, - { - code: "foo = { [computedBar]: 0 };", - options: [{ ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({ a: obj.fo_o } = bar);", - options: [{ allow: ["fo_o"] }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({ a: obj.foo } = bar);", - options: [{ allow: ["fo_o"] }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({ a: obj.fo_o } = bar);", - options: [{ properties: "never" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({ a: obj.fo_o.b_ar } = bar);", - options: [{ properties: "never" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({ a: { b: obj.fo_o } } = bar);", - options: [{ properties: "never" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "([obj.fo_o] = bar);", - options: [{ properties: "never" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({ c: [ob.fo_o]} = bar);", - options: [{ properties: "never" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "([obj.fo_o.b_ar] = bar);", - options: [{ properties: "never" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({obj} = baz.fo_o);", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "([obj] = baz.fo_o);", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "([obj.foo = obj.fo_o] = bar);", - options: [{ properties: "always" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class C { camelCase; #camelCase; #camelCase2() {} }", - options: [{ properties: "always" }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { snake_case; #snake_case; #snake_case2() {} }", - options: [{ properties: "never" }], - languageOptions: { ecmaVersion: 2022 } - }, + { + code: "var _camelCased = aGlobalVariable", + options: [{ ignoreGlobals: false }], + languageOptions: { globals: { aGlobalVariable: "readonly" } }, + }, + { + code: "var camelCased = _aGlobalVariable", + options: [{ ignoreGlobals: false }], + languageOptions: { globals: { _aGlobalVariable: "readonly" } }, + }, + { + code: "var camelCased = a_global_variable", + options: [{ ignoreGlobals: true }], + languageOptions: { globals: { a_global_variable: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + }, + { + code: "a_global_variable.foo()", + options: [{ ignoreGlobals: true }], + languageOptions: { globals: { a_global_variable: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + }, + { + code: "a_global_variable[undefined]", + options: [{ ignoreGlobals: true }], + languageOptions: { globals: { a_global_variable: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + }, + { + code: "var foo = a_global_variable.bar", + options: [{ ignoreGlobals: true }], + languageOptions: { globals: { a_global_variable: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + }, + { + code: "a_global_variable.foo = bar", + options: [{ ignoreGlobals: true }], + languageOptions: { globals: { a_global_variable: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + }, + { + code: "( { foo: a_global_variable.bar } = baz )", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "readonly", + }, + }, + }, + { + code: "a_global_variable = foo", + options: [{ ignoreGlobals: true }], + languageOptions: { globals: { a_global_variable: "writable" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + }, + { + code: "a_global_variable = foo", + options: [{ ignoreGlobals: true }], + languageOptions: { globals: { a_global_variable: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + }, + { + code: "({ a_global_variable } = foo)", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "writable", + }, + }, + }, + { + code: "({ snake_cased: a_global_variable } = foo)", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "writable", + }, + }, + }, + { + code: "({ snake_cased: a_global_variable = foo } = bar)", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "writable", + }, + }, + }, + { + code: "[a_global_variable] = bar", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "writable", + }, + }, + }, + { + code: "[a_global_variable = foo] = bar", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "writable", + }, + }, + }, + { + code: "foo[a_global_variable] = bar", + options: [{ ignoreGlobals: true }], + languageOptions: { + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "readonly", + }, + }, + }, + { + code: "var foo = { [a_global_variable]: bar }", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "readonly", + }, + }, + }, + { + code: "var { [a_global_variable]: foo } = bar", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "readonly", + }, + }, + }, + { + code: "function foo({ no_camelcased: camelCased }) {};", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({ no_camelcased: _leading }) {};", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({ no_camelcased: trailing_ }) {};", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({ camelCased = 'default value' }) {};", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({ _leading = 'default value' }) {};", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({ trailing_ = 'default value' }) {};", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({ camelCased }) {};", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({ _leading }) {}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({ trailing_ }) {}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "ignored_foo = 0;", + options: [{ allow: ["ignored_foo"] }], + }, + { + code: "ignored_foo = 0; ignored_bar = 1;", + options: [{ allow: ["ignored_foo", "ignored_bar"] }], + }, + { + code: "user_id = 0;", + options: [{ allow: ["_id$"] }], + }, + { + code: "__option_foo__ = 0;", + options: [{ allow: ["__option_foo__"] }], + }, + { + code: "__option_foo__ = 0; user_id = 0; foo = 1", + options: [{ allow: ["__option_foo__", "_id$"] }], + }, + { + code: "fo_o = 0;", + options: [{ allow: ["__option_foo__", "fo_o"] }], + }, + { + code: "user = 0;", + options: [{ allow: [] }], + }, + { + code: "foo = { [computedBar]: 0 };", + options: [{ ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ a: obj.fo_o } = bar);", + options: [{ allow: ["fo_o"] }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ a: obj.foo } = bar);", + options: [{ allow: ["fo_o"] }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ a: obj.fo_o } = bar);", + options: [{ properties: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ a: obj.fo_o.b_ar } = bar);", + options: [{ properties: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ a: { b: obj.fo_o } } = bar);", + options: [{ properties: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "([obj.fo_o] = bar);", + options: [{ properties: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ c: [ob.fo_o]} = bar);", + options: [{ properties: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "([obj.fo_o.b_ar] = bar);", + options: [{ properties: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({obj} = baz.fo_o);", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "([obj] = baz.fo_o);", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "([obj.foo = obj.fo_o] = bar);", + options: [{ properties: "always" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class C { camelCase; #camelCase; #camelCase2() {} }", + options: [{ properties: "always" }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { snake_case; #snake_case; #snake_case2() {} }", + options: [{ properties: "never" }], + languageOptions: { ecmaVersion: 2022 }, + }, - // Combinations of `properties` and `ignoreDestructuring` - { - code: ` + // Combinations of `properties` and `ignoreDestructuring` + { + code: ` const { some_property } = obj; const bar = { some_property }; @@ -421,1058 +486,1199 @@ ruleTester.run("camelcase", rule, { console.log(some_property) }; `, - options: [{ properties: "never", ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 2022 } - }, + options: [{ properties: "never", ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 2022 }, + }, - // https://github.com/eslint/eslint/issues/15572 - { - code: ` + // https://github.com/eslint/eslint/issues/15572 + { + code: ` const { some_property } = obj; doSomething({ some_property }); `, - options: [{ properties: "never", ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 2022 } - } - ], - invalid: [ - { - code: "first_name = \"Nicholas\"", - errors: [ - { - messageId: "notCamelCase", - data: { name: "first_name" }, - type: "Identifier" - } - ] - }, - { - code: "__private_first_name = \"Patrick\"", - errors: [ - { - messageId: "notCamelCase", - data: { name: "__private_first_name" }, - type: "Identifier" - } - ] - }, - { - code: "function foo_bar(){}", - errors: [ - { - messageId: "notCamelCase", - data: { name: "foo_bar" }, - type: "Identifier" - } - ] - }, - { - code: "obj.foo_bar = function(){};", - errors: [ - { - messageId: "notCamelCase", - data: { name: "foo_bar" }, - type: "Identifier" - } - ] - }, - { - code: "bar_baz.foo = function(){};", - errors: [ - { - messageId: "notCamelCase", - data: { name: "bar_baz" }, - type: "Identifier" - } - ] - }, - { - code: "[foo_bar.baz]", - errors: [ - { - messageId: "notCamelCase", - data: { name: "foo_bar" }, - type: "Identifier" - } - ] - }, - { - code: "if (foo.bar_baz === boom.bam_pow) { [foo_bar.baz] }", - errors: [ - { - messageId: "notCamelCase", - data: { name: "foo_bar" }, - type: "Identifier" - } - ] - }, - { - code: "foo.bar_baz = boom.bam_pow", - errors: [ - { - messageId: "notCamelCase", - data: { name: "bar_baz" }, - type: "Identifier" - } - ] - }, - { - code: "var foo = { bar_baz: boom.bam_pow }", - errors: [ - { - messageId: "notCamelCase", - data: { name: "bar_baz" }, - type: "Identifier" - } - ] - }, - { - code: "var foo = { bar_baz: boom.bam_pow }", - options: [{ ignoreDestructuring: true }], - errors: [ - { - messageId: "notCamelCase", - data: { name: "bar_baz" }, - type: "Identifier" - } - ] - }, - { - code: "foo.qux.boom_pow = { bar: boom.bam_pow }", - errors: [ - { - messageId: "notCamelCase", - data: { name: "boom_pow" }, - type: "Identifier" - } - ] - }, - { - code: "var o = {bar_baz: 1}", - options: [{ properties: "always" }], - errors: [ - { - messageId: "notCamelCase", - data: { name: "bar_baz" }, - type: "Identifier" - } - ] - }, - { - code: "obj.a_b = 2;", - options: [{ properties: "always" }], - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_b" }, - type: "Identifier" - } - ] - }, - { - code: "var { category_id: category_alias } = query;", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "category_alias" }, - type: "Identifier" - } - ] - }, - { - code: "var { category_id: category_alias } = query;", - options: [{ ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "category_alias" }, - type: "Identifier" - } - ] - }, - { - code: "var { [category_id]: categoryId } = query;", - options: [{ ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "category_id" }, - type: "Identifier" - } - ] - }, - { - code: "var { [category_id]: categoryId } = query;", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "category_id" }, - type: "Identifier" - } - ] - }, - { - code: "var { category_id: categoryId, ...other_props } = query;", - options: [{ ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 2018 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "other_props" }, - type: "Identifier" - } - ] - }, - { - code: "var { category_id } = query;", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "category_id" }, - type: "Identifier" - } - ] - }, - { - code: "var { category_id: category_id } = query;", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "category_id" }, - type: "Identifier" - } - ] - }, - { - code: "var { category_id = 1 } = query;", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "category_id" }, - type: "Identifier" - } - ] - }, - { - code: "import no_camelcased from \"external-module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camelcased" }, - type: "Identifier" - } - ] - }, - { - code: "import * as no_camelcased from \"external-module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camelcased" }, - type: "Identifier" - } - ] - }, - { - code: "import { no_camelcased } from \"external-module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camelcased" }, - type: "Identifier" - } - ] - }, - { - code: "import { no_camelcased as no_camel_cased } from \"external module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camel_cased" }, - type: "Identifier" - } - ] - }, - { - code: "import { camelCased as no_camel_cased } from \"external module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camel_cased" }, - type: "Identifier" - } - ] - }, - { - code: "import { 'snake_cased' as snake_cased } from 'mod'", - languageOptions: { ecmaVersion: 2022, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "snake_cased" }, - type: "Identifier" - } - ] - }, - { - code: "import { 'snake_cased' as another_snake_cased } from 'mod'", - options: [{ ignoreImports: true }], - languageOptions: { ecmaVersion: 2022, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "another_snake_cased" }, - type: "Identifier" - } - ] - }, - { - code: "import { camelCased, no_camelcased } from \"external-module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camelcased" }, - type: "Identifier" - } - ] - }, - { - code: "import { no_camelcased as camelCased, another_no_camelcased } from \"external-module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "another_no_camelcased" }, - type: "Identifier" - } - ] - }, - { - code: "import camelCased, { no_camelcased } from \"external-module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camelcased" }, - type: "Identifier" - } - ] - }, - { - code: "import no_camelcased, { another_no_camelcased as camelCased } from \"external-module\";", - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camelcased" }, - type: "Identifier" - } - ] - }, - { - code: "import snake_cased from 'mod'", - options: [{ ignoreImports: true }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "snake_cased" }, - type: "Identifier" - } - ] - }, - { - code: "import * as snake_cased from 'mod'", - options: [{ ignoreImports: true }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "snake_cased" }, - type: "Identifier" - } - ] - }, - { - code: "import snake_cased from 'mod'", - options: [{ ignoreImports: false }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "snake_cased" }, - type: "Identifier" - } - ] - }, - { - code: "import * as snake_cased from 'mod'", - options: [{ ignoreImports: false }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "snake_cased" }, - type: "Identifier" - } - ] - }, - { - code: "var camelCased = snake_cased", - options: [{ ignoreGlobals: false }], - languageOptions: { globals: { snake_cased: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "snake_cased" }, - type: "Identifier" - } - ] - }, - { - code: "a_global_variable.foo()", - options: [{ ignoreGlobals: false }], - languageOptions: { globals: { snake_cased: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier" - } - ] - }, - { - code: "a_global_variable[undefined]", - options: [{ ignoreGlobals: false }], - languageOptions: { globals: { snake_cased: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier" - } - ] - }, - { - code: "var camelCased = snake_cased", - languageOptions: { globals: { snake_cased: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "snake_cased" }, - type: "Identifier" - } - ] - }, - { - code: "var camelCased = snake_cased", - options: [{}], - languageOptions: { globals: { snake_cased: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "snake_cased" }, - type: "Identifier" - } - ] - }, - { - code: "foo.a_global_variable = bar", - options: [{ ignoreGlobals: true }], - languageOptions: { globals: { a_global_variable: "writable" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier" - } - ] - }, - { - code: "var foo = { a_global_variable: bar }", - options: [{ ignoreGlobals: true }], - languageOptions: { globals: { a_global_variable: "writable" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier" - } - ] - }, - { - code: "var foo = { a_global_variable: a_global_variable }", - options: [{ ignoreGlobals: true }], - languageOptions: { globals: { a_global_variable: "writable" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier", - column: 13 - } - ] - }, - { - code: "var foo = { a_global_variable() {} }", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "writable" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier" - } - ] - }, - { - code: "class Foo { a_global_variable() {} }", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "writable" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier" - } - ] - }, - { - code: "a_global_variable: for (;;);", - options: [{ ignoreGlobals: true }], - languageOptions: { globals: { a_global_variable: "writable" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier" - } - ] - }, - { - code: "if (foo) { let a_global_variable; a_global_variable = bar; }", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "writable" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier", - column: 16 - }, - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier", - column: 35 - } - ] - }, - { - code: "function foo(a_global_variable) { foo = a_global_variable; }", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "writable" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier", - column: 14 - }, - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier", - column: 41 - } - ] - }, - { - code: "var a_global_variable", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier" - } - ] - }, - { - code: "function a_global_variable () {}", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier" - } - ] - }, - { - code: "const a_global_variable = foo; bar = a_global_variable", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "writable" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier", - column: 7 - }, - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier", - column: 38 - } - ] - }, - { - code: "bar = a_global_variable; var a_global_variable;", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "writable" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier", - column: 7 - }, - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier", - column: 30 - } - ] - }, - { - code: "var foo = { a_global_variable }", - options: [{ ignoreGlobals: true }], - languageOptions: { ecmaVersion: 6, globals: { a_global_variable: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase - errors: [ - { - messageId: "notCamelCase", - data: { name: "a_global_variable" }, - type: "Identifier" - } - ] - }, - { - code: "undefined_variable;", - options: [{ ignoreGlobals: true }], - errors: [ - { - messageId: "notCamelCase", - data: { name: "undefined_variable" }, - type: "Identifier" - } - ] - }, - { - code: "implicit_global = 1;", - options: [{ ignoreGlobals: true }], - errors: [ - { - messageId: "notCamelCase", - data: { name: "implicit_global" }, - type: "Identifier" - } - ] - }, - { - code: "export * as snake_cased from 'mod'", - languageOptions: { ecmaVersion: 2020, sourceType: "module" }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "snake_cased" }, - type: "Identifier" - } - ] - }, - { - code: "function foo({ no_camelcased }) {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camelcased" }, - type: "Identifier" - } - ] - }, - { - code: "function foo({ no_camelcased = 'default value' }) {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camelcased" }, - type: "Identifier" - } - ] - }, - { - code: "const no_camelcased = 0; function foo({ camelcased_value = no_camelcased}) {}", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camelcased" }, - type: "Identifier" - }, - { - messageId: "notCamelCase", - data: { name: "camelcased_value" }, - type: "Identifier" - } - ] - }, - { - code: "const { bar: no_camelcased } = foo;", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camelcased" }, - type: "Identifier" - } - ] - }, - { - code: "function foo({ value_1: my_default }) {}", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "my_default" }, - type: "Identifier" - } - ] - }, - { - code: "function foo({ isCamelcased: no_camelcased }) {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camelcased" }, - type: "Identifier" - } - ] - }, - { - code: "var { foo: bar_baz = 1 } = quz;", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "bar_baz" }, - type: "Identifier" - } - ] - }, - { - code: "const { no_camelcased = false } = bar;", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camelcased" }, - type: "Identifier" - } - ] - }, - { - code: "const { no_camelcased = foo_bar } = bar;", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "no_camelcased" }, - type: "Identifier" - } - ] - }, - { - code: "not_ignored_foo = 0;", - options: [{ allow: ["ignored_bar"] }], - errors: [ - { - messageId: "notCamelCase", - data: { name: "not_ignored_foo" }, - type: "Identifier" - } - ] - }, - { - code: "not_ignored_foo = 0;", - options: [{ allow: ["_id$"] }], - errors: [ - { - messageId: "notCamelCase", - data: { name: "not_ignored_foo" }, - type: "Identifier" - } - ] - }, - { - code: "foo = { [computed_bar]: 0 };", - options: [{ ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "computed_bar" }, - type: "Identifier" - } - ] - }, - { - code: "({ a: obj.fo_o } = bar);", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "fo_o" }, - type: "Identifier" - } - ] - }, - { - code: "({ a: obj.fo_o } = bar);", - options: [{ ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "fo_o" }, - type: "Identifier" - } - ] - }, - { - code: "({ a: obj.fo_o.b_ar } = baz);", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "b_ar" }, - type: "Identifier" - } - ] - }, - { - code: "({ a: { b: { c: obj.fo_o } } } = bar);", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "fo_o" }, - type: "Identifier" - } - ] - }, - { - code: "({ a: { b: { c: obj.fo_o.b_ar } } } = baz);", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "b_ar" }, - type: "Identifier" - } - ] - }, - { - code: "([obj.fo_o] = bar);", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "fo_o" }, - type: "Identifier" - } - ] - }, - { - code: "([obj.fo_o] = bar);", - options: [{ ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "fo_o" }, - type: "Identifier" - } - ] - }, - { - code: "([obj.fo_o = 1] = bar);", - options: [{ properties: "always" }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "fo_o" }, - type: "Identifier" - } - ] - }, - { - code: "({ a: [obj.fo_o] } = bar);", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "fo_o" }, - type: "Identifier" - } - ] - }, - { - code: "({ a: { b: [obj.fo_o] } } = bar);", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "fo_o" }, - type: "Identifier" - } - ] - }, - { - code: "([obj.fo_o.ba_r] = baz);", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "ba_r" }, - type: "Identifier" - } - ] - }, - { - code: "({...obj.fo_o} = baz);", - languageOptions: { ecmaVersion: 9 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "fo_o" }, - type: "Identifier" - } - ] - }, - { - code: "({...obj.fo_o.ba_r} = baz);", - languageOptions: { ecmaVersion: 9 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "ba_r" }, - type: "Identifier" - } - ] - }, - { - code: "({c: {...obj.fo_o }} = baz);", - languageOptions: { ecmaVersion: 9 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "fo_o" }, - type: "Identifier" - } - ] - }, + options: [{ properties: "never", ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 2022 }, + }, - // Optional chaining. - { - code: "obj.o_k.non_camelcase = 0", - options: [{ properties: "always" }], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "notCamelCase", data: { name: "non_camelcase" } }] - }, - { - code: "(obj?.o_k).non_camelcase = 0", - options: [{ properties: "always" }], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "notCamelCase", data: { name: "non_camelcase" } }] - }, + // Import attribute keys + { + code: "import foo from 'foo.json' with { my_type: 'json' }", + options: [ + { + properties: "always", + ignoreImports: false, + }, + ], + languageOptions: { ecmaVersion: 2025, sourceType: "module" }, + }, + { + code: "export * from 'foo.json' with { my_type: 'json' }", + options: [ + { + properties: "always", + ignoreImports: false, + }, + ], + languageOptions: { ecmaVersion: 2025, sourceType: "module" }, + }, + { + code: "export { default } from 'foo.json' with { my_type: 'json' }", + options: [ + { + properties: "always", + ignoreImports: false, + }, + ], + languageOptions: { ecmaVersion: 2025, sourceType: "module" }, + }, + { + code: "import('foo.json', { my_with: { my_type: 'json' } })", + options: [ + { + properties: "always", + ignoreImports: false, + }, + ], + languageOptions: { ecmaVersion: 2025 }, + }, + { + code: "import('foo.json', { 'with': { my_type: 'json' } })", + options: [ + { + properties: "always", + ignoreImports: false, + }, + ], + languageOptions: { ecmaVersion: 2025 }, + }, + { + code: "import('foo.json', { my_with: { my_type } })", + options: [ + { + properties: "always", + ignoreImports: false, + }, + ], + languageOptions: { ecmaVersion: 2025 }, + }, + { + code: "import('foo.json', { my_with: { my_type } })", + options: [ + { + properties: "always", + ignoreImports: false, + }, + ], + languageOptions: { + ecmaVersion: 2025, + globals: { + my_type: true, // eslint-disable-line camelcase -- for testing + }, + }, + }, + ], + invalid: [ + { + code: 'first_name = "Nicholas"', + errors: [ + { + messageId: "notCamelCase", + data: { name: "first_name" }, + type: "Identifier", + }, + ], + }, + { + code: '__private_first_name = "Patrick"', + errors: [ + { + messageId: "notCamelCase", + data: { name: "__private_first_name" }, + type: "Identifier", + }, + ], + }, + { + code: "function foo_bar(){}", + errors: [ + { + messageId: "notCamelCase", + data: { name: "foo_bar" }, + type: "Identifier", + }, + ], + }, + { + code: "obj.foo_bar = function(){};", + errors: [ + { + messageId: "notCamelCase", + data: { name: "foo_bar" }, + type: "Identifier", + }, + ], + }, + { + code: "bar_baz.foo = function(){};", + errors: [ + { + messageId: "notCamelCase", + data: { name: "bar_baz" }, + type: "Identifier", + }, + ], + }, + { + code: "[foo_bar.baz]", + errors: [ + { + messageId: "notCamelCase", + data: { name: "foo_bar" }, + type: "Identifier", + }, + ], + }, + { + code: "if (foo.bar_baz === boom.bam_pow) { [foo_bar.baz] }", + errors: [ + { + messageId: "notCamelCase", + data: { name: "foo_bar" }, + type: "Identifier", + }, + ], + }, + { + code: "foo.bar_baz = boom.bam_pow", + errors: [ + { + messageId: "notCamelCase", + data: { name: "bar_baz" }, + type: "Identifier", + }, + ], + }, + { + code: "var foo = { bar_baz: boom.bam_pow }", + errors: [ + { + messageId: "notCamelCase", + data: { name: "bar_baz" }, + type: "Identifier", + }, + ], + }, + { + code: "var foo = { bar_baz: boom.bam_pow }", + options: [{ ignoreDestructuring: true }], + errors: [ + { + messageId: "notCamelCase", + data: { name: "bar_baz" }, + type: "Identifier", + }, + ], + }, + { + code: "foo.qux.boom_pow = { bar: boom.bam_pow }", + errors: [ + { + messageId: "notCamelCase", + data: { name: "boom_pow" }, + type: "Identifier", + }, + ], + }, + { + code: "var o = {bar_baz: 1}", + options: [{ properties: "always" }], + errors: [ + { + messageId: "notCamelCase", + data: { name: "bar_baz" }, + type: "Identifier", + }, + ], + }, + { + code: "obj.a_b = 2;", + options: [{ properties: "always" }], + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_b" }, + type: "Identifier", + }, + ], + }, + { + code: "var { category_id: category_alias } = query;", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "category_alias" }, + type: "Identifier", + }, + ], + }, + { + code: "var { category_id: category_alias } = query;", + options: [{ ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "category_alias" }, + type: "Identifier", + }, + ], + }, + { + code: "var { [category_id]: categoryId } = query;", + options: [{ ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "category_id" }, + type: "Identifier", + }, + ], + }, + { + code: "var { [category_id]: categoryId } = query;", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "category_id" }, + type: "Identifier", + }, + ], + }, + { + code: "var { category_id: categoryId, ...other_props } = query;", + options: [{ ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 2018 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "other_props" }, + type: "Identifier", + }, + ], + }, + { + code: "var { category_id } = query;", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "category_id" }, + type: "Identifier", + }, + ], + }, + { + code: "var { category_id: category_id } = query;", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "category_id" }, + type: "Identifier", + }, + ], + }, + { + code: "var { category_id = 1 } = query;", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "category_id" }, + type: "Identifier", + }, + ], + }, + { + code: 'import no_camelcased from "external-module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camelcased" }, + type: "Identifier", + }, + ], + }, + { + code: 'import * as no_camelcased from "external-module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camelcased" }, + type: "Identifier", + }, + ], + }, + { + code: 'import { no_camelcased } from "external-module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camelcased" }, + type: "Identifier", + }, + ], + }, + { + code: 'import { no_camelcased as no_camel_cased } from "external module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camel_cased" }, + type: "Identifier", + }, + ], + }, + { + code: 'import { camelCased as no_camel_cased } from "external module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camel_cased" }, + type: "Identifier", + }, + ], + }, + { + code: "import { 'snake_cased' as snake_cased } from 'mod'", + languageOptions: { ecmaVersion: 2022, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "snake_cased" }, + type: "Identifier", + }, + ], + }, + { + code: "import { 'snake_cased' as another_snake_cased } from 'mod'", + options: [{ ignoreImports: true }], + languageOptions: { ecmaVersion: 2022, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "another_snake_cased" }, + type: "Identifier", + }, + ], + }, + { + code: 'import { camelCased, no_camelcased } from "external-module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camelcased" }, + type: "Identifier", + }, + ], + }, + { + code: 'import { no_camelcased as camelCased, another_no_camelcased } from "external-module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "another_no_camelcased" }, + type: "Identifier", + }, + ], + }, + { + code: 'import camelCased, { no_camelcased } from "external-module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camelcased" }, + type: "Identifier", + }, + ], + }, + { + code: 'import no_camelcased, { another_no_camelcased as camelCased } from "external-module";', + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camelcased" }, + type: "Identifier", + }, + ], + }, + { + code: "import snake_cased from 'mod'", + options: [{ ignoreImports: true }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "snake_cased" }, + type: "Identifier", + }, + ], + }, + { + code: "import * as snake_cased from 'mod'", + options: [{ ignoreImports: true }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "snake_cased" }, + type: "Identifier", + }, + ], + }, + { + code: "import snake_cased from 'mod'", + options: [{ ignoreImports: false }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "snake_cased" }, + type: "Identifier", + }, + ], + }, + { + code: "import * as snake_cased from 'mod'", + options: [{ ignoreImports: false }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "snake_cased" }, + type: "Identifier", + }, + ], + }, + { + code: "var camelCased = snake_cased", + options: [{ ignoreGlobals: false }], + languageOptions: { globals: { snake_cased: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + errors: [ + { + messageId: "notCamelCase", + data: { name: "snake_cased" }, + type: "Identifier", + }, + ], + }, + { + code: "a_global_variable.foo()", + options: [{ ignoreGlobals: false }], + languageOptions: { globals: { snake_cased: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + }, + ], + }, + { + code: "a_global_variable[undefined]", + options: [{ ignoreGlobals: false }], + languageOptions: { globals: { snake_cased: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + }, + ], + }, + { + code: "var camelCased = snake_cased", + languageOptions: { globals: { snake_cased: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + errors: [ + { + messageId: "notCamelCase", + data: { name: "snake_cased" }, + type: "Identifier", + }, + ], + }, + { + code: "var camelCased = snake_cased", + options: [{}], + languageOptions: { globals: { snake_cased: "readonly" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + errors: [ + { + messageId: "notCamelCase", + data: { name: "snake_cased" }, + type: "Identifier", + }, + ], + }, + { + code: "foo.a_global_variable = bar", + options: [{ ignoreGlobals: true }], + languageOptions: { globals: { a_global_variable: "writable" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + }, + ], + }, + { + code: "var foo = { a_global_variable: bar }", + options: [{ ignoreGlobals: true }], + languageOptions: { globals: { a_global_variable: "writable" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + }, + ], + }, + { + code: "var foo = { a_global_variable: a_global_variable }", + options: [{ ignoreGlobals: true }], + languageOptions: { globals: { a_global_variable: "writable" } }, // eslint-disable-line camelcase -- Testing non-CamelCase + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + column: 13, + }, + ], + }, + { + code: "var foo = { a_global_variable() {} }", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "writable", + }, + }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + }, + ], + }, + { + code: "class Foo { a_global_variable() {} }", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "writable", + }, + }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + }, + ], + }, + { + code: "a_global_variable: for (;;);", + options: [{ ignoreGlobals: true }], + languageOptions: { + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "writable", + }, + }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + }, + ], + }, + { + code: "if (foo) { let a_global_variable; a_global_variable = bar; }", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "writable", + }, + }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + column: 16, + }, + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + column: 35, + }, + ], + }, + { + code: "function foo(a_global_variable) { foo = a_global_variable; }", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "writable", + }, + }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + column: 14, + }, + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + column: 41, + }, + ], + }, + { + code: "var a_global_variable", + options: [{ ignoreGlobals: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + }, + ], + }, + { + code: "function a_global_variable () {}", + options: [{ ignoreGlobals: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + }, + ], + }, + { + code: "const a_global_variable = foo; bar = a_global_variable", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "writable", + }, + }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + column: 7, + }, + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + column: 38, + }, + ], + }, + { + code: "bar = a_global_variable; var a_global_variable;", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "writable", + }, + }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + column: 7, + }, + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + column: 30, + }, + ], + }, + { + code: "var foo = { a_global_variable }", + options: [{ ignoreGlobals: true }], + languageOptions: { + ecmaVersion: 6, + globals: { + // eslint-disable-next-line camelcase -- Testing non-CamelCase + a_global_variable: "readonly", + }, + }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "a_global_variable" }, + type: "Identifier", + }, + ], + }, + { + code: "undefined_variable;", + options: [{ ignoreGlobals: true }], + errors: [ + { + messageId: "notCamelCase", + data: { name: "undefined_variable" }, + type: "Identifier", + }, + ], + }, + { + code: "implicit_global = 1;", + options: [{ ignoreGlobals: true }], + errors: [ + { + messageId: "notCamelCase", + data: { name: "implicit_global" }, + type: "Identifier", + }, + ], + }, + { + code: "export * as snake_cased from 'mod'", + languageOptions: { ecmaVersion: 2020, sourceType: "module" }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "snake_cased" }, + type: "Identifier", + }, + ], + }, + { + code: "function foo({ no_camelcased }) {};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camelcased" }, + type: "Identifier", + }, + ], + }, + { + code: "function foo({ no_camelcased = 'default value' }) {};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camelcased" }, + type: "Identifier", + }, + ], + }, + { + code: "const no_camelcased = 0; function foo({ camelcased_value = no_camelcased}) {}", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camelcased" }, + type: "Identifier", + }, + { + messageId: "notCamelCase", + data: { name: "camelcased_value" }, + type: "Identifier", + }, + ], + }, + { + code: "const { bar: no_camelcased } = foo;", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camelcased" }, + type: "Identifier", + }, + ], + }, + { + code: "function foo({ value_1: my_default }) {}", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "my_default" }, + type: "Identifier", + }, + ], + }, + { + code: "function foo({ isCamelcased: no_camelcased }) {};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camelcased" }, + type: "Identifier", + }, + ], + }, + { + code: "var { foo: bar_baz = 1 } = quz;", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "bar_baz" }, + type: "Identifier", + }, + ], + }, + { + code: "const { no_camelcased = false } = bar;", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camelcased" }, + type: "Identifier", + }, + ], + }, + { + code: "const { no_camelcased = foo_bar } = bar;", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "no_camelcased" }, + type: "Identifier", + }, + ], + }, + { + code: "not_ignored_foo = 0;", + options: [{ allow: ["ignored_bar"] }], + errors: [ + { + messageId: "notCamelCase", + data: { name: "not_ignored_foo" }, + type: "Identifier", + }, + ], + }, + { + code: "not_ignored_foo = 0;", + options: [{ allow: ["_id$"] }], + errors: [ + { + messageId: "notCamelCase", + data: { name: "not_ignored_foo" }, + type: "Identifier", + }, + ], + }, + { + code: "foo = { [computed_bar]: 0 };", + options: [{ ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "computed_bar" }, + type: "Identifier", + }, + ], + }, + { + code: "({ a: obj.fo_o } = bar);", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "fo_o" }, + type: "Identifier", + }, + ], + }, + { + code: "({ a: obj.fo_o } = bar);", + options: [{ ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "fo_o" }, + type: "Identifier", + }, + ], + }, + { + code: "({ a: obj.fo_o.b_ar } = baz);", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "b_ar" }, + type: "Identifier", + }, + ], + }, + { + code: "({ a: { b: { c: obj.fo_o } } } = bar);", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "fo_o" }, + type: "Identifier", + }, + ], + }, + { + code: "({ a: { b: { c: obj.fo_o.b_ar } } } = baz);", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "b_ar" }, + type: "Identifier", + }, + ], + }, + { + code: "([obj.fo_o] = bar);", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "fo_o" }, + type: "Identifier", + }, + ], + }, + { + code: "([obj.fo_o] = bar);", + options: [{ ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "fo_o" }, + type: "Identifier", + }, + ], + }, + { + code: "([obj.fo_o = 1] = bar);", + options: [{ properties: "always" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "fo_o" }, + type: "Identifier", + }, + ], + }, + { + code: "({ a: [obj.fo_o] } = bar);", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "fo_o" }, + type: "Identifier", + }, + ], + }, + { + code: "({ a: { b: [obj.fo_o] } } = bar);", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "fo_o" }, + type: "Identifier", + }, + ], + }, + { + code: "([obj.fo_o.ba_r] = baz);", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "ba_r" }, + type: "Identifier", + }, + ], + }, + { + code: "({...obj.fo_o} = baz);", + languageOptions: { ecmaVersion: 9 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "fo_o" }, + type: "Identifier", + }, + ], + }, + { + code: "({...obj.fo_o.ba_r} = baz);", + languageOptions: { ecmaVersion: 9 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "ba_r" }, + type: "Identifier", + }, + ], + }, + { + code: "({c: {...obj.fo_o }} = baz);", + languageOptions: { ecmaVersion: 9 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "fo_o" }, + type: "Identifier", + }, + ], + }, - // class public/private fields, private methods. - { - code: "class C { snake_case; }", - options: [{ properties: "always" }], - languageOptions: { ecmaVersion: 2022 }, - errors: [{ messageId: "notCamelCase", data: { name: "snake_case" } }] - }, - { - code: "class C { #snake_case; foo() { this.#snake_case; } }", - options: [{ properties: "always" }], - languageOptions: { ecmaVersion: 2022 }, - errors: [{ messageId: "notCamelCasePrivate", data: { name: "snake_case" }, column: 11 }] - }, - { - code: "class C { #snake_case() {} }", - options: [{ properties: "always" }], - languageOptions: { ecmaVersion: 2022 }, - errors: [{ messageId: "notCamelCasePrivate", data: { name: "snake_case" } }] - }, + // Optional chaining. + { + code: "obj.o_k.non_camelcase = 0", + options: [{ properties: "always" }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { messageId: "notCamelCase", data: { name: "non_camelcase" } }, + ], + }, + { + code: "(obj?.o_k).non_camelcase = 0", + options: [{ properties: "always" }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { messageId: "notCamelCase", data: { name: "non_camelcase" } }, + ], + }, - // Combinations of `properties` and `ignoreDestructuring` - { - code: ` + // class public/private fields, private methods. + { + code: "class C { snake_case; }", + options: [{ properties: "always" }], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "notCamelCase", data: { name: "snake_case" } }, + ], + }, + { + code: "class C { #snake_case; foo() { this.#snake_case; } }", + options: [{ properties: "always" }], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "notCamelCasePrivate", + data: { name: "snake_case" }, + column: 11, + }, + ], + }, + { + code: "class C { #snake_case() {} }", + options: [{ properties: "always" }], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "notCamelCasePrivate", + data: { name: "snake_case" }, + }, + ], + }, + + // Combinations of `properties` and `ignoreDestructuring` + { + code: ` const { some_property } = obj; doSomething({ some_property }); `, - options: [{ properties: "always", ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "some_property" }, - line: 3, - column: 27 - } - ] - }, - { - code: ` + options: [{ properties: "always", ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "some_property" }, + line: 3, + column: 27, + }, + ], + }, + { + code: ` const { some_property } = obj; doSomething({ some_property }); doSomething({ [some_property]: "bar" }); `, - options: [{ properties: "never", ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "some_property" }, - line: 4, - column: 28 - } - ] - }, - { - code: ` + options: [{ properties: "never", ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "some_property" }, + line: 4, + column: 28, + }, + ], + }, + { + code: ` const { some_property } = obj; const bar = { some_property }; @@ -1485,28 +1691,64 @@ ruleTester.run("camelcase", rule, { console.log(some_property) }; `, - options: [{ properties: "always", ignoreDestructuring: true }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - messageId: "notCamelCase", - data: { name: "some_property" }, - line: 4, - column: 27 - }, - { - messageId: "notCamelCase", - data: { name: "some_property" }, - line: 6, - column: 17 - }, - { - messageId: "notCamelCase", - data: { name: "some_property" }, - line: 8, - column: 27 - } - ] - } - ] + options: [{ properties: "always", ignoreDestructuring: true }], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "some_property" }, + line: 4, + column: 27, + }, + { + messageId: "notCamelCase", + data: { name: "some_property" }, + line: 6, + column: 17, + }, + { + messageId: "notCamelCase", + data: { name: "some_property" }, + line: 8, + column: 27, + }, + ], + }, + + // Not an import attribute key + { + code: "import('foo.json', { my_with: { [my_type]: 'json' } })", + options: [ + { + properties: "always", + ignoreImports: false, + }, + ], + languageOptions: { ecmaVersion: 2025 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "my_type" }, + type: "Identifier", + }, + ], + }, + { + code: "import('foo.json', { my_with: { my_type: my_json } })", + options: [ + { + properties: "always", + ignoreImports: false, + }, + ], + languageOptions: { ecmaVersion: 2025 }, + errors: [ + { + messageId: "notCamelCase", + data: { name: "my_json" }, + type: "Identifier", + }, + ], + }, + ], }); diff --git a/tests/lib/rules/capitalized-comments.js b/tests/lib/rules/capitalized-comments.js index ac7e73754838..5cdcbb5d5ab3 100644 --- a/tests/lib/rules/capitalized-comments.js +++ b/tests/lib/rules/capitalized-comments.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/capitalized-comments"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,892 +18,1053 @@ const rule = require("../../../lib/rules/capitalized-comments"), const ruleTester = new RuleTester(); ruleTester.run("capitalized-comments", rule, { + valid: [ + // No options: capitalization required + "//Uppercase", + "// Uppercase", + "/*Uppercase */", + "/* Uppercase */", + "/*\nUppercase */", + "/** Uppercase */", + "/**\nUppercase */", + "//\xDCber", + "//\u03A0", + "/* Uppercase\nsecond line need not be uppercase */", - valid: [ + // No options: Skips comments that only contain whitespace + "// ", + "//\t", + "/* */", + "/*\t*/", + "/*\n*/", + "/*\r*/", + "/*\r\n*/", + "/*\u2028*/", + "/*\u2029*/", - // No options: capitalization required - "//Uppercase", - "// Uppercase", - "/*Uppercase */", - "/* Uppercase */", - "/*\nUppercase */", - "/** Uppercase */", - "/**\nUppercase */", - "//\xDCber", - "//\u03A0", - "/* Uppercase\nsecond line need not be uppercase */", + // No options: non-alphabetical is okay + "//123", + "// 123", + "/*123*/", + "/* 123 */", + "/**123 */", + "/** 123 */", + "/**\n123 */", + "/*\n123 */", + "/*123\nsecond line need not be uppercase */", + "/**\n * @fileoverview This is a file */", - // No options: Skips comments that only contain whitespace - "// ", - "//\t", - "/* */", - "/*\t*/", - "/*\n*/", - "/*\r*/", - "/*\r\n*/", - "/*\u2028*/", - "/*\u2029*/", + // No options: eslint/istanbul/jshint/jscs/globals?/exported are okay + "// jscs: enable", + "// jscs:disable", + "// eslint-disable-line", + "// eslint-disable-next-line", + "/* eslint semi:off */", + "/* eslint-env node */", + "/* istanbul ignore next */", + "/* jshint asi:true */", + "/* jscs: enable */", + "/* global var1, var2 */", + "/* global var1:true, var2 */", + "/* globals var1, var2 */", + "/* globals var1:true, var2 */", + "/* exported myVar */", - // No options: non-alphabetical is okay - "//123", - "// 123", - "/*123*/", - "/* 123 */", - "/**123 */", - "/** 123 */", - "/**\n123 */", - "/*\n123 */", - "/*123\nsecond line need not be uppercase */", - "/**\n * @fileoverview This is a file */", + // Ignores shebangs + "#!foo", + { code: "#!foo", options: ["always"] }, + { code: "#!Foo", options: ["never"] }, + "#!/usr/bin/env node", + { code: "#!/usr/bin/env node", options: ["always"] }, + { code: "#!/usr/bin/env node", options: ["never"] }, - // No options: eslint/istanbul/jshint/jscs/globals?/exported are okay - "// jscs: enable", - "// jscs:disable", - "// eslint-disable-line", - "// eslint-disable-next-line", - "/* eslint semi:off */", - "/* eslint-env node */", - "/* istanbul ignore next */", - "/* jshint asi:true */", - "/* jscs: enable */", - "/* global var1, var2 */", - "/* global var1:true, var2 */", - "/* globals var1, var2 */", - "/* globals var1:true, var2 */", - "/* exported myVar */", + // Using "always" string option + { code: "//Uppercase", options: ["always"] }, + { code: "// Uppercase", options: ["always"] }, + { code: "/*Uppercase */", options: ["always"] }, + { code: "/* Uppercase */", options: ["always"] }, + { code: "/*\nUppercase */", options: ["always"] }, + { code: "/** Uppercase */", options: ["always"] }, + { code: "/**\nUppercase */", options: ["always"] }, + { code: "//\xDCber", options: ["always"] }, + { code: "//\u03A0", options: ["always"] }, + { + code: "/* Uppercase\nsecond line need not be uppercase */", + options: ["always"], + }, - // Ignores shebangs - "#!foo", - { code: "#!foo", options: ["always"] }, - { code: "#!Foo", options: ["never"] }, - "#!/usr/bin/env node", - { code: "#!/usr/bin/env node", options: ["always"] }, - { code: "#!/usr/bin/env node", options: ["never"] }, + // Using "always" string option: non-alphabetical is okay + { code: "//123", options: ["always"] }, + { code: "// 123", options: ["always"] }, + { code: "/*123*/", options: ["always"] }, + { code: "/**123*/", options: ["always"] }, + { code: "/* 123 */", options: ["always"] }, + { code: "/** 123*/", options: ["always"] }, + { code: "/**\n123*/", options: ["always"] }, + { code: "/*\n123 */", options: ["always"] }, + { + code: "/*123\nsecond line need not be uppercase */", + options: ["always"], + }, + { + code: "/**\n @todo: foobar\n */", + options: ["always"], + }, + { + code: "/**\n * @fileoverview This is a file */", + options: ["always"], + }, - // Using "always" string option - { code: "//Uppercase", options: ["always"] }, - { code: "// Uppercase", options: ["always"] }, - { code: "/*Uppercase */", options: ["always"] }, - { code: "/* Uppercase */", options: ["always"] }, - { code: "/*\nUppercase */", options: ["always"] }, - { code: "/** Uppercase */", options: ["always"] }, - { code: "/**\nUppercase */", options: ["always"] }, - { code: "//\xDCber", options: ["always"] }, - { code: "//\u03A0", options: ["always"] }, - { - code: "/* Uppercase\nsecond line need not be uppercase */", - options: ["always"] - }, + // Using "always" string option: eslint/istanbul/jshint/jscs/globals?/exported are okay + { code: "// jscs: enable", options: ["always"] }, + { code: "// jscs:disable", options: ["always"] }, + { code: "// eslint-disable-line", options: ["always"] }, + { code: "// eslint-disable-next-line", options: ["always"] }, + { code: "/* eslint semi:off */", options: ["always"] }, + { code: "/* eslint-env node */", options: ["always"] }, + { code: "/* istanbul ignore next */", options: ["always"] }, + { code: "/* jshint asi:true */", options: ["always"] }, + { code: "/* jscs: enable */", options: ["always"] }, + { code: "/* global var1, var2 */", options: ["always"] }, + { code: "/* global var1:true, var2 */", options: ["always"] }, + { code: "/* globals var1, var2 */", options: ["always"] }, + { code: "/* globals var1:true, var2 */", options: ["always"] }, + { code: "/* exported myVar */", options: ["always"] }, - // Using "always" string option: non-alphabetical is okay - { code: "//123", options: ["always"] }, - { code: "// 123", options: ["always"] }, - { code: "/*123*/", options: ["always"] }, - { code: "/**123*/", options: ["always"] }, - { code: "/* 123 */", options: ["always"] }, - { code: "/** 123*/", options: ["always"] }, - { code: "/**\n123*/", options: ["always"] }, - { code: "/*\n123 */", options: ["always"] }, - { - code: "/*123\nsecond line need not be uppercase */", - options: ["always"] - }, - { - code: "/**\n @todo: foobar\n */", - options: ["always"] - }, - { - code: "/**\n * @fileoverview This is a file */", - options: ["always"] - }, + // Using "never" string option + { code: "//lowercase", options: ["never"] }, + { code: "// lowercase", options: ["never"] }, + { code: "/*lowercase */", options: ["never"] }, + { code: "/* lowercase */", options: ["never"] }, + { code: "/*\nlowercase */", options: ["never"] }, + { code: "//\xFCber", options: ["never"] }, + { code: "//\u03C0", options: ["never"] }, + { + code: "/* lowercase\nSecond line need not be lowercase */", + options: ["never"], + }, - // Using "always" string option: eslint/istanbul/jshint/jscs/globals?/exported are okay - { code: "// jscs: enable", options: ["always"] }, - { code: "// jscs:disable", options: ["always"] }, - { code: "// eslint-disable-line", options: ["always"] }, - { code: "// eslint-disable-next-line", options: ["always"] }, - { code: "/* eslint semi:off */", options: ["always"] }, - { code: "/* eslint-env node */", options: ["always"] }, - { code: "/* istanbul ignore next */", options: ["always"] }, - { code: "/* jshint asi:true */", options: ["always"] }, - { code: "/* jscs: enable */", options: ["always"] }, - { code: "/* global var1, var2 */", options: ["always"] }, - { code: "/* global var1:true, var2 */", options: ["always"] }, - { code: "/* globals var1, var2 */", options: ["always"] }, - { code: "/* globals var1:true, var2 */", options: ["always"] }, - { code: "/* exported myVar */", options: ["always"] }, + // Using "never" string option: non-alphabetical is okay + { code: "//123", options: ["never"] }, + { code: "// 123", options: ["never"] }, + { code: "/*123*/", options: ["never"] }, + { code: "/* 123 */", options: ["never"] }, + { code: "/*\n123 */", options: ["never"] }, + { + code: "/*123\nsecond line need not be uppercase */", + options: ["never"], + }, + { + code: "/**\n @TODO: foobar\n */", + options: ["never"], + }, + { + code: "/**\n * @Fileoverview This is a file */", + options: ["never"], + }, - // Using "never" string option - { code: "//lowercase", options: ["never"] }, - { code: "// lowercase", options: ["never"] }, - { code: "/*lowercase */", options: ["never"] }, - { code: "/* lowercase */", options: ["never"] }, - { code: "/*\nlowercase */", options: ["never"] }, - { code: "//\xFCber", options: ["never"] }, - { code: "//\u03C0", options: ["never"] }, - { - code: "/* lowercase\nSecond line need not be lowercase */", - options: ["never"] - }, + // If first word in comment matches ignorePattern, don't warn + { + code: "// matching", + options: ["always", { ignorePattern: "match" }], + }, + { + code: "// Matching", + options: ["never", { ignorePattern: "Match" }], + }, + { + code: "// bar", + options: ["always", { ignorePattern: "foo|bar" }], + }, + { + code: "// Bar", + options: ["never", { ignorePattern: "Foo|Bar" }], + }, - // Using "never" string option: non-alphabetical is okay - { code: "//123", options: ["never"] }, - { code: "// 123", options: ["never"] }, - { code: "/*123*/", options: ["never"] }, - { code: "/* 123 */", options: ["never"] }, - { code: "/*\n123 */", options: ["never"] }, - { - code: "/*123\nsecond line need not be uppercase */", - options: ["never"] - }, - { - code: "/**\n @TODO: foobar\n */", - options: ["never"] - }, - { - code: "/**\n * @Fileoverview This is a file */", - options: ["never"] - }, + // Inline comments are not warned if ignoreInlineComments: true + { + code: "foo(/* ignored */ a);", + options: ["always", { ignoreInlineComments: true }], + }, + { + code: "foo(/* Ignored */ a);", + options: ["never", { ignoreInlineComments: true }], + }, - // If first word in comment matches ignorePattern, don't warn - { - code: "// matching", - options: ["always", { ignorePattern: "match" }] - }, - { - code: "// Matching", - options: ["never", { ignorePattern: "Match" }] - }, - { - code: "// bar", - options: ["always", { ignorePattern: "foo|bar" }] - }, - { - code: "// Bar", - options: ["never", { ignorePattern: "Foo|Bar" }] - }, + // Inline comments can span multiple lines + { + code: "foo(/*\nignored */ a);", + options: ["always", { ignoreInlineComments: true }], + }, + { + code: "foo(/*\nIgnored */ a);", + options: ["never", { ignoreInlineComments: true }], + }, - // Inline comments are not warned if ignoreInlineComments: true - { - code: "foo(/* ignored */ a);", - options: ["always", { ignoreInlineComments: true }] - }, - { - code: "foo(/* Ignored */ a);", - options: ["never", { ignoreInlineComments: true }] - }, + // Tolerating consecutive comments + { + code: [ + "// This comment is valid since it is capitalized,", + "// and this one is valid since it follows a valid one,", + "// and same with this one.", + ].join("\n"), + options: ["always", { ignoreConsecutiveComments: true }], + }, + { + code: [ + "/* This comment is valid since it is capitalized, */", + "/* and this one is valid since it follows a valid one, */", + "/* and same with this one. */", + ].join("\n"), + options: ["always", { ignoreConsecutiveComments: true }], + }, + { + code: [ + "/*", + " * This comment is valid since it is capitalized,", + " */", + "/* and this one is valid since it follows a valid one, */", + "/*", + " * and same with this one.", + " */", + ].join("\n"), + options: ["always", { ignoreConsecutiveComments: true }], + }, + { + code: [ + "// This comment is valid since it is capitalized,", + "// and this one is valid since it follows a valid one,", + "foo();", + "// This comment now has to be capitalized.", + ].join("\n"), + options: ["always", { ignoreConsecutiveComments: true }], + }, - // Inline comments can span multiple lines - { - code: "foo(/*\nignored */ a);", - options: ["always", { ignoreInlineComments: true }] - }, - { - code: "foo(/*\nIgnored */ a);", - options: ["never", { ignoreInlineComments: true }] - }, + // Comments which start with URLs should always be valid + { + code: "// https://github.com", + options: ["always"], + }, + { + code: "// HTTPS://GITHUB.COM", + options: ["never"], + }, - // Tolerating consecutive comments - { - code: [ - "// This comment is valid since it is capitalized,", - "// and this one is valid since it follows a valid one,", - "// and same with this one." - ].join("\n"), - options: ["always", { ignoreConsecutiveComments: true }] - }, - { - code: [ - "/* This comment is valid since it is capitalized, */", - "/* and this one is valid since it follows a valid one, */", - "/* and same with this one. */" - ].join("\n"), - options: ["always", { ignoreConsecutiveComments: true }] - }, - { - code: [ - "/*", - " * This comment is valid since it is capitalized,", - " */", - "/* and this one is valid since it follows a valid one, */", - "/*", - " * and same with this one.", - " */" - ].join("\n"), - options: ["always", { ignoreConsecutiveComments: true }] - }, - { - code: [ - "// This comment is valid since it is capitalized,", - "// and this one is valid since it follows a valid one,", - "foo();", - "// This comment now has to be capitalized." - ].join("\n"), - options: ["always", { ignoreConsecutiveComments: true }] - }, + // Using different options for line/block comments + { + code: [ + "// Valid capitalized line comment", + "/* Valid capitalized block comment */", + "// lineCommentIgnorePattern", + "/* blockCommentIgnorePattern */", + ].join("\n"), + options: [ + "always", + { + line: { + ignorePattern: "lineCommentIgnorePattern", + }, + block: { + ignorePattern: "blockCommentIgnorePattern", + }, + }, + ], + }, + ], - // Comments which start with URLs should always be valid - { - code: "// https://github.com", - options: ["always"] - }, - { - code: "// HTTPS://GITHUB.COM", - options: ["never"] - }, + invalid: [ + // No options: capitalization required + { + code: "//lowercase", + output: "//Lowercase", + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "// lowercase", + output: "// Lowercase", + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*lowercase */", + output: "/*Lowercase */", + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/* lowercase */", + output: "/* Lowercase */", + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/** lowercase */", + output: "/** Lowercase */", + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*\nlowercase */", + output: "/*\nLowercase */", + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/**\nlowercase */", + output: "/**\nLowercase */", + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "//Ãŧber", + output: "//Über", + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "//Ī€", + output: "//Π", + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/* lowercase\nSecond line need not be lowercase */", + output: "/* Lowercase\nSecond line need not be lowercase */", + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "// ęŽŗęŽƒę­š", + output: "// áŖęŽƒę­š", + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/* đŗĄđŗĄđŗĄ */", // right-to-left-text + output: "/* đ˛ĄđŗĄđŗĄ */", + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, - // Using different options for line/block comments - { - code: [ - "// Valid capitalized line comment", - "/* Valid capitalized block comment */", - "// lineCommentIgnorePattern", - "/* blockCommentIgnorePattern */" - ].join("\n"), - options: [ - "always", - { - line: { - ignorePattern: "lineCommentIgnorePattern" - }, - block: { - ignorePattern: "blockCommentIgnorePattern" - } - } - ] - } - ], + // Using "always" string option + { + code: "//lowercase", + output: "//Lowercase", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "// lowercase", + output: "// Lowercase", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*lowercase */", + output: "/*Lowercase */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/* lowercase */", + output: "/* Lowercase */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/** lowercase */", + output: "/** Lowercase */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/**\nlowercase */", + output: "/**\nLowercase */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "//Ãŧber", + output: "//Über", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "//Ī€", + output: "//Π", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/* lowercase\nSecond line need not be lowercase */", + output: "/* Lowercase\nSecond line need not be lowercase */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, - invalid: [ + // Using "never" string option + { + code: "//Uppercase", + output: "//uppercase", + options: ["never"], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "// Uppercase", + output: "// uppercase", + options: ["never"], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*Uppercase */", + output: "/*uppercase */", + options: ["never"], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/* Uppercase */", + output: "/* uppercase */", + options: ["never"], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*\nUppercase */", + output: "/*\nuppercase */", + options: ["never"], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "//Über", + output: "//Ãŧber", + options: ["never"], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "//Π", + output: "//Ī€", + options: ["never"], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/* Uppercase\nsecond line need not be uppercase */", + output: "/* uppercase\nsecond line need not be uppercase */", + options: ["never"], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "// Გ", // Georgian Mtavruli Capital Letter Gan (U+1C92) + output: "// გ", // Georgian Letter Gan (U+10D2) + options: ["never"], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "// đ‘ĸĸ", // Warang Citi Capital Letter Wi (U+118A2) + output: "// đ‘Ŗ‚", // Warang Citi Small Letter Wi (U+118C2) + options: ["never"], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1, + }, + ], + }, - // No options: capitalization required - { - code: "//lowercase", - output: "//Lowercase", - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "// lowercase", - output: "// Lowercase", - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*lowercase */", - output: "/*Lowercase */", - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/* lowercase */", - output: "/* Lowercase */", - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/** lowercase */", - output: "/** Lowercase */", - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*\nlowercase */", - output: "/*\nLowercase */", - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/**\nlowercase */", - output: "/**\nLowercase */", - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "//Ãŧber", - output: "//Über", - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "//Ī€", - output: "//Π", - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/* lowercase\nSecond line need not be lowercase */", - output: "/* Lowercase\nSecond line need not be lowercase */", - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, + // Default ignore words should be warned if there are non-whitespace characters in the way + { + code: "//* jscs: enable", + output: "//* Jscs: enable", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "//* jscs:disable", + output: "//* Jscs:disable", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "//* eslint-disable-line", + output: "//* Eslint-disable-line", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "//* eslint-disable-next-line", + output: "//* Eslint-disable-next-line", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*\n * eslint semi:off */", + output: "/*\n * Eslint semi:off */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*\n * eslint-env node */", + output: "/*\n * Eslint-env node */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*\n * istanbul ignore next */", + output: "/*\n * Istanbul ignore next */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*\n * jshint asi:true */", + output: "/*\n * Jshint asi:true */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*\n * jscs: enable */", + output: "/*\n * Jscs: enable */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*\n * global var1, var2 */", + output: "/*\n * Global var1, var2 */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*\n * global var1:true, var2 */", + output: "/*\n * Global var1:true, var2 */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*\n * globals var1, var2 */", + output: "/*\n * Globals var1, var2 */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*\n * globals var1:true, var2 */", + output: "/*\n * Globals var1:true, var2 */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "/*\n * exported myVar */", + output: "/*\n * Exported myVar */", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, - // Using "always" string option - { - code: "//lowercase", - output: "//Lowercase", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "// lowercase", - output: "// Lowercase", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*lowercase */", - output: "/*Lowercase */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/* lowercase */", - output: "/* Lowercase */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/** lowercase */", - output: "/** Lowercase */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/**\nlowercase */", - output: "/**\nLowercase */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "//Ãŧber", - output: "//Über", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "//Ī€", - output: "//Π", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/* lowercase\nSecond line need not be lowercase */", - output: "/* Lowercase\nSecond line need not be lowercase */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, + // Inline comments should be warned if ignoreInlineComments is omitted or false + { + code: "foo(/* invalid */a);", + output: "foo(/* Invalid */a);", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 5, + }, + ], + }, + { + code: "foo(/* invalid */a);", + output: "foo(/* Invalid */a);", + options: ["always", { ignoreInlineComments: false }], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 5, + }, + ], + }, - // Using "never" string option - { - code: "//Uppercase", - output: "//uppercase", - options: ["never"], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "// Uppercase", - output: "// uppercase", - options: ["never"], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*Uppercase */", - output: "/*uppercase */", - options: ["never"], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/* Uppercase */", - output: "/* uppercase */", - options: ["never"], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*\nUppercase */", - output: "/*\nuppercase */", - options: ["never"], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "//Über", - output: "//Ãŧber", - options: ["never"], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "//Π", - output: "//Ī€", - options: ["never"], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/* Uppercase\nsecond line need not be uppercase */", - output: "/* uppercase\nsecond line need not be uppercase */", - options: ["never"], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 1, - column: 1 - }] - }, + // ignoreInlineComments should only allow inline comments to pass + { + code: "foo(a, // not an inline comment\nb);", + output: "foo(a, // Not an inline comment\nb);", + options: ["always", { ignoreInlineComments: true }], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 8, + }, + ], + }, + { + code: "foo(a, /* not an inline comment */\nb);", + output: "foo(a, /* Not an inline comment */\nb);", + options: ["always", { ignoreInlineComments: true }], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 8, + }, + ], + }, + { + code: "foo(a,\n/* not an inline comment */b);", + output: "foo(a,\n/* Not an inline comment */b);", + options: ["always", { ignoreInlineComments: true }], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 2, + column: 1, + }, + ], + }, + { + code: "foo(a,\n/* not an inline comment */\nb);", + output: "foo(a,\n/* Not an inline comment */\nb);", + options: ["always", { ignoreInlineComments: true }], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 2, + column: 1, + }, + ], + }, + { + code: "foo(a, // Not an inline comment\nb);", + output: "foo(a, // not an inline comment\nb);", + options: ["never", { ignoreInlineComments: true }], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 8, + }, + ], + }, + { + code: "foo(a, /* Not an inline comment */\nb);", + output: "foo(a, /* not an inline comment */\nb);", + options: ["never", { ignoreInlineComments: true }], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 8, + }, + ], + }, + { + code: "foo(a,\n/* Not an inline comment */b);", + output: "foo(a,\n/* not an inline comment */b);", + options: ["never", { ignoreInlineComments: true }], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 2, + column: 1, + }, + ], + }, + { + code: "foo(a,\n/* Not an inline comment */\nb);", + output: "foo(a,\n/* not an inline comment */\nb);", + options: ["never", { ignoreInlineComments: true }], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 2, + column: 1, + }, + ], + }, - // Default ignore words should be warned if there are non-whitespace characters in the way - { - code: "//* jscs: enable", - output: "//* Jscs: enable", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "//* jscs:disable", - output: "//* Jscs:disable", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "//* eslint-disable-line", - output: "//* Eslint-disable-line", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "//* eslint-disable-next-line", - output: "//* Eslint-disable-next-line", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*\n * eslint semi:off */", - output: "/*\n * Eslint semi:off */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*\n * eslint-env node */", - output: "/*\n * Eslint-env node */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*\n * istanbul ignore next */", - output: "/*\n * Istanbul ignore next */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*\n * jshint asi:true */", - output: "/*\n * Jshint asi:true */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*\n * jscs: enable */", - output: "/*\n * Jscs: enable */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*\n * global var1, var2 */", - output: "/*\n * Global var1, var2 */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*\n * global var1:true, var2 */", - output: "/*\n * Global var1:true, var2 */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*\n * globals var1, var2 */", - output: "/*\n * Globals var1, var2 */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*\n * globals var1:true, var2 */", - output: "/*\n * Globals var1:true, var2 */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "/*\n * exported myVar */", - output: "/*\n * Exported myVar */", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, + // Comments which do not match ignorePattern are still warned + { + code: "// not matching", + output: "// Not matching", + options: ["always", { ignorePattern: "ignored?" }], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "// Not matching", + output: "// not matching", + options: ["never", { ignorePattern: "ignored?" }], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1, + }, + ], + }, - // Inline comments should be warned if ignoreInlineComments is omitted or false - { - code: "foo(/* invalid */a);", - output: "foo(/* Invalid */a);", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 5 - }] - }, - { - code: "foo(/* invalid */a);", - output: "foo(/* Invalid */a);", - options: ["always", { ignoreInlineComments: false }], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 5 - }] - }, + // ignoreConsecutiveComments only applies to comments with no tokens between them + { + code: [ + "// This comment is valid since it is capitalized,", + "// and this one is valid since it follows a valid one,", + "foo();", + "// this comment is now invalid.", + ].join("\n"), + output: [ + "// This comment is valid since it is capitalized,", + "// and this one is valid since it follows a valid one,", + "foo();", + "// This comment is now invalid.", + ].join("\n"), + options: ["always", { ignoreConsecutiveComments: true }], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 4, + column: 1, + }, + ], + }, - // ignoreInlineComments should only allow inline comments to pass - { - code: "foo(a, // not an inline comment\nb);", - output: "foo(a, // Not an inline comment\nb);", - options: ["always", { ignoreInlineComments: true }], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 8 - }] - }, - { - code: "foo(a, /* not an inline comment */\nb);", - output: "foo(a, /* Not an inline comment */\nb);", - options: ["always", { ignoreInlineComments: true }], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 8 - }] - }, - { - code: "foo(a,\n/* not an inline comment */b);", - output: "foo(a,\n/* Not an inline comment */b);", - options: ["always", { ignoreInlineComments: true }], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 2, - column: 1 - }] - }, - { - code: "foo(a,\n/* not an inline comment */\nb);", - output: "foo(a,\n/* Not an inline comment */\nb);", - options: ["always", { ignoreInlineComments: true }], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 2, - column: 1 - }] - }, - { - code: "foo(a, // Not an inline comment\nb);", - output: "foo(a, // not an inline comment\nb);", - options: ["never", { ignoreInlineComments: true }], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 1, - column: 8 - }] - }, - { - code: "foo(a, /* Not an inline comment */\nb);", - output: "foo(a, /* not an inline comment */\nb);", - options: ["never", { ignoreInlineComments: true }], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 1, - column: 8 - }] - }, - { - code: "foo(a,\n/* Not an inline comment */b);", - output: "foo(a,\n/* not an inline comment */b);", - options: ["never", { ignoreInlineComments: true }], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 2, - column: 1 - }] - }, - { - code: "foo(a,\n/* Not an inline comment */\nb);", - output: "foo(a,\n/* not an inline comment */\nb);", - options: ["never", { ignoreInlineComments: true }], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 2, - column: 1 - }] - }, + // Only the initial comment should warn if ignoreConsecutiveComments:true + { + code: [ + "// this comment is invalid since it is not capitalized,", + "// but this one is ignored since it is consecutive.", + ].join("\n"), + output: [ + "// This comment is invalid since it is not capitalized,", + "// but this one is ignored since it is consecutive.", + ].join("\n"), + options: ["always", { ignoreConsecutiveComments: true }], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: [ + "// This comment is invalid since it is not capitalized,", + "// But this one is ignored since it is consecutive.", + ].join("\n"), + output: [ + "// this comment is invalid since it is not capitalized,", + "// But this one is ignored since it is consecutive.", + ].join("\n"), + options: ["never", { ignoreConsecutiveComments: true }], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1, + }, + ], + }, - // Comments which do not match ignorePattern are still warned - { - code: "// not matching", - output: "// Not matching", - options: ["always", { ignorePattern: "ignored?" }], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "// Not matching", - output: "// not matching", - options: ["never", { ignorePattern: "ignored?" }], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 1, - column: 1 - }] - }, + // Consecutive comments should warn if ignoreConsecutiveComments:false + { + code: [ + "// This comment is valid since it is capitalized,", + "// but this one is invalid even if it follows a valid one.", + ].join("\n"), + output: [ + "// This comment is valid since it is capitalized,", + "// But this one is invalid even if it follows a valid one.", + ].join("\n"), + options: ["always", { ignoreConsecutiveComments: false }], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 2, + column: 1, + }, + ], + }, - // ignoreConsecutiveComments only applies to comments with no tokens between them - { - code: [ - "// This comment is valid since it is capitalized,", - "// and this one is valid since it follows a valid one,", - "foo();", - "// this comment is now invalid." - ].join("\n"), - output: [ - "// This comment is valid since it is capitalized,", - "// and this one is valid since it follows a valid one,", - "foo();", - "// This comment is now invalid." - ].join("\n"), - options: ["always", { ignoreConsecutiveComments: true }], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 4, - column: 1 - }] - }, - - // Only the initial comment should warn if ignoreConsecutiveComments:true - { - code: [ - "// this comment is invalid since it is not capitalized,", - "// but this one is ignored since it is consecutive." - ].join("\n"), - output: [ - "// This comment is invalid since it is not capitalized,", - "// but this one is ignored since it is consecutive." - ].join("\n"), - options: ["always", { ignoreConsecutiveComments: true }], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: [ - "// This comment is invalid since it is not capitalized,", - "// But this one is ignored since it is consecutive." - ].join("\n"), - output: [ - "// this comment is invalid since it is not capitalized,", - "// But this one is ignored since it is consecutive." - ].join("\n"), - options: ["never", { ignoreConsecutiveComments: true }], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 1, - column: 1 - }] - }, - - // Consecutive comments should warn if ignoreConsecutiveComments:false - { - code: [ - "// This comment is valid since it is capitalized,", - "// but this one is invalid even if it follows a valid one." - ].join("\n"), - output: [ - "// This comment is valid since it is capitalized,", - "// But this one is invalid even if it follows a valid one." - ].join("\n"), - options: ["always", { ignoreConsecutiveComments: false }], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 2, - column: 1 - }] - }, - - // Comments are warned if URL is not at the start of the comment - { - code: "// should fail. https://github.com", - output: "// Should fail. https://github.com", - options: ["always"], - errors: [{ - messageId: "unexpectedLowercaseComment", - line: 1, - column: 1 - }] - }, - { - code: "// Should fail. https://github.com", - output: "// should fail. https://github.com", - options: ["never"], - errors: [{ - messageId: "unexpectedUppercaseComment", - line: 1, - column: 1 - }] - } - ] + // Comments are warned if URL is not at the start of the comment + { + code: "// should fail. https://github.com", + output: "// Should fail. https://github.com", + options: ["always"], + errors: [ + { + messageId: "unexpectedLowercaseComment", + line: 1, + column: 1, + }, + ], + }, + { + code: "// Should fail. https://github.com", + output: "// should fail. https://github.com", + options: ["never"], + errors: [ + { + messageId: "unexpectedUppercaseComment", + line: 1, + column: 1, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/class-methods-use-this.js b/tests/lib/rules/class-methods-use-this.js index 7d2619f6f4bb..3574e1bfc637 100644 --- a/tests/lib/rules/class-methods-use-this.js +++ b/tests/lib/rules/class-methods-use-this.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/class-methods-use-this"); -const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); +const RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -19,198 +19,1346 @@ const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); const ruleTester = new RuleTester(); ruleTester.run("class-methods-use-this", rule, { - valid: [ - { code: "class A { constructor() {} }", languageOptions: { ecmaVersion: 6 } }, - { code: "class A { foo() {this} }", languageOptions: { ecmaVersion: 6 } }, - { code: "class A { foo() {this.bar = 'bar';} }", languageOptions: { ecmaVersion: 6 } }, - { code: "class A { foo() {bar(this);} }", languageOptions: { ecmaVersion: 6 } }, - { code: "class A extends B { foo() {super.foo();} }", languageOptions: { ecmaVersion: 6 } }, - { code: "class A { foo() { if(true) { return this; } } }", languageOptions: { ecmaVersion: 6 } }, - { code: "class A { static foo() {} }", languageOptions: { ecmaVersion: 6 } }, - { code: "({ a(){} });", languageOptions: { ecmaVersion: 6 } }, - { code: "class A { foo() { () => this; } }", languageOptions: { ecmaVersion: 6 } }, - { code: "({ a: function () {} });", languageOptions: { ecmaVersion: 6 } }, - { code: "class A { foo() {this} bar() {} }", options: [{ exceptMethods: ["bar"] }], languageOptions: { ecmaVersion: 6 } }, - { code: "class A { \"foo\"() { } }", options: [{ exceptMethods: ["foo"] }], languageOptions: { ecmaVersion: 6 } }, - { code: "class A { 42() { } }", options: [{ exceptMethods: ["42"] }], languageOptions: { ecmaVersion: 6 } }, - { code: "class A { foo = function() {this} }", languageOptions: { ecmaVersion: 2022 } }, - { code: "class A { foo = () => {this} }", languageOptions: { ecmaVersion: 2022 } }, - { code: "class A { foo = () => {super.toString} }", languageOptions: { ecmaVersion: 2022 } }, - { code: "class A { static foo = function() {} }", languageOptions: { ecmaVersion: 2022 } }, - { code: "class A { static foo = () => {} }", languageOptions: { ecmaVersion: 2022 } }, - { code: "class A { #bar() {} }", options: [{ exceptMethods: ["#bar"] }], languageOptions: { ecmaVersion: 2022 } }, - { code: "class A { foo = function () {} }", options: [{ enforceForClassFields: false }], languageOptions: { ecmaVersion: 2022 } }, - { code: "class A { foo = () => {} }", options: [{ enforceForClassFields: false }], languageOptions: { ecmaVersion: 2022 } }, - { code: "class A { foo() { return class { [this.foo] = 1 }; } }", languageOptions: { ecmaVersion: 2022 } }, - { code: "class A { static {} }", languageOptions: { ecmaVersion: 2022 } } - ], - invalid: [ - { - code: "class A { foo() {} }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } - ] - }, - { - code: "class A { foo() {/**this**/} }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } - ] - }, - { - code: "class A { foo() {var a = function () {this};} }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } - ] - }, - { - code: "class A { foo() {var a = function () {var b = function(){this}};} }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } - ] - }, - { - code: "class A { foo() {window.this} }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } - ] - }, - { - code: "class A { foo() {that.this = 'this';} }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } - ] - }, - { - code: "class A { foo() { () => undefined; } }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } - ] - }, - { - code: "class A { foo() {} bar() {} }", - options: [{ exceptMethods: ["bar"] }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method 'foo'" } } - ] - }, - { - code: "class A { foo() {} hasOwnProperty() {} }", - options: [{ exceptMethods: ["foo"] }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { type: "FunctionExpression", line: 1, column: 20, messageId: "missingThis", data: { name: "method 'hasOwnProperty'" } } - ] - }, - { - code: "class A { [foo]() {} }", - options: [{ exceptMethods: ["foo"] }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { type: "FunctionExpression", line: 1, column: 11, messageId: "missingThis", data: { name: "method" } } - ] - }, - { - code: "class A { #foo() { } foo() {} #bar() {} }", - options: [{ exceptMethods: ["#foo"] }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { type: "FunctionExpression", line: 1, column: 22, messageId: "missingThis", data: { name: "method 'foo'" } }, - { type: "FunctionExpression", line: 1, column: 31, messageId: "missingThis", data: { name: "private method #bar" } } - ] - }, - { - code: "class A { foo(){} 'bar'(){} 123(){} [`baz`](){} [a](){} [f(a)](){} get quux(){} set[a](b){} *quuux(){} }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "missingThis", data: { name: "method 'foo'" }, type: "FunctionExpression", column: 11 }, - { messageId: "missingThis", data: { name: "method 'bar'" }, type: "FunctionExpression", column: 19 }, - { messageId: "missingThis", data: { name: "method '123'" }, type: "FunctionExpression", column: 29 }, - { messageId: "missingThis", data: { name: "method 'baz'" }, type: "FunctionExpression", column: 37 }, - { messageId: "missingThis", data: { name: "method" }, type: "FunctionExpression", column: 49 }, - { messageId: "missingThis", data: { name: "method" }, type: "FunctionExpression", column: 57 }, - { messageId: "missingThis", data: { name: "getter 'quux'" }, type: "FunctionExpression", column: 68 }, - { messageId: "missingThis", data: { name: "setter" }, type: "FunctionExpression", column: 81 }, - { messageId: "missingThis", data: { name: "generator method 'quuux'" }, type: "FunctionExpression", column: 93 } - ] - }, - { - code: "class A { foo = function() {} }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 25 } - ] - }, - { - code: "class A { foo = () => {} }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 17 } - ] - }, - { - code: "class A { #foo = function() {} }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "missingThis", data: { name: "private method #foo" }, column: 11, endColumn: 26 } - ] - }, - { - code: "class A { #foo = () => {} }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "missingThis", data: { name: "private method #foo" }, column: 11, endColumn: 18 } - ] - }, - { - code: "class A { #foo() {} }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "missingThis", data: { name: "private method #foo" }, column: 11, endColumn: 15 } - ] - }, - { - code: "class A { get #foo() {} }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "missingThis", data: { name: "private getter #foo" }, column: 11, endColumn: 19 } - ] - }, - { - code: "class A { set #foo(x) {} }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "missingThis", data: { name: "private setter #foo" }, column: 11, endColumn: 19 } - ] - }, - { - code: "class A { foo () { return class { foo = this }; } }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 15 } - ] - }, - { - code: "class A { foo () { return function () { foo = this }; } }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 15 } - ] - }, - { - code: "class A { foo () { return class { static { this; } } } }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "missingThis", data: { name: "method 'foo'" }, column: 11, endColumn: 15 } - ] - } - ] + valid: [ + { + code: "class A { constructor() {} }", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { foo() {this} }", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { foo() {this.bar = 'bar';} }", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { foo() {bar(this);} }", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A extends B { foo() {super.foo();} }", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { foo() { if(true) { return this; } } }", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { static foo() {} }", + languageOptions: { ecmaVersion: 6 }, + }, + { code: "({ a(){} });", languageOptions: { ecmaVersion: 6 } }, + { + code: "class A { foo() { () => this; } }", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ a: function () {} });", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { foo() {this} bar() {} }", + options: [{ exceptMethods: ["bar"] }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'class A { "foo"() { } }', + options: [{ exceptMethods: ["foo"] }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { 42() { } }", + options: [{ exceptMethods: ["42"] }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { foo = function() {this} }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class A { foo = () => {this} }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class A { foo = () => {super.toString} }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class A { static foo = function() {} }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class A { static foo = () => {} }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class A { #bar() {} }", + options: [{ exceptMethods: ["#bar"] }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class A { foo = function () {} }", + options: [{ enforceForClassFields: false }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class A { foo = () => {} }", + options: [{ enforceForClassFields: false }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class A { foo() { return class { [this.foo] = 1 }; } }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class A { static {} }", + languageOptions: { ecmaVersion: 2022 }, + }, + ], + invalid: [ + { + code: "class A { foo() {} }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "FunctionExpression", + line: 1, + column: 11, + messageId: "missingThis", + data: { name: "method 'foo'" }, + }, + ], + }, + { + code: "class A { foo() {/**this**/} }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "FunctionExpression", + line: 1, + column: 11, + messageId: "missingThis", + data: { name: "method 'foo'" }, + }, + ], + }, + { + code: "class A { foo() {var a = function () {this};} }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "FunctionExpression", + line: 1, + column: 11, + messageId: "missingThis", + data: { name: "method 'foo'" }, + }, + ], + }, + { + code: "class A { foo() {var a = function () {var b = function(){this}};} }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "FunctionExpression", + line: 1, + column: 11, + messageId: "missingThis", + data: { name: "method 'foo'" }, + }, + ], + }, + { + code: "class A { foo() {window.this} }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "FunctionExpression", + line: 1, + column: 11, + messageId: "missingThis", + data: { name: "method 'foo'" }, + }, + ], + }, + { + code: "class A { foo() {that.this = 'this';} }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "FunctionExpression", + line: 1, + column: 11, + messageId: "missingThis", + data: { name: "method 'foo'" }, + }, + ], + }, + { + code: "class A { foo() { () => undefined; } }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "FunctionExpression", + line: 1, + column: 11, + messageId: "missingThis", + data: { name: "method 'foo'" }, + }, + ], + }, + { + code: "class A { foo() {} bar() {} }", + options: [{ exceptMethods: ["bar"] }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "FunctionExpression", + line: 1, + column: 11, + messageId: "missingThis", + data: { name: "method 'foo'" }, + }, + ], + }, + { + code: "class A { foo() {} hasOwnProperty() {} }", + options: [{ exceptMethods: ["foo"] }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "FunctionExpression", + line: 1, + column: 20, + messageId: "missingThis", + data: { name: "method 'hasOwnProperty'" }, + }, + ], + }, + { + code: "class A { [foo]() {} }", + options: [{ exceptMethods: ["foo"] }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + type: "FunctionExpression", + line: 1, + column: 11, + messageId: "missingThis", + data: { name: "method" }, + }, + ], + }, + { + code: "class A { #foo() { } foo() {} #bar() {} }", + options: [{ exceptMethods: ["#foo"] }], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + type: "FunctionExpression", + line: 1, + column: 22, + messageId: "missingThis", + data: { name: "method 'foo'" }, + }, + { + type: "FunctionExpression", + line: 1, + column: 31, + messageId: "missingThis", + data: { name: "private method #bar" }, + }, + ], + }, + { + code: "class A { foo(){} 'bar'(){} 123(){} [`baz`](){} [a](){} [f(a)](){} get quux(){} set[a](b){} *quuux(){} }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingThis", + data: { name: "method 'foo'" }, + type: "FunctionExpression", + column: 11, + }, + { + messageId: "missingThis", + data: { name: "method 'bar'" }, + type: "FunctionExpression", + column: 19, + }, + { + messageId: "missingThis", + data: { name: "method '123'" }, + type: "FunctionExpression", + column: 29, + }, + { + messageId: "missingThis", + data: { name: "method 'baz'" }, + type: "FunctionExpression", + column: 37, + }, + { + messageId: "missingThis", + data: { name: "method" }, + type: "FunctionExpression", + column: 49, + }, + { + messageId: "missingThis", + data: { name: "method" }, + type: "FunctionExpression", + column: 57, + }, + { + messageId: "missingThis", + data: { name: "getter 'quux'" }, + type: "FunctionExpression", + column: 68, + }, + { + messageId: "missingThis", + data: { name: "setter" }, + type: "FunctionExpression", + column: 81, + }, + { + messageId: "missingThis", + data: { name: "generator method 'quuux'" }, + type: "FunctionExpression", + column: 93, + }, + ], + }, + { + code: "class A { foo = function() {} }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingThis", + data: { name: "method 'foo'" }, + column: 11, + endColumn: 25, + }, + ], + }, + { + code: "class A { foo = () => {} }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingThis", + data: { name: "method 'foo'" }, + column: 11, + endColumn: 17, + }, + ], + }, + { + code: "class A { #foo = function() {} }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingThis", + data: { name: "private method #foo" }, + column: 11, + endColumn: 26, + }, + ], + }, + { + code: "class A { #foo = () => {} }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingThis", + data: { name: "private method #foo" }, + column: 11, + endColumn: 18, + }, + ], + }, + { + code: "class A { #foo() {} }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingThis", + data: { name: "private method #foo" }, + column: 11, + endColumn: 15, + }, + ], + }, + { + code: "class A { get #foo() {} }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingThis", + data: { name: "private getter #foo" }, + column: 11, + endColumn: 19, + }, + ], + }, + { + code: "class A { set #foo(x) {} }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingThis", + data: { name: "private setter #foo" }, + column: 11, + endColumn: 19, + }, + ], + }, + { + code: "class A { foo () { return class { foo = this }; } }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingThis", + data: { name: "method 'foo'" }, + column: 11, + endColumn: 15, + }, + ], + }, + { + code: "class A { foo () { return function () { foo = this }; } }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingThis", + data: { name: "method 'foo'" }, + column: 11, + endColumn: 15, + }, + ], + }, + { + code: "class A { foo () { return class { static { this; } } } }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingThis", + data: { name: "method 'foo'" }, + column: 11, + endColumn: 15, + }, + ], + }, + ], +}); + +const ruleTesterTypeScript = new RuleTester({ + languageOptions: { + parser: require("@typescript-eslint/parser"), + }, +}); + +ruleTesterTypeScript.run("class-methods-use-this", rule, { + valid: [ + "class A { constructor() {} }", + "class A { foo() {this} }", + "class A { foo() {this.bar = 'bar';} }", + "class A { foo() {bar(this);} }", + "class A extends B { foo() {super.foo();} }", + "class A { foo() { if(true) { return this; } } }", + "class A { static foo() {} }", + "({ a(){} });", + "class A { foo() { () => this; } }", + "({ a: function () {} });", + { + code: "class A { foo() {this} bar() {} }", + options: [{ exceptMethods: ["bar"] }], + }, + { + code: 'class A { "foo"() { } }', + options: [{ exceptMethods: ["foo"] }], + }, + { code: "class A { 42() { } }", options: [{ exceptMethods: ["42"] }] }, + "class A { foo = function() {this} }", + "class A { foo = () => {this} }", + "class A { accessor foo = function() {this} }", + "class A { accessor foo = () => {this} }", + "class A { accessor foo = 1; }", + "class A { foo = () => {super.toString} }", + "class A { static foo = function() {} }", + "class A { static foo = () => {} }", + "class A { static accessor foo = function() {} }", + "class A { static accessor foo = () => {} }", + { + code: "class A { #bar() {} }", + options: [{ exceptMethods: ["#bar"] }], + }, + { + code: "class A { foo = function () {} }", + options: [{ enforceForClassFields: false }], + }, + { + code: "class A { foo = () => {} }", + options: [{ enforceForClassFields: false }], + }, + { + code: "class A { accessor foo = function () {} }", + options: [{ enforceForClassFields: false }], + }, + { + code: "class A { accessor foo = () => {} }", + options: [{ enforceForClassFields: false }], + }, + { + code: "class A { override foo = () => {} }", + options: [{ enforceForClassFields: false }], + }, + { + code: "class Foo implements Bar { property = () => {} }", + options: [{ enforceForClassFields: false }], + }, + "class A { foo() { return class { [this.foo] = 1 }; } }", + "class A { static {} }", + { + code: "class Foo { override method() {} }", + options: [{ ignoreOverrideMethods: true }], + }, + { + code: "class Foo { private override method() {} }", + options: [{ ignoreOverrideMethods: true }], + }, + { + code: "class Foo { protected override method() {} }", + options: [{ ignoreOverrideMethods: true }], + }, + { + code: "class Foo { override accessor method = () => {} }", + options: [{ ignoreOverrideMethods: true }], + }, + { + code: "class Foo { override get getter(): number {} }", + options: [{ ignoreOverrideMethods: true }], + }, + { + code: "class Foo { private override get getter(): number {} }", + options: [{ ignoreOverrideMethods: true }], + }, + { + code: "class Foo { protected override get getter(): number {} }", + options: [{ ignoreOverrideMethods: true }], + }, + { + code: "class Foo { override set setter(v: number) {} }", + options: [{ ignoreOverrideMethods: true }], + }, + { + code: "class Foo { private override set setter(v: number) {} }", + options: [{ ignoreOverrideMethods: true }], + }, + { + code: "class Foo { protected override set setter(v: number) {} }", + options: [{ ignoreOverrideMethods: true }], + }, + { + code: "class Foo implements Bar { override method() {} }", + options: [ + { + ignoreOverrideMethods: true, + ignoreClassesWithImplements: "all", + }, + ], + }, + { + code: "class Foo implements Bar { private override method() {} }", + options: [ + { + ignoreOverrideMethods: true, + ignoreClassesWithImplements: "public-fields", + }, + ], + }, + { + code: "class Foo implements Bar { protected override method() {} }", + options: [ + { + ignoreOverrideMethods: true, + ignoreClassesWithImplements: "public-fields", + }, + ], + }, + { + code: "class Foo implements Bar { override get getter(): number {} }", + options: [ + { + ignoreOverrideMethods: true, + ignoreClassesWithImplements: "all", + }, + ], + }, + { + code: "class Foo implements Bar { private override get getter(): number {} }", + options: [ + { + ignoreOverrideMethods: true, + ignoreClassesWithImplements: "public-fields", + }, + ], + }, + { + code: "class Foo implements Bar { protected override get getter(): number {} }", + options: [ + { + ignoreOverrideMethods: true, + ignoreClassesWithImplements: "public-fields", + }, + ], + }, + { + code: "class Foo implements Bar { override set setter(v: number) {} }", + options: [ + { + ignoreOverrideMethods: true, + ignoreClassesWithImplements: "all", + }, + ], + }, + { + code: "class Foo implements Bar { private override set setter(v: number) {} }", + options: [ + { + ignoreOverrideMethods: true, + ignoreClassesWithImplements: "public-fields", + }, + ], + }, + { + code: "class Foo implements Bar { protected override set setter(v: number) {} }", + options: [ + { + ignoreOverrideMethods: true, + ignoreClassesWithImplements: "public-fields", + }, + ], + }, + { + code: "class Foo { override property = () => {} }", + options: [{ ignoreOverrideMethods: true }], + }, + { + code: "class Foo { private override property = () => {} }", + options: [{ ignoreOverrideMethods: true }], + }, + { + code: "class Foo { protected override property = () => {} }", + options: [{ ignoreOverrideMethods: true }], + }, + { + code: "class Foo implements Bar { override property = () => {} }", + options: [ + { + ignoreOverrideMethods: true, + ignoreClassesWithImplements: "all", + }, + ], + }, + { + code: "class Foo implements Bar { private override property = () => {} }", + options: [ + { + ignoreOverrideMethods: true, + ignoreClassesWithImplements: "public-fields", + }, + ], + }, + { + code: "class Foo implements Bar { protected override property = () => {} }", + options: [ + { + ignoreOverrideMethods: true, + ignoreClassesWithImplements: "public-fields", + }, + ], + }, + { + code: "class Foo implements Bar { method() {} }", + options: [{ ignoreClassesWithImplements: "all" }], + }, + { + code: "class Foo implements Bar { accessor method = () => {} }", + options: [{ ignoreClassesWithImplements: "all" }], + }, + { + code: "class Foo implements Bar { get getter() {} }", + options: [{ ignoreClassesWithImplements: "all" }], + }, + { + code: "class Foo implements Bar { set setter() {} }", + options: [{ ignoreClassesWithImplements: "all" }], + }, + { + code: "class Foo implements Bar { property = () => {} }", + options: [{ ignoreClassesWithImplements: "all" }], + }, + ], + invalid: [ + { + code: ` + class Foo { + method() {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + private method() {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + protected method() {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + accessor method = function () {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + accessor method = () => {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + private accessor method = () => {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + protected accessor method = () => {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class A { + foo() { + return class { + accessor bar = this; + }; + } + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Derived extends Base { + override method() {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Derived extends Base { + property = () => {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Derived extends Base { + public property = () => {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Derived extends Base { + override property = () => {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + #method() {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + get getter(): number {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + private get getter(): number {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + protected get getter(): number {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + get #getter(): number {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + private set setter(b: number) {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + protected set setter(b: number) {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + set #setter(b: number) {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + + { + code: ` + function fn() { + this.foo = 303; + + class Foo { + method() {} + } + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + override method() {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + override get getter(): number {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + override set setter(v: number) {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + override method() {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + override get getter(): number {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + override set setter(v: number) {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo { + override property = () => {}; + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + override property = () => {}; + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + method() {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + #method() {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + #method() {} + } + `, + options: [ + { + ignoreClassesWithImplements: "public-fields", + }, + ], + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + private method() {} + } + `, + options: [ + { + ignoreClassesWithImplements: "public-fields", + }, + ], + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + protected method() {} + } + `, + options: [ + { + ignoreClassesWithImplements: "public-fields", + }, + ], + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + get getter(): number {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + get #getter(): number {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + get #getter(): number {} + } + `, + options: [ + { + ignoreClassesWithImplements: "public-fields", + }, + ], + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + private get getter(): number {} + } + `, + options: [ + { + ignoreClassesWithImplements: "public-fields", + }, + ], + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + protected get getter(): number {} + } + `, + options: [ + { + ignoreClassesWithImplements: "public-fields", + }, + ], + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + set setter(v: number) {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + set #setter(v: number) {} + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + set #setter(v: number) {} + } + `, + options: [ + { + ignoreClassesWithImplements: "public-fields", + }, + ], + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + private set setter(v: number) {} + } + `, + options: [ + { + ignoreClassesWithImplements: "public-fields", + }, + ], + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + protected set setter(v: number) {} + } + `, + options: [ + { + ignoreClassesWithImplements: "public-fields", + }, + ], + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + property = () => {}; + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + #property = () => {}; + } + `, + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + #property = () => {}; + } + `, + options: [ + { + ignoreClassesWithImplements: "public-fields", + }, + ], + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + private property = () => {}; + } + `, + options: [ + { + ignoreClassesWithImplements: "public-fields", + }, + ], + errors: [ + { + messageId: "missingThis", + }, + ], + }, + { + code: ` + class Foo implements Bar { + protected property = () => {}; + } + `, + options: [ + { + ignoreClassesWithImplements: "public-fields", + }, + ], + errors: [ + { + messageId: "missingThis", + }, + ], + }, + ], }); diff --git a/tests/lib/rules/comma-dangle.js b/tests/lib/rules/comma-dangle.js index d7917a8f5161..b2282f040ff3 100644 --- a/tests/lib/rules/comma-dangle.js +++ b/tests/lib/rules/comma-dangle.js @@ -9,10 +9,10 @@ // Requirements //------------------------------------------------------------------------------ -const path = require("path"), - { unIndent } = require("../../_utils"), - rule = require("../../../lib/rules/comma-dangle"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); +const path = require("node:path"), + { unIndent } = require("../../_utils"), + rule = require("../../../lib/rules/comma-dangle"), + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Helpers @@ -24,10 +24,12 @@ const path = require("path"), * @returns {Object} The parser object. */ function parser(name) { - return require(path.resolve( - __dirname, - `../../fixtures/parsers/comma-dangle/${name}.js` - )); + return require( + path.resolve( + __dirname, + `../../fixtures/parsers/comma-dangle/${name}.js`, + ), + ); } //------------------------------------------------------------------------------ @@ -35,1844 +37,1896 @@ function parser(name) { //------------------------------------------------------------------------------ const ruleTester = new RuleTester({ - plugins: { - custom: { - rules: { - "add-named-import": { - meta: { - fixable: "code" - }, - create(context) { - return { - ImportDeclaration(node) { - const sourceCode = context.sourceCode; - const closingBrace = sourceCode.getLastToken(node, token => token.value === "}"); - const addComma = sourceCode.getTokenBefore(closingBrace).value !== ","; + plugins: { + custom: { + rules: { + "add-named-import": { + meta: { + fixable: "code", + }, + create(context) { + return { + ImportDeclaration(node) { + const sourceCode = context.sourceCode; + const closingBrace = sourceCode.getLastToken( + node, + token => token.value === "}", + ); + const addComma = + sourceCode.getTokenBefore(closingBrace) + .value !== ","; - context.report({ - message: "Add I18nManager.", - node, - fix(fixer) { - return fixer.insertTextBefore(closingBrace, `${addComma ? "," : ""}I18nManager`); - } - }); - } - }; - } - } - } - } - }, - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } + context.report({ + message: "Add I18nManager.", + node, + fix(fixer) { + return fixer.insertTextBefore( + closingBrace, + `${addComma ? "," : ""}I18nManager`, + ); + }, + }); + }, + }; + }, + }, + }, + }, + }, + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, }); - ruleTester.run("comma-dangle", rule, { - valid: [ - "var foo = { bar: 'baz' }", - "var foo = {\nbar: 'baz'\n}", - "var foo = [ 'baz' ]", - "var foo = [\n'baz'\n]", - "[,,]", - "[\n,\n,\n]", - "[,]", - "[\n,\n]", - "[]", - "[\n]", - { code: "var foo = [\n (bar ? baz : qux),\n ];", options: ["always-multiline"] }, - { code: "var foo = { bar: 'baz' }", options: ["never"] }, - { code: "var foo = {\nbar: 'baz'\n}", options: ["never"] }, - { code: "var foo = [ 'baz' ]", options: ["never"] }, - { code: "var { a, b } = foo;", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [ a, b ] = foo;", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "var { a,\n b, \n} = foo;", options: ["only-multiline"], languageOptions: { ecmaVersion: 6 } }, - { code: "var [ a,\n b, \n] = foo;", options: ["only-multiline"], languageOptions: { ecmaVersion: 6 } }, - - { code: "[(1),]", options: ["always"] }, - { code: "var x = { foo: (1),};", options: ["always"] }, - { code: "var foo = { bar: 'baz', }", options: ["always"] }, - { code: "var foo = {\nbar: 'baz',\n}", options: ["always"] }, - { code: "var foo = {\nbar: 'baz'\n,}", options: ["always"] }, - { code: "var foo = [ 'baz', ]", options: ["always"] }, - { code: "var foo = [\n'baz',\n]", options: ["always"] }, - { code: "var foo = [\n'baz'\n,]", options: ["always"] }, - { code: "[,,]", options: ["always"] }, - { code: "[\n,\n,\n]", options: ["always"] }, - { code: "[,]", options: ["always"] }, - { code: "[\n,\n]", options: ["always"] }, - { code: "[]", options: ["always"] }, - { code: "[\n]", options: ["always"] }, - - { code: "var foo = { bar: 'baz' }", options: ["always-multiline"] }, - { code: "var foo = { bar: 'baz' }", options: ["only-multiline"] }, - { code: "var foo = {\nbar: 'baz',\n}", options: ["always-multiline"] }, - { code: "var foo = {\nbar: 'baz',\n}", options: ["only-multiline"] }, - { code: "var foo = [ 'baz' ]", options: ["always-multiline"] }, - { code: "var foo = [ 'baz' ]", options: ["only-multiline"] }, - { code: "var foo = [\n'baz',\n]", options: ["always-multiline"] }, - { code: "var foo = [\n'baz',\n]", options: ["only-multiline"] }, - { code: "var foo = { bar:\n\n'bar' }", options: ["always-multiline"] }, - { code: "var foo = { bar:\n\n'bar' }", options: ["only-multiline"] }, - { code: "var foo = {a: 1, b: 2, c: 3, d: 4}", options: ["always-multiline"] }, - { code: "var foo = {a: 1, b: 2, c: 3, d: 4}", options: ["only-multiline"] }, - { code: "var foo = {a: 1, b: 2,\n c: 3, d: 4}", options: ["always-multiline"] }, - { code: "var foo = {a: 1, b: 2,\n c: 3, d: 4}", options: ["only-multiline"] }, - { code: "var foo = {x: {\nfoo: 'bar',\n}}", options: ["always-multiline"] }, - { code: "var foo = {x: {\nfoo: 'bar',\n}}", options: ["only-multiline"] }, - { code: "var foo = new Map([\n[key, {\na: 1,\nb: 2,\nc: 3,\n}],\n])", options: ["always-multiline"] }, - { code: "var foo = new Map([\n[key, {\na: 1,\nb: 2,\nc: 3,\n}],\n])", options: ["only-multiline"] }, - - // https://github.com/eslint/eslint/issues/3627 - { - code: "var [a, ...rest] = [];", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\n a,\n ...rest\n] = [];", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\n a,\n ...rest\n] = [];", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [\n a,\n ...rest\n] = [];", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "[a, ...rest] = [];", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "for ([a, ...rest] of []);", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var a = [b, ...spread,];", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - - // https://github.com/eslint/eslint/issues/7297 - { - code: "var {foo, ...bar} = baz", - options: ["always"], - languageOptions: { ecmaVersion: 2018 } - }, + valid: [ + "var foo = { bar: 'baz' }", + "var foo = {\nbar: 'baz'\n}", + "var foo = [ 'baz' ]", + "var foo = [\n'baz'\n]", + "[,,]", + "[\n,\n,\n]", + "[,]", + "[\n,\n]", + "[]", + "[\n]", + { + code: "var foo = [\n (bar ? baz : qux),\n ];", + options: ["always-multiline"], + }, + { code: "var foo = { bar: 'baz' }", options: ["never"] }, + { code: "var foo = {\nbar: 'baz'\n}", options: ["never"] }, + { code: "var foo = [ 'baz' ]", options: ["never"] }, + { + code: "var { a, b } = foo;", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [ a, b ] = foo;", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { a,\n b, \n} = foo;", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [ a,\n b, \n] = foo;", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 6 }, + }, - // https://github.com/eslint/eslint/issues/3794 - { - code: "import {foo,} from 'foo';", - options: ["always"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import foo from 'foo';", - options: ["always"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import foo, {abc,} from 'foo';", - options: ["always"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import * as foo from 'foo';", - options: ["always"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "export {foo,} from 'foo';", - options: ["always"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import {foo} from 'foo';", - options: ["never"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import foo from 'foo';", - options: ["never"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import foo, {abc} from 'foo';", - options: ["never"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import * as foo from 'foo';", - options: ["never"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "export {foo} from 'foo';", - options: ["never"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import {foo} from 'foo';", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import {foo} from 'foo';", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "export {foo} from 'foo';", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "export {foo} from 'foo';", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import {\n foo,\n} from 'foo';", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import {\n foo,\n} from 'foo';", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "export {\n foo,\n} from 'foo';", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "export {\n foo,\n} from 'foo';", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import {foo} from \n'foo';", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import {foo} from \n'foo';", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "function foo(a) {}", - options: ["always"] - }, - { - code: "foo(a)", - options: ["always"] - }, - { - code: "function foo(a) {}", - options: ["never"] - }, - { - code: "foo(a)", - options: ["never"] - }, - { - code: "function foo(a,\nb) {}", - options: ["always-multiline"] - }, - { - code: "foo(a,\nb\n)", - options: ["always-multiline"] - }, - { - code: "function foo(a,\nb\n) {}", - options: ["always-multiline"] - }, - { - code: "foo(a,\nb)", - options: ["always-multiline"] - }, - { - code: "function foo(a,\nb) {}", - options: ["only-multiline"] - }, - { - code: "foo(a,\nb)", - options: ["only-multiline"] - }, - { - code: "function foo(a) {}", - options: ["always"], - languageOptions: { ecmaVersion: 7 } - }, - { - code: "foo(a)", - options: ["always"], - languageOptions: { ecmaVersion: 7 } - }, - { - code: "function foo(a) {}", - options: ["never"], - languageOptions: { ecmaVersion: 7 } - }, - { - code: "foo(a)", - options: ["never"], - languageOptions: { ecmaVersion: 7 } - }, - { - code: "function foo(a,\nb) {}", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 7 } - }, - { - code: "foo(a,\nb)", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 7 } - }, - { - code: "function foo(a,\nb\n) {}", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 7 } - }, - { - code: "foo(a,\nb\n)", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 7 } - }, - { - code: "function foo(a,\nb) {}", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 7 } - }, - { - code: "foo(a,\nb)", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 7 } - }, - { - code: "function foo(a) {}", - options: ["never"], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(a)", - options: ["never"], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "function foo(a,) {}", - options: ["always"], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(a,)", - options: ["always"], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "function foo(\na,\nb,\n) {}", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(\na,b)", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "function foo(a,b) {}", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(a,b)", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "function foo(a,b) {}", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(a,b)", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 8 } - }, + { code: "[(1),]", options: ["always"] }, + { code: "var x = { foo: (1),};", options: ["always"] }, + { code: "var foo = { bar: 'baz', }", options: ["always"] }, + { code: "var foo = {\nbar: 'baz',\n}", options: ["always"] }, + { code: "var foo = {\nbar: 'baz'\n,}", options: ["always"] }, + { code: "var foo = [ 'baz', ]", options: ["always"] }, + { code: "var foo = [\n'baz',\n]", options: ["always"] }, + { code: "var foo = [\n'baz'\n,]", options: ["always"] }, + { code: "[,,]", options: ["always"] }, + { code: "[\n,\n,\n]", options: ["always"] }, + { code: "[,]", options: ["always"] }, + { code: "[\n,\n]", options: ["always"] }, + { code: "[]", options: ["always"] }, + { code: "[\n]", options: ["always"] }, - // trailing comma in functions - { - code: "function foo(a) {} ", - options: [{}], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(a)", - options: [{}], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "function foo(a) {} ", - options: [{ functions: "never" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(a)", - options: [{ functions: "never" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "function foo(a,) {}", - options: [{ functions: "always" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "function bar(a, ...b) {}", - options: [{ functions: "always" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(a,)", - options: [{ functions: "always" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(a,)", - options: [{ functions: "always" }], - languageOptions: { ecmaVersion: 9 } - }, - { - code: "bar(...a,)", - options: [{ functions: "always" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "function foo(a) {} ", - options: [{ functions: "always-multiline" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(a)", - options: [{ functions: "always-multiline" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "function foo(\na,\nb,\n) {} ", - options: [{ functions: "always-multiline" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "function foo(\na,\n...b\n) {} ", - options: [{ functions: "always-multiline" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(\na,\nb,\n)", - options: [{ functions: "always-multiline" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(\na,\n...b,\n)", - options: [{ functions: "always-multiline" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "function foo(a) {} ", - options: [{ functions: "only-multiline" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(a)", - options: [{ functions: "only-multiline" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "function foo(\na,\nb,\n) {} ", - options: [{ functions: "only-multiline" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(\na,\nb,\n)", - options: [{ functions: "only-multiline" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "function foo(\na,\nb\n) {} ", - options: [{ functions: "only-multiline" }], - languageOptions: { ecmaVersion: 8 } - }, - { - code: "foo(\na,\nb\n)", - options: [{ functions: "only-multiline" }], - languageOptions: { ecmaVersion: 8 } - }, + { code: "var foo = { bar: 'baz' }", options: ["always-multiline"] }, + { code: "var foo = { bar: 'baz' }", options: ["only-multiline"] }, + { code: "var foo = {\nbar: 'baz',\n}", options: ["always-multiline"] }, + { code: "var foo = {\nbar: 'baz',\n}", options: ["only-multiline"] }, + { code: "var foo = [ 'baz' ]", options: ["always-multiline"] }, + { code: "var foo = [ 'baz' ]", options: ["only-multiline"] }, + { code: "var foo = [\n'baz',\n]", options: ["always-multiline"] }, + { code: "var foo = [\n'baz',\n]", options: ["only-multiline"] }, + { code: "var foo = { bar:\n\n'bar' }", options: ["always-multiline"] }, + { code: "var foo = { bar:\n\n'bar' }", options: ["only-multiline"] }, + { + code: "var foo = {a: 1, b: 2, c: 3, d: 4}", + options: ["always-multiline"], + }, + { + code: "var foo = {a: 1, b: 2, c: 3, d: 4}", + options: ["only-multiline"], + }, + { + code: "var foo = {a: 1, b: 2,\n c: 3, d: 4}", + options: ["always-multiline"], + }, + { + code: "var foo = {a: 1, b: 2,\n c: 3, d: 4}", + options: ["only-multiline"], + }, + { + code: "var foo = {x: {\nfoo: 'bar',\n}}", + options: ["always-multiline"], + }, + { + code: "var foo = {x: {\nfoo: 'bar',\n}}", + options: ["only-multiline"], + }, + { + code: "var foo = new Map([\n[key, {\na: 1,\nb: 2,\nc: 3,\n}],\n])", + options: ["always-multiline"], + }, + { + code: "var foo = new Map([\n[key, {\na: 1,\nb: 2,\nc: 3,\n}],\n])", + options: ["only-multiline"], + }, - // https://github.com/eslint/eslint/issues/7370 - { - code: "function foo({a}: {a: string,}) {}", - options: ["never"], - languageOptions: { - parser: parser("object-pattern-1") - } - }, - { - code: "function foo({a,}: {a: string}) {}", - options: ["always"], - languageOptions: { - parser: parser("object-pattern-2") - } - }, - { - code: "function foo(a): {b: boolean,} {}", - options: [{ functions: "never" }], - languageOptions: { - parser: parser("return-type-1") - } - }, - { - code: "function foo(a,): {b: boolean} {}", - options: [{ functions: "always" }], - languageOptions: { - parser: parser("return-type-2") - } - }, + // https://github.com/eslint/eslint/issues/3627 + { + code: "var [a, ...rest] = [];", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\n a,\n ...rest\n] = [];", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\n a,\n ...rest\n] = [];", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [\n a,\n ...rest\n] = [];", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "[a, ...rest] = [];", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "for ([a, ...rest] of []);", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var a = [b, ...spread,];", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, - // https://github.com/eslint/eslint/issues/16442 - { - code: "function f(\n a,\n b\n) {}", - options: ["always-multiline"], - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } - }, - { - code: "f(\n a,\n b\n);", - options: ["always-multiline"], - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } - }, - { - code: "function f(\n a,\n b\n) {}", - options: ["always-multiline"], - languageOptions: { - ecmaVersion: 2016 - } - }, - { - code: "f(\n a,\n b\n);", - options: ["always-multiline"], - languageOptions: { - ecmaVersion: 2016 - } - } + // https://github.com/eslint/eslint/issues/7297 + { + code: "var {foo, ...bar} = baz", + options: ["always"], + languageOptions: { ecmaVersion: 2018 }, + }, - ], - invalid: [ - { - code: "var foo = { bar: 'baz', }", - output: "var foo = { bar: 'baz' }", - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 1, - column: 23, - endColumn: 24 - } - ] - }, - { - code: "var foo = {\nbar: 'baz',\n}", - output: "var foo = {\nbar: 'baz'\n}", - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 2, - column: 11, - endColumn: 12 - } - ] - }, - { - code: "foo({ bar: 'baz', qux: 'quux', });", - output: "foo({ bar: 'baz', qux: 'quux' });", - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 1, - column: 30 - } - ] - }, - { - code: "foo({\nbar: 'baz',\nqux: 'quux',\n});", - output: "foo({\nbar: 'baz',\nqux: 'quux'\n});", - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 3, - column: 12 - } - ] - }, - { - code: "var foo = [ 'baz', ]", - output: "var foo = [ 'baz' ]", - errors: [ - { - messageId: "unexpected", - type: "Literal", - line: 1, - column: 18 - } - ] - }, - { - code: "var foo = [ 'baz',\n]", - output: "var foo = [ 'baz'\n]", - errors: [ - { - messageId: "unexpected", - type: "Literal", - line: 1, - column: 18 - } - ] - }, - { - code: "var foo = { bar: 'bar'\n\n, }", - output: "var foo = { bar: 'bar'\n\n }", - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 3, - column: 1 - } - ] - }, + // https://github.com/eslint/eslint/issues/3794 + { + code: "import {foo,} from 'foo';", + options: ["always"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import foo from 'foo';", + options: ["always"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import foo, {abc,} from 'foo';", + options: ["always"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import * as foo from 'foo';", + options: ["always"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "export {foo,} from 'foo';", + options: ["always"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import {foo} from 'foo';", + options: ["never"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import foo from 'foo';", + options: ["never"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import foo, {abc} from 'foo';", + options: ["never"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import * as foo from 'foo';", + options: ["never"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "export {foo} from 'foo';", + options: ["never"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import {foo} from 'foo';", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import {foo} from 'foo';", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "export {foo} from 'foo';", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "export {foo} from 'foo';", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import {\n foo,\n} from 'foo';", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import {\n foo,\n} from 'foo';", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "export {\n foo,\n} from 'foo';", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "export {\n foo,\n} from 'foo';", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import {foo} from \n'foo';", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import {foo} from \n'foo';", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "function foo(a) {}", + options: ["always"], + }, + { + code: "foo(a)", + options: ["always"], + }, + { + code: "function foo(a) {}", + options: ["never"], + }, + { + code: "foo(a)", + options: ["never"], + }, + { + code: "function foo(a,\nb) {}", + options: ["always-multiline"], + }, + { + code: "foo(a,\nb\n)", + options: ["always-multiline"], + }, + { + code: "function foo(a,\nb\n) {}", + options: ["always-multiline"], + }, + { + code: "foo(a,\nb)", + options: ["always-multiline"], + }, + { + code: "function foo(a,\nb) {}", + options: ["only-multiline"], + }, + { + code: "foo(a,\nb)", + options: ["only-multiline"], + }, + { + code: "function foo(a) {}", + options: ["always"], + languageOptions: { ecmaVersion: 7 }, + }, + { + code: "foo(a)", + options: ["always"], + languageOptions: { ecmaVersion: 7 }, + }, + { + code: "function foo(a) {}", + options: ["never"], + languageOptions: { ecmaVersion: 7 }, + }, + { + code: "foo(a)", + options: ["never"], + languageOptions: { ecmaVersion: 7 }, + }, + { + code: "function foo(a,\nb) {}", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 7 }, + }, + { + code: "foo(a,\nb)", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 7 }, + }, + { + code: "function foo(a,\nb\n) {}", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 7 }, + }, + { + code: "foo(a,\nb\n)", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 7 }, + }, + { + code: "function foo(a,\nb) {}", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 7 }, + }, + { + code: "foo(a,\nb)", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 7 }, + }, + { + code: "function foo(a) {}", + options: ["never"], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(a)", + options: ["never"], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "function foo(a,) {}", + options: ["always"], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(a,)", + options: ["always"], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "function foo(\na,\nb,\n) {}", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(\na,b)", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "function foo(a,b) {}", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(a,b)", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "function foo(a,b) {}", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(a,b)", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 8 }, + }, + // trailing comma in functions + { + code: "function foo(a) {} ", + options: [{}], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(a)", + options: [{}], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "function foo(a) {} ", + options: [{ functions: "never" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(a)", + options: [{ functions: "never" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "function foo(a,) {}", + options: [{ functions: "always" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "function bar(a, ...b) {}", + options: [{ functions: "always" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(a,)", + options: [{ functions: "always" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(a,)", + options: [{ functions: "always" }], + languageOptions: { ecmaVersion: 9 }, + }, + { + code: "bar(...a,)", + options: [{ functions: "always" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "function foo(a) {} ", + options: [{ functions: "always-multiline" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(a)", + options: [{ functions: "always-multiline" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "function foo(\na,\nb,\n) {} ", + options: [{ functions: "always-multiline" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "function foo(\na,\n...b\n) {} ", + options: [{ functions: "always-multiline" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(\na,\nb,\n)", + options: [{ functions: "always-multiline" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(\na,\n...b,\n)", + options: [{ functions: "always-multiline" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "function foo(a) {} ", + options: [{ functions: "only-multiline" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(a)", + options: [{ functions: "only-multiline" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "function foo(\na,\nb,\n) {} ", + options: [{ functions: "only-multiline" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(\na,\nb,\n)", + options: [{ functions: "only-multiline" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "function foo(\na,\nb\n) {} ", + options: [{ functions: "only-multiline" }], + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "foo(\na,\nb\n)", + options: [{ functions: "only-multiline" }], + languageOptions: { ecmaVersion: 8 }, + }, - { - code: "var foo = { bar: 'baz', }", - output: "var foo = { bar: 'baz' }", - options: ["never"], - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 1, - column: 23 - } - ] - }, - { - code: "var foo = { bar: 'baz', }", - output: "var foo = { bar: 'baz' }", - options: ["only-multiline"], - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 1, - column: 23 - } - ] - }, - { - code: "var foo = {\nbar: 'baz',\n}", - output: "var foo = {\nbar: 'baz'\n}", - options: ["never"], - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 2, - column: 11 - } - ] - }, - { - code: "foo({ bar: 'baz', qux: 'quux', });", - output: "foo({ bar: 'baz', qux: 'quux' });", - options: ["never"], - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 1, - column: 30 - } - ] - }, - { - code: "foo({ bar: 'baz', qux: 'quux', });", - output: "foo({ bar: 'baz', qux: 'quux' });", - options: ["only-multiline"], - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 1, - column: 30 - } - ] - }, + // https://github.com/eslint/eslint/issues/7370 + { + code: "function foo({a}: {a: string,}) {}", + options: ["never"], + languageOptions: { + parser: parser("object-pattern-1"), + }, + }, + { + code: "function foo({a,}: {a: string}) {}", + options: ["always"], + languageOptions: { + parser: parser("object-pattern-2"), + }, + }, + { + code: "function foo(a): {b: boolean,} {}", + options: [{ functions: "never" }], + languageOptions: { + parser: parser("return-type-1"), + }, + }, + { + code: "function foo(a,): {b: boolean} {}", + options: [{ functions: "always" }], + languageOptions: { + parser: parser("return-type-2"), + }, + }, - { - code: "var foo = { bar: 'baz' }", - output: "var foo = { bar: 'baz', }", - options: ["always"], - errors: [ - { - messageId: "missing", - type: "Property", - line: 1, - column: 23, - endLine: 1, - endColumn: 24 - } - ] - }, - { - code: "var foo = {\nbar: 'baz'\n}", - output: "var foo = {\nbar: 'baz',\n}", - options: ["always"], - errors: [ - { - messageId: "missing", - type: "Property", - line: 2, - column: 11, - endLine: 3, - endColumn: 1 - } - ] - }, - { - code: "var foo = {\nbar: 'baz'\r\n}", - output: "var foo = {\nbar: 'baz',\r\n}", - options: ["always"], - errors: [ - { - messageId: "missing", - type: "Property", - line: 2, - column: 11, - endLine: 3, - endColumn: 1 - } - ] - }, - { - code: "foo({ bar: 'baz', qux: 'quux' });", - output: "foo({ bar: 'baz', qux: 'quux', });", - options: ["always"], - errors: [ - { - messageId: "missing", - type: "Property", - line: 1, - column: 30, - endLine: 1, - endColumn: 31 + // https://github.com/eslint/eslint/issues/16442 + { + code: "function f(\n a,\n b\n) {}", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, + }, + { + code: "f(\n a,\n b\n);", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, + }, + { + code: "function f(\n a,\n b\n) {}", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 2016, + }, + }, + { + code: "f(\n a,\n b\n);", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 2016, + }, + }, + ], + invalid: [ + { + code: "var foo = { bar: 'baz', }", + output: "var foo = { bar: 'baz' }", + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 1, + column: 23, + endColumn: 24, + }, + ], + }, + { + code: "var foo = {\nbar: 'baz',\n}", + output: "var foo = {\nbar: 'baz'\n}", + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 2, + column: 11, + endColumn: 12, + }, + ], + }, + { + code: "foo({ bar: 'baz', qux: 'quux', });", + output: "foo({ bar: 'baz', qux: 'quux' });", + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 1, + column: 30, + }, + ], + }, + { + code: "foo({\nbar: 'baz',\nqux: 'quux',\n});", + output: "foo({\nbar: 'baz',\nqux: 'quux'\n});", + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 3, + column: 12, + }, + ], + }, + { + code: "var foo = [ 'baz', ]", + output: "var foo = [ 'baz' ]", + errors: [ + { + messageId: "unexpected", + type: "Literal", + line: 1, + column: 18, + }, + ], + }, + { + code: "var foo = [ 'baz',\n]", + output: "var foo = [ 'baz'\n]", + errors: [ + { + messageId: "unexpected", + type: "Literal", + line: 1, + column: 18, + }, + ], + }, + { + code: "var foo = { bar: 'bar'\n\n, }", + output: "var foo = { bar: 'bar'\n\n }", + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 3, + column: 1, + }, + ], + }, - } - ] - }, - { - code: "foo({\nbar: 'baz',\nqux: 'quux'\n});", - output: "foo({\nbar: 'baz',\nqux: 'quux',\n});", - options: ["always"], - errors: [ - { - messageId: "missing", - type: "Property", - line: 3, - column: 12, - endLine: 4, - endColumn: 1 - } - ] - }, - { - code: "var foo = [ 'baz' ]", - output: "var foo = [ 'baz', ]", - options: ["always"], - errors: [ - { - messageId: "missing", - type: "Literal", - line: 1, - column: 18 - } - ] - }, - { - code: "var foo = ['baz']", - output: "var foo = ['baz',]", - options: ["always"], - errors: [ - { - messageId: "missing", - type: "Literal", - line: 1, - column: 17, - endColumn: 18 - } - ] - }, - { - code: "var foo = [ 'baz'\n]", - output: "var foo = [ 'baz',\n]", - options: ["always"], - errors: [ - { - messageId: "missing", - type: "Literal", - line: 1, - column: 18 - } - ] - }, - { - code: "var foo = { bar:\n\n'bar' }", - output: "var foo = { bar:\n\n'bar', }", - options: ["always"], - errors: [ - { - messageId: "missing", - type: "Property", - line: 3, - column: 6 - } - ] - }, + { + code: "var foo = { bar: 'baz', }", + output: "var foo = { bar: 'baz' }", + options: ["never"], + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 1, + column: 23, + }, + ], + }, + { + code: "var foo = { bar: 'baz', }", + output: "var foo = { bar: 'baz' }", + options: ["only-multiline"], + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 1, + column: 23, + }, + ], + }, + { + code: "var foo = {\nbar: 'baz',\n}", + output: "var foo = {\nbar: 'baz'\n}", + options: ["never"], + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 2, + column: 11, + }, + ], + }, + { + code: "foo({ bar: 'baz', qux: 'quux', });", + output: "foo({ bar: 'baz', qux: 'quux' });", + options: ["never"], + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 1, + column: 30, + }, + ], + }, + { + code: "foo({ bar: 'baz', qux: 'quux', });", + output: "foo({ bar: 'baz', qux: 'quux' });", + options: ["only-multiline"], + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 1, + column: 30, + }, + ], + }, - { - code: "var foo = {\nbar: 'baz'\n}", - output: "var foo = {\nbar: 'baz',\n}", - options: ["always-multiline"], - errors: [ - { - messageId: "missing", - type: "Property", - line: 2, - column: 11 - } - ] - }, - { - code: - "var foo = [\n" + - " bar,\n" + - " (\n" + - " baz\n" + - " )\n" + - "];", - output: - "var foo = [\n" + - " bar,\n" + - " (\n" + - " baz\n" + - " ),\n" + - "];", - options: ["always"], - errors: [ - { - messageId: "missing", - type: "Identifier", - line: 5, - column: 4 - } - ] - }, - { - code: - "var foo = {\n" + - " foo: 'bar',\n" + - " baz: (\n" + - " qux\n" + - " )\n" + - "};", - output: - "var foo = {\n" + - " foo: 'bar',\n" + - " baz: (\n" + - " qux\n" + - " ),\n" + - "};", - options: ["always"], - errors: [ - { - messageId: "missing", - type: "Property", - line: 5, - column: 4 - } - ] - }, - { + { + code: "var foo = { bar: 'baz' }", + output: "var foo = { bar: 'baz', }", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "Property", + line: 1, + column: 23, + endLine: 1, + endColumn: 24, + }, + ], + }, + { + code: "var foo = {\nbar: 'baz'\n}", + output: "var foo = {\nbar: 'baz',\n}", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "Property", + line: 2, + column: 11, + endLine: 3, + endColumn: 1, + }, + ], + }, + { + code: "var foo = {\nbar: 'baz'\r\n}", + output: "var foo = {\nbar: 'baz',\r\n}", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "Property", + line: 2, + column: 11, + endLine: 3, + endColumn: 1, + }, + ], + }, + { + code: "foo({ bar: 'baz', qux: 'quux' });", + output: "foo({ bar: 'baz', qux: 'quux', });", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "Property", + line: 1, + column: 30, + endLine: 1, + endColumn: 31, + }, + ], + }, + { + code: "foo({\nbar: 'baz',\nqux: 'quux'\n});", + output: "foo({\nbar: 'baz',\nqux: 'quux',\n});", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "Property", + line: 3, + column: 12, + endLine: 4, + endColumn: 1, + }, + ], + }, + { + code: "var foo = [ 'baz' ]", + output: "var foo = [ 'baz', ]", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "Literal", + line: 1, + column: 18, + }, + ], + }, + { + code: "var foo = ['baz']", + output: "var foo = ['baz',]", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "Literal", + line: 1, + column: 17, + endColumn: 18, + }, + ], + }, + { + code: "var foo = [ 'baz'\n]", + output: "var foo = [ 'baz',\n]", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "Literal", + line: 1, + column: 18, + }, + ], + }, + { + code: "var foo = { bar:\n\n'bar' }", + output: "var foo = { bar:\n\n'bar', }", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "Property", + line: 3, + column: 6, + }, + ], + }, - // https://github.com/eslint/eslint/issues/7291 - code: - "var foo = [\n" + - " (bar\n" + - " ? baz\n" + - " : qux\n" + - " )\n" + - "];", - output: - "var foo = [\n" + - " (bar\n" + - " ? baz\n" + - " : qux\n" + - " ),\n" + - "];", - options: ["always"], - errors: [ - { - messageId: "missing", - type: "ConditionalExpression", - line: 5, - column: 4 - } - ] - }, - { - code: "var foo = { bar: 'baz', }", - output: "var foo = { bar: 'baz' }", - options: ["always-multiline"], - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 1, - column: 23 - } - ] - }, - { - code: "foo({\nbar: 'baz',\nqux: 'quux'\n});", - output: "foo({\nbar: 'baz',\nqux: 'quux',\n});", - options: ["always-multiline"], - errors: [ - { - messageId: "missing", - type: "Property", - line: 3, - column: 12 - } - ] - }, - { - code: "foo({ bar: 'baz', qux: 'quux', });", - output: "foo({ bar: 'baz', qux: 'quux' });", - options: ["always-multiline"], - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 1, - column: 30 - } - ] - }, - { - code: "var foo = [\n'baz'\n]", - output: "var foo = [\n'baz',\n]", - options: ["always-multiline"], - errors: [ - { - messageId: "missing", - type: "Literal", - line: 2, - column: 6 - } - ] - }, - { - code: "var foo = ['baz',]", - output: "var foo = ['baz']", - options: ["always-multiline"], - errors: [ - { - messageId: "unexpected", - type: "Literal", - line: 1, - column: 17 - } - ] - }, - { - code: "var foo = ['baz',]", - output: "var foo = ['baz']", - options: ["only-multiline"], - errors: [ - { - messageId: "unexpected", - type: "Literal", - line: 1, - column: 17 - } - ] - }, - { - code: "var foo = {x: {\nfoo: 'bar',\n},}", - output: "var foo = {x: {\nfoo: 'bar',\n}}", - options: ["always-multiline"], - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 3, - column: 2 - } - ] - }, - { - code: "var foo = {a: 1, b: 2,\nc: 3, d: 4,}", - output: "var foo = {a: 1, b: 2,\nc: 3, d: 4}", - options: ["always-multiline"], - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 2, - column: 11 - } - ] - }, - { - code: "var foo = {a: 1, b: 2,\nc: 3, d: 4,}", - output: "var foo = {a: 1, b: 2,\nc: 3, d: 4}", - options: ["only-multiline"], - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 2, - column: 11 - } - ] - }, - { - code: "var foo = [{\na: 1,\nb: 2,\nc: 3,\nd: 4,\n},]", - output: "var foo = [{\na: 1,\nb: 2,\nc: 3,\nd: 4,\n}]", - options: ["always-multiline"], - errors: [ - { - messageId: "unexpected", - type: "ObjectExpression", - line: 6, - column: 2 - } - ] - }, - { - code: "var { a, b, } = foo;", - output: "var { a, b } = foo;", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 1, - column: 11 - } - ] - }, - { - code: "var { a, b, } = foo;", - output: "var { a, b } = foo;", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 1, - column: 11 - } - ] - }, - { - code: "var [ a, b, ] = foo;", - output: "var [ a, b ] = foo;", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpected", - type: "Identifier", - line: 1, - column: 11 - } - ] - }, - { - code: "var [ a, b, ] = foo;", - output: "var [ a, b ] = foo;", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpected", - type: "Identifier", - line: 1, - column: 11 - } - ] - }, - { - code: "[(1),]", - output: "[(1)]", - options: ["never"], - errors: [ - { - messageId: "unexpected", - type: "Literal", - line: 1, - column: 5 - } - ] - }, - { - code: "[(1),]", - output: "[(1)]", - options: ["only-multiline"], - errors: [ - { - messageId: "unexpected", - type: "Literal", - line: 1, - column: 5 - } - ] - }, - { - code: "var x = { foo: (1),};", - output: "var x = { foo: (1)};", - options: ["never"], - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 1, - column: 19 - } - ] - }, - { - code: "var x = { foo: (1),};", - output: "var x = { foo: (1)};", - options: ["only-multiline"], - errors: [ - { - messageId: "unexpected", - type: "Property", - line: 1, - column: 19 - } - ] - }, + { + code: "var foo = {\nbar: 'baz'\n}", + output: "var foo = {\nbar: 'baz',\n}", + options: ["always-multiline"], + errors: [ + { + messageId: "missing", + type: "Property", + line: 2, + column: 11, + }, + ], + }, + { + code: + "var foo = [\n" + + " bar,\n" + + " (\n" + + " baz\n" + + " )\n" + + "];", + output: + "var foo = [\n" + + " bar,\n" + + " (\n" + + " baz\n" + + " ),\n" + + "];", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "Identifier", + line: 5, + column: 4, + }, + ], + }, + { + code: + "var foo = {\n" + + " foo: 'bar',\n" + + " baz: (\n" + + " qux\n" + + " )\n" + + "};", + output: + "var foo = {\n" + + " foo: 'bar',\n" + + " baz: (\n" + + " qux\n" + + " ),\n" + + "};", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "Property", + line: 5, + column: 4, + }, + ], + }, + { + // https://github.com/eslint/eslint/issues/7291 + code: + "var foo = [\n" + + " (bar\n" + + " ? baz\n" + + " : qux\n" + + " )\n" + + "];", + output: + "var foo = [\n" + + " (bar\n" + + " ? baz\n" + + " : qux\n" + + " ),\n" + + "];", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "ConditionalExpression", + line: 5, + column: 4, + }, + ], + }, + { + code: "var foo = { bar: 'baz', }", + output: "var foo = { bar: 'baz' }", + options: ["always-multiline"], + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 1, + column: 23, + }, + ], + }, + { + code: "foo({\nbar: 'baz',\nqux: 'quux'\n});", + output: "foo({\nbar: 'baz',\nqux: 'quux',\n});", + options: ["always-multiline"], + errors: [ + { + messageId: "missing", + type: "Property", + line: 3, + column: 12, + }, + ], + }, + { + code: "foo({ bar: 'baz', qux: 'quux', });", + output: "foo({ bar: 'baz', qux: 'quux' });", + options: ["always-multiline"], + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 1, + column: 30, + }, + ], + }, + { + code: "var foo = [\n'baz'\n]", + output: "var foo = [\n'baz',\n]", + options: ["always-multiline"], + errors: [ + { + messageId: "missing", + type: "Literal", + line: 2, + column: 6, + }, + ], + }, + { + code: "var foo = ['baz',]", + output: "var foo = ['baz']", + options: ["always-multiline"], + errors: [ + { + messageId: "unexpected", + type: "Literal", + line: 1, + column: 17, + }, + ], + }, + { + code: "var foo = ['baz',]", + output: "var foo = ['baz']", + options: ["only-multiline"], + errors: [ + { + messageId: "unexpected", + type: "Literal", + line: 1, + column: 17, + }, + ], + }, + { + code: "var foo = {x: {\nfoo: 'bar',\n},}", + output: "var foo = {x: {\nfoo: 'bar',\n}}", + options: ["always-multiline"], + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 3, + column: 2, + }, + ], + }, + { + code: "var foo = {a: 1, b: 2,\nc: 3, d: 4,}", + output: "var foo = {a: 1, b: 2,\nc: 3, d: 4}", + options: ["always-multiline"], + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 2, + column: 11, + }, + ], + }, + { + code: "var foo = {a: 1, b: 2,\nc: 3, d: 4,}", + output: "var foo = {a: 1, b: 2,\nc: 3, d: 4}", + options: ["only-multiline"], + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 2, + column: 11, + }, + ], + }, + { + code: "var foo = [{\na: 1,\nb: 2,\nc: 3,\nd: 4,\n},]", + output: "var foo = [{\na: 1,\nb: 2,\nc: 3,\nd: 4,\n}]", + options: ["always-multiline"], + errors: [ + { + messageId: "unexpected", + type: "ObjectExpression", + line: 6, + column: 2, + }, + ], + }, + { + code: "var { a, b, } = foo;", + output: "var { a, b } = foo;", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 1, + column: 11, + }, + ], + }, + { + code: "var { a, b, } = foo;", + output: "var { a, b } = foo;", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 1, + column: 11, + }, + ], + }, + { + code: "var [ a, b, ] = foo;", + output: "var [ a, b ] = foo;", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpected", + type: "Identifier", + line: 1, + column: 11, + }, + ], + }, + { + code: "var [ a, b, ] = foo;", + output: "var [ a, b ] = foo;", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpected", + type: "Identifier", + line: 1, + column: 11, + }, + ], + }, + { + code: "[(1),]", + output: "[(1)]", + options: ["never"], + errors: [ + { + messageId: "unexpected", + type: "Literal", + line: 1, + column: 5, + }, + ], + }, + { + code: "[(1),]", + output: "[(1)]", + options: ["only-multiline"], + errors: [ + { + messageId: "unexpected", + type: "Literal", + line: 1, + column: 5, + }, + ], + }, + { + code: "var x = { foo: (1),};", + output: "var x = { foo: (1)};", + options: ["never"], + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 1, + column: 19, + }, + ], + }, + { + code: "var x = { foo: (1),};", + output: "var x = { foo: (1)};", + options: ["only-multiline"], + errors: [ + { + messageId: "unexpected", + type: "Property", + line: 1, + column: 19, + }, + ], + }, - // https://github.com/eslint/eslint/issues/3794 - { - code: "import {foo} from 'foo';", - output: "import {foo,} from 'foo';", - options: ["always"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ messageId: "missing", type: "ImportSpecifier" }] - }, - { - code: "import foo, {abc} from 'foo';", - output: "import foo, {abc,} from 'foo';", - options: ["always"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ messageId: "missing", type: "ImportSpecifier" }] - }, - { - code: "export {foo} from 'foo';", - output: "export {foo,} from 'foo';", - options: ["always"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ messageId: "missing", type: "ExportSpecifier" }] - }, - { - code: "import {foo,} from 'foo';", - output: "import {foo} from 'foo';", - options: ["never"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ messageId: "unexpected", type: "ImportSpecifier" }] - }, - { - code: "import {foo,} from 'foo';", - output: "import {foo} from 'foo';", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ messageId: "unexpected", type: "ImportSpecifier" }] - }, - { - code: "import foo, {abc,} from 'foo';", - output: "import foo, {abc} from 'foo';", - options: ["never"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ messageId: "unexpected", type: "ImportSpecifier" }] - }, - { - code: "import foo, {abc,} from 'foo';", - output: "import foo, {abc} from 'foo';", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ messageId: "unexpected", type: "ImportSpecifier" }] - }, - { - code: "export {foo,} from 'foo';", - output: "export {foo} from 'foo';", - options: ["never"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ messageId: "unexpected", type: "ExportSpecifier" }] - }, - { - code: "export {foo,} from 'foo';", - output: "export {foo} from 'foo';", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ messageId: "unexpected", type: "ExportSpecifier" }] - }, - { - code: "import {foo,} from 'foo';", - output: "import {foo} from 'foo';", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ messageId: "unexpected", type: "ImportSpecifier" }] - }, - { - code: "export {foo,} from 'foo';", - output: "export {foo} from 'foo';", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ messageId: "unexpected", type: "ExportSpecifier" }] - }, - { - code: "import {\n foo\n} from 'foo';", - output: "import {\n foo,\n} from 'foo';", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ messageId: "missing", type: "ImportSpecifier" }] - }, - { - code: "export {\n foo\n} from 'foo';", - output: "export {\n foo,\n} from 'foo';", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ messageId: "missing", type: "ExportSpecifier" }] - }, + // https://github.com/eslint/eslint/issues/3794 + { + code: "import {foo} from 'foo';", + output: "import {foo,} from 'foo';", + options: ["always"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "missing", type: "ImportSpecifier" }], + }, + { + code: "import foo, {abc} from 'foo';", + output: "import foo, {abc,} from 'foo';", + options: ["always"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "missing", type: "ImportSpecifier" }], + }, + { + code: "export {foo} from 'foo';", + output: "export {foo,} from 'foo';", + options: ["always"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "missing", type: "ExportSpecifier" }], + }, + { + code: "import {foo,} from 'foo';", + output: "import {foo} from 'foo';", + options: ["never"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpected", type: "ImportSpecifier" }], + }, + { + code: "import {foo,} from 'foo';", + output: "import {foo} from 'foo';", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpected", type: "ImportSpecifier" }], + }, + { + code: "import foo, {abc,} from 'foo';", + output: "import foo, {abc} from 'foo';", + options: ["never"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpected", type: "ImportSpecifier" }], + }, + { + code: "import foo, {abc,} from 'foo';", + output: "import foo, {abc} from 'foo';", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpected", type: "ImportSpecifier" }], + }, + { + code: "export {foo,} from 'foo';", + output: "export {foo} from 'foo';", + options: ["never"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpected", type: "ExportSpecifier" }], + }, + { + code: "export {foo,} from 'foo';", + output: "export {foo} from 'foo';", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpected", type: "ExportSpecifier" }], + }, + { + code: "import {foo,} from 'foo';", + output: "import {foo} from 'foo';", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpected", type: "ImportSpecifier" }], + }, + { + code: "export {foo,} from 'foo';", + output: "export {foo} from 'foo';", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "unexpected", type: "ExportSpecifier" }], + }, + { + code: "import {\n foo\n} from 'foo';", + output: "import {\n foo,\n} from 'foo';", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "missing", type: "ImportSpecifier" }], + }, + { + code: "export {\n foo\n} from 'foo';", + output: "export {\n foo,\n} from 'foo';", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [{ messageId: "missing", type: "ExportSpecifier" }], + }, - // https://github.com/eslint/eslint/issues/6233 - { - code: "var foo = {a: (1)}", - output: "var foo = {a: (1),}", - options: ["always"], - errors: [{ messageId: "missing", type: "Property" }] - }, - { - code: "var foo = [(1)]", - output: "var foo = [(1),]", - options: ["always"], - errors: [{ messageId: "missing", type: "Literal" }] - }, - { - code: "var foo = [\n1,\n(2)\n]", - output: "var foo = [\n1,\n(2),\n]", - options: ["always-multiline"], - errors: [{ messageId: "missing", type: "Literal" }] - }, + // https://github.com/eslint/eslint/issues/6233 + { + code: "var foo = {a: (1)}", + output: "var foo = {a: (1),}", + options: ["always"], + errors: [{ messageId: "missing", type: "Property" }], + }, + { + code: "var foo = [(1)]", + output: "var foo = [(1),]", + options: ["always"], + errors: [{ messageId: "missing", type: "Literal" }], + }, + { + code: "var foo = [\n1,\n(2)\n]", + output: "var foo = [\n1,\n(2),\n]", + options: ["always-multiline"], + errors: [{ messageId: "missing", type: "Literal" }], + }, - // trailing commas in functions - { - code: "function foo(a,) {}", - output: "function foo(a) {}", - options: [{ functions: "never" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "(function foo(a,) {})", - output: "(function foo(a) {})", - options: [{ functions: "never" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "(a,) => a", - output: "(a) => a", - options: [{ functions: "never" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "(a,) => (a)", - output: "(a) => (a)", - options: [{ functions: "never" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "({foo(a,) {}})", - output: "({foo(a) {}})", - options: [{ functions: "never" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "class A {foo(a,) {}}", - output: "class A {foo(a) {}}", - options: [{ functions: "never" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "foo(a,)", - output: "foo(a)", - options: [{ functions: "never" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "foo(...a,)", - output: "foo(...a)", - options: [{ functions: "never" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "SpreadElement" }] - }, + // trailing commas in functions + { + code: "function foo(a,) {}", + output: "function foo(a) {}", + options: [{ functions: "never" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "(function foo(a,) {})", + output: "(function foo(a) {})", + options: [{ functions: "never" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "(a,) => a", + output: "(a) => a", + options: [{ functions: "never" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "(a,) => (a)", + output: "(a) => (a)", + options: [{ functions: "never" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "({foo(a,) {}})", + output: "({foo(a) {}})", + options: [{ functions: "never" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "class A {foo(a,) {}}", + output: "class A {foo(a) {}}", + options: [{ functions: "never" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "foo(a,)", + output: "foo(a)", + options: [{ functions: "never" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "foo(...a,)", + output: "foo(...a)", + options: [{ functions: "never" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "SpreadElement" }], + }, - { - code: "function foo(a) {}", - output: "function foo(a,) {}", - options: [{ functions: "always" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "(function foo(a) {})", - output: "(function foo(a,) {})", - options: [{ functions: "always" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "(a) => a", - output: "(a,) => a", - options: [{ functions: "always" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "(a) => (a)", - output: "(a,) => (a)", - options: [{ functions: "always" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "({foo(a) {}})", - output: "({foo(a,) {}})", - options: [{ functions: "always" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "class A {foo(a) {}}", - output: "class A {foo(a,) {}}", - options: [{ functions: "always" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "foo(a)", - output: "foo(a,)", - options: [{ functions: "always" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "foo(...a)", - output: "foo(...a,)", - options: [{ functions: "always" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "SpreadElement" }] - }, + { + code: "function foo(a) {}", + output: "function foo(a,) {}", + options: [{ functions: "always" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "(function foo(a) {})", + output: "(function foo(a,) {})", + options: [{ functions: "always" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "(a) => a", + output: "(a,) => a", + options: [{ functions: "always" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "(a) => (a)", + output: "(a,) => (a)", + options: [{ functions: "always" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "({foo(a) {}})", + output: "({foo(a,) {}})", + options: [{ functions: "always" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "class A {foo(a) {}}", + output: "class A {foo(a,) {}}", + options: [{ functions: "always" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "foo(a)", + output: "foo(a,)", + options: [{ functions: "always" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "foo(...a)", + output: "foo(...a,)", + options: [{ functions: "always" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "SpreadElement" }], + }, - { - code: "function foo(a,) {}", - output: "function foo(a) {}", - options: [{ functions: "always-multiline" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "(function foo(a,) {})", - output: "(function foo(a) {})", - options: [{ functions: "always-multiline" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "foo(a,)", - output: "foo(a)", - options: [{ functions: "always-multiline" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "foo(...a,)", - output: "foo(...a)", - options: [{ functions: "always-multiline" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "SpreadElement" }] - }, - { - code: "function foo(\na,\nb\n) {}", - output: "function foo(\na,\nb,\n) {}", - options: [{ functions: "always-multiline" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "foo(\na,\nb\n)", - output: "foo(\na,\nb,\n)", - options: [{ functions: "always-multiline" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "foo(\n...a,\n...b\n)", - output: "foo(\n...a,\n...b,\n)", - options: [{ functions: "always-multiline" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "SpreadElement" }] - }, + { + code: "function foo(a,) {}", + output: "function foo(a) {}", + options: [{ functions: "always-multiline" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "(function foo(a,) {})", + output: "(function foo(a) {})", + options: [{ functions: "always-multiline" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "foo(a,)", + output: "foo(a)", + options: [{ functions: "always-multiline" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "foo(...a,)", + output: "foo(...a)", + options: [{ functions: "always-multiline" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "SpreadElement" }], + }, + { + code: "function foo(\na,\nb\n) {}", + output: "function foo(\na,\nb,\n) {}", + options: [{ functions: "always-multiline" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "foo(\na,\nb\n)", + output: "foo(\na,\nb,\n)", + options: [{ functions: "always-multiline" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "foo(\n...a,\n...b\n)", + output: "foo(\n...a,\n...b,\n)", + options: [{ functions: "always-multiline" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "SpreadElement" }], + }, - { - code: "function foo(a,) {}", - output: "function foo(a) {}", - options: [{ functions: "only-multiline" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "(function foo(a,) {})", - output: "(function foo(a) {})", - options: [{ functions: "only-multiline" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "foo(a,)", - output: "foo(a)", - options: [{ functions: "only-multiline" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "foo(...a,)", - output: "foo(...a)", - options: [{ functions: "only-multiline" }], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "SpreadElement" }] - }, - { - code: "function foo(a,) {}", - output: "function foo(a) {}", - options: ["never"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "(function foo(a,) {})", - output: "(function foo(a) {})", - options: ["never"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "(a,) => a", - output: "(a) => a", - options: ["never"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "(a,) => (a)", - output: "(a) => (a)", - options: ["never"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "({foo(a,) {}})", - output: "({foo(a) {}})", - options: ["never"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "class A {foo(a,) {}}", - output: "class A {foo(a) {}}", - options: ["never"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "foo(a,)", - output: "foo(a)", - options: ["never"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "foo(...a,)", - output: "foo(...a)", - options: ["never"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "SpreadElement" }] - }, + { + code: "function foo(a,) {}", + output: "function foo(a) {}", + options: [{ functions: "only-multiline" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "(function foo(a,) {})", + output: "(function foo(a) {})", + options: [{ functions: "only-multiline" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "foo(a,)", + output: "foo(a)", + options: [{ functions: "only-multiline" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "foo(...a,)", + output: "foo(...a)", + options: [{ functions: "only-multiline" }], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "SpreadElement" }], + }, + { + code: "function foo(a,) {}", + output: "function foo(a) {}", + options: ["never"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "(function foo(a,) {})", + output: "(function foo(a) {})", + options: ["never"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "(a,) => a", + output: "(a) => a", + options: ["never"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "(a,) => (a)", + output: "(a) => (a)", + options: ["never"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "({foo(a,) {}})", + output: "({foo(a) {}})", + options: ["never"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "class A {foo(a,) {}}", + output: "class A {foo(a) {}}", + options: ["never"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "foo(a,)", + output: "foo(a)", + options: ["never"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "foo(...a,)", + output: "foo(...a)", + options: ["never"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "SpreadElement" }], + }, - { - code: "function foo(a) {}", - output: "function foo(a,) {}", - options: ["always"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "(function foo(a) {})", - output: "(function foo(a,) {})", - options: ["always"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "(a) => a", - output: "(a,) => a", - options: ["always"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "(a) => (a)", - output: "(a,) => (a)", - options: ["always"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "({foo(a) {}})", - output: "({foo(a,) {},})", - options: ["always"], - languageOptions: { ecmaVersion: 8 }, - errors: [ - { messageId: "missing", type: "Identifier" }, - { messageId: "missing", type: "Property" } - ] - }, - { - code: "class A {foo(a) {}}", - output: "class A {foo(a,) {}}", - options: ["always"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "foo(a)", - output: "foo(a,)", - options: ["always"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "foo(...a)", - output: "foo(...a,)", - options: ["always"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "SpreadElement" }] - }, + { + code: "function foo(a) {}", + output: "function foo(a,) {}", + options: ["always"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "(function foo(a) {})", + output: "(function foo(a,) {})", + options: ["always"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "(a) => a", + output: "(a,) => a", + options: ["always"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "(a) => (a)", + output: "(a,) => (a)", + options: ["always"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "({foo(a) {}})", + output: "({foo(a,) {},})", + options: ["always"], + languageOptions: { ecmaVersion: 8 }, + errors: [ + { messageId: "missing", type: "Identifier" }, + { messageId: "missing", type: "Property" }, + ], + }, + { + code: "class A {foo(a) {}}", + output: "class A {foo(a,) {}}", + options: ["always"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "foo(a)", + output: "foo(a,)", + options: ["always"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "foo(...a)", + output: "foo(...a,)", + options: ["always"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "SpreadElement" }], + }, - { - code: "function foo(a,) {}", - output: "function foo(a) {}", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "(function foo(a,) {})", - output: "(function foo(a) {})", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "foo(a,)", - output: "foo(a)", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "foo(...a,)", - output: "foo(...a)", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "SpreadElement" }] - }, - { - code: "function foo(\na,\nb\n) {}", - output: "function foo(\na,\nb,\n) {}", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "foo(\na,\nb\n)", - output: "foo(\na,\nb,\n)", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, - { - code: "foo(\n...a,\n...b\n)", - output: "foo(\n...a,\n...b,\n)", - options: ["always-multiline"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "missing", type: "SpreadElement" }] - }, + { + code: "function foo(a,) {}", + output: "function foo(a) {}", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "(function foo(a,) {})", + output: "(function foo(a) {})", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "foo(a,)", + output: "foo(a)", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "foo(...a,)", + output: "foo(...a)", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "SpreadElement" }], + }, + { + code: "function foo(\na,\nb\n) {}", + output: "function foo(\na,\nb,\n) {}", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "foo(\na,\nb\n)", + output: "foo(\na,\nb,\n)", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, + { + code: "foo(\n...a,\n...b\n)", + output: "foo(\n...a,\n...b,\n)", + options: ["always-multiline"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "missing", type: "SpreadElement" }], + }, - { - code: "function foo(a,) {}", - output: "function foo(a) {}", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "(function foo(a,) {})", - output: "(function foo(a) {})", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "foo(a,)", - output: "foo(a)", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "Identifier" }] - }, - { - code: "foo(...a,)", - output: "foo(...a)", - options: ["only-multiline"], - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected", type: "SpreadElement" }] - }, - { - code: "function foo(a) {}", - output: "function foo(a,) {}", - options: ["always"], - languageOptions: { ecmaVersion: 9 }, - errors: [{ messageId: "missing", type: "Identifier" }] - }, + { + code: "function foo(a,) {}", + output: "function foo(a) {}", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "(function foo(a,) {})", + output: "(function foo(a) {})", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "foo(a,)", + output: "foo(a)", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "Identifier" }], + }, + { + code: "foo(...a,)", + output: "foo(...a)", + options: ["only-multiline"], + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected", type: "SpreadElement" }], + }, + { + code: "function foo(a) {}", + output: "function foo(a,) {}", + options: ["always"], + languageOptions: { ecmaVersion: 9 }, + errors: [{ messageId: "missing", type: "Identifier" }], + }, - // separated options - { - code: `let {a,} = {a: 1,}; + // separated options + { + code: `let {a,} = {a: 1,}; let [b,] = [1,]; import {c,} from "foo"; let d = 0;export {d,}; (function foo(e,) {})(f,);`, - output: `let {a} = {a: 1}; + output: `let {a} = {a: 1}; let [b,] = [1,]; import {c,} from "foo"; let d = 0;export {d,}; (function foo(e,) {})(f,);`, - options: [{ - objects: "never", - arrays: "ignore", - imports: "ignore", - exports: "ignore", - functions: "ignore" - }], - languageOptions: { ecmaVersion: 8, sourceType: "module" }, - errors: [ - { messageId: "unexpected", line: 1 }, - { messageId: "unexpected", line: 1 } - ] - }, - { - code: `let {a,} = {a: 1,}; + options: [ + { + objects: "never", + arrays: "ignore", + imports: "ignore", + exports: "ignore", + functions: "ignore", + }, + ], + languageOptions: { ecmaVersion: 8, sourceType: "module" }, + errors: [ + { messageId: "unexpected", line: 1 }, + { messageId: "unexpected", line: 1 }, + ], + }, + { + code: `let {a,} = {a: 1,}; let [b,] = [1,]; import {c,} from "foo"; let d = 0;export {d,}; (function foo(e,) {})(f,);`, - output: `let {a,} = {a: 1,}; + output: `let {a,} = {a: 1,}; let [b] = [1]; import {c,} from "foo"; let d = 0;export {d,}; (function foo(e,) {})(f,);`, - options: [{ - objects: "ignore", - arrays: "never", - imports: "ignore", - exports: "ignore", - functions: "ignore" - }], - languageOptions: { ecmaVersion: 8, sourceType: "module" }, - errors: [ - { messageId: "unexpected", line: 2 }, - { messageId: "unexpected", line: 2 } - ] - }, - { - code: `let {a,} = {a: 1,}; + options: [ + { + objects: "ignore", + arrays: "never", + imports: "ignore", + exports: "ignore", + functions: "ignore", + }, + ], + languageOptions: { ecmaVersion: 8, sourceType: "module" }, + errors: [ + { messageId: "unexpected", line: 2 }, + { messageId: "unexpected", line: 2 }, + ], + }, + { + code: `let {a,} = {a: 1,}; let [b,] = [1,]; import {c,} from "foo"; let d = 0;export {d,}; (function foo(e,) {})(f,);`, - output: `let {a,} = {a: 1,}; + output: `let {a,} = {a: 1,}; let [b,] = [1,]; import {c} from "foo"; let d = 0;export {d,}; (function foo(e,) {})(f,);`, - options: [{ - objects: "ignore", - arrays: "ignore", - imports: "never", - exports: "ignore", - functions: "ignore" - }], - languageOptions: { ecmaVersion: 8, sourceType: "module" }, - errors: [ - { messageId: "unexpected", line: 3 } - ] - }, - { - code: `let {a,} = {a: 1,}; + options: [ + { + objects: "ignore", + arrays: "ignore", + imports: "never", + exports: "ignore", + functions: "ignore", + }, + ], + languageOptions: { ecmaVersion: 8, sourceType: "module" }, + errors: [{ messageId: "unexpected", line: 3 }], + }, + { + code: `let {a,} = {a: 1,}; let [b,] = [1,]; import {c,} from "foo"; let d = 0;export {d,}; (function foo(e,) {})(f,);`, - output: `let {a,} = {a: 1,}; + output: `let {a,} = {a: 1,}; let [b,] = [1,]; import {c,} from "foo"; let d = 0;export {d}; (function foo(e,) {})(f,);`, - options: [{ - objects: "ignore", - arrays: "ignore", - imports: "ignore", - exports: "never", - functions: "ignore" - }], - languageOptions: { ecmaVersion: 8, sourceType: "module" }, - errors: [ - { messageId: "unexpected", line: 4 } - ] - }, - { - code: `let {a,} = {a: 1,}; + options: [ + { + objects: "ignore", + arrays: "ignore", + imports: "ignore", + exports: "never", + functions: "ignore", + }, + ], + languageOptions: { ecmaVersion: 8, sourceType: "module" }, + errors: [{ messageId: "unexpected", line: 4 }], + }, + { + code: `let {a,} = {a: 1,}; let [b,] = [1,]; import {c,} from "foo"; let d = 0;export {d,}; (function foo(e,) {})(f,);`, - output: `let {a,} = {a: 1,}; + output: `let {a,} = {a: 1,}; let [b,] = [1,]; import {c,} from "foo"; let d = 0;export {d,}; (function foo(e) {})(f);`, - options: [{ - objects: "ignore", - arrays: "ignore", - imports: "ignore", - exports: "ignore", - functions: "never" - }], - languageOptions: { ecmaVersion: 8, sourceType: "module" }, - errors: [ - { messageId: "unexpected", line: 5 }, - { messageId: "unexpected", line: 5 } - ] - }, + options: [ + { + objects: "ignore", + arrays: "ignore", + imports: "ignore", + exports: "ignore", + functions: "never", + }, + ], + languageOptions: { ecmaVersion: 8, sourceType: "module" }, + errors: [ + { messageId: "unexpected", line: 5 }, + { messageId: "unexpected", line: 5 }, + ], + }, - // https://github.com/eslint/eslint/issues/7370 - { - code: "function foo({a}: {a: string,}) {}", - output: "function foo({a,}: {a: string,}) {}", - options: ["always"], - languageOptions: { - parser: parser("object-pattern-1") - }, - errors: [{ messageId: "missing" }] - }, - { - code: "function foo({a,}: {a: string}) {}", - output: "function foo({a}: {a: string}) {}", - options: ["never"], - languageOptions: { - parser: parser("object-pattern-2") - }, - errors: [{ messageId: "unexpected" }] - }, - { - code: "function foo(a): {b: boolean,} {}", - output: "function foo(a,): {b: boolean,} {}", - options: [{ functions: "always" }], - languageOptions: { - parser: parser("return-type-1") - }, - errors: [{ messageId: "missing" }] - }, - { - code: "function foo(a,): {b: boolean} {}", - output: "function foo(a): {b: boolean} {}", - options: [{ functions: "never" }], - languageOptions: { - parser: parser("return-type-2") - }, - errors: [{ messageId: "unexpected" }] - }, + // https://github.com/eslint/eslint/issues/7370 + { + code: "function foo({a}: {a: string,}) {}", + output: "function foo({a,}: {a: string,}) {}", + options: ["always"], + languageOptions: { + parser: parser("object-pattern-1"), + }, + errors: [{ messageId: "missing" }], + }, + { + code: "function foo({a,}: {a: string}) {}", + output: "function foo({a}: {a: string}) {}", + options: ["never"], + languageOptions: { + parser: parser("object-pattern-2"), + }, + errors: [{ messageId: "unexpected" }], + }, + { + code: "function foo(a): {b: boolean,} {}", + output: "function foo(a,): {b: boolean,} {}", + options: [{ functions: "always" }], + languageOptions: { + parser: parser("return-type-1"), + }, + errors: [{ messageId: "missing" }], + }, + { + code: "function foo(a,): {b: boolean} {}", + output: "function foo(a): {b: boolean} {}", + options: [{ functions: "never" }], + languageOptions: { + parser: parser("return-type-2"), + }, + errors: [{ messageId: "unexpected" }], + }, - // https://github.com/eslint/eslint/issues/11502 - { - code: "foo(a,)", - output: "foo(a)", - languageOptions: { ecmaVersion: 8 }, - errors: [{ messageId: "unexpected" }] - }, + // https://github.com/eslint/eslint/issues/11502 + { + code: "foo(a,)", + output: "foo(a)", + languageOptions: { ecmaVersion: 8 }, + errors: [{ messageId: "unexpected" }], + }, - // https://github.com/eslint/eslint/issues/15660 - { - code: unIndent` + // https://github.com/eslint/eslint/issues/15660 + { + code: unIndent` /*eslint custom/add-named-import:1*/ import { StyleSheet, @@ -1884,7 +1938,7 @@ let d = 0;export {d,}; SafeAreaView } from 'react-native'; `, - output: unIndent` + output: unIndent` /*eslint custom/add-named-import:1*/ import { StyleSheet, @@ -1896,12 +1950,12 @@ let d = 0;export {d,}; SafeAreaView, } from 'react-native'; `, - options: [{ imports: "always-multiline" }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: 2 - }, - { - code: unIndent` + options: [{ imports: "always-multiline" }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: 2, + }, + { + code: unIndent` /*eslint custom/add-named-import:1*/ import { StyleSheet, @@ -1913,7 +1967,7 @@ let d = 0;export {d,}; SafeAreaView, } from 'react-native'; `, - output: unIndent` + output: unIndent` /*eslint custom/add-named-import:1*/ import { StyleSheet, @@ -1925,67 +1979,75 @@ let d = 0;export {d,}; SafeAreaView } from 'react-native'; `, - options: [{ imports: "never" }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: 2 - }, + options: [{ imports: "never" }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: 2, + }, - // https://github.com/eslint/eslint/issues/16442 - { - code: "function f(\n a,\n b\n) {}", - output: "function f(\n a,\n b,\n) {}", - options: ["always-multiline"], - languageOptions: { - ecmaVersion: 2017 - }, - errors: [{ - messageId: "missing", - type: "Identifier", - line: 3, - column: 3 - }] - }, - { - code: "f(\n a,\n b\n);", - output: "f(\n a,\n b,\n);", - options: ["always-multiline"], - languageOptions: { - ecmaVersion: 2017 - }, - errors: [{ - messageId: "missing", - type: "Identifier", - line: 3, - column: 3 - }] - }, - { - code: "function f(\n a,\n b\n) {}", - output: "function f(\n a,\n b,\n) {}", - options: ["always-multiline"], - languageOptions: { - ecmaVersion: "latest" - }, - errors: [{ - messageId: "missing", - type: "Identifier", - line: 3, - column: 3 - }] - }, - { - code: "f(\n a,\n b\n);", - output: "f(\n a,\n b,\n);", - options: ["always-multiline"], - languageOptions: { - ecmaVersion: "latest" - }, - errors: [{ - messageId: "missing", - type: "Identifier", - line: 3, - column: 3 - }] - } - ] + // https://github.com/eslint/eslint/issues/16442 + { + code: "function f(\n a,\n b\n) {}", + output: "function f(\n a,\n b,\n) {}", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 2017, + }, + errors: [ + { + messageId: "missing", + type: "Identifier", + line: 3, + column: 3, + }, + ], + }, + { + code: "f(\n a,\n b\n);", + output: "f(\n a,\n b,\n);", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: 2017, + }, + errors: [ + { + messageId: "missing", + type: "Identifier", + line: 3, + column: 3, + }, + ], + }, + { + code: "function f(\n a,\n b\n) {}", + output: "function f(\n a,\n b,\n) {}", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: "latest", + }, + errors: [ + { + messageId: "missing", + type: "Identifier", + line: 3, + column: 3, + }, + ], + }, + { + code: "f(\n a,\n b\n);", + output: "f(\n a,\n b,\n);", + options: ["always-multiline"], + languageOptions: { + ecmaVersion: "latest", + }, + errors: [ + { + messageId: "missing", + type: "Identifier", + line: 3, + column: 3, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/comma-spacing.js b/tests/lib/rules/comma-spacing.js index 4b73d15da1b9..88bec7402d9f 100644 --- a/tests/lib/rules/comma-spacing.js +++ b/tests/lib/rules/comma-spacing.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/comma-spacing"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,525 +18,711 @@ const rule = require("../../../lib/rules/comma-spacing"), const ruleTester = new RuleTester(); ruleTester.run("comma-spacing", rule, { - valid: [ - "myfunc(404, true/* bla bla bla */, 'hello');", - "myfunc(404, true /* bla bla bla */, 'hello');", - "myfunc(404, true/* bla bla bla *//* hi */, 'hello');", - "myfunc(404, true/* bla bla bla */ /* hi */, 'hello');", - "myfunc(404, true, /* bla bla bla */ 'hello');", - "myfunc(404, // comment\n true, /* bla bla bla */ 'hello');", - { code: "myfunc(404, // comment\n true,/* bla bla bla */ 'hello');", options: [{ before: false, after: false }] }, - "var a = 1, b = 2;", - "var arr = [,];", - "var arr = [, ];", - "var arr = [ ,];", - "var arr = [ , ];", - "var arr = [1,];", - "var arr = [1, ];", - "var arr = [, 2];", - "var arr = [ , 2];", - "var arr = [1, 2];", - "var arr = [,,];", - "var arr = [ ,,];", - "var arr = [, ,];", - "var arr = [,, ];", - "var arr = [ , ,];", - "var arr = [ ,, ];", - "var arr = [, , ];", - "var arr = [ , , ];", - "var arr = [1, , ];", - "var arr = [, 2, ];", - "var arr = [, , 3];", - "var arr = [,, 3];", - "var arr = [1, 2, ];", - "var arr = [, 2, 3];", - "var arr = [1, , 3];", - "var arr = [1, 2, 3];", - "var arr = [1, 2, 3,];", - "var arr = [1, 2, 3, ];", - "var obj = {'foo':'bar', 'baz':'qur'};", - "var obj = {'foo':'bar', 'baz':'qur', };", - "var obj = {'foo':'bar', 'baz':'qur',};", - "var obj = {'foo':'bar', 'baz':\n'qur'};", - "var obj = {'foo':\n'bar', 'baz':\n'qur'};", - "function foo(a, b){}", - { code: "function foo(a, b = 1){}", languageOptions: { ecmaVersion: 6 } }, - { code: "function foo(a = 1, b, c){}", languageOptions: { ecmaVersion: 6 } }, - { code: "var foo = (a, b) => {}", languageOptions: { ecmaVersion: 6 } }, - { code: "var foo = (a=1, b) => {}", languageOptions: { ecmaVersion: 6 } }, - { code: "var foo = a => a + 2", languageOptions: { ecmaVersion: 6 } }, - "a, b", - "var a = (1 + 2, 2);", - "a(b, c)", - "new A(b, c)", - "foo((a), b)", - "var b = ((1 + 2), 2);", - "parseInt((a + b), 10)", - "go.boom((a + b), 10)", - "go.boom((a + b), 10, (4))", - "var x = [ (a + c), (b + b) ]", - "[' , ']", - { code: "[` , `]", languageOptions: { ecmaVersion: 6 } }, - { code: "`${[1, 2]}`", languageOptions: { ecmaVersion: 6 } }, - { code: "fn(a, b,)", languageOptions: { ecmaVersion: 2018 } }, // #11295 - { code: "const fn = (a, b,) => {}", languageOptions: { ecmaVersion: 2018 } }, // #11295 - { code: "const fn = function (a, b,) {}", languageOptions: { ecmaVersion: 2018 } }, // #11295 - { code: "function fn(a, b,) {}", languageOptions: { ecmaVersion: 2018 } }, // #11295 - "foo(/,/, 'a')", - "var x = ',,,,,';", - "var code = 'var foo = 1, bar = 3;'", - "['apples', \n 'oranges'];", - "{x: 'var x,y,z'}", - { code: "var obj = {'foo':\n'bar' ,'baz':\n'qur'};", options: [{ before: true, after: false }] }, - { code: "var a = 1 ,b = 2;", options: [{ before: true, after: false }] }, - { code: "function foo(a ,b){}", options: [{ before: true, after: false }] }, - { code: "var arr = [,];", options: [{ before: true, after: false }] }, - { code: "var arr = [ ,];", options: [{ before: true, after: false }] }, - { code: "var arr = [, ];", options: [{ before: true, after: false }] }, - { code: "var arr = [ , ];", options: [{ before: true, after: false }] }, - { code: "var arr = [1 ,];", options: [{ before: true, after: false }] }, - { code: "var arr = [1 , ];", options: [{ before: true, after: false }] }, - { code: "var arr = [ ,2];", options: [{ before: true, after: false }] }, - { code: "var arr = [1 ,2];", options: [{ before: true, after: false }] }, - { code: "var arr = [,,];", options: [{ before: true, after: false }] }, - { code: "var arr = [ ,,];", options: [{ before: true, after: false }] }, - { code: "var arr = [, ,];", options: [{ before: true, after: false }] }, - { code: "var arr = [,, ];", options: [{ before: true, after: false }] }, - { code: "var arr = [ , ,];", options: [{ before: true, after: false }] }, - { code: "var arr = [ ,, ];", options: [{ before: true, after: false }] }, - { code: "var arr = [, , ];", options: [{ before: true, after: false }] }, - { code: "var arr = [ , , ];", options: [{ before: true, after: false }] }, - { code: "var arr = [1 , ,];", options: [{ before: true, after: false }] }, - { code: "var arr = [ ,2 ,];", options: [{ before: true, after: false }] }, - { code: "var arr = [,2 , ];", options: [{ before: true, after: false }] }, - { code: "var arr = [ , ,3];", options: [{ before: true, after: false }] }, - { code: "var arr = [1 ,2 ,];", options: [{ before: true, after: false }] }, - { code: "var arr = [ ,2 ,3];", options: [{ before: true, after: false }] }, - { code: "var arr = [1 , ,3];", options: [{ before: true, after: false }] }, - { code: "var arr = [1 ,2 ,3];", options: [{ before: true, after: false }] }, - { code: "var obj = {'foo':'bar' , 'baz':'qur'};", options: [{ before: true, after: true }] }, - { code: "var obj = {'foo':'bar' ,'baz':'qur' , };", options: [{ before: true, after: false }] }, - { code: "var a = 1 , b = 2;", options: [{ before: true, after: true }] }, - { code: "var arr = [, ];", options: [{ before: true, after: true }] }, - { code: "var arr = [,,];", options: [{ before: true, after: true }] }, - { code: "var arr = [1 , ];", options: [{ before: true, after: true }] }, - { code: "var arr = [ , 2];", options: [{ before: true, after: true }] }, - { code: "var arr = [1 , 2];", options: [{ before: true, after: true }] }, - { code: "var arr = [, , ];", options: [{ before: true, after: true }] }, - { code: "var arr = [1 , , ];", options: [{ before: true, after: true }] }, - { code: "var arr = [ , 2 , ];", options: [{ before: true, after: true }] }, - { code: "var arr = [ , , 3];", options: [{ before: true, after: true }] }, - { code: "var arr = [1 , 2 , ];", options: [{ before: true, after: true }] }, - { code: "var arr = [, 2 , 3];", options: [{ before: true, after: true }] }, - { code: "var arr = [1 , , 3];", options: [{ before: true, after: true }] }, - { code: "var arr = [1 , 2 , 3];", options: [{ before: true, after: true }] }, - { code: "a , b", options: [{ before: true, after: true }] }, - { code: "var arr = [,];", options: [{ before: false, after: false }] }, - { code: "var arr = [ ,];", options: [{ before: false, after: false }] }, - { code: "var arr = [1,];", options: [{ before: false, after: false }] }, - { code: "var arr = [,2];", options: [{ before: false, after: false }] }, - { code: "var arr = [ ,2];", options: [{ before: false, after: false }] }, - { code: "var arr = [1,2];", options: [{ before: false, after: false }] }, - { code: "var arr = [,,];", options: [{ before: false, after: false }] }, - { code: "var arr = [ , , ];", options: [{ before: false, after: false }] }, - { code: "var arr = [ ,,];", options: [{ before: false, after: false }] }, - { code: "var arr = [1,,];", options: [{ before: false, after: false }] }, - { code: "var arr = [,2,];", options: [{ before: false, after: false }] }, - { code: "var arr = [ ,2,];", options: [{ before: false, after: false }] }, - { code: "var arr = [,,3];", options: [{ before: false, after: false }] }, - { code: "var arr = [1,2,];", options: [{ before: false, after: false }] }, - { code: "var arr = [,2,3];", options: [{ before: false, after: false }] }, - { code: "var arr = [1,,3];", options: [{ before: false, after: false }] }, - { code: "var arr = [1,2,3];", options: [{ before: false, after: false }] }, - { code: "var a = (1 + 2,2)", options: [{ before: false, after: false }] }, - { code: "var a; console.log(`${a}`, \"a\");", languageOptions: { ecmaVersion: 6 } }, - { code: "var [a, b] = [1, 2];", languageOptions: { ecmaVersion: 6 } }, - { code: "var [a, b, ] = [1, 2];", languageOptions: { ecmaVersion: 6 } }, - { code: "var [a, b,] = [1, 2];", languageOptions: { ecmaVersion: 6 } }, - { code: "var [a, , b] = [1, 2, 3];", languageOptions: { ecmaVersion: 6 } }, - { code: "var [a,, b] = [1, 2, 3];", languageOptions: { ecmaVersion: 6 } }, - { code: "var [ , b] = a;", languageOptions: { ecmaVersion: 6 } }, - { code: "var [, b] = a;", languageOptions: { ecmaVersion: 6 } }, - { code: "var { a,} = a;", languageOptions: { ecmaVersion: 6 } }, - { code: "import { a,} from 'mod';", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, - { code: ",", languageOptions: { ecmaVersion: 6, parserOptions: { ecmaFeatures: { jsx: true } } } }, - { code: " , ", languageOptions: { ecmaVersion: 6, parserOptions: { ecmaFeatures: { jsx: true } } } }, - { code: "Hello, world", options: [{ before: true, after: false }], languageOptions: { ecmaVersion: 6, parserOptions: { ecmaFeatures: { jsx: true } } } }, + valid: [ + "myfunc(404, true/* bla bla bla */, 'hello');", + "myfunc(404, true /* bla bla bla */, 'hello');", + "myfunc(404, true/* bla bla bla *//* hi */, 'hello');", + "myfunc(404, true/* bla bla bla */ /* hi */, 'hello');", + "myfunc(404, true, /* bla bla bla */ 'hello');", + "myfunc(404, // comment\n true, /* bla bla bla */ 'hello');", + { + code: "myfunc(404, // comment\n true,/* bla bla bla */ 'hello');", + options: [{ before: false, after: false }], + }, + "var a = 1, b = 2;", + "var arr = [,];", + "var arr = [, ];", + "var arr = [ ,];", + "var arr = [ , ];", + "var arr = [1,];", + "var arr = [1, ];", + "var arr = [, 2];", + "var arr = [ , 2];", + "var arr = [1, 2];", + "var arr = [,,];", + "var arr = [ ,,];", + "var arr = [, ,];", + "var arr = [,, ];", + "var arr = [ , ,];", + "var arr = [ ,, ];", + "var arr = [, , ];", + "var arr = [ , , ];", + "var arr = [1, , ];", + "var arr = [, 2, ];", + "var arr = [, , 3];", + "var arr = [,, 3];", + "var arr = [1, 2, ];", + "var arr = [, 2, 3];", + "var arr = [1, , 3];", + "var arr = [1, 2, 3];", + "var arr = [1, 2, 3,];", + "var arr = [1, 2, 3, ];", + "var obj = {'foo':'bar', 'baz':'qur'};", + "var obj = {'foo':'bar', 'baz':'qur', };", + "var obj = {'foo':'bar', 'baz':'qur',};", + "var obj = {'foo':'bar', 'baz':\n'qur'};", + "var obj = {'foo':\n'bar', 'baz':\n'qur'};", + "function foo(a, b){}", + { + code: "function foo(a, b = 1){}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo(a = 1, b, c){}", + languageOptions: { ecmaVersion: 6 }, + }, + { code: "var foo = (a, b) => {}", languageOptions: { ecmaVersion: 6 } }, + { + code: "var foo = (a=1, b) => {}", + languageOptions: { ecmaVersion: 6 }, + }, + { code: "var foo = a => a + 2", languageOptions: { ecmaVersion: 6 } }, + "a, b", + "var a = (1 + 2, 2);", + "a(b, c)", + "new A(b, c)", + "foo((a), b)", + "var b = ((1 + 2), 2);", + "parseInt((a + b), 10)", + "go.boom((a + b), 10)", + "go.boom((a + b), 10, (4))", + "var x = [ (a + c), (b + b) ]", + "[' , ']", + { code: "[` , `]", languageOptions: { ecmaVersion: 6 } }, + { code: "`${[1, 2]}`", languageOptions: { ecmaVersion: 6 } }, + { code: "fn(a, b,)", languageOptions: { ecmaVersion: 2018 } }, // #11295 + { + code: "const fn = (a, b,) => {}", + languageOptions: { ecmaVersion: 2018 }, + }, // #11295 + { + code: "const fn = function (a, b,) {}", + languageOptions: { ecmaVersion: 2018 }, + }, // #11295 + { + code: "function fn(a, b,) {}", + languageOptions: { ecmaVersion: 2018 }, + }, // #11295 + "foo(/,/, 'a')", + "var x = ',,,,,';", + "var code = 'var foo = 1, bar = 3;'", + "['apples', \n 'oranges'];", + "{x: 'var x,y,z'}", + { + code: "var obj = {'foo':\n'bar' ,'baz':\n'qur'};", + options: [{ before: true, after: false }], + }, + { + code: "var a = 1 ,b = 2;", + options: [{ before: true, after: false }], + }, + { + code: "function foo(a ,b){}", + options: [{ before: true, after: false }], + }, + { code: "var arr = [,];", options: [{ before: true, after: false }] }, + { code: "var arr = [ ,];", options: [{ before: true, after: false }] }, + { code: "var arr = [, ];", options: [{ before: true, after: false }] }, + { code: "var arr = [ , ];", options: [{ before: true, after: false }] }, + { code: "var arr = [1 ,];", options: [{ before: true, after: false }] }, + { + code: "var arr = [1 , ];", + options: [{ before: true, after: false }], + }, + { code: "var arr = [ ,2];", options: [{ before: true, after: false }] }, + { + code: "var arr = [1 ,2];", + options: [{ before: true, after: false }], + }, + { code: "var arr = [,,];", options: [{ before: true, after: false }] }, + { code: "var arr = [ ,,];", options: [{ before: true, after: false }] }, + { code: "var arr = [, ,];", options: [{ before: true, after: false }] }, + { code: "var arr = [,, ];", options: [{ before: true, after: false }] }, + { + code: "var arr = [ , ,];", + options: [{ before: true, after: false }], + }, + { + code: "var arr = [ ,, ];", + options: [{ before: true, after: false }], + }, + { + code: "var arr = [, , ];", + options: [{ before: true, after: false }], + }, + { + code: "var arr = [ , , ];", + options: [{ before: true, after: false }], + }, + { + code: "var arr = [1 , ,];", + options: [{ before: true, after: false }], + }, + { + code: "var arr = [ ,2 ,];", + options: [{ before: true, after: false }], + }, + { + code: "var arr = [,2 , ];", + options: [{ before: true, after: false }], + }, + { + code: "var arr = [ , ,3];", + options: [{ before: true, after: false }], + }, + { + code: "var arr = [1 ,2 ,];", + options: [{ before: true, after: false }], + }, + { + code: "var arr = [ ,2 ,3];", + options: [{ before: true, after: false }], + }, + { + code: "var arr = [1 , ,3];", + options: [{ before: true, after: false }], + }, + { + code: "var arr = [1 ,2 ,3];", + options: [{ before: true, after: false }], + }, + { + code: "var obj = {'foo':'bar' , 'baz':'qur'};", + options: [{ before: true, after: true }], + }, + { + code: "var obj = {'foo':'bar' ,'baz':'qur' , };", + options: [{ before: true, after: false }], + }, + { + code: "var a = 1 , b = 2;", + options: [{ before: true, after: true }], + }, + { code: "var arr = [, ];", options: [{ before: true, after: true }] }, + { code: "var arr = [,,];", options: [{ before: true, after: true }] }, + { code: "var arr = [1 , ];", options: [{ before: true, after: true }] }, + { code: "var arr = [ , 2];", options: [{ before: true, after: true }] }, + { + code: "var arr = [1 , 2];", + options: [{ before: true, after: true }], + }, + { code: "var arr = [, , ];", options: [{ before: true, after: true }] }, + { + code: "var arr = [1 , , ];", + options: [{ before: true, after: true }], + }, + { + code: "var arr = [ , 2 , ];", + options: [{ before: true, after: true }], + }, + { + code: "var arr = [ , , 3];", + options: [{ before: true, after: true }], + }, + { + code: "var arr = [1 , 2 , ];", + options: [{ before: true, after: true }], + }, + { + code: "var arr = [, 2 , 3];", + options: [{ before: true, after: true }], + }, + { + code: "var arr = [1 , , 3];", + options: [{ before: true, after: true }], + }, + { + code: "var arr = [1 , 2 , 3];", + options: [{ before: true, after: true }], + }, + { code: "a , b", options: [{ before: true, after: true }] }, + { code: "var arr = [,];", options: [{ before: false, after: false }] }, + { code: "var arr = [ ,];", options: [{ before: false, after: false }] }, + { code: "var arr = [1,];", options: [{ before: false, after: false }] }, + { code: "var arr = [,2];", options: [{ before: false, after: false }] }, + { + code: "var arr = [ ,2];", + options: [{ before: false, after: false }], + }, + { + code: "var arr = [1,2];", + options: [{ before: false, after: false }], + }, + { code: "var arr = [,,];", options: [{ before: false, after: false }] }, + { + code: "var arr = [ , , ];", + options: [{ before: false, after: false }], + }, + { + code: "var arr = [ ,,];", + options: [{ before: false, after: false }], + }, + { + code: "var arr = [1,,];", + options: [{ before: false, after: false }], + }, + { + code: "var arr = [,2,];", + options: [{ before: false, after: false }], + }, + { + code: "var arr = [ ,2,];", + options: [{ before: false, after: false }], + }, + { + code: "var arr = [,,3];", + options: [{ before: false, after: false }], + }, + { + code: "var arr = [1,2,];", + options: [{ before: false, after: false }], + }, + { + code: "var arr = [,2,3];", + options: [{ before: false, after: false }], + }, + { + code: "var arr = [1,,3];", + options: [{ before: false, after: false }], + }, + { + code: "var arr = [1,2,3];", + options: [{ before: false, after: false }], + }, + { + code: "var a = (1 + 2,2)", + options: [{ before: false, after: false }], + }, + { + code: 'var a; console.log(`${a}`, "a");', + languageOptions: { ecmaVersion: 6 }, + }, + { code: "var [a, b] = [1, 2];", languageOptions: { ecmaVersion: 6 } }, + { code: "var [a, b, ] = [1, 2];", languageOptions: { ecmaVersion: 6 } }, + { code: "var [a, b,] = [1, 2];", languageOptions: { ecmaVersion: 6 } }, + { + code: "var [a, , b] = [1, 2, 3];", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [a,, b] = [1, 2, 3];", + languageOptions: { ecmaVersion: 6 }, + }, + { code: "var [ , b] = a;", languageOptions: { ecmaVersion: 6 } }, + { code: "var [, b] = a;", languageOptions: { ecmaVersion: 6 } }, + { code: "var { a,} = a;", languageOptions: { ecmaVersion: 6 } }, + { + code: "import { a,} from 'mod';", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: ",", + languageOptions: { + ecmaVersion: 6, + parserOptions: { ecmaFeatures: { jsx: true } }, + }, + }, + { + code: " , ", + languageOptions: { + ecmaVersion: 6, + parserOptions: { ecmaFeatures: { jsx: true } }, + }, + }, + { + code: "Hello, world", + options: [{ before: true, after: false }], + languageOptions: { + ecmaVersion: 6, + parserOptions: { ecmaFeatures: { jsx: true } }, + }, + }, - // For backwards compatibility. Ignoring spacing between a comment and comma of a null element was possibly unintentional. - { code: "[a, /**/ , ]", options: [{ before: false, after: true }] }, - { code: "[a , /**/, ]", options: [{ before: true, after: true }] }, - { code: "[a, /**/ , ] = foo", options: [{ before: false, after: true }], languageOptions: { ecmaVersion: 6 } }, - { code: "[a , /**/, ] = foo", options: [{ before: true, after: true }], languageOptions: { ecmaVersion: 6 } } - ], + // For backwards compatibility. Ignoring spacing between a comment and comma of a null element was possibly unintentional. + { code: "[a, /**/ , ]", options: [{ before: false, after: true }] }, + { code: "[a , /**/, ]", options: [{ before: true, after: true }] }, + { + code: "[a, /**/ , ] = foo", + options: [{ before: false, after: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "[a , /**/, ] = foo", + options: [{ before: true, after: true }], + languageOptions: { ecmaVersion: 6 }, + }, + ], - invalid: [ - { - code: "a(b,c)", - output: "a(b , c)", - options: [{ before: true, after: true }], - errors: [ - { - messageId: "missing", - data: { loc: "before" }, - type: "Punctuator" - }, - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "new A(b,c)", - output: "new A(b , c)", - options: [{ before: true, after: true }], - errors: [ - { - messageId: "missing", - data: { loc: "before" }, - type: "Punctuator" - }, - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "var a = 1 ,b = 2;", - output: "var a = 1, b = 2;", - errors: [ - { - message: "There should be no space before ','.", - type: "Punctuator" - }, - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "var arr = [1 , 2];", - output: "var arr = [1, 2];", - errors: [ - { - message: "There should be no space before ','.", - type: "Punctuator" - } - ] - }, - { - code: "var arr = [1 , ];", - output: "var arr = [1, ];", - errors: [ - { - message: "There should be no space before ','.", - type: "Punctuator" - } - ] - }, - { - code: "var arr = [1 ,2];", - output: "var arr = [1, 2];", - errors: [ - { - message: "There should be no space before ','.", - type: "Punctuator" - }, - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "var arr = [(1) , 2];", - output: "var arr = [(1), 2];", - errors: [ - { - message: "There should be no space before ','.", - type: "Punctuator" - } - ] - }, - { - code: "var arr = [1, 2];", - output: "var arr = [1 ,2];", - options: [{ before: true, after: false }], - errors: [ - { - messageId: "missing", - data: { loc: "before" }, - type: "Punctuator" - }, - { - message: "There should be no space after ','.", - type: "Punctuator" - } - ] - }, - { - code: "var arr = [1\n , 2];", - output: "var arr = [1\n ,2];", - options: [{ before: false, after: false }], - errors: [ - { - message: "There should be no space after ','.", - type: "Punctuator" - } - ] - }, - { - code: "var arr = [1,\n 2];", - output: "var arr = [1 ,\n 2];", - options: [{ before: true, after: false }], - errors: [ - { - messageId: "missing", - data: { loc: "before" }, - type: "Punctuator" - } - ] - }, - { - code: "var obj = {'foo':\n'bar', 'baz':\n'qur'};", - output: "var obj = {'foo':\n'bar' ,'baz':\n'qur'};", - options: [{ before: true, after: false }], - errors: [ - { - messageId: "missing", - data: { loc: "before" }, - type: "Punctuator" - }, - { - message: "There should be no space after ','.", - type: "Punctuator" - } - ] - }, - { - code: "var obj = {a: 1\n ,b: 2};", - output: "var obj = {a: 1\n , b: 2};", - options: [{ before: false, after: true }], - errors: [ - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "var obj = {a: 1 ,\n b: 2};", - output: "var obj = {a: 1,\n b: 2};", - options: [{ before: false, after: false }], - errors: [ - { - message: "There should be no space before ','.", - type: "Punctuator" - } - ] - }, - { - code: "var arr = [1 ,2];", - output: "var arr = [1 , 2];", - options: [{ before: true, after: true }], - errors: [ - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "var arr = [1,2];", - output: "var arr = [1 , 2];", - options: [{ before: true, after: true }], - errors: [ - { - messageId: "missing", - data: { loc: "before" }, - type: "Punctuator" - }, - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "var obj = {'foo':\n'bar','baz':\n'qur'};", - output: "var obj = {'foo':\n'bar' , 'baz':\n'qur'};", - options: [{ before: true, after: true }], - errors: [ - { - messageId: "missing", - data: { loc: "before" }, - type: "Punctuator" - }, - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "var arr = [1 , 2];", - output: "var arr = [1,2];", - options: [{ before: false, after: false }], - errors: [ - { - message: "There should be no space before ','.", - type: "Punctuator" - }, - { - message: "There should be no space after ','.", - type: "Punctuator" - } - ] - }, - { - code: "a ,b", - output: "a, b", - options: [{ before: false, after: true }], - errors: [ - { - message: "There should be no space before ','.", - type: "Punctuator" - }, - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "function foo(a,b){}", - output: "function foo(a , b){}", - options: [{ before: true, after: true }], - errors: [ - { - messageId: "missing", - data: { loc: "before" }, - type: "Punctuator" - }, - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "var foo = (a,b) => {}", - output: "var foo = (a , b) => {}", - options: [{ before: true, after: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missing", - data: { loc: "before" }, - type: "Punctuator" - }, - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "var foo = (a = 1,b) => {}", - output: "var foo = (a = 1 , b) => {}", - options: [{ before: true, after: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missing", - data: { loc: "before" }, - type: "Punctuator" - }, - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "function foo(a = 1 ,b = 2) {}", - output: "function foo(a = 1, b = 2) {}", - options: [{ before: false, after: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - message: "There should be no space before ','.", - type: "Punctuator" - }, - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "{foo(1 ,2)}", - output: "{foo(1, 2)}", - languageOptions: { ecmaVersion: 6, parserOptions: { ecmaFeatures: { jsx: true } } }, - errors: [ - { - message: "There should be no space before ','.", - type: "Punctuator" - }, - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "myfunc(404, true/* bla bla bla */ , 'hello');", - output: "myfunc(404, true/* bla bla bla */, 'hello');", - errors: [ - { - message: "There should be no space before ','.", - type: "Punctuator" - } - ] - }, - { - code: "myfunc(404, true,/* bla bla bla */ 'hello');", - output: "myfunc(404, true, /* bla bla bla */ 'hello');", - errors: [ - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - }, - { - code: "myfunc(404,// comment\n true, 'hello');", - output: "myfunc(404, // comment\n true, 'hello');", - errors: [ - { - messageId: "missing", - data: { loc: "after" }, - type: "Punctuator" - } - ] - } - ] + invalid: [ + { + code: "a(b,c)", + output: "a(b , c)", + options: [{ before: true, after: true }], + errors: [ + { + messageId: "missing", + data: { loc: "before" }, + type: "Punctuator", + }, + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "new A(b,c)", + output: "new A(b , c)", + options: [{ before: true, after: true }], + errors: [ + { + messageId: "missing", + data: { loc: "before" }, + type: "Punctuator", + }, + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "var a = 1 ,b = 2;", + output: "var a = 1, b = 2;", + errors: [ + { + message: "There should be no space before ','.", + type: "Punctuator", + }, + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "var arr = [1 , 2];", + output: "var arr = [1, 2];", + errors: [ + { + message: "There should be no space before ','.", + type: "Punctuator", + }, + ], + }, + { + code: "var arr = [1 , ];", + output: "var arr = [1, ];", + errors: [ + { + message: "There should be no space before ','.", + type: "Punctuator", + }, + ], + }, + { + code: "var arr = [1 ,2];", + output: "var arr = [1, 2];", + errors: [ + { + message: "There should be no space before ','.", + type: "Punctuator", + }, + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "var arr = [(1) , 2];", + output: "var arr = [(1), 2];", + errors: [ + { + message: "There should be no space before ','.", + type: "Punctuator", + }, + ], + }, + { + code: "var arr = [1, 2];", + output: "var arr = [1 ,2];", + options: [{ before: true, after: false }], + errors: [ + { + messageId: "missing", + data: { loc: "before" }, + type: "Punctuator", + }, + { + message: "There should be no space after ','.", + type: "Punctuator", + }, + ], + }, + { + code: "var arr = [1\n , 2];", + output: "var arr = [1\n ,2];", + options: [{ before: false, after: false }], + errors: [ + { + message: "There should be no space after ','.", + type: "Punctuator", + }, + ], + }, + { + code: "var arr = [1,\n 2];", + output: "var arr = [1 ,\n 2];", + options: [{ before: true, after: false }], + errors: [ + { + messageId: "missing", + data: { loc: "before" }, + type: "Punctuator", + }, + ], + }, + { + code: "var obj = {'foo':\n'bar', 'baz':\n'qur'};", + output: "var obj = {'foo':\n'bar' ,'baz':\n'qur'};", + options: [{ before: true, after: false }], + errors: [ + { + messageId: "missing", + data: { loc: "before" }, + type: "Punctuator", + }, + { + message: "There should be no space after ','.", + type: "Punctuator", + }, + ], + }, + { + code: "var obj = {a: 1\n ,b: 2};", + output: "var obj = {a: 1\n , b: 2};", + options: [{ before: false, after: true }], + errors: [ + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "var obj = {a: 1 ,\n b: 2};", + output: "var obj = {a: 1,\n b: 2};", + options: [{ before: false, after: false }], + errors: [ + { + message: "There should be no space before ','.", + type: "Punctuator", + }, + ], + }, + { + code: "var arr = [1 ,2];", + output: "var arr = [1 , 2];", + options: [{ before: true, after: true }], + errors: [ + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "var arr = [1,2];", + output: "var arr = [1 , 2];", + options: [{ before: true, after: true }], + errors: [ + { + messageId: "missing", + data: { loc: "before" }, + type: "Punctuator", + }, + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "var obj = {'foo':\n'bar','baz':\n'qur'};", + output: "var obj = {'foo':\n'bar' , 'baz':\n'qur'};", + options: [{ before: true, after: true }], + errors: [ + { + messageId: "missing", + data: { loc: "before" }, + type: "Punctuator", + }, + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "var arr = [1 , 2];", + output: "var arr = [1,2];", + options: [{ before: false, after: false }], + errors: [ + { + message: "There should be no space before ','.", + type: "Punctuator", + }, + { + message: "There should be no space after ','.", + type: "Punctuator", + }, + ], + }, + { + code: "a ,b", + output: "a, b", + options: [{ before: false, after: true }], + errors: [ + { + message: "There should be no space before ','.", + type: "Punctuator", + }, + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "function foo(a,b){}", + output: "function foo(a , b){}", + options: [{ before: true, after: true }], + errors: [ + { + messageId: "missing", + data: { loc: "before" }, + type: "Punctuator", + }, + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "var foo = (a,b) => {}", + output: "var foo = (a , b) => {}", + options: [{ before: true, after: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missing", + data: { loc: "before" }, + type: "Punctuator", + }, + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "var foo = (a = 1,b) => {}", + output: "var foo = (a = 1 , b) => {}", + options: [{ before: true, after: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missing", + data: { loc: "before" }, + type: "Punctuator", + }, + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "function foo(a = 1 ,b = 2) {}", + output: "function foo(a = 1, b = 2) {}", + options: [{ before: false, after: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: "There should be no space before ','.", + type: "Punctuator", + }, + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "{foo(1 ,2)}", + output: "{foo(1, 2)}", + languageOptions: { + ecmaVersion: 6, + parserOptions: { ecmaFeatures: { jsx: true } }, + }, + errors: [ + { + message: "There should be no space before ','.", + type: "Punctuator", + }, + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "myfunc(404, true/* bla bla bla */ , 'hello');", + output: "myfunc(404, true/* bla bla bla */, 'hello');", + errors: [ + { + message: "There should be no space before ','.", + type: "Punctuator", + }, + ], + }, + { + code: "myfunc(404, true,/* bla bla bla */ 'hello');", + output: "myfunc(404, true, /* bla bla bla */ 'hello');", + errors: [ + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + { + code: "myfunc(404,// comment\n true, 'hello');", + output: "myfunc(404, // comment\n true, 'hello');", + errors: [ + { + messageId: "missing", + data: { loc: "after" }, + type: "Punctuator", + }, + ], + }, + ], }); diff --git a/tests/lib/rules/comma-style.js b/tests/lib/rules/comma-style.js index e8835bb6e838..032392d33c5b 100644 --- a/tests/lib/rules/comma-style.js +++ b/tests/lib/rules/comma-style.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/comma-style"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,651 +18,815 @@ const rule = require("../../../lib/rules/comma-style"), const ruleTester = new RuleTester(); ruleTester.run("comma-style", rule, { + valid: [ + "var foo = 1, bar = 3;", + "var foo = {'a': 1, 'b': 2};", + "var foo = [1, 2];", + "var foo = [, 2];", + "var foo = [1, ];", + "var foo = ['apples', \n 'oranges'];", + "var foo = {'a': 1, \n 'b': 2, \n'c': 3};", + "var foo = {'a': 1, \n 'b': 2, 'c':\n 3};", + "var foo = {'a': 1, \n 'b': 2, 'c': [{'d': 1}, \n {'e': 2}, \n {'f': 3}]};", + "var foo = [1, \n2, \n3];", + "function foo(){var a=[1,\n 2]}", + "function foo(){return {'a': 1,\n'b': 2}}", + "var foo = \n1, \nbar = \n2;", + "var foo = [\n(bar),\nbaz\n];", + "var foo = [\n(bar\n),\nbaz\n];", + "var foo = [\n(\nbar\n),\nbaz\n];", + "new Foo(a\n,b);", + { code: "var foo = [\n(bar\n)\n,baz\n];", options: ["first"] }, + "var foo = \n1, \nbar = [1,\n2,\n3]", + { code: "var foo = ['apples'\n,'oranges'];", options: ["first"] }, + { code: "var foo = 1, bar = 2;", options: ["first"] }, + { code: "var foo = 1 \n ,bar = 2;", options: ["first"] }, + { + code: "var foo = {'a': 1 \n ,'b': 2 \n,'c': 3};", + options: ["first"], + }, + { code: "var foo = [1 \n ,2 \n, 3];", options: ["first"] }, + { + code: "function foo(){return {'a': 1\n,'b': 2}}", + options: ["first"], + }, + { code: "function foo(){var a=[1\n, 2]}", options: ["first"] }, + { code: "new Foo(a,\nb);", options: ["first"] }, + "f(1\n, 2);", + "function foo(a\n, b) { return a + b; }", + { + code: "var a = 'a',\no = 'o';", + options: ["first", { exceptions: { VariableDeclaration: true } }], + }, + { + code: "var arr = ['a',\n'o'];", + options: ["first", { exceptions: { ArrayExpression: true } }], + }, + { + code: "var obj = {a: 'a',\nb: 'b'};", + options: ["first", { exceptions: { ObjectExpression: true } }], + }, + { + code: "var a = 'a',\no = 'o',\narr = [1,\n2];", + options: [ + "first", + { + exceptions: { + VariableDeclaration: true, + ArrayExpression: true, + }, + }, + ], + }, + { + code: "var ar ={fst:1,\nsnd: [1,\n2]};", + options: [ + "first", + { + exceptions: { + ArrayExpression: true, + ObjectExpression: true, + }, + }, + ], + }, + { + code: "var a = 'a',\nar ={fst:1,\nsnd: [1,\n2]};", + options: [ + "first", + { + exceptions: { + ArrayExpression: true, + ObjectExpression: true, + VariableDeclaration: true, + }, + }, + ], + }, + { + code: "const foo = (a\n, b) => { return a + b; }", + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "function foo([a\n, b]) { return a + b; }", + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "const foo = ([a\n, b]) => { return a + b; }", + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "import { a\n, b } from './source';", + languageOptions: { + ecmaVersion: 6, + sourceType: "module", + }, + }, + { + code: "const foo = function (a\n, b) { return a + b; }", + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "var {foo\n, bar} = {foo:'apples', bar:'oranges'};", + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "var {foo\n, bar} = {foo:'apples', bar:'oranges'};", + options: [ + "first", + { + exceptions: { + ObjectPattern: true, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "new Foo(a,\nb);", + options: [ + "first", + { + exceptions: { + NewExpression: true, + }, + }, + ], + }, + { + code: "f(1\n, 2);", + options: [ + "last", + { + exceptions: { + CallExpression: true, + }, + }, + ], + }, + { + code: "function foo(a\n, b) { return a + b; }", + options: [ + "last", + { + exceptions: { + FunctionDeclaration: true, + }, + }, + ], + }, + { + code: "const foo = function (a\n, b) { return a + b; }", + options: [ + "last", + { + exceptions: { + FunctionExpression: true, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "function foo([a\n, b]) { return a + b; }", + options: [ + "last", + { + exceptions: { + ArrayPattern: true, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "const foo = (a\n, b) => { return a + b; }", + options: [ + "last", + { + exceptions: { + ArrowFunctionExpression: true, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "const foo = ([a\n, b]) => { return a + b; }", + options: [ + "last", + { + exceptions: { + ArrayPattern: true, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "import { a\n, b } from './source';", + options: [ + "last", + { + exceptions: { + ImportDeclaration: true, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + sourceType: "module", + }, + }, + { + code: "var {foo\n, bar} = {foo:'apples', bar:'oranges'};", + options: [ + "last", + { + exceptions: { + ObjectPattern: true, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "new Foo(a,\nb);", + options: [ + "last", + { + exceptions: { + NewExpression: false, + }, + }, + ], + }, + { + code: "new Foo(a\n,b);", + options: [ + "last", + { + exceptions: { + NewExpression: true, + }, + }, + ], + }, + "var foo = [\n , \n 1, \n 2 \n];", + { + code: "const [\n , \n , \n a, \n b, \n] = arr;", + options: [ + "last", + { + exceptions: { + ArrayPattern: false, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "const [\n ,, \n a, \n b, \n] = arr;", + options: [ + "last", + { + exceptions: { + ArrayPattern: false, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "const arr = [\n 1 \n , \n ,2 \n]", + options: ["first"], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "const arr = [\n ,'fifi' \n]", + options: ["first"], + languageOptions: { + ecmaVersion: 6, + }, + }, + ], - valid: [ - "var foo = 1, bar = 3;", - "var foo = {'a': 1, 'b': 2};", - "var foo = [1, 2];", - "var foo = [, 2];", - "var foo = [1, ];", - "var foo = ['apples', \n 'oranges'];", - "var foo = {'a': 1, \n 'b': 2, \n'c': 3};", - "var foo = {'a': 1, \n 'b': 2, 'c':\n 3};", - "var foo = {'a': 1, \n 'b': 2, 'c': [{'d': 1}, \n {'e': 2}, \n {'f': 3}]};", - "var foo = [1, \n2, \n3];", - "function foo(){var a=[1,\n 2]}", - "function foo(){return {'a': 1,\n'b': 2}}", - "var foo = \n1, \nbar = \n2;", - "var foo = [\n(bar),\nbaz\n];", - "var foo = [\n(bar\n),\nbaz\n];", - "var foo = [\n(\nbar\n),\nbaz\n];", - "new Foo(a\n,b);", - { code: "var foo = [\n(bar\n)\n,baz\n];", options: ["first"] }, - "var foo = \n1, \nbar = [1,\n2,\n3]", - { code: "var foo = ['apples'\n,'oranges'];", options: ["first"] }, - { code: "var foo = 1, bar = 2;", options: ["first"] }, - { code: "var foo = 1 \n ,bar = 2;", options: ["first"] }, - { code: "var foo = {'a': 1 \n ,'b': 2 \n,'c': 3};", options: ["first"] }, - { code: "var foo = [1 \n ,2 \n, 3];", options: ["first"] }, - { code: "function foo(){return {'a': 1\n,'b': 2}}", options: ["first"] }, - { code: "function foo(){var a=[1\n, 2]}", options: ["first"] }, - { code: "new Foo(a,\nb);", options: ["first"] }, - "f(1\n, 2);", - "function foo(a\n, b) { return a + b; }", - { - code: "var a = 'a',\no = 'o';", - options: ["first", { exceptions: { VariableDeclaration: true } }] - }, - { - code: "var arr = ['a',\n'o'];", - options: ["first", { exceptions: { ArrayExpression: true } }] - }, - { - code: "var obj = {a: 'a',\nb: 'b'};", - options: ["first", { exceptions: { ObjectExpression: true } }] - }, - { - code: "var a = 'a',\no = 'o',\narr = [1,\n2];", - options: ["first", { exceptions: { VariableDeclaration: true, ArrayExpression: true } }] - }, - { - code: "var ar ={fst:1,\nsnd: [1,\n2]};", - options: ["first", { exceptions: { ArrayExpression: true, ObjectExpression: true } }] - }, - { - code: "var a = 'a',\nar ={fst:1,\nsnd: [1,\n2]};", - options: ["first", { - exceptions: { - ArrayExpression: true, - ObjectExpression: true, - VariableDeclaration: true - } - }] - }, - { - code: "const foo = (a\n, b) => { return a + b; }", - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "function foo([a\n, b]) { return a + b; }", - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "const foo = ([a\n, b]) => { return a + b; }", - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "import { a\n, b } from './source';", - languageOptions: { - ecmaVersion: 6, - sourceType: "module" - } - }, - { - code: "const foo = function (a\n, b) { return a + b; }", - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "var {foo\n, bar} = {foo:'apples', bar:'oranges'};", - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "var {foo\n, bar} = {foo:'apples', bar:'oranges'};", - options: ["first", { - exceptions: { - ObjectPattern: true - } - }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "new Foo(a,\nb);", - options: ["first", { - exceptions: { - NewExpression: true - } - }] - }, - { - code: "f(1\n, 2);", - options: ["last", { - exceptions: { - CallExpression: true - } - }] - }, - { - code: "function foo(a\n, b) { return a + b; }", - options: ["last", { - exceptions: { - FunctionDeclaration: true - } - }] - }, - { - code: "const foo = function (a\n, b) { return a + b; }", - options: ["last", { - exceptions: { - FunctionExpression: true - } - }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "function foo([a\n, b]) { return a + b; }", - options: ["last", { - exceptions: { - ArrayPattern: true - } - }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "const foo = (a\n, b) => { return a + b; }", - options: ["last", { - exceptions: { - ArrowFunctionExpression: true - } - }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "const foo = ([a\n, b]) => { return a + b; }", - options: ["last", { - exceptions: { - ArrayPattern: true - } - }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "import { a\n, b } from './source';", - options: ["last", { - exceptions: { - ImportDeclaration: true - } - }], - languageOptions: { - ecmaVersion: 6, - sourceType: "module" - } - }, - { - code: "var {foo\n, bar} = {foo:'apples', bar:'oranges'};", - options: ["last", { - exceptions: { - ObjectPattern: true - } - }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "new Foo(a,\nb);", - options: ["last", { - exceptions: { - NewExpression: false - } - }] - }, - { - code: "new Foo(a\n,b);", - options: ["last", { - exceptions: { - NewExpression: true - } - }] - }, - "var foo = [\n , \n 1, \n 2 \n];", - { - code: "const [\n , \n , \n a, \n b, \n] = arr;", - options: ["last", { - exceptions: { - ArrayPattern: false - } - }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "const [\n ,, \n a, \n b, \n] = arr;", - options: ["last", { - exceptions: { - ArrayPattern: false - } - }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "const arr = [\n 1 \n , \n ,2 \n]", - options: ["first"], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "const arr = [\n ,'fifi' \n]", - options: ["first"], - languageOptions: { - ecmaVersion: 6 - } - } - - ], - - invalid: [ - { - code: "var foo = { a: 1. //comment \n, b: 2\n}", - output: "var foo = { a: 1., //comment \n b: 2\n}", - errors: [{ - messageId: "expectedCommaLast", - type: "Property" - }] - }, - { - code: "var foo = { a: 1. //comment \n //comment1 \n //comment2 \n, b: 2\n}", - output: "var foo = { a: 1., //comment \n //comment1 \n //comment2 \n b: 2\n}", - errors: [{ - messageId: "expectedCommaLast", - type: "Property" - }] - }, - { - code: "var foo = 1\n,\nbar = 2;", - output: "var foo = 1,\nbar = 2;", - errors: [{ - messageId: "unexpectedLineBeforeAndAfterComma", - type: "VariableDeclarator" - }] - }, - { - code: "var foo = 1 //comment\n,\nbar = 2;", - output: "var foo = 1, //comment\nbar = 2;", - errors: [{ - messageId: "unexpectedLineBeforeAndAfterComma", - type: "VariableDeclarator" - }] - }, - { - code: "var foo = 1 //comment\n, // comment 2\nbar = 2;", - output: "var foo = 1, //comment // comment 2\nbar = 2;", - errors: [{ - messageId: "unexpectedLineBeforeAndAfterComma", - type: "VariableDeclarator" - }] - }, - { - code: "new Foo(a\n,\nb);", - output: "new Foo(a,\nb);", - options: ["last", { - exceptions: { - NewExpression: false - } - }], - errors: [{ messageId: "unexpectedLineBeforeAndAfterComma" }] - }, - { - code: "var foo = 1\n,bar = 2;", - output: "var foo = 1,\nbar = 2;", - errors: [{ - messageId: "expectedCommaLast", - type: "VariableDeclarator", - column: 1, - endColumn: 2 - }] - }, - { - code: "f([1,2\n,3]);", - output: "f([1,2,\n3]);", - errors: [{ - messageId: "expectedCommaLast", - type: "Literal" - }] - }, - { - code: "f([1,2\n,]);", - output: "f([1,2,\n]);", - errors: [{ - messageId: "expectedCommaLast", - type: "Punctuator" - }] - }, - { - code: "f([,2\n,3]);", - output: "f([,2,\n3]);", - errors: [{ - messageId: "expectedCommaLast", - type: "Literal" - }] - }, - { - code: "var foo = ['apples'\n, 'oranges'];", - output: "var foo = ['apples',\n 'oranges'];", - errors: [{ - messageId: "expectedCommaLast", - type: "Literal" - }] - }, - { - code: "var [foo\n, bar] = ['apples', 'oranges'];", - output: "var [foo,\n bar] = ['apples', 'oranges'];", - options: ["last", { - exceptions: { - ArrayPattern: false - } - }], - languageOptions: { - ecmaVersion: 6 - }, - errors: [{ - messageId: "expectedCommaLast", - type: "Identifier" - }] - }, - { - code: "f(1\n, 2);", - output: "f(1,\n 2);", - options: ["last", { - exceptions: { - CallExpression: false - } - }], - errors: [{ - messageId: "expectedCommaLast", - type: "Literal" - }] - }, - { - code: "function foo(a\n, b) { return a + b; }", - output: "function foo(a,\n b) { return a + b; }", - options: ["last", { - exceptions: { - FunctionDeclaration: false - } - }], - errors: [{ - messageId: "expectedCommaLast", - type: "Identifier" - }] - }, - { - code: "const foo = function (a\n, b) { return a + b; }", - output: "const foo = function (a,\n b) { return a + b; }", - options: ["last", { - exceptions: { - FunctionExpression: false - } - }], - languageOptions: { - ecmaVersion: 6, - sourceType: "module" - }, - errors: [{ - messageId: "expectedCommaLast", - type: "Identifier" - }] - }, - { - code: "function foo([a\n, b]) { return a + b; }", - output: "function foo([a,\n b]) { return a + b; }", - options: ["last", { - exceptions: { - ArrayPattern: false - } - }], - languageOptions: { - ecmaVersion: 6 - }, - errors: [{ - messageId: "expectedCommaLast", - type: "Identifier" - }] - }, - { - code: "const foo = (a\n, b) => { return a + b; }", - output: "const foo = (a,\n b) => { return a + b; }", - options: ["last", { - exceptions: { - ArrowFunctionExpression: false - } - }], - languageOptions: { - ecmaVersion: 6 - }, - errors: [{ - messageId: "expectedCommaLast", - type: "Identifier" - }] - }, - { - code: "const foo = ([a\n, b]) => { return a + b; }", - output: "const foo = ([a,\n b]) => { return a + b; }", - options: ["last", { - exceptions: { - ArrayPattern: false - } - }], - languageOptions: { - ecmaVersion: 6 - }, - errors: [{ - messageId: "expectedCommaLast", - type: "Identifier" - }] - }, - { - code: "import { a\n, b } from './source';", - output: "import { a,\n b } from './source';", - options: ["last", { - exceptions: { - ImportDeclaration: false - } - }], - languageOptions: { - ecmaVersion: 6, - sourceType: "module" - }, - errors: [{ - messageId: "expectedCommaLast", - type: "ImportSpecifier" - }] - }, - { - code: "var {foo\n, bar} = {foo:'apples', bar:'oranges'};", - output: "var {foo,\n bar} = {foo:'apples', bar:'oranges'};", - options: ["last", { - exceptions: { - ObjectPattern: false - } - }], - languageOptions: { - ecmaVersion: 6 - }, - errors: [{ - messageId: "expectedCommaLast", - type: "Property" - }] - }, - { - code: "var foo = 1,\nbar = 2;", - output: "var foo = 1\n,bar = 2;", - options: ["first"], - errors: [{ - messageId: "expectedCommaFirst", - type: "VariableDeclarator", - column: 12, - endColumn: 13 - }] - }, - { - code: "f([1,\n2,3]);", - output: "f([1\n,2,3]);", - options: ["first"], - errors: [{ - messageId: "expectedCommaFirst", - type: "Literal" - }] - }, - { - code: "var foo = ['apples', \n 'oranges'];", - output: "var foo = ['apples' \n ,'oranges'];", - options: ["first"], - errors: [{ - messageId: "expectedCommaFirst", - type: "Literal" - }] - }, - { - code: "var foo = {'a': 1, \n 'b': 2\n ,'c': 3};", - output: "var foo = {'a': 1 \n ,'b': 2\n ,'c': 3};", - options: ["first"], - errors: [{ - messageId: "expectedCommaFirst", - type: "Property" - }] - }, - { - code: "var a = 'a',\no = 'o',\narr = [1,\n2];", - output: "var a = 'a',\no = 'o',\narr = [1\n,2];", - options: ["first", { exceptions: { VariableDeclaration: true } }], - errors: [{ - messageId: "expectedCommaFirst", - type: "Literal" - }] - }, - { - code: "var a = 'a',\nobj = {a: 'a',\nb: 'b'};", - output: "var a = 'a',\nobj = {a: 'a'\n,b: 'b'};", - options: ["first", { exceptions: { VariableDeclaration: true } }], - errors: [{ - messageId: "expectedCommaFirst", - type: "Property" - }] - }, - { - code: "var a = 'a',\nobj = {a: 'a',\nb: 'b'};", - output: "var a = 'a'\n,obj = {a: 'a',\nb: 'b'};", - options: ["first", { exceptions: { ObjectExpression: true } }], - errors: [{ - messageId: "expectedCommaFirst", - type: "VariableDeclarator" - }] - }, - { - code: "var a = 'a',\narr = [1,\n2];", - output: "var a = 'a'\n,arr = [1,\n2];", - options: ["first", { exceptions: { ArrayExpression: true } }], - errors: [{ - messageId: "expectedCommaFirst", - type: "VariableDeclarator" - }] - }, - { - code: "var ar =[1,\n{a: 'a',\nb: 'b'}];", - output: "var ar =[1,\n{a: 'a'\n,b: 'b'}];", - options: ["first", { exceptions: { ArrayExpression: true } }], - errors: [{ - messageId: "expectedCommaFirst", - type: "Property" - }] - }, - { - code: "var ar =[1,\n{a: 'a',\nb: 'b'}];", - output: "var ar =[1\n,{a: 'a',\nb: 'b'}];", - options: ["first", { exceptions: { ObjectExpression: true } }], - errors: [{ - messageId: "expectedCommaFirst", - type: "ObjectExpression" - }] - }, - { - code: "var ar ={fst:1,\nsnd: [1,\n2]};", - output: "var ar ={fst:1,\nsnd: [1\n,2]};", - options: ["first", { exceptions: { ObjectExpression: true } }], - errors: [{ - messageId: "expectedCommaFirst", - type: "Literal" - }] - }, - { - code: "var ar ={fst:1,\nsnd: [1,\n2]};", - output: "var ar ={fst:1\n,snd: [1,\n2]};", - options: ["first", { exceptions: { ArrayExpression: true } }], - errors: [{ - messageId: "expectedCommaFirst", - type: "Property" - }] - }, - { - code: "new Foo(a,\nb);", - output: "new Foo(a\n,b);", - options: ["first", { - exceptions: { - NewExpression: false - } - }], - errors: [{ messageId: "expectedCommaFirst" }] - }, - { - code: "var foo = [\n(bar\n)\n,\nbaz\n];", - output: "var foo = [\n(bar\n),\nbaz\n];", - errors: [{ - messageId: "unexpectedLineBeforeAndAfterComma", - type: "Identifier", - column: 1, - endColumn: 2 - }] - }, - { - code: "[(foo),\n,\nbar]", - output: "[(foo),,\nbar]", - errors: [{ messageId: "unexpectedLineBeforeAndAfterComma" }] - }, - { - code: "new Foo(a\n,b);", - output: "new Foo(a,\nb);", - options: ["last", { - exceptions: { - NewExpression: false - } - }], - errors: [{ messageId: "expectedCommaLast" }] - }, - { - code: "[\n[foo(3)],\n,\nbar\n];", - output: "[\n[foo(3)],,\nbar\n];", - errors: [{ messageId: "unexpectedLineBeforeAndAfterComma" }] - }, - { - - // https://github.com/eslint/eslint/issues/10632 - code: "[foo//\n,/*block\ncomment*/];", - output: "[foo,//\n/*block\ncomment*/];", - errors: [{ messageId: "unexpectedLineBeforeAndAfterComma" }] - } - ] + invalid: [ + { + code: "var foo = { a: 1. //comment \n, b: 2\n}", + output: "var foo = { a: 1., //comment \n b: 2\n}", + errors: [ + { + messageId: "expectedCommaLast", + type: "Property", + }, + ], + }, + { + code: "var foo = { a: 1. //comment \n //comment1 \n //comment2 \n, b: 2\n}", + output: "var foo = { a: 1., //comment \n //comment1 \n //comment2 \n b: 2\n}", + errors: [ + { + messageId: "expectedCommaLast", + type: "Property", + }, + ], + }, + { + code: "var foo = 1\n,\nbar = 2;", + output: "var foo = 1,\nbar = 2;", + errors: [ + { + messageId: "unexpectedLineBeforeAndAfterComma", + type: "VariableDeclarator", + }, + ], + }, + { + code: "var foo = 1 //comment\n,\nbar = 2;", + output: "var foo = 1, //comment\nbar = 2;", + errors: [ + { + messageId: "unexpectedLineBeforeAndAfterComma", + type: "VariableDeclarator", + }, + ], + }, + { + code: "var foo = 1 //comment\n, // comment 2\nbar = 2;", + output: "var foo = 1, //comment // comment 2\nbar = 2;", + errors: [ + { + messageId: "unexpectedLineBeforeAndAfterComma", + type: "VariableDeclarator", + }, + ], + }, + { + code: "new Foo(a\n,\nb);", + output: "new Foo(a,\nb);", + options: [ + "last", + { + exceptions: { + NewExpression: false, + }, + }, + ], + errors: [{ messageId: "unexpectedLineBeforeAndAfterComma" }], + }, + { + code: "var foo = 1\n,bar = 2;", + output: "var foo = 1,\nbar = 2;", + errors: [ + { + messageId: "expectedCommaLast", + type: "VariableDeclarator", + column: 1, + endColumn: 2, + }, + ], + }, + { + code: "f([1,2\n,3]);", + output: "f([1,2,\n3]);", + errors: [ + { + messageId: "expectedCommaLast", + type: "Literal", + }, + ], + }, + { + code: "f([1,2\n,]);", + output: "f([1,2,\n]);", + errors: [ + { + messageId: "expectedCommaLast", + type: "Punctuator", + }, + ], + }, + { + code: "f([,2\n,3]);", + output: "f([,2,\n3]);", + errors: [ + { + messageId: "expectedCommaLast", + type: "Literal", + }, + ], + }, + { + code: "var foo = ['apples'\n, 'oranges'];", + output: "var foo = ['apples',\n 'oranges'];", + errors: [ + { + messageId: "expectedCommaLast", + type: "Literal", + }, + ], + }, + { + code: "var [foo\n, bar] = ['apples', 'oranges'];", + output: "var [foo,\n bar] = ['apples', 'oranges'];", + options: [ + "last", + { + exceptions: { + ArrayPattern: false, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + }, + errors: [ + { + messageId: "expectedCommaLast", + type: "Identifier", + }, + ], + }, + { + code: "f(1\n, 2);", + output: "f(1,\n 2);", + options: [ + "last", + { + exceptions: { + CallExpression: false, + }, + }, + ], + errors: [ + { + messageId: "expectedCommaLast", + type: "Literal", + }, + ], + }, + { + code: "function foo(a\n, b) { return a + b; }", + output: "function foo(a,\n b) { return a + b; }", + options: [ + "last", + { + exceptions: { + FunctionDeclaration: false, + }, + }, + ], + errors: [ + { + messageId: "expectedCommaLast", + type: "Identifier", + }, + ], + }, + { + code: "const foo = function (a\n, b) { return a + b; }", + output: "const foo = function (a,\n b) { return a + b; }", + options: [ + "last", + { + exceptions: { + FunctionExpression: false, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + sourceType: "module", + }, + errors: [ + { + messageId: "expectedCommaLast", + type: "Identifier", + }, + ], + }, + { + code: "function foo([a\n, b]) { return a + b; }", + output: "function foo([a,\n b]) { return a + b; }", + options: [ + "last", + { + exceptions: { + ArrayPattern: false, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + }, + errors: [ + { + messageId: "expectedCommaLast", + type: "Identifier", + }, + ], + }, + { + code: "const foo = (a\n, b) => { return a + b; }", + output: "const foo = (a,\n b) => { return a + b; }", + options: [ + "last", + { + exceptions: { + ArrowFunctionExpression: false, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + }, + errors: [ + { + messageId: "expectedCommaLast", + type: "Identifier", + }, + ], + }, + { + code: "const foo = ([a\n, b]) => { return a + b; }", + output: "const foo = ([a,\n b]) => { return a + b; }", + options: [ + "last", + { + exceptions: { + ArrayPattern: false, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + }, + errors: [ + { + messageId: "expectedCommaLast", + type: "Identifier", + }, + ], + }, + { + code: "import { a\n, b } from './source';", + output: "import { a,\n b } from './source';", + options: [ + "last", + { + exceptions: { + ImportDeclaration: false, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + sourceType: "module", + }, + errors: [ + { + messageId: "expectedCommaLast", + type: "ImportSpecifier", + }, + ], + }, + { + code: "var {foo\n, bar} = {foo:'apples', bar:'oranges'};", + output: "var {foo,\n bar} = {foo:'apples', bar:'oranges'};", + options: [ + "last", + { + exceptions: { + ObjectPattern: false, + }, + }, + ], + languageOptions: { + ecmaVersion: 6, + }, + errors: [ + { + messageId: "expectedCommaLast", + type: "Property", + }, + ], + }, + { + code: "var foo = 1,\nbar = 2;", + output: "var foo = 1\n,bar = 2;", + options: ["first"], + errors: [ + { + messageId: "expectedCommaFirst", + type: "VariableDeclarator", + column: 12, + endColumn: 13, + }, + ], + }, + { + code: "f([1,\n2,3]);", + output: "f([1\n,2,3]);", + options: ["first"], + errors: [ + { + messageId: "expectedCommaFirst", + type: "Literal", + }, + ], + }, + { + code: "var foo = ['apples', \n 'oranges'];", + output: "var foo = ['apples' \n ,'oranges'];", + options: ["first"], + errors: [ + { + messageId: "expectedCommaFirst", + type: "Literal", + }, + ], + }, + { + code: "var foo = {'a': 1, \n 'b': 2\n ,'c': 3};", + output: "var foo = {'a': 1 \n ,'b': 2\n ,'c': 3};", + options: ["first"], + errors: [ + { + messageId: "expectedCommaFirst", + type: "Property", + }, + ], + }, + { + code: "var a = 'a',\no = 'o',\narr = [1,\n2];", + output: "var a = 'a',\no = 'o',\narr = [1\n,2];", + options: ["first", { exceptions: { VariableDeclaration: true } }], + errors: [ + { + messageId: "expectedCommaFirst", + type: "Literal", + }, + ], + }, + { + code: "var a = 'a',\nobj = {a: 'a',\nb: 'b'};", + output: "var a = 'a',\nobj = {a: 'a'\n,b: 'b'};", + options: ["first", { exceptions: { VariableDeclaration: true } }], + errors: [ + { + messageId: "expectedCommaFirst", + type: "Property", + }, + ], + }, + { + code: "var a = 'a',\nobj = {a: 'a',\nb: 'b'};", + output: "var a = 'a'\n,obj = {a: 'a',\nb: 'b'};", + options: ["first", { exceptions: { ObjectExpression: true } }], + errors: [ + { + messageId: "expectedCommaFirst", + type: "VariableDeclarator", + }, + ], + }, + { + code: "var a = 'a',\narr = [1,\n2];", + output: "var a = 'a'\n,arr = [1,\n2];", + options: ["first", { exceptions: { ArrayExpression: true } }], + errors: [ + { + messageId: "expectedCommaFirst", + type: "VariableDeclarator", + }, + ], + }, + { + code: "var ar =[1,\n{a: 'a',\nb: 'b'}];", + output: "var ar =[1,\n{a: 'a'\n,b: 'b'}];", + options: ["first", { exceptions: { ArrayExpression: true } }], + errors: [ + { + messageId: "expectedCommaFirst", + type: "Property", + }, + ], + }, + { + code: "var ar =[1,\n{a: 'a',\nb: 'b'}];", + output: "var ar =[1\n,{a: 'a',\nb: 'b'}];", + options: ["first", { exceptions: { ObjectExpression: true } }], + errors: [ + { + messageId: "expectedCommaFirst", + type: "ObjectExpression", + }, + ], + }, + { + code: "var ar ={fst:1,\nsnd: [1,\n2]};", + output: "var ar ={fst:1,\nsnd: [1\n,2]};", + options: ["first", { exceptions: { ObjectExpression: true } }], + errors: [ + { + messageId: "expectedCommaFirst", + type: "Literal", + }, + ], + }, + { + code: "var ar ={fst:1,\nsnd: [1,\n2]};", + output: "var ar ={fst:1\n,snd: [1,\n2]};", + options: ["first", { exceptions: { ArrayExpression: true } }], + errors: [ + { + messageId: "expectedCommaFirst", + type: "Property", + }, + ], + }, + { + code: "new Foo(a,\nb);", + output: "new Foo(a\n,b);", + options: [ + "first", + { + exceptions: { + NewExpression: false, + }, + }, + ], + errors: [{ messageId: "expectedCommaFirst" }], + }, + { + code: "var foo = [\n(bar\n)\n,\nbaz\n];", + output: "var foo = [\n(bar\n),\nbaz\n];", + errors: [ + { + messageId: "unexpectedLineBeforeAndAfterComma", + type: "Identifier", + column: 1, + endColumn: 2, + }, + ], + }, + { + code: "[(foo),\n,\nbar]", + output: "[(foo),,\nbar]", + errors: [{ messageId: "unexpectedLineBeforeAndAfterComma" }], + }, + { + code: "new Foo(a\n,b);", + output: "new Foo(a,\nb);", + options: [ + "last", + { + exceptions: { + NewExpression: false, + }, + }, + ], + errors: [{ messageId: "expectedCommaLast" }], + }, + { + code: "[\n[foo(3)],\n,\nbar\n];", + output: "[\n[foo(3)],,\nbar\n];", + errors: [{ messageId: "unexpectedLineBeforeAndAfterComma" }], + }, + { + // https://github.com/eslint/eslint/issues/10632 + code: "[foo//\n,/*block\ncomment*/];", + output: "[foo,//\n/*block\ncomment*/];", + errors: [{ messageId: "unexpectedLineBeforeAndAfterComma" }], + }, + ], }); diff --git a/tests/lib/rules/complexity.js b/tests/lib/rules/complexity.js index 3f26629fa450..7eb6f497f7a9 100644 --- a/tests/lib/rules/complexity.js +++ b/tests/lib/rules/complexity.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/complexity"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Helpers @@ -18,20 +18,20 @@ const rule = require("../../../lib/rules/complexity"), /** * Generates a code string with the amount of complexity specified in the parameter - * @param {int} complexity The level of complexity + * @param {number} complexity The level of complexity * @returns {string} Code with the amount of complexity specified in the parameter * @private */ function createComplexity(complexity) { - let funcString = "function test (a) { if (a === 1) {"; + let funcString = "function test (a) { if (a === 1) {"; - for (let i = 2; i < complexity; i++) { - funcString += `} else if (a === ${i}) {`; - } + for (let i = 2; i < complexity; i++) { + funcString += `} else if (a === ${i}) {`; + } - funcString += "} };"; + funcString += "} };"; - return funcString; + return funcString; } /** @@ -42,10 +42,10 @@ function createComplexity(complexity) { * @returns {Object} The error object */ function makeError(name, complexity, max) { - return { - messageId: "complex", - data: { name, complexity, max } - }; + return { + messageId: "complex", + data: { name, complexity, max }, + }; } //------------------------------------------------------------------------------ @@ -55,473 +55,855 @@ function makeError(name, complexity, max) { const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2021 } }); ruleTester.run("complexity", rule, { - valid: [ - "function a(x) {}", - { code: "function b(x) {}", options: [1] }, - { code: "function a(x) {if (true) {return x;}}", options: [2] }, - { code: "function a(x) {if (true) {return x;} else {return x+1;}}", options: [2] }, - { code: "function a(x) {if (true) {return x;} else if (false) {return x+1;} else {return 4;}}", options: [3] }, - { code: "function a(x) {for(var i = 0; i < 5; i ++) {x ++;} return x;}", options: [2] }, - { code: "function a(obj) {for(var i in obj) {obj[i] = 3;}}", options: [2] }, - { code: "function a(x) {for(var i = 0; i < 5; i ++) {if(i % 2 === 0) {x ++;}} return x;}", options: [3] }, - { code: "function a(obj) {if(obj){ for(var x in obj) {try {x.getThis();} catch (e) {x.getThat();}}} else {return false;}}", options: [4] }, - { code: "function a(x) {try {x.getThis();} catch (e) {x.getThat();}}", options: [2] }, - { code: "function a(x) {return x === 4 ? 3 : 5;}", options: [2] }, - { code: "function a(x) {return x === 4 ? 3 : (x === 3 ? 2 : 1);}", options: [3] }, - { code: "function a(x) {return x || 4;}", options: [2] }, - { code: "function a(x) {x && 4;}", options: [2] }, - { code: "function a(x) {x ?? 4;}", options: [2] }, - { code: "function a(x) {x ||= 4;}", options: [2] }, - { code: "function a(x) {x &&= 4;}", options: [2] }, - { code: "function a(x) {x ??= 4;}", options: [2] }, - { code: "function a(x) {x = 4;}", options: [1] }, - { code: "function a(x) {x |= 4;}", options: [1] }, - { code: "function a(x) {x &= 4;}", options: [1] }, - { code: "function a(x) {x += 4;}", options: [1] }, - { code: "function a(x) {x >>= 4;}", options: [1] }, - { code: "function a(x) {x >>>= 4;}", options: [1] }, - { code: "function a(x) {x == 4;}", options: [1] }, - { code: "function a(x) {x === 4;}", options: [1] }, - { code: "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: 3;}}", options: [3] }, - { code: "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: if(x == 'foo') {5;};}}", options: [4] }, - { code: "function a(x) {while(true) {'foo';}}", options: [2] }, - { code: "function a(x) {do {'foo';} while (true)}", options: [2] }, - { code: "if (foo) { bar(); }", options: [3] }, - { code: "var a = (x) => {do {'foo';} while (true)}", options: [2], languageOptions: { ecmaVersion: 6 } }, + valid: [ + "function a(x) {}", + { code: "function b(x) {}", options: [1] }, + { code: "function a(x) {if (true) {return x;}}", options: [2] }, + { + code: "function a(x) {if (true) {return x;} else {return x+1;}}", + options: [2], + }, + { + code: "function a(x) {if (true) {return x;} else if (false) {return x+1;} else {return 4;}}", + options: [3], + }, + { + code: "function a(x) {for(var i = 0; i < 5; i ++) {x ++;} return x;}", + options: [2], + }, + { + code: "function a(obj) {for(var i in obj) {obj[i] = 3;}}", + options: [2], + }, + { + code: "function a(x) {for(var i = 0; i < 5; i ++) {if(i % 2 === 0) {x ++;}} return x;}", + options: [3], + }, + { + code: "function a(obj) {if(obj){ for(var x in obj) {try {x.getThis();} catch (e) {x.getThat();}}} else {return false;}}", + options: [4], + }, + { + code: "function a(x) {try {x.getThis();} catch (e) {x.getThat();}}", + options: [2], + }, + { code: "function a(x) {return x === 4 ? 3 : 5;}", options: [2] }, + { + code: "function a(x) {return x === 4 ? 3 : (x === 3 ? 2 : 1);}", + options: [3], + }, + { code: "function a(x) {return x || 4;}", options: [2] }, + { code: "function a(x) {x && 4;}", options: [2] }, + { code: "function a(x) {x ?? 4;}", options: [2] }, + { code: "function a(x) {x ||= 4;}", options: [2] }, + { code: "function a(x) {x &&= 4;}", options: [2] }, + { code: "function a(x) {x ??= 4;}", options: [2] }, + { code: "function a(x) {x = 4;}", options: [1] }, + { code: "function a(x) {x |= 4;}", options: [1] }, + { code: "function a(x) {x &= 4;}", options: [1] }, + { code: "function a(x) {x += 4;}", options: [1] }, + { code: "function a(x) {x >>= 4;}", options: [1] }, + { code: "function a(x) {x >>>= 4;}", options: [1] }, + { code: "function a(x) {x == 4;}", options: [1] }, + { code: "function a(x) {x === 4;}", options: [1] }, + { + code: "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: 3;}}", + options: [3], + }, + { + code: "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: if(x == 'foo') {5;};}}", + options: [4], + }, + { code: "function a(x) {while(true) {'foo';}}", options: [2] }, + { code: "function a(x) {do {'foo';} while (true)}", options: [2] }, + { code: "if (foo) { bar(); }", options: [3] }, + { + code: "var a = (x) => {do {'foo';} while (true)}", + options: [2], + languageOptions: { ecmaVersion: 6 }, + }, - // class fields - { code: "function foo() { class C { x = a || b; y = c || d; } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "function foo() { class C { static x = a || b; static y = c || d; } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "function foo() { class C { x = a || b; y = c || d; } e || f; }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "function foo() { a || b; class C { x = c || d; y = e || f; } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "function foo() { class C { [x || y] = a || b; } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { x = a || b; y() { c || d; } z = e || f; }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { x() { a || b; } y = c || d; z() { e || f; } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { x = (() => { a || b }) || (() => { c || d }) }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { x = () => { a || b }; y = () => { c || d } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { x = a || (() => { b || c }); }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { x = class { y = a || b; z = c || d; }; }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { x = a || class { y = b || c; z = d || e; }; }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { x; y = a; static z; static q = b; }", options: [1], languageOptions: { ecmaVersion: 2022 } }, + // modified complexity + { + code: "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: 3;}}", + options: [{ max: 2, variant: "modified" }], + }, + { + code: "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: if(x == 'foo') {5;};}}", + options: [{ max: 3, variant: "modified" }], + }, - // class static blocks - { code: "function foo() { class C { static { a || b; } static { c || d; } } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "function foo() { a || b; class C { static { c || d; } } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "function foo() { class C { static { a || b; } } c || d; }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "function foo() { class C { static { a || b; } } class D { static { c || d; } } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { a || b; } static { c || d; } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { a || b; } static { c || d; } static { e || f; } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { () => a || b; c || d; } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { a || b; () => c || d; } static { c || d; } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { a } }", options: [1], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { a } static { b } }", options: [1], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { a || b; } } class D { static { c || d; } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { a || b; } static c = d || e; }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static a = b || c; static { c || d; } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { a || b; } c = d || e; }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { a = b || c; static { d || e; } }", options: [2], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { a || b; c || d; } }", options: [3], languageOptions: { ecmaVersion: 2022 } }, - { code: "class C { static { if (a || b) c = d || e; } }", options: [4], languageOptions: { ecmaVersion: 2022 } }, + // class fields + { + code: "function foo() { class C { x = a || b; y = c || d; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "function foo() { class C { static x = a || b; static y = c || d; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "function foo() { class C { x = a || b; y = c || d; } e || f; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "function foo() { a || b; class C { x = c || d; y = e || f; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "function foo() { class C { [x || y] = a || b; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { x = a || b; y() { c || d; } z = e || f; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { x() { a || b; } y = c || d; z() { e || f; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { x = (() => { a || b }) || (() => { c || d }) }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { x = () => { a || b }; y = () => { c || d } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { x = a || (() => { b || c }); }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { x = class { y = a || b; z = c || d; }; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { x = a || class { y = b || c; z = d || e; }; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { x; y = a; static z; static q = b; }", + options: [1], + languageOptions: { ecmaVersion: 2022 }, + }, - // object property options - { code: "function b(x) {}", options: [{ max: 1 }] } - ], - invalid: [ - { code: "function a(x) {}", options: [0], errors: [makeError("Function 'a'", 1, 0)] }, - { code: "var func = function () {}", options: [0], errors: [makeError("Function", 1, 0)] }, - { code: "var obj = { a(x) {} }", options: [0], languageOptions: { ecmaVersion: 6 }, errors: [makeError("Method 'a'", 1, 0)] }, - { code: "class Test { a(x) {} }", options: [0], languageOptions: { ecmaVersion: 6 }, errors: [makeError("Method 'a'", 1, 0)] }, - { code: "var a = (x) => {if (true) {return x;}}", options: [1], languageOptions: { ecmaVersion: 6 }, errors: 1 }, - { code: "function a(x) {if (true) {return x;}}", options: [1], errors: 1 }, - { code: "function a(x) {if (true) {return x;} else {return x+1;}}", options: [1], errors: 1 }, - { code: "function a(x) {if (true) {return x;} else if (false) {return x+1;} else {return 4;}}", options: [2], errors: 1 }, - { code: "function a(x) {for(var i = 0; i < 5; i ++) {x ++;} return x;}", options: [1], errors: 1 }, - { code: "function a(obj) {for(var i in obj) {obj[i] = 3;}}", options: [1], errors: 1 }, - { code: "function a(obj) {for(var i of obj) {obj[i] = 3;}}", options: [1], languageOptions: { ecmaVersion: 6 }, errors: 1 }, - { code: "function a(x) {for(var i = 0; i < 5; i ++) {if(i % 2 === 0) {x ++;}} return x;}", options: [2], errors: 1 }, - { code: "function a(obj) {if(obj){ for(var x in obj) {try {x.getThis();} catch (e) {x.getThat();}}} else {return false;}}", options: [3], errors: 1 }, - { code: "function a(x) {try {x.getThis();} catch (e) {x.getThat();}}", options: [1], errors: 1 }, - { code: "function a(x) {return x === 4 ? 3 : 5;}", options: [1], errors: 1 }, - { code: "function a(x) {return x === 4 ? 3 : (x === 3 ? 2 : 1);}", options: [2], errors: 1 }, - { code: "function a(x) {return x || 4;}", options: [1], errors: 1 }, - { code: "function a(x) {x && 4;}", options: [1], errors: 1 }, - { code: "function a(x) {x ?? 4;}", options: [1], errors: 1 }, - { code: "function a(x) {x ||= 4;}", options: [1], errors: 1 }, - { code: "function a(x) {x &&= 4;}", options: [1], errors: 1 }, - { code: "function a(x) {x ??= 4;}", options: [1], errors: 1 }, - { code: "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: 3;}}", options: [2], errors: 1 }, - { code: "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: if(x == 'foo') {5;};}}", options: [3], errors: 1 }, - { code: "function a(x) {while(true) {'foo';}}", options: [1], errors: 1 }, - { code: "function a(x) {do {'foo';} while (true)}", options: [1], errors: 1 }, - { code: "function a(x) {(function() {while(true){'foo';}})(); (function() {while(true){'bar';}})();}", options: [1], errors: 2 }, - { code: "function a(x) {(function() {while(true){'foo';}})(); (function() {'bar';})();}", options: [1], errors: 1 }, - { code: "var obj = { a(x) { return x ? 0 : 1; } };", options: [1], languageOptions: { ecmaVersion: 6 }, errors: [makeError("Method 'a'", 2, 1)] }, - { code: "var obj = { a: function b(x) { return x ? 0 : 1; } };", options: [1], errors: [makeError("Method 'a'", 2, 1)] }, - { - code: createComplexity(21), - errors: [makeError("Function 'test'", 21, 20)] - }, - { - code: createComplexity(21), - options: [{}], - errors: [makeError("Function 'test'", 21, 20)] - }, + // class static blocks + { + code: "function foo() { class C { static { a || b; } static { c || d; } } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "function foo() { a || b; class C { static { c || d; } } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "function foo() { class C { static { a || b; } } c || d; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "function foo() { class C { static { a || b; } } class D { static { c || d; } } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { a || b; } static { c || d; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { a || b; } static { c || d; } static { e || f; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { () => a || b; c || d; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { a || b; () => c || d; } static { c || d; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { a } }", + options: [1], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { a } static { b } }", + options: [1], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { a || b; } } class D { static { c || d; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { a || b; } static c = d || e; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static a = b || c; static { c || d; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { a || b; } c = d || e; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { a = b || c; static { d || e; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { a || b; c || d; } }", + options: [3], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static { if (a || b) c = d || e; } }", + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, - // class fields - { - code: "function foo () { a || b; class C { x; } c || d; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Function 'foo'", 3, 2)] - }, - { - code: "function foo () { a || b; class C { x = c; } d || e; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Function 'foo'", 3, 2)] - }, - { - code: "function foo () { a || b; class C { [x || y]; } }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Function 'foo'", 3, 2)] - }, - { - code: "function foo () { a || b; class C { [x || y] = c; } }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Function 'foo'", 3, 2)] - }, - { - code: "function foo () { class C { [x || y]; } a || b; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Function 'foo'", 3, 2)] - }, - { - code: "function foo () { class C { [x || y] = a; } b || c; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Function 'foo'", 3, 2)] - }, - { - code: "function foo () { class C { [x || y]; [z || q]; } }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Function 'foo'", 3, 2)] - }, - { - code: "function foo () { class C { [x || y] = a; [z || q] = b; } }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Function 'foo'", 3, 2)] - }, - { - code: "function foo () { a || b; class C { x = c || d; } e || f; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Function 'foo'", 3, 2)] - }, - { - code: "class C { x(){ a || b; } y = c || d || e; z() { f || g; } }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class field initializer", 3, 2)] - }, - { - code: "class C { x = a || b; y() { c || d || e; } z = f || g; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Method 'y'", 3, 2)] - }, - { - code: "class C { x; y() { c || d || e; } z; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Method 'y'", 3, 2)] - }, - { - code: "class C { x = a || b; }", - options: [1], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class field initializer", 2, 1)] - }, - { - code: "(class { x = a || b; })", - options: [1], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class field initializer", 2, 1)] - }, - { - code: "class C { static x = a || b; }", - options: [1], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class field initializer", 2, 1)] - }, - { - code: "(class { x = a ? b : c; })", - options: [1], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class field initializer", 2, 1)] - }, - { - code: "class C { x = a || b || c; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class field initializer", 3, 2)] - }, - { - code: "class C { x = a || b; y = b || c || d; z = e || f; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [{ - ...makeError("Class field initializer", 3, 2), - line: 1, - column: 27, - endLine: 1, - endColumn: 38 - }] - }, - { - code: "class C { x = a || b || c; y = d || e; z = f || g || h; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - ...makeError("Class field initializer", 3, 2), - line: 1, - column: 15, - endLine: 1, - endColumn: 26 - }, - { - ...makeError("Class field initializer", 3, 2), - line: 1, - column: 44, - endLine: 1, - endColumn: 55 - } - ] - }, - { - code: "class C { x = () => a || b || c; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Method 'x'", 3, 2)] - }, - { - code: "class C { x = (() => a || b || c) || d; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Arrow function", 3, 2)] - }, - { - code: "class C { x = () => a || b || c; y = d || e; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Method 'x'", 3, 2)] - }, - { - code: "class C { x = () => a || b || c; y = d || e || f; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - makeError("Method 'x'", 3, 2), - { - ...makeError("Class field initializer", 3, 2), - line: 1, - column: 38, - endLine: 1, - endColumn: 49 - } - ] - }, - { - code: "class C { x = function () { a || b }; y = function () { c || d }; }", - options: [1], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - makeError("Method 'x'", 2, 1), - makeError("Method 'y'", 2, 1) - ] - }, - { - code: "class C { x = class { [y || z]; }; }", - options: [1], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - ...makeError("Class field initializer", 2, 1), - line: 1, - column: 15, - endLine: 1, - endColumn: 34 - } - ] - }, - { - code: "class C { x = class { [y || z] = a; }; }", - options: [1], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - ...makeError("Class field initializer", 2, 1), - line: 1, - column: 15, - endLine: 1, - endColumn: 38 - } - ] - }, - { - code: "class C { x = class { y = a || b; }; }", - options: [1], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - ...makeError("Class field initializer", 2, 1), - line: 1, - column: 27, - endLine: 1, - endColumn: 33 - } - ] - }, + // object property options + { code: "function b(x) {}", options: [{ max: 1 }] }, - // class static blocks - { - code: "function foo () { a || b; class C { static {} } c || d; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Function 'foo'", 3, 2)] - }, - { - code: "function foo () { a || b; class C { static { c || d; } } e || f; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Function 'foo'", 3, 2)] - }, - { - code: "class C { static { a || b; } }", - options: [1], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class static block", 2, 1)] - }, - { - code: "class C { static { a || b || c; } }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class static block", 3, 2)] - }, - { - code: "class C { static { a || b; c || d; } }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class static block", 3, 2)] - }, - { - code: "class C { static { a || b; c || d; e || f; } }", - options: [3], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class static block", 4, 3)] - }, - { - code: "class C { static { a || b; c || d; { e || f; } } }", - options: [3], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class static block", 4, 3)] - }, - { - code: "class C { static { if (a || b) c = d || e; } }", - options: [3], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class static block", 4, 3)] - }, - { - code: "class C { static { if (a || b) c = (d => e || f)() || (g => h || i)(); } }", - options: [3], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class static block", 4, 3)] - }, - { - code: "class C { x(){ a || b; } static { c || d || e; } z() { f || g; } }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class static block", 3, 2)] - }, - { - code: "class C { x = a || b; static { c || d || e; } y = f || g; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class static block", 3, 2)] - }, - { - code: "class C { static x = a || b; static { c || d || e; } static y = f || g; }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Class static block", 3, 2)] - }, - { - code: "class C { static { a || b; } static(){ c || d || e; } static { f || g; } }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Method 'static'", 3, 2)] - }, - { - code: "class C { static { a || b; } static static(){ c || d || e; } static { f || g; } }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [makeError("Static method 'static'", 3, 2)] - }, - { - code: "class C { static { a || b; } static x = c || d || e; static { f || g; } }", - options: [2], - languageOptions: { ecmaVersion: 2022 }, - errors: [{ - ...makeError("Class field initializer", 3, 2), - column: 41, - endColumn: 52 - }] - }, - { - code: "class C { static { a || b || c || d; } static { e || f || g; } }", - options: [3], - languageOptions: { ecmaVersion: 2022 }, - errors: [{ - ...makeError("Class static block", 4, 3), - column: 11, - endColumn: 39 - }] - }, - { - code: "class C { static { a || b || c; } static { d || e || f || g; } }", - options: [3], - languageOptions: { ecmaVersion: 2022 }, - errors: [{ - ...makeError("Class static block", 4, 3), - column: 35, - endColumn: 63 - }] - }, - { - code: "class C { static { a || b || c || d; } static { e || f || g || h; } }", - options: [3], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - ...makeError("Class static block", 4, 3), - column: 11, - endColumn: 39 - }, - { - ...makeError("Class static block", 4, 3), - column: 40, - endColumn: 68 - } - ] - }, + // optional chaining + { + code: "function a(b) { b?.c; }", + options: [{ max: 2 }], + }, - // object property options - { code: "function a(x) {}", options: [{ max: 0 }], errors: [makeError("Function 'a'", 1, 0)] } - ] + // default function parameter values + { + code: "function a(b = '') {}", + options: [{ max: 2 }], + }, + + // default destructuring values + { + code: "function a(b) { const { c = '' } = b; }", + options: [{ max: 2 }], + }, + { + code: "function a(b) { const [ c = '' ] = b; }", + options: [{ max: 2 }], + }, + ], + invalid: [ + { + code: "function a(x) {}", + options: [0], + errors: [makeError("Function 'a'", 1, 0)], + }, + { + code: "var func = function () {}", + options: [0], + errors: [makeError("Function", 1, 0)], + }, + { + code: "var obj = { a(x) {} }", + options: [0], + languageOptions: { ecmaVersion: 6 }, + errors: [makeError("Method 'a'", 1, 0)], + }, + { + code: "class Test { a(x) {} }", + options: [0], + languageOptions: { ecmaVersion: 6 }, + errors: [makeError("Method 'a'", 1, 0)], + }, + { + code: "var a = (x) => {if (true) {return x;}}", + options: [1], + languageOptions: { ecmaVersion: 6 }, + errors: 1, + }, + { + code: "function a(x) {if (true) {return x;}}", + options: [1], + errors: 1, + }, + { + code: "function a(x) {if (true) {return x;} else {return x+1;}}", + options: [1], + errors: 1, + }, + { + code: "function a(x) {if (true) {return x;} else if (false) {return x+1;} else {return 4;}}", + options: [2], + errors: 1, + }, + { + code: "function a(x) {for(var i = 0; i < 5; i ++) {x ++;} return x;}", + options: [1], + errors: 1, + }, + { + code: "function a(obj) {for(var i in obj) {obj[i] = 3;}}", + options: [1], + errors: 1, + }, + { + code: "function a(obj) {for(var i of obj) {obj[i] = 3;}}", + options: [1], + languageOptions: { ecmaVersion: 6 }, + errors: 1, + }, + { + code: "function a(x) {for(var i = 0; i < 5; i ++) {if(i % 2 === 0) {x ++;}} return x;}", + options: [2], + errors: 1, + }, + { + code: "function a(obj) {if(obj){ for(var x in obj) {try {x.getThis();} catch (e) {x.getThat();}}} else {return false;}}", + options: [3], + errors: 1, + }, + { + code: "function a(x) {try {x.getThis();} catch (e) {x.getThat();}}", + options: [1], + errors: 1, + }, + { + code: "function a(x) {return x === 4 ? 3 : 5;}", + options: [1], + errors: 1, + }, + { + code: "function a(x) {return x === 4 ? 3 : (x === 3 ? 2 : 1);}", + options: [2], + errors: 1, + }, + { code: "function a(x) {return x || 4;}", options: [1], errors: 1 }, + { code: "function a(x) {x && 4;}", options: [1], errors: 1 }, + { code: "function a(x) {x ?? 4;}", options: [1], errors: 1 }, + { code: "function a(x) {x ||= 4;}", options: [1], errors: 1 }, + { code: "function a(x) {x &&= 4;}", options: [1], errors: 1 }, + { code: "function a(x) {x ??= 4;}", options: [1], errors: 1 }, + { + code: "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: 3;}}", + options: [2], + errors: 1, + }, + { + code: "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: if(x == 'foo') {5;};}}", + options: [3], + errors: 1, + }, + { + code: "function a(x) {while(true) {'foo';}}", + options: [1], + errors: 1, + }, + { + code: "function a(x) {do {'foo';} while (true)}", + options: [1], + errors: 1, + }, + { + code: "function a(x) {(function() {while(true){'foo';}})(); (function() {while(true){'bar';}})();}", + options: [1], + errors: 2, + }, + { + code: "function a(x) {(function() {while(true){'foo';}})(); (function() {'bar';})();}", + options: [1], + errors: 1, + }, + { + code: "var obj = { a(x) { return x ? 0 : 1; } };", + options: [1], + languageOptions: { ecmaVersion: 6 }, + errors: [makeError("Method 'a'", 2, 1)], + }, + { + code: "var obj = { a: function b(x) { return x ? 0 : 1; } };", + options: [1], + errors: [makeError("Method 'a'", 2, 1)], + }, + { + code: createComplexity(21), + errors: [makeError("Function 'test'", 21, 20)], + }, + { + code: createComplexity(21), + options: [{}], + errors: [makeError("Function 'test'", 21, 20)], + }, + + // modified complexity + { + code: "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: 3;}}", + options: [{ max: 1, variant: "modified" }], + errors: [makeError("Function 'a'", 2, 1)], + }, + { + code: "function a(x) {switch(x){case 1: 1; break; case 2: 2; break; default: if(x == 'foo') {5;};}}", + options: [{ max: 2, variant: "modified" }], + errors: [makeError("Function 'a'", 3, 2)], + }, + + // class fields + { + code: "function foo () { a || b; class C { x; } c || d; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)], + }, + { + code: "function foo () { a || b; class C { x = c; } d || e; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)], + }, + { + code: "function foo () { a || b; class C { [x || y]; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)], + }, + { + code: "function foo () { a || b; class C { [x || y] = c; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)], + }, + { + code: "function foo () { class C { [x || y]; } a || b; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)], + }, + { + code: "function foo () { class C { [x || y] = a; } b || c; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)], + }, + { + code: "function foo () { class C { [x || y]; [z || q]; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)], + }, + { + code: "function foo () { class C { [x || y] = a; [z || q] = b; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)], + }, + { + code: "function foo () { a || b; class C { x = c || d; } e || f; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)], + }, + { + code: "class C { x(){ a || b; } y = c || d || e; z() { f || g; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class field initializer", 3, 2)], + }, + { + code: "class C { x = a || b; y() { c || d || e; } z = f || g; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Method 'y'", 3, 2)], + }, + { + code: "class C { x; y() { c || d || e; } z; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Method 'y'", 3, 2)], + }, + { + code: "class C { x = a || b; }", + options: [1], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class field initializer", 2, 1)], + }, + { + code: "(class { x = a || b; })", + options: [1], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class field initializer", 2, 1)], + }, + { + code: "class C { static x = a || b; }", + options: [1], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class field initializer", 2, 1)], + }, + { + code: "(class { x = a ? b : c; })", + options: [1], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class field initializer", 2, 1)], + }, + { + code: "class C { x = a || b || c; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class field initializer", 3, 2)], + }, + { + code: "class C { x = a || b; y = b || c || d; z = e || f; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + ...makeError("Class field initializer", 3, 2), + line: 1, + column: 27, + endLine: 1, + endColumn: 38, + }, + ], + }, + { + code: "class C { x = a || b || c; y = d || e; z = f || g || h; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + ...makeError("Class field initializer", 3, 2), + line: 1, + column: 15, + endLine: 1, + endColumn: 26, + }, + { + ...makeError("Class field initializer", 3, 2), + line: 1, + column: 44, + endLine: 1, + endColumn: 55, + }, + ], + }, + { + code: "class C { x = () => a || b || c; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Method 'x'", 3, 2)], + }, + { + code: "class C { x = (() => a || b || c) || d; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Arrow function", 3, 2)], + }, + { + code: "class C { x = () => a || b || c; y = d || e; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Method 'x'", 3, 2)], + }, + { + code: "class C { x = () => a || b || c; y = d || e || f; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + makeError("Method 'x'", 3, 2), + { + ...makeError("Class field initializer", 3, 2), + line: 1, + column: 38, + endLine: 1, + endColumn: 49, + }, + ], + }, + { + code: "class C { x = function () { a || b }; y = function () { c || d }; }", + options: [1], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + makeError("Method 'x'", 2, 1), + makeError("Method 'y'", 2, 1), + ], + }, + { + code: "class C { x = class { [y || z]; }; }", + options: [1], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + ...makeError("Class field initializer", 2, 1), + line: 1, + column: 15, + endLine: 1, + endColumn: 34, + }, + ], + }, + { + code: "class C { x = class { [y || z] = a; }; }", + options: [1], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + ...makeError("Class field initializer", 2, 1), + line: 1, + column: 15, + endLine: 1, + endColumn: 38, + }, + ], + }, + { + code: "class C { x = class { y = a || b; }; }", + options: [1], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + ...makeError("Class field initializer", 2, 1), + line: 1, + column: 27, + endLine: 1, + endColumn: 33, + }, + ], + }, + + // class static blocks + { + code: "function foo () { a || b; class C { static {} } c || d; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)], + }, + { + code: "function foo () { a || b; class C { static { c || d; } } e || f; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Function 'foo'", 3, 2)], + }, + { + code: "class C { static { a || b; } }", + options: [1], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 2, 1)], + }, + { + code: "class C { static { a || b || c; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 3, 2)], + }, + { + code: "class C { static { a || b; c || d; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 3, 2)], + }, + { + code: "class C { static { a || b; c || d; e || f; } }", + options: [3], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 4, 3)], + }, + { + code: "class C { static { a || b; c || d; { e || f; } } }", + options: [3], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 4, 3)], + }, + { + code: "class C { static { if (a || b) c = d || e; } }", + options: [3], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 4, 3)], + }, + { + code: "class C { static { if (a || b) c = (d => e || f)() || (g => h || i)(); } }", + options: [3], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 4, 3)], + }, + { + code: "class C { x(){ a || b; } static { c || d || e; } z() { f || g; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 3, 2)], + }, + { + code: "class C { x = a || b; static { c || d || e; } y = f || g; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 3, 2)], + }, + { + code: "class C { static x = a || b; static { c || d || e; } static y = f || g; }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Class static block", 3, 2)], + }, + { + code: "class C { static { a || b; } static(){ c || d || e; } static { f || g; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Method 'static'", 3, 2)], + }, + { + code: "class C { static { a || b; } static static(){ c || d || e; } static { f || g; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [makeError("Static method 'static'", 3, 2)], + }, + { + code: "class C { static { a || b; } static x = c || d || e; static { f || g; } }", + options: [2], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + ...makeError("Class field initializer", 3, 2), + column: 41, + endColumn: 52, + }, + ], + }, + { + code: "class C { static { a || b || c || d; } static { e || f || g; } }", + options: [3], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + ...makeError("Class static block", 4, 3), + column: 11, + endColumn: 39, + }, + ], + }, + { + code: "class C { static { a || b || c; } static { d || e || f || g; } }", + options: [3], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + ...makeError("Class static block", 4, 3), + column: 35, + endColumn: 63, + }, + ], + }, + { + code: "class C { static { a || b || c || d; } static { e || f || g || h; } }", + options: [3], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + ...makeError("Class static block", 4, 3), + column: 11, + endColumn: 39, + }, + { + ...makeError("Class static block", 4, 3), + column: 40, + endColumn: 68, + }, + ], + }, + + // object property options + { + code: "function a(x) {}", + options: [{ max: 0 }], + errors: [makeError("Function 'a'", 1, 0)], + }, + + // optional chaining + { + code: "function a(b) { b?.c; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)], + }, + { + code: "function a(b) { b?.['c']; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)], + }, + { + code: "function a(b) { b?.c; d || e; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)], + }, + { + code: "function a(b) { b?.c?.d; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)], + }, + { + code: "function a(b) { b?.['c']?.['d']; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)], + }, + { + code: "function a(b) { b?.c?.['d']; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)], + }, + { + code: "function a(b) { b?.c.d?.e; }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)], + }, + { + code: "function a(b) { b?.c?.(); }", + options: [{ max: 2 }], + errors: [makeError("Function 'a'", 3, 2)], + }, + { + code: "function a(b) { b?.c?.()?.(); }", + options: [{ max: 3 }], + errors: [makeError("Function 'a'", 4, 3)], + }, + + // default function parameter values + { + code: "function a(b = '') {}", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)], + }, + + // default destructuring values + { + code: "function a(b) { const { c = '' } = b; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)], + }, + { + code: "function a(b) { const [ c = '' ] = b; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 2, 1)], + }, + { + code: "function a(b) { const [ { c: d = '' } = {} ] = b; }", + options: [{ max: 1 }], + errors: [makeError("Function 'a'", 3, 1)], + }, + ], }); diff --git a/tests/lib/rules/computed-property-spacing.js b/tests/lib/rules/computed-property-spacing.js index 9a51e2f9ddac..cee13f18e497 100644 --- a/tests/lib/rules/computed-property-spacing.js +++ b/tests/lib/rules/computed-property-spacing.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/computed-property-spacing"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,2133 +18,2103 @@ const rule = require("../../../lib/rules/computed-property-spacing"), const ruleTester = new RuleTester(); ruleTester.run("computed-property-spacing", rule, { + valid: [ + // default - never + "obj[foo]", + "obj['foo']", + { code: "var x = {[b]: a}", languageOptions: { ecmaVersion: 6 } }, - valid: [ + // always + { code: "obj[ foo ]", options: ["always"] }, + { code: "obj[\nfoo\n]", options: ["always"] }, + { code: "obj[ 'foo' ]", options: ["always"] }, + { code: "obj[ 'foo' + 'bar' ]", options: ["always"] }, + { code: "obj[ obj2[ foo ] ]", options: ["always"] }, + { + code: "obj.map(function(item) { return [\n1,\n2,\n3,\n4\n]; })", + options: ["always"], + }, + { + code: "obj[ 'map' ](function(item) { return [\n1,\n2,\n3,\n4\n]; })", + options: ["always"], + }, + { + code: "obj[ 'for' + 'Each' ](function(item) { return [\n1,\n2,\n3,\n4\n]; })", + options: ["always"], + }, + { code: "var foo = obj[ 1 ]", options: ["always"] }, + { code: "var foo = obj[ 'foo' ];", options: ["always"] }, + { code: "var foo = obj[ [1, 1] ];", options: ["always"] }, - // default - never - "obj[foo]", - "obj['foo']", - { code: "var x = {[b]: a}", languageOptions: { ecmaVersion: 6 } }, + // always - objectLiteralComputedProperties + { + code: 'var x = {[ "a" ]: a}', + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var y = {[ x ]: a}", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'var x = {[ "a" ]() {}}', + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var y = {[ x ]() {}}", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, - // always - { code: "obj[ foo ]", options: ["always"] }, - { code: "obj[\nfoo\n]", options: ["always"] }, - { code: "obj[ 'foo' ]", options: ["always"] }, - { code: "obj[ 'foo' + 'bar' ]", options: ["always"] }, - { code: "obj[ obj2[ foo ] ]", options: ["always"] }, - { code: "obj.map(function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["always"] }, - { code: "obj[ 'map' ](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["always"] }, - { code: "obj[ 'for' + 'Each' ](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["always"] }, - { code: "var foo = obj[ 1 ]", options: ["always"] }, - { code: "var foo = obj[ 'foo' ];", options: ["always"] }, - { code: "var foo = obj[ [1, 1] ];", options: ["always"] }, + // always - unrelated cases + { code: "var foo = {};", options: ["always"] }, + { code: "var foo = [];", options: ["always"] }, - // always - objectLiteralComputedProperties - { code: "var x = {[ \"a\" ]: a}", options: ["always"], languageOptions: { ecmaVersion: 6 } }, - { code: "var y = {[ x ]: a}", options: ["always"], languageOptions: { ecmaVersion: 6 } }, - { code: "var x = {[ \"a\" ]() {}}", options: ["always"], languageOptions: { ecmaVersion: 6 } }, - { code: "var y = {[ x ]() {}}", options: ["always"], languageOptions: { ecmaVersion: 6 } }, + // never + { code: "obj[foo]", options: ["never"] }, + { code: "obj['foo']", options: ["never"] }, + { code: "obj['foo' + 'bar']", options: ["never"] }, + { code: "obj['foo'+'bar']", options: ["never"] }, + { code: "obj[obj2[foo]]", options: ["never"] }, + { + code: "obj.map(function(item) { return [\n1,\n2,\n3,\n4\n]; })", + options: ["never"], + }, + { + code: "obj['map'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", + options: ["never"], + }, + { + code: "obj['for' + 'Each'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", + options: ["never"], + }, + { code: "obj[\nfoo]", options: ["never"] }, + { code: "obj[foo\n]", options: ["never"] }, + { code: "var foo = obj[1]", options: ["never"] }, + { code: "var foo = obj['foo'];", options: ["never"] }, + { code: "var foo = obj[[ 1, 1 ]];", options: ["never"] }, - // always - unrelated cases - { code: "var foo = {};", options: ["always"] }, - { code: "var foo = [];", options: ["always"] }, + // never - objectLiteralComputedProperties + { + code: 'var x = {["a"]: a}', + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var y = {[x]: a}", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: 'var x = {["a"]() {}}', + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var y = {[x]() {}}", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, - // never - { code: "obj[foo]", options: ["never"] }, - { code: "obj['foo']", options: ["never"] }, - { code: "obj['foo' + 'bar']", options: ["never"] }, - { code: "obj['foo'+'bar']", options: ["never"] }, - { code: "obj[obj2[foo]]", options: ["never"] }, - { code: "obj.map(function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["never"] }, - { code: "obj['map'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["never"] }, - { code: "obj['for' + 'Each'](function(item) { return [\n1,\n2,\n3,\n4\n]; })", options: ["never"] }, - { code: "obj[\nfoo]", options: ["never"] }, - { code: "obj[foo\n]", options: ["never"] }, - { code: "var foo = obj[1]", options: ["never"] }, - { code: "var foo = obj['foo'];", options: ["never"] }, - { code: "var foo = obj[[ 1, 1 ]];", options: ["never"] }, + // never - unrelated cases + { code: "var foo = {};", options: ["never"] }, + { code: "var foo = [];", options: ["never"] }, - // never - objectLiteralComputedProperties - { code: "var x = {[\"a\"]: a}", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "var y = {[x]: a}", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "var x = {[\"a\"]() {}}", options: ["never"], languageOptions: { ecmaVersion: 6 } }, - { code: "var y = {[x]() {}}", options: ["never"], languageOptions: { ecmaVersion: 6 } }, + //------------------------------------------------------------------------------ + // Classes + //------------------------------------------------------------------------------ - // never - unrelated cases - { code: "var foo = {};", options: ["never"] }, - { code: "var foo = [];", options: ["never"] }, + // explicitly disabled option + { + code: "class A { [ a ](){} }", + options: ["never", { enforceForClassMembers: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", + options: ["never", { enforceForClassMembers: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { [a](){} }", + options: ["always", { enforceForClassMembers: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { [a](){} get [b](){} set [b](foo){} static [c](){} static get [d](){} static set [d](bar){} }", + options: ["always", { enforceForClassMembers: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { [ a ]; }", + options: ["never", { enforceForClassMembers: false }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class A { [a]; }", + options: ["always", { enforceForClassMembers: false }], + languageOptions: { ecmaVersion: 2022 }, + }, - //------------------------------------------------------------------------------ - // Classes - //------------------------------------------------------------------------------ + // valid spacing + { + code: "A = class { [a](){} }", + options: ["never", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { [a] ( ) { } }", + options: ["never", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { [ \n a \n ](){} }", + options: ["never", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { [a](){} get [b](){} set [b](foo){} static [c](){} static get [d](){} static set [d](bar){} }", + options: ["never", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { [ a ](){} }", + options: ["always", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { [ a ](){}[ b ](){} }", + options: ["always", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { [\na\n](){} }", + options: ["always", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", + options: ["always", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { [a]; static [a]; [a] = 0; static [a] = 0; }", + options: ["never", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "A = class { [ a ]; static [ a ]; [ a ] = 0; static [ a ] = 0; }", + options: ["always", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 2022 }, + }, - // explicitly disabled option - { - code: "class A { [ a ](){} }", - options: ["never", { enforceForClassMembers: false }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", - options: ["never", { enforceForClassMembers: false }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { [a](){} }", - options: ["always", { enforceForClassMembers: false }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { [a](){} get [b](){} set [b](foo){} static [c](){} static get [d](){} static set [d](bar){} }", - options: ["always", { enforceForClassMembers: false }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { [ a ]; }", - options: ["never", { enforceForClassMembers: false }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class A { [a]; }", - options: ["always", { enforceForClassMembers: false }], - languageOptions: { ecmaVersion: 2022 } - }, + // non-computed + { + code: "class A { a ( ) { } get b(){} set b ( foo ){} static c (){} static get d() {} static set d( bar ) {} }", + options: ["never", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class {a(){}get b(){}set b(foo){}static c(){}static get d(){}static set d(bar){}}", + options: ["always", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "A = class { foo; #a; static #b; #c = 0; static #d = 0; }", + options: ["never", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "A = class { foo; #a; static #b; #c = 0; static #d = 0; }", + options: ["always", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 2022 }, + }, - // valid spacing - { - code: "A = class { [a](){} }", - options: ["never", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { [a] ( ) { } }", - options: ["never", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { [ \n a \n ](){} }", - options: ["never", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { [a](){} get [b](){} set [b](foo){} static [c](){} static get [d](){} static set [d](bar){} }", - options: ["never", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { [ a ](){} }", - options: ["always", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { [ a ](){}[ b ](){} }", - options: ["always", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { [\na\n](){} }", - options: ["always", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", - options: ["always", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { [a]; static [a]; [a] = 0; static [a] = 0; }", - options: ["never", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "A = class { [ a ]; static [ a ]; [ a ] = 0; static [ a ] = 0; }", - options: ["always", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 2022 } - }, + // handling of parens and comments + { + code: ["const foo = {", " [ (a) ]: 1", "}"].join("\n"), + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: ["const foo = {", " [ ( a ) ]: 1", "}"].join("\n"), + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: ["const foo = {", " [( a )]: 1", "}"].join("\n"), + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: ["const foo = {", " [ /**/ a /**/ ]: 1", "}"].join("\n"), + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: ["const foo = {", " [/**/ a /**/]: 1", "}"].join("\n"), + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: ["const foo = {", " [ a[ b ] ]: 1", "}"].join("\n"), + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: ["const foo = {", " [a[b]]: 1", "}"].join("\n"), + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: ["const foo = {", " [ a[ /**/ b ]/**/ ]: 1", "}"].join("\n"), + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: ["const foo = {", " [/**/a[b /**/] /**/]: 1", "}"].join( + "\n", + ), + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, - // non-computed - { - code: "class A { a ( ) { } get b(){} set b ( foo ){} static c (){} static get d() {} static set d( bar ) {} }", - options: ["never", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class {a(){}get b(){}set b(foo){}static c(){}static get d(){}static set d(bar){}}", - options: ["always", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "A = class { foo; #a; static #b; #c = 0; static #d = 0; }", - options: ["never", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "A = class { foo; #a; static #b; #c = 0; static #d = 0; }", - options: ["always", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 2022 } - }, + // Destructuring Assignment + { + code: "const { [a]: someProp } = obj;", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ [a]: someProp } = obj);", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "const { [ a ]: someProp } = obj;", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ [ a ]: someProp } = obj);", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + ], - // handling of parens and comments - { - code: [ - "const foo = {", - " [ (a) ]: 1", - "}" - ].join("\n"), - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: [ - "const foo = {", - " [ ( a ) ]: 1", - "}" - ].join("\n"), - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: [ - "const foo = {", - " [( a )]: 1", - "}" - ].join("\n"), - options: ["never"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: [ - "const foo = {", - " [ /**/ a /**/ ]: 1", - "}" - ].join("\n"), - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: [ - "const foo = {", - " [/**/ a /**/]: 1", - "}" - ].join("\n"), - options: ["never"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: [ - "const foo = {", - " [ a[ b ] ]: 1", - "}" - ].join("\n"), - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: [ - "const foo = {", - " [a[b]]: 1", - "}" - ].join("\n"), - options: ["never"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: [ - "const foo = {", - " [ a[ /**/ b ]/**/ ]: 1", - "}" - ].join("\n"), - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: [ - "const foo = {", - " [/**/a[b /**/] /**/]: 1", - "}" - ].join("\n"), - options: ["never"], - languageOptions: { ecmaVersion: 6 } - }, + invalid: [ + { + code: "var foo = obj[ 1];", + output: "var foo = obj[ 1 ];", + options: ["always"], + errors: [ + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MemberExpression", + line: 1, + column: 17, + endLine: 1, + endColumn: 18, + }, + ], + }, + { + code: "var foo = obj[1 ];", + output: "var foo = obj[ 1 ];", + options: ["always"], + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MemberExpression", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "var foo = obj[ 1];", + output: "var foo = obj[1];", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MemberExpression", + line: 1, + column: 15, + endLine: 1, + endColumn: 16, + }, + ], + }, + { + code: "var foo = obj[1 ];", + output: "var foo = obj[1];", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MemberExpression", + line: 1, + column: 16, + endLine: 1, + endColumn: 17, + }, + ], + }, + { + code: "obj[ foo ]", + output: "obj[foo]", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MemberExpression", + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MemberExpression", + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + ], + }, + { + code: "obj[foo ]", + output: "obj[foo]", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MemberExpression", + line: 1, + column: 8, + endLine: 1, + endColumn: 9, + }, + ], + }, + { + code: "obj[ foo]", + output: "obj[foo]", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MemberExpression", + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + ], + }, + { + code: "var foo = obj[1]", + output: "var foo = obj[ 1 ]", + options: ["always"], + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MemberExpression", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MemberExpression", + line: 1, + column: 16, + endLine: 1, + endColumn: 17, + }, + ], + }, - // Destructuring Assignment - { - code: "const { [a]: someProp } = obj;", - options: ["never"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({ [a]: someProp } = obj);", - options: ["never"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "const { [ a ]: someProp } = obj;", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({ [ a ]: someProp } = obj);", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - } + // multiple spaces + { + code: "obj[ foo]", + output: "obj[foo]", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MemberExpression", + line: 1, + column: 5, + endLine: 1, + endColumn: 9, + }, + ], + }, + { + code: "obj[ foo ]", + output: "obj[foo]", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MemberExpression", + line: 1, + column: 5, + endLine: 1, + endColumn: 7, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MemberExpression", + line: 1, + column: 10, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: "obj[ foo ]", + output: "obj[foo]", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MemberExpression", + line: 1, + column: 5, + endLine: 1, + endColumn: 8, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MemberExpression", + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: "obj[ foo + \n bar ]", + output: "obj[foo + \n bar]", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MemberExpression", + line: 1, + column: 5, + endLine: 1, + endColumn: 6, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MemberExpression", + line: 2, + column: 6, + endLine: 2, + endColumn: 9, + }, + ], + }, + { + code: "obj[\n foo ]", + output: "obj[\n foo]", + options: ["never"], + errors: [ + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MemberExpression", + line: 2, + column: 5, + endLine: 2, + endColumn: 7, + }, + ], + }, - ], + // always - objectLiteralComputedProperties + { + code: "var x = {[a]: b}", + output: "var x = {[ a ]: b}", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "Property", + line: 1, + column: 10, + endLine: 1, + endColumn: 11, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "Property", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "var x = {[a ]: b}", + output: "var x = {[ a ]: b}", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "Property", + line: 1, + column: 10, + endLine: 1, + endColumn: 11, + }, + ], + }, + { + code: "var x = {[ a]: b}", + output: "var x = {[ a ]: b}", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "Property", + line: 1, + column: 13, + endLine: 1, + endColumn: 14, + }, + ], + }, - invalid: [ - { - code: "var foo = obj[ 1];", - output: "var foo = obj[ 1 ];", - options: ["always"], - errors: [ - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MemberExpression", - line: 1, - column: 17, - endLine: 1, - endColumn: 18 + // never - objectLiteralComputedProperties + { + code: "var x = {[ a ]: b}", + output: "var x = {[a]: b}", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "Property", + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "Property", + line: 1, + column: 13, + endLine: 1, + endColumn: 14, + }, + ], + }, + { + code: "var x = {[a ]: b}", + output: "var x = {[a]: b}", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "Property", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "var x = {[ a]: b}", + output: "var x = {[a]: b}", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "Property", + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, + { + code: "var x = {[ a\n]: b}", + output: "var x = {[a\n]: b}", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "Property", + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + ], + }, - } - ] - }, - { - code: "var foo = obj[1 ];", - output: "var foo = obj[ 1 ];", - options: ["always"], - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MemberExpression", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - } - ] - }, - { - code: "var foo = obj[ 1];", - output: "var foo = obj[1];", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MemberExpression", - line: 1, - column: 15, - endLine: 1, - endColumn: 16 - } - ] - }, - { - code: "var foo = obj[1 ];", - output: "var foo = obj[1];", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MemberExpression", - line: 1, - column: 16, - endLine: 1, - endColumn: 17 - } - ] - }, - { - code: "obj[ foo ]", - output: "obj[foo]", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MemberExpression", - line: 1, - column: 5, - endLine: 1, - endColumn: 6 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MemberExpression", - line: 1, - column: 9, - endLine: 1, - endColumn: 10 - } - ] - }, - { - code: "obj[foo ]", - output: "obj[foo]", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MemberExpression", - line: 1, - column: 8, - endLine: 1, - endColumn: 9 - } - ] - }, - { - code: "obj[ foo]", - output: "obj[foo]", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MemberExpression", - line: 1, - column: 5, - endLine: 1, - endColumn: 6 - } - ] - }, - { - code: "var foo = obj[1]", - output: "var foo = obj[ 1 ]", - options: ["always"], - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MemberExpression", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MemberExpression", - line: 1, - column: 16, - endLine: 1, - endColumn: 17 - } - ] - }, + // test default settings for classes + { + code: "class A { [ a ](){} }", + output: "class A { [a](){} }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "class A { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", + output: "class A { [a](){} get [b](){} set [c](foo){} static [d](){} static get [e](){} static set [f](bar){} }", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 26, + endLine: 1, + endColumn: 27, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 28, + endLine: 1, + endColumn: 29, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 40, + endLine: 1, + endColumn: 41, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 42, + endLine: 1, + endColumn: 43, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 60, + endLine: 1, + endColumn: 61, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 62, + endLine: 1, + endColumn: 63, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 81, + endLine: 1, + endColumn: 82, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 83, + endLine: 1, + endColumn: 84, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 102, + endLine: 1, + endColumn: 103, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 104, + endLine: 1, + endColumn: 105, + }, + ], + }, + { + code: "A = class { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", + output: "A = class { [a](){} get [b](){} set [c](foo){} static [d](){} static get [e](){} static set [f](bar){} }", + options: ["never", {}], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 16, + endLine: 1, + endColumn: 17, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 28, + endLine: 1, + endColumn: 29, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 30, + endLine: 1, + endColumn: 31, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 42, + endLine: 1, + endColumn: 43, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 44, + endLine: 1, + endColumn: 45, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 62, + endLine: 1, + endColumn: 63, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 64, + endLine: 1, + endColumn: 65, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 83, + endLine: 1, + endColumn: 84, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 85, + endLine: 1, + endColumn: 86, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 104, + endLine: 1, + endColumn: 105, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 106, + endLine: 1, + endColumn: 107, + }, + ], + }, + { + code: "A = class { [a](){} }", + output: "A = class { [ a ](){} }", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 13, + endLine: 1, + endColumn: 14, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 15, + endLine: 1, + endColumn: 16, + }, + ], + }, + { + code: "A = class { [a](){} get [b](){} set [c](foo){} static [d](){} static get [e](){} static set [f](bar){} }", + output: "A = class { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 13, + endLine: 1, + endColumn: 14, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 15, + endLine: 1, + endColumn: 16, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 25, + endLine: 1, + endColumn: 26, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 27, + endLine: 1, + endColumn: 28, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 37, + endLine: 1, + endColumn: 38, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 39, + endLine: 1, + endColumn: 40, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 55, + endLine: 1, + endColumn: 56, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 57, + endLine: 1, + endColumn: 58, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 74, + endLine: 1, + endColumn: 75, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 76, + endLine: 1, + endColumn: 77, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 93, + endLine: 1, + endColumn: 94, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 95, + endLine: 1, + endColumn: 96, + }, + ], + }, + { + code: "class A { [a](){} get [b](){} set [c](foo){} static [d](){} static get [e](){} static set [f](bar){} }", + output: "class A { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", + options: ["always", {}], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 11, + endLine: 1, + endColumn: 12, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 13, + endLine: 1, + endColumn: 14, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 23, + endLine: 1, + endColumn: 24, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 25, + endLine: 1, + endColumn: 26, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 35, + endLine: 1, + endColumn: 36, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 37, + endLine: 1, + endColumn: 38, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 53, + endLine: 1, + endColumn: 54, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 55, + endLine: 1, + endColumn: 56, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 72, + endLine: 1, + endColumn: 73, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 74, + endLine: 1, + endColumn: 75, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 91, + endLine: 1, + endColumn: 92, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 93, + endLine: 1, + endColumn: 94, + }, + ], + }, - // multiple spaces - { - code: "obj[ foo]", - output: "obj[foo]", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MemberExpression", - line: 1, - column: 5, - endLine: 1, - endColumn: 9 - } - ] - }, - { - code: "obj[ foo ]", - output: "obj[foo]", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MemberExpression", - line: 1, - column: 5, - endLine: 1, - endColumn: 7 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MemberExpression", - line: 1, - column: 10, - endLine: 1, - endColumn: 12 - } - ] - }, - { - code: "obj[ foo ]", - output: "obj[foo]", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MemberExpression", - line: 1, - column: 5, - endLine: 1, - endColumn: 8 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MemberExpression", - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - } - ] - }, - { - code: "obj[ foo + \n bar ]", - output: "obj[foo + \n bar]", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MemberExpression", - line: 1, - column: 5, - endLine: 1, - endColumn: 6 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MemberExpression", - line: 2, - column: 6, - endLine: 2, - endColumn: 9 - } - ] - }, - { - code: "obj[\n foo ]", - output: "obj[\n foo]", - options: ["never"], - errors: [ - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MemberExpression", - line: 2, - column: 5, - endLine: 2, - endColumn: 7 - } - ] - }, + // never - classes + { + code: "class A { [ a](){} }", + output: "class A { [a](){} }", + options: ["never", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 12, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "A = class { [a](){} b(){} static [c ](){} static [d](){}}", + output: "A = class { [a](){} b(){} static [c](){} static [d](){}}", + options: ["never", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 36, + endLine: 1, + endColumn: 37, + }, + ], + }, + { + code: "class A { get [a ](){} set [ a](foo){} get b(){} static set b(bar){} static get [ a](){} static set [a ](baz){} }", + output: "class A { get [a](){} set [a](foo){} get b(){} static set b(bar){} static get [a](){} static set [a](baz){} }", + options: ["never", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 17, + endLine: 1, + endColumn: 18, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 29, + endLine: 1, + endColumn: 30, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 82, + endLine: 1, + endColumn: 83, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 103, + endLine: 1, + endColumn: 104, + }, + ], + }, + { + code: "A = class { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", + output: "A = class { [a](){} get [b](){} set [c](foo){} static [d](){} static get [e](){} static set [f](bar){} }", + options: ["never", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 16, + endLine: 1, + endColumn: 17, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 28, + endLine: 1, + endColumn: 29, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 30, + endLine: 1, + endColumn: 31, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 42, + endLine: 1, + endColumn: 43, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 44, + endLine: 1, + endColumn: 45, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 62, + endLine: 1, + endColumn: 63, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 64, + endLine: 1, + endColumn: 65, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 83, + endLine: 1, + endColumn: 84, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 85, + endLine: 1, + endColumn: 86, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 104, + endLine: 1, + endColumn: 105, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 106, + endLine: 1, + endColumn: 107, + }, + ], + }, + { + code: "class A { [ a]; [b ]; [ c ]; [ a] = 0; [b ] = 0; [ c ] = 0; }", + output: "class A { [a]; [b]; [c]; [a] = 0; [b] = 0; [c] = 0; }", + options: ["never", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + column: 12, + endColumn: 13, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + column: 19, + endColumn: 20, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + column: 24, + endColumn: 25, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + column: 26, + endColumn: 27, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + column: 31, + endColumn: 32, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + column: 42, + endColumn: 43, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + column: 51, + endColumn: 52, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + column: 53, + endColumn: 54, + }, + ], + }, - // always - objectLiteralComputedProperties - { - code: "var x = {[a]: b}", - output: "var x = {[ a ]: b}", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "Property", - line: 1, - column: 10, - endLine: 1, - endColumn: 11 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "Property", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - } - ] - }, - { - code: "var x = {[a ]: b}", - output: "var x = {[ a ]: b}", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "Property", - line: 1, - column: 10, - endLine: 1, - endColumn: 11 - } - ] - }, - { - code: "var x = {[ a]: b}", - output: "var x = {[ a ]: b}", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "Property", - line: 1, - column: 13, - endLine: 1, - endColumn: 14 - } - ] - }, + // always - classes + { + code: "class A { [ a](){} }", + output: "class A { [ a ](){} }", + options: ["always", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 14, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "A = class { [ a ](){} b(){} static [c ](){} static [ d ](){}}", + output: "A = class { [ a ](){} b(){} static [ c ](){} static [ d ](){}}", + options: ["always", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 36, + endLine: 1, + endColumn: 37, + }, + ], + }, + { + code: "class A { get [a ](){} set [ a](foo){} get b(){} static set b(bar){} static get [ a](){} static set [a ](baz){} }", + output: "class A { get [ a ](){} set [ a ](foo){} get b(){} static set b(bar){} static get [ a ](){} static set [ a ](baz){} }", + options: ["always", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 15, + endLine: 1, + endColumn: 16, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 31, + endLine: 1, + endColumn: 32, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 84, + endLine: 1, + endColumn: 85, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 101, + endLine: 1, + endColumn: 102, + }, + ], + }, + { + code: "A = class { [a](){} get [b](){} set [c](foo){} static [d](){} static get [e](){} static set [f](bar){} }", + output: "A = class { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", + options: ["always", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 13, + endLine: 1, + endColumn: 14, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 15, + endLine: 1, + endColumn: 16, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 25, + endLine: 1, + endColumn: 26, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 27, + endLine: 1, + endColumn: 28, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 37, + endLine: 1, + endColumn: 38, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 39, + endLine: 1, + endColumn: 40, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 55, + endLine: 1, + endColumn: 56, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 57, + endLine: 1, + endColumn: 58, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 74, + endLine: 1, + endColumn: 75, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 76, + endLine: 1, + endColumn: 77, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MethodDefinition", + line: 1, + column: 93, + endLine: 1, + endColumn: 94, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MethodDefinition", + line: 1, + column: 95, + endLine: 1, + endColumn: 96, + }, + ], + }, + { + code: "class A { [ a]; [b ]; [c]; [ a] = 0; [b ] = 0; [c] = 0; }", + output: "class A { [ a ]; [ b ]; [ c ]; [ a ] = 0; [ b ] = 0; [ c ] = 0; }", + options: ["always", { enforceForClassMembers: true }], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "missingSpaceBefore", + column: 14, + endColumn: 15, + }, + { + messageId: "missingSpaceAfter", + column: 17, + endColumn: 18, + }, + { + messageId: "missingSpaceAfter", + column: 23, + endColumn: 24, + }, + { + messageId: "missingSpaceBefore", + column: 25, + endColumn: 26, + }, + { + messageId: "missingSpaceBefore", + column: 31, + endColumn: 32, + }, + { + messageId: "missingSpaceAfter", + column: 38, + endColumn: 39, + }, + { + messageId: "missingSpaceAfter", + column: 48, + endColumn: 49, + }, + { + messageId: "missingSpaceBefore", + column: 50, + endColumn: 51, + }, + ], + }, - // never - objectLiteralComputedProperties - { - code: "var x = {[ a ]: b}", - output: "var x = {[a]: b}", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "Property", - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "Property", - line: 1, - column: 13, - endLine: 1, - endColumn: 14 - } - ] - }, - { - code: "var x = {[a ]: b}", - output: "var x = {[a]: b}", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "Property", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - } - ] - }, - { - code: "var x = {[ a]: b}", - output: "var x = {[a]: b}", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "Property", - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - } - ] - }, - { - code: "var x = {[ a\n]: b}", - output: "var x = {[a\n]: b}", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "Property", - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - } - ] - }, + // handling of parens and comments + { + code: ["const foo = {", " [(a)]: 1", "}"].join("\n"), + output: ["const foo = {", " [ (a) ]: 1", "}"].join("\n"), + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "Property", + line: 2, + column: 3, + endLine: 2, + endColumn: 4, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "Property", + line: 2, + column: 7, + endLine: 2, + endColumn: 8, + }, + ], + }, + { + code: ["const foo = {", " [( a )]: 1", "}"].join("\n"), + output: ["const foo = {", " [ ( a ) ]: 1", "}"].join("\n"), + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "Property", + line: 2, + column: 3, + endLine: 2, + endColumn: 4, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "Property", + line: 2, + column: 9, + endLine: 2, + endColumn: 10, + }, + ], + }, + { + code: ["const foo = {", " [ ( a ) ]: 1", "}"].join("\n"), + output: ["const foo = {", " [( a )]: 1", "}"].join("\n"), + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "Property", + line: 2, + column: 4, + endLine: 2, + endColumn: 5, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "Property", + line: 2, + column: 10, + endLine: 2, + endColumn: 11, + }, + ], + }, + { + code: ["const foo = {", " [/**/ a /**/]: 1", "}"].join("\n"), + output: ["const foo = {", " [ /**/ a /**/ ]: 1", "}"].join("\n"), + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "Property", + line: 2, + column: 3, + endLine: 2, + endColumn: 4, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "Property", + line: 2, + column: 15, + endLine: 2, + endColumn: 16, + }, + ], + }, + { + code: ["const foo = {", " [ /**/ a /**/ ]: 1", "}"].join("\n"), + output: ["const foo = {", " [/**/ a /**/]: 1", "}"].join("\n"), + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "Property", + line: 2, + column: 4, + endLine: 2, + endColumn: 5, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "Property", + line: 2, + column: 16, + endLine: 2, + endColumn: 17, + }, + ], + }, + { + code: ["const foo = {", " [a[b]]: 1", "}"].join("\n"), + output: ["const foo = {", " [ a[ b ] ]: 1", "}"].join("\n"), + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "Property", + line: 2, + column: 3, + endLine: 2, + endColumn: 4, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MemberExpression", + line: 2, + column: 5, + endLine: 2, + endColumn: 6, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "MemberExpression", + line: 2, + column: 7, + endLine: 2, + endColumn: 8, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "Property", + line: 2, + column: 8, + endLine: 2, + endColumn: 9, + }, + ], + }, + { + code: ["const foo = {", " [ a[ b ] ]: 1", "}"].join("\n"), + output: ["const foo = {", " [a[b]]: 1", "}"].join("\n"), + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "Property", + line: 2, + column: 4, + endLine: 2, + endColumn: 5, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MemberExpression", + line: 2, + column: 7, + endLine: 2, + endColumn: 8, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MemberExpression", + line: 2, + column: 9, + endLine: 2, + endColumn: 10, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "Property", + line: 2, + column: 11, + endLine: 2, + endColumn: 12, + }, + ], + }, + { + code: ["const foo = {", " [a[/**/ b ]/**/]: 1", "}"].join("\n"), + output: ["const foo = {", " [ a[ /**/ b ]/**/ ]: 1", "}"].join( + "\n", + ), + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "Property", + line: 2, + column: 3, + endLine: 2, + endColumn: 4, + }, + { + messageId: "missingSpaceAfter", + data: { tokenValue: "[" }, + type: "MemberExpression", + line: 2, + column: 5, + endLine: 2, + endColumn: 6, + }, + { + messageId: "missingSpaceBefore", + data: { tokenValue: "]" }, + type: "Property", + line: 2, + column: 18, + endLine: 2, + endColumn: 19, + }, + ], + }, + { + code: ["const foo = {", " [ /**/a[ b /**/ ] /**/]: 1", "}"].join( + "\n", + ), + output: ["const foo = {", " [/**/a[b /**/] /**/]: 1", "}"].join( + "\n", + ), + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "Property", + line: 2, + column: 4, + endLine: 2, + endColumn: 5, + }, + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + type: "MemberExpression", + line: 2, + column: 11, + endLine: 2, + endColumn: 12, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + type: "MemberExpression", + line: 2, + column: 18, + endLine: 2, + endColumn: 19, + }, + ], + }, - // test default settings for classes - { - code: "class A { [ a ](){} }", - output: "class A { [a](){} }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - } - ] - }, - { - code: "class A { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", - output: "class A { [a](){} get [b](){} set [c](foo){} static [d](){} static get [e](){} static set [f](bar){} }", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 26, - endLine: 1, - endColumn: 27 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 28, - endLine: 1, - endColumn: 29 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 40, - endLine: 1, - endColumn: 41 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 42, - endLine: 1, - endColumn: 43 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 60, - endLine: 1, - endColumn: 61 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 62, - endLine: 1, - endColumn: 63 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 81, - endLine: 1, - endColumn: 82 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 83, - endLine: 1, - endColumn: 84 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 102, - endLine: 1, - endColumn: 103 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 104, - endLine: 1, - endColumn: 105 - } - ] - }, - { - code: "A = class { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", - output: "A = class { [a](){} get [b](){} set [c](foo){} static [d](){} static get [e](){} static set [f](bar){} }", - options: ["never", {}], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 16, - endLine: 1, - endColumn: 17 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 28, - endLine: 1, - endColumn: 29 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 30, - endLine: 1, - endColumn: 31 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 42, - endLine: 1, - endColumn: 43 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 44, - endLine: 1, - endColumn: 45 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 62, - endLine: 1, - endColumn: 63 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 64, - endLine: 1, - endColumn: 65 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 83, - endLine: 1, - endColumn: 84 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 85, - endLine: 1, - endColumn: 86 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 104, - endLine: 1, - endColumn: 105 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 106, - endLine: 1, - endColumn: 107 - } - ] - }, - { - code: "A = class { [a](){} }", - output: "A = class { [ a ](){} }", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 13, - endLine: 1, - endColumn: 14 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 15, - endLine: 1, - endColumn: 16 - } - ] - }, - { - code: "A = class { [a](){} get [b](){} set [c](foo){} static [d](){} static get [e](){} static set [f](bar){} }", - output: "A = class { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 13, - endLine: 1, - endColumn: 14 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 15, - endLine: 1, - endColumn: 16 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 25, - endLine: 1, - endColumn: 26 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 27, - endLine: 1, - endColumn: 28 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 37, - endLine: 1, - endColumn: 38 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 39, - endLine: 1, - endColumn: 40 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 55, - endLine: 1, - endColumn: 56 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 57, - endLine: 1, - endColumn: 58 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 74, - endLine: 1, - endColumn: 75 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 76, - endLine: 1, - endColumn: 77 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 93, - endLine: 1, - endColumn: 94 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 95, - endLine: 1, - endColumn: 96 - } - ] - }, - { - code: "class A { [a](){} get [b](){} set [c](foo){} static [d](){} static get [e](){} static set [f](bar){} }", - output: "class A { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", - options: ["always", {}], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 11, - endLine: 1, - endColumn: 12 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 13, - endLine: 1, - endColumn: 14 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 23, - endLine: 1, - endColumn: 24 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 25, - endLine: 1, - endColumn: 26 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 35, - endLine: 1, - endColumn: 36 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 37, - endLine: 1, - endColumn: 38 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 53, - endLine: 1, - endColumn: 54 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 55, - endLine: 1, - endColumn: 56 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 72, - endLine: 1, - endColumn: 73 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 74, - endLine: 1, - endColumn: 75 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 91, - endLine: 1, - endColumn: 92 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 93, - endLine: 1, - endColumn: 94 - } - ] - }, + // Optional chaining + { + code: "obj?.[1];", + output: "obj?.[ 1 ];", + options: ["always"], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { messageId: "missingSpaceAfter", data: { tokenValue: "[" } }, + { messageId: "missingSpaceBefore", data: { tokenValue: "]" } }, + ], + }, + { + code: "obj?.[ 1 ];", + output: "obj?.[1];", + options: ["never"], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + }, + ], + }, - // never - classes - { - code: "class A { [ a](){} }", - output: "class A { [a](){} }", - options: ["never", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 12, - endLine: 1, - endColumn: 13 - } - ] - }, - { - code: "A = class { [a](){} b(){} static [c ](){} static [d](){}}", - output: "A = class { [a](){} b(){} static [c](){} static [d](){}}", - options: ["never", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 36, - endLine: 1, - endColumn: 37 - } - ] - }, - { - code: "class A { get [a ](){} set [ a](foo){} get b(){} static set b(bar){} static get [ a](){} static set [a ](baz){} }", - output: "class A { get [a](){} set [a](foo){} get b(){} static set b(bar){} static get [a](){} static set [a](baz){} }", - options: ["never", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 17, - endLine: 1, - endColumn: 18 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 29, - endLine: 1, - endColumn: 30 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 82, - endLine: 1, - endColumn: 83 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 103, - endLine: 1, - endColumn: 104 - } - ] - }, - { - code: "A = class { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", - output: "A = class { [a](){} get [b](){} set [c](foo){} static [d](){} static get [e](){} static set [f](bar){} }", - options: ["never", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 16, - endLine: 1, - endColumn: 17 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 28, - endLine: 1, - endColumn: 29 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 30, - endLine: 1, - endColumn: 31 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 42, - endLine: 1, - endColumn: 43 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 44, - endLine: 1, - endColumn: 45 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 62, - endLine: 1, - endColumn: 63 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 64, - endLine: 1, - endColumn: 65 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 83, - endLine: 1, - endColumn: 84 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 85, - endLine: 1, - endColumn: 86 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 104, - endLine: 1, - endColumn: 105 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 106, - endLine: 1, - endColumn: 107 - } - ] - }, - { - code: "class A { [ a]; [b ]; [ c ]; [ a] = 0; [b ] = 0; [ c ] = 0; }", - output: "class A { [a]; [b]; [c]; [a] = 0; [b] = 0; [c] = 0; }", - options: ["never", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - column: 12, - endColumn: 13 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - column: 19, - endColumn: 20 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - column: 24, - endColumn: 25 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - column: 26, - endColumn: 27 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - column: 31, - endColumn: 32 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - column: 42, - endColumn: 43 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - column: 51, - endColumn: 52 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - column: 53, - endColumn: 54 - } - ] - }, - - // always - classes - { - code: "class A { [ a](){} }", - output: "class A { [ a ](){} }", - options: ["always", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 14, - endLine: 1, - endColumn: 15 - } - ] - }, - { - code: "A = class { [ a ](){} b(){} static [c ](){} static [ d ](){}}", - output: "A = class { [ a ](){} b(){} static [ c ](){} static [ d ](){}}", - options: ["always", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 36, - endLine: 1, - endColumn: 37 - } - ] - }, - { - code: "class A { get [a ](){} set [ a](foo){} get b(){} static set b(bar){} static get [ a](){} static set [a ](baz){} }", - output: "class A { get [ a ](){} set [ a ](foo){} get b(){} static set b(bar){} static get [ a ](){} static set [ a ](baz){} }", - options: ["always", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 15, - endLine: 1, - endColumn: 16 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 31, - endLine: 1, - endColumn: 32 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 84, - endLine: 1, - endColumn: 85 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 101, - endLine: 1, - endColumn: 102 - } - ] - }, - { - code: "A = class { [a](){} get [b](){} set [c](foo){} static [d](){} static get [e](){} static set [f](bar){} }", - output: "A = class { [ a ](){} get [ b ](){} set [ c ](foo){} static [ d ](){} static get [ e ](){} static set [ f ](bar){} }", - options: ["always", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 13, - endLine: 1, - endColumn: 14 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 15, - endLine: 1, - endColumn: 16 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 25, - endLine: 1, - endColumn: 26 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 27, - endLine: 1, - endColumn: 28 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 37, - endLine: 1, - endColumn: 38 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 39, - endLine: 1, - endColumn: 40 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 55, - endLine: 1, - endColumn: 56 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 57, - endLine: 1, - endColumn: 58 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 74, - endLine: 1, - endColumn: 75 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 76, - endLine: 1, - endColumn: 77 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MethodDefinition", - line: 1, - column: 93, - endLine: 1, - endColumn: 94 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MethodDefinition", - line: 1, - column: 95, - endLine: 1, - endColumn: 96 - } - ] - }, - { - code: "class A { [ a]; [b ]; [c]; [ a] = 0; [b ] = 0; [c] = 0; }", - output: "class A { [ a ]; [ b ]; [ c ]; [ a ] = 0; [ b ] = 0; [ c ] = 0; }", - options: ["always", { enforceForClassMembers: true }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - messageId: "missingSpaceBefore", - column: 14, - endColumn: 15 - }, - { - messageId: "missingSpaceAfter", - column: 17, - endColumn: 18 - }, - { - messageId: "missingSpaceAfter", - column: 23, - endColumn: 24 - }, - { - messageId: "missingSpaceBefore", - column: 25, - endColumn: 26 - }, - { - messageId: "missingSpaceBefore", - column: 31, - endColumn: 32 - }, - { - messageId: "missingSpaceAfter", - column: 38, - endColumn: 39 - }, - { - messageId: "missingSpaceAfter", - column: 48, - endColumn: 49 - }, - { - messageId: "missingSpaceBefore", - column: 50, - endColumn: 51 - } - ] - }, - - // handling of parens and comments - { - code: [ - "const foo = {", - " [(a)]: 1", - "}" - ].join("\n"), - output: [ - "const foo = {", - " [ (a) ]: 1", - "}" - ].join("\n"), - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "Property", - line: 2, - column: 3, - endLine: 2, - endColumn: 4 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "Property", - line: 2, - column: 7, - endLine: 2, - endColumn: 8 - } - ] - }, - { - code: [ - "const foo = {", - " [( a )]: 1", - "}" - ].join("\n"), - output: [ - "const foo = {", - " [ ( a ) ]: 1", - "}" - ].join("\n"), - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "Property", - line: 2, - column: 3, - endLine: 2, - endColumn: 4 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "Property", - line: 2, - column: 9, - endLine: 2, - endColumn: 10 - } - ] - }, - { - code: [ - "const foo = {", - " [ ( a ) ]: 1", - "}" - ].join("\n"), - output: [ - "const foo = {", - " [( a )]: 1", - "}" - ].join("\n"), - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "Property", - line: 2, - column: 4, - endLine: 2, - endColumn: 5 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "Property", - line: 2, - column: 10, - endLine: 2, - endColumn: 11 - } - ] - }, - { - code: [ - "const foo = {", - " [/**/ a /**/]: 1", - "}" - ].join("\n"), - output: [ - "const foo = {", - " [ /**/ a /**/ ]: 1", - "}" - ].join("\n"), - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "Property", - line: 2, - column: 3, - endLine: 2, - endColumn: 4 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "Property", - line: 2, - column: 15, - endLine: 2, - endColumn: 16 - } - ] - }, - { - code: [ - "const foo = {", - " [ /**/ a /**/ ]: 1", - "}" - ].join("\n"), - output: [ - "const foo = {", - " [/**/ a /**/]: 1", - "}" - ].join("\n"), - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "Property", - line: 2, - column: 4, - endLine: 2, - endColumn: 5 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "Property", - line: 2, - column: 16, - endLine: 2, - endColumn: 17 - } - ] - }, - { - code: [ - "const foo = {", - " [a[b]]: 1", - "}" - ].join("\n"), - output: [ - "const foo = {", - " [ a[ b ] ]: 1", - "}" - ].join("\n"), - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "Property", - line: 2, - column: 3, - endLine: 2, - endColumn: 4 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MemberExpression", - line: 2, - column: 5, - endLine: 2, - endColumn: 6 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "MemberExpression", - line: 2, - column: 7, - endLine: 2, - endColumn: 8 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "Property", - line: 2, - column: 8, - endLine: 2, - endColumn: 9 - } - ] - }, - { - code: [ - "const foo = {", - " [ a[ b ] ]: 1", - "}" - ].join("\n"), - output: [ - "const foo = {", - " [a[b]]: 1", - "}" - ].join("\n"), - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "Property", - line: 2, - column: 4, - endLine: 2, - endColumn: 5 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MemberExpression", - line: 2, - column: 7, - endLine: 2, - endColumn: 8 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MemberExpression", - line: 2, - column: 9, - endLine: 2, - endColumn: 10 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "Property", - line: 2, - column: 11, - endLine: 2, - endColumn: 12 - } - ] - }, - { - code: [ - "const foo = {", - " [a[/**/ b ]/**/]: 1", - "}" - ].join("\n"), - output: [ - "const foo = {", - " [ a[ /**/ b ]/**/ ]: 1", - "}" - ].join("\n"), - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "Property", - line: 2, - column: 3, - endLine: 2, - endColumn: 4 - }, - { - messageId: "missingSpaceAfter", - data: { tokenValue: "[" }, - type: "MemberExpression", - line: 2, - column: 5, - endLine: 2, - endColumn: 6 - }, - { - messageId: "missingSpaceBefore", - data: { tokenValue: "]" }, - type: "Property", - line: 2, - column: 18, - endLine: 2, - endColumn: 19 - } - ] - }, - { - code: [ - "const foo = {", - " [ /**/a[ b /**/ ] /**/]: 1", - "}" - ].join("\n"), - output: [ - "const foo = {", - " [/**/a[b /**/] /**/]: 1", - "}" - ].join("\n"), - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "Property", - line: 2, - column: 4, - endLine: 2, - endColumn: 5 - }, - { - messageId: "unexpectedSpaceAfter", - data: { tokenValue: "[" }, - type: "MemberExpression", - line: 2, - column: 11, - endLine: 2, - endColumn: 12 - }, - { - messageId: "unexpectedSpaceBefore", - data: { tokenValue: "]" }, - type: "MemberExpression", - line: 2, - column: 18, - endLine: 2, - endColumn: 19 - } - ] - }, - - // Optional chaining - { - code: "obj?.[1];", - output: "obj?.[ 1 ];", - options: ["always"], - languageOptions: { ecmaVersion: 2020 }, - errors: [ - { messageId: "missingSpaceAfter", data: { tokenValue: "[" } }, - { messageId: "missingSpaceBefore", data: { tokenValue: "]" } } - ] - }, - { - code: "obj?.[ 1 ];", - output: "obj?.[1];", - options: ["never"], - languageOptions: { ecmaVersion: 2020 }, - errors: [ - { messageId: "unexpectedSpaceAfter", data: { tokenValue: "[" } }, - { messageId: "unexpectedSpaceBefore", data: { tokenValue: "]" } } - ] - }, - - // Destructuring Assignment - { - code: "const { [ a]: someProp } = obj;", - output: "const { [a]: someProp } = obj;", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "unexpectedSpaceAfter", data: { tokenValue: "[" } } - ] - }, - { - code: "const { [a ]: someProp } = obj;", - output: "const { [a]: someProp } = obj;", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "unexpectedSpaceBefore", data: { tokenValue: "]" } } - ] - }, - { - code: "const { [ a ]: someProp } = obj;", - output: "const { [a]: someProp } = obj;", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "unexpectedSpaceAfter", data: { tokenValue: "[" } }, - { messageId: "unexpectedSpaceBefore", data: { tokenValue: "]" } } - ] - }, - { - code: "({ [ a ]: someProp } = obj);", - output: "({ [a]: someProp } = obj);", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "unexpectedSpaceAfter", data: { tokenValue: "[" } }, - { messageId: "unexpectedSpaceBefore", data: { tokenValue: "]" } } - ] - }, - { - code: "const { [a]: someProp } = obj;", - output: "const { [ a ]: someProp } = obj;", - options: ["always"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "missingSpaceAfter", data: { tokenValue: "[" } }, - { messageId: "missingSpaceBefore", data: { tokenValue: "]" } } - ] - }, - { - code: "({ [a]: someProp } = obj);", - output: "({ [ a ]: someProp } = obj);", - options: ["always"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "missingSpaceAfter", data: { tokenValue: "[" } }, - { messageId: "missingSpaceBefore", data: { tokenValue: "]" } } - ] - } - ] + // Destructuring Assignment + { + code: "const { [ a]: someProp } = obj;", + output: "const { [a]: someProp } = obj;", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + }, + ], + }, + { + code: "const { [a ]: someProp } = obj;", + output: "const { [a]: someProp } = obj;", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + }, + ], + }, + { + code: "const { [ a ]: someProp } = obj;", + output: "const { [a]: someProp } = obj;", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + }, + ], + }, + { + code: "({ [ a ]: someProp } = obj);", + output: "({ [a]: someProp } = obj);", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "unexpectedSpaceAfter", + data: { tokenValue: "[" }, + }, + { + messageId: "unexpectedSpaceBefore", + data: { tokenValue: "]" }, + }, + ], + }, + { + code: "const { [a]: someProp } = obj;", + output: "const { [ a ]: someProp } = obj;", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingSpaceAfter", data: { tokenValue: "[" } }, + { messageId: "missingSpaceBefore", data: { tokenValue: "]" } }, + ], + }, + { + code: "({ [a]: someProp } = obj);", + output: "({ [ a ]: someProp } = obj);", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { messageId: "missingSpaceAfter", data: { tokenValue: "[" } }, + { messageId: "missingSpaceBefore", data: { tokenValue: "]" } }, + ], + }, + ], }); diff --git a/tests/lib/rules/consistent-return.js b/tests/lib/rules/consistent-return.js index 2e01eef6fd3a..bf82bce373f1 100644 --- a/tests/lib/rules/consistent-return.js +++ b/tests/lib/rules/consistent-return.js @@ -9,354 +9,392 @@ // Requirements //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/consistent-return"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, }); ruleTester.run("consistent-return", rule, { + valid: [ + "function foo() { return; }", + "function foo() { if (true) return; }", + "function foo() { if (true) return; else return; }", + "function foo() { if (true) return true; else return false; }", + "f(function() { return; })", + "f(function() { if (true) return; })", + "f(function() { if (true) return; else return; })", + "f(function() { if (true) return true; else return false; })", + "function foo() { function bar() { return true; } return; }", + "function foo() { function bar() { return; } return false; }", + "function Foo() { if (!(this instanceof Foo)) return new Foo(); }", + "function foo() { if (true) return 5; else return undefined; }", + "function foo() { if (true) return 5; else return void 0; }", + { + code: "function foo() { if (true) return; else return undefined; }", + options: [{ treatUndefinedAsUnspecified: true }], + }, + { + code: "function foo() { if (true) return; else return void 0; }", + options: [{ treatUndefinedAsUnspecified: true }], + }, + { + code: "function foo() { if (true) return undefined; else return; }", + options: [{ treatUndefinedAsUnspecified: true }], + }, + { + code: "function foo() { if (true) return undefined; else return void 0; }", + options: [{ treatUndefinedAsUnspecified: true }], + }, + { + code: "function foo() { if (true) return void 0; else return; }", + options: [{ treatUndefinedAsUnspecified: true }], + }, + { + code: "function foo() { if (true) return void 0; else return undefined; }", + options: [{ treatUndefinedAsUnspecified: true }], + }, + { + code: "var x = () => { return {}; };", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "if (true) { return 1; } return 0;", + languageOptions: { + ecmaVersion: 6, + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + }, - valid: [ - "function foo() { return; }", - "function foo() { if (true) return; }", - "function foo() { if (true) return; else return; }", - "function foo() { if (true) return true; else return false; }", - "f(function() { return; })", - "f(function() { if (true) return; })", - "f(function() { if (true) return; else return; })", - "f(function() { if (true) return true; else return false; })", - "function foo() { function bar() { return true; } return; }", - "function foo() { function bar() { return; } return false; }", - "function Foo() { if (!(this instanceof Foo)) return new Foo(); }", - { code: "function foo() { if (true) return; else return undefined; }", options: [{ treatUndefinedAsUnspecified: true }] }, - { code: "function foo() { if (true) return; else return void 0; }", options: [{ treatUndefinedAsUnspecified: true }] }, - { code: "function foo() { if (true) return undefined; else return; }", options: [{ treatUndefinedAsUnspecified: true }] }, - { code: "function foo() { if (true) return undefined; else return void 0; }", options: [{ treatUndefinedAsUnspecified: true }] }, - { code: "function foo() { if (true) return void 0; else return; }", options: [{ treatUndefinedAsUnspecified: true }] }, - { code: "function foo() { if (true) return void 0; else return undefined; }", options: [{ treatUndefinedAsUnspecified: true }] }, - { code: "var x = () => { return {}; };", languageOptions: { ecmaVersion: 6 } }, - { code: "if (true) { return 1; } return 0;", languageOptions: { ecmaVersion: 6, parserOptions: { ecmaFeatures: { globalReturn: true } } } }, + // https://github.com/eslint/eslint/issues/7790 + { + code: "class Foo { constructor() { if (true) return foo; } }", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var Foo = class { constructor() { if (true) return foo; } }", + languageOptions: { ecmaVersion: 6 }, + }, + ], - // https://github.com/eslint/eslint/issues/7790 - { code: "class Foo { constructor() { if (true) return foo; } }", languageOptions: { ecmaVersion: 6 } }, - { code: "var Foo = class { constructor() { if (true) return foo; } }", languageOptions: { ecmaVersion: 6 } } - ], - - invalid: [ - { - code: "function foo() { if (true) return true; else return; }", - errors: [ - { - messageId: "missingReturnValue", - data: { name: "Function 'foo'" }, - type: "ReturnStatement", - line: 1, - column: 46, - endLine: 1, - endColumn: 53 - } - ] - }, - { - code: "var foo = () => { if (true) return true; else return; }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingReturnValue", - data: { name: "Arrow function" }, - type: "ReturnStatement", - line: 1, - column: 47, - endLine: 1, - endColumn: 54 - } - ] - }, - { - code: "function foo() { if (true) return; else return false; }", - errors: [ - { - messageId: "unexpectedReturnValue", - data: { name: "Function 'foo'" }, - type: "ReturnStatement", - line: 1, - column: 41, - endLine: 1, - endColumn: 54 - } - ] - }, - { - code: "f(function() { if (true) return true; else return; })", - errors: [ - { - messageId: "missingReturnValue", - data: { name: "Function" }, - type: "ReturnStatement", - line: 1, - column: 44, - endLine: 1, - endColumn: 51 - } - ] - }, - { - code: "f(function() { if (true) return; else return false; })", - errors: [ - { - messageId: "unexpectedReturnValue", - data: { name: "Function" }, - type: "ReturnStatement", - line: 1, - column: 39, - endLine: 1, - endColumn: 52 - } - ] - }, - { - code: "f(a => { if (true) return; else return false; })", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedReturnValue", - data: { name: "Arrow function" }, - type: "ReturnStatement", - line: 1, - column: 33, - endLine: 1, - endColumn: 46 - } - ] - }, - { - code: "function foo() { if (true) return true; return undefined; }", - options: [{ treatUndefinedAsUnspecified: true }], - errors: [ - { - messageId: "missingReturnValue", - data: { name: "Function 'foo'" }, - type: "ReturnStatement", - line: 1, - column: 41, - endLine: 1, - endColumn: 58 - } - ] - }, - { - code: "function foo() { if (true) return true; return void 0; }", - options: [{ treatUndefinedAsUnspecified: true }], - errors: [ - { - messageId: "missingReturnValue", - data: { name: "Function 'foo'" }, - type: "ReturnStatement", - line: 1, - column: 41, - endLine: 1, - endColumn: 55 - } - ] - }, - { - code: "function foo() { if (true) return undefined; return true; }", - options: [{ treatUndefinedAsUnspecified: true }], - errors: [ - { - messageId: "unexpectedReturnValue", - data: { name: "Function 'foo'" }, - type: "ReturnStatement", - line: 1, - column: 46, - endLine: 1, - endColumn: 58 - } - ] - }, - { - code: "function foo() { if (true) return void 0; return true; }", - options: [{ treatUndefinedAsUnspecified: true }], - errors: [ - { - messageId: "unexpectedReturnValue", - data: { name: "Function 'foo'" }, - type: "ReturnStatement", - line: 1, - column: 43, - endLine: 1, - endColumn: 55 - } - ] - }, - { - code: "if (true) { return 1; } return;", - languageOptions: { parserOptions: { ecmaFeatures: { globalReturn: true } } }, - errors: [ - { - messageId: "missingReturnValue", - data: { name: "Program" }, - type: "ReturnStatement", - line: 1, - column: 25, - endLine: 1, - endColumn: 32 - } - ] - }, - { - code: "function foo() { if (a) return true; }", - errors: [ - { - messageId: "missingReturn", - data: { name: "function 'foo'" }, - type: "FunctionDeclaration", - line: 1, - column: 10, - endLine: 1, - endColumn: 13 - } - ] - }, - { - code: "function _foo() { if (a) return true; }", - errors: [ - { - messageId: "missingReturn", - data: { name: "function '_foo'" }, - type: "FunctionDeclaration", - line: 1, - column: 10, - endLine: 1, - endColumn: 14 - } - ] - }, - { - code: "f(function foo() { if (a) return true; });", - errors: [ - { - messageId: "missingReturn", - data: { name: "function 'foo'" }, - type: "FunctionExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 15 - } - ] - }, - { - code: "f(function() { if (a) return true; });", - errors: [ - { - messageId: "missingReturn", - data: { name: "function" }, - type: "FunctionExpression", - line: 1, - column: 3, - endLine: 1, - endColumn: 11 - } - ] - }, - { - code: "f(() => { if (a) return true; });", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingReturn", - data: { name: "arrow function" }, - type: "ArrowFunctionExpression", - line: 1, - column: 6, - endLine: 1, - endColumn: 8 - } - ] - }, - { - code: "var obj = {foo() { if (a) return true; }};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingReturn", - data: { name: "method 'foo'" }, - type: "FunctionExpression", - line: 1, - column: 12, - endLine: 1, - endColumn: 15 - } - ] - }, - { - code: "class A {foo() { if (a) return true; }};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingReturn", - data: { name: "method 'foo'" }, - type: "FunctionExpression", - line: 1, - column: 10, - endLine: 1, - endColumn: 13 - } - ] - }, - { - code: "if (a) return true;", - languageOptions: { parserOptions: { ecmaFeatures: { globalReturn: true } } }, - errors: [ - { - messageId: "missingReturn", - data: { name: "program" }, - type: "Program", - line: 1, - column: 1, - endLine: void 0, - endColumn: void 0 - } - ] - }, - { - code: "class A { CapitalizedFunction() { if (a) return true; } }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingReturn", - data: { name: "method 'CapitalizedFunction'" }, - type: "FunctionExpression", - line: 1, - column: 11, - endLine: 1, - endColumn: 30 - } - ] - }, - { - code: "({ constructor() { if (a) return true; } });", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingReturn", - data: { name: "method 'constructor'" }, - type: "FunctionExpression", - line: 1, - column: 4, - endLine: 1, - endColumn: 15 - } - ] - } - ] + invalid: [ + { + code: "function foo() { if (true) return true; else return; }", + errors: [ + { + messageId: "missingReturnValue", + data: { name: "Function 'foo'" }, + type: "ReturnStatement", + line: 1, + column: 46, + endLine: 1, + endColumn: 53, + }, + ], + }, + { + code: "var foo = () => { if (true) return true; else return; }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturnValue", + data: { name: "Arrow function" }, + type: "ReturnStatement", + line: 1, + column: 47, + endLine: 1, + endColumn: 54, + }, + ], + }, + { + code: "function foo() { if (true) return; else return false; }", + errors: [ + { + messageId: "unexpectedReturnValue", + data: { name: "Function 'foo'" }, + type: "ReturnStatement", + line: 1, + column: 41, + endLine: 1, + endColumn: 54, + }, + ], + }, + { + code: "f(function() { if (true) return true; else return; })", + errors: [ + { + messageId: "missingReturnValue", + data: { name: "Function" }, + type: "ReturnStatement", + line: 1, + column: 44, + endLine: 1, + endColumn: 51, + }, + ], + }, + { + code: "f(function() { if (true) return; else return false; })", + errors: [ + { + messageId: "unexpectedReturnValue", + data: { name: "Function" }, + type: "ReturnStatement", + line: 1, + column: 39, + endLine: 1, + endColumn: 52, + }, + ], + }, + { + code: "f(a => { if (true) return; else return false; })", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedReturnValue", + data: { name: "Arrow function" }, + type: "ReturnStatement", + line: 1, + column: 33, + endLine: 1, + endColumn: 46, + }, + ], + }, + { + code: "function foo() { if (true) return true; return undefined; }", + options: [{ treatUndefinedAsUnspecified: true }], + errors: [ + { + messageId: "missingReturnValue", + data: { name: "Function 'foo'" }, + type: "ReturnStatement", + line: 1, + column: 41, + endLine: 1, + endColumn: 58, + }, + ], + }, + { + code: "function foo() { if (true) return true; return void 0; }", + options: [{ treatUndefinedAsUnspecified: true }], + errors: [ + { + messageId: "missingReturnValue", + data: { name: "Function 'foo'" }, + type: "ReturnStatement", + line: 1, + column: 41, + endLine: 1, + endColumn: 55, + }, + ], + }, + { + code: "function foo() { if (true) return undefined; return true; }", + options: [{ treatUndefinedAsUnspecified: true }], + errors: [ + { + messageId: "unexpectedReturnValue", + data: { name: "Function 'foo'" }, + type: "ReturnStatement", + line: 1, + column: 46, + endLine: 1, + endColumn: 58, + }, + ], + }, + { + code: "function foo() { if (true) return void 0; return true; }", + options: [{ treatUndefinedAsUnspecified: true }], + errors: [ + { + messageId: "unexpectedReturnValue", + data: { name: "Function 'foo'" }, + type: "ReturnStatement", + line: 1, + column: 43, + endLine: 1, + endColumn: 55, + }, + ], + }, + { + code: "if (true) { return 1; } return;", + languageOptions: { + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + errors: [ + { + messageId: "missingReturnValue", + data: { name: "Program" }, + type: "ReturnStatement", + line: 1, + column: 25, + endLine: 1, + endColumn: 32, + }, + ], + }, + { + code: "function foo() { if (a) return true; }", + errors: [ + { + messageId: "missingReturn", + data: { name: "function 'foo'" }, + type: "FunctionDeclaration", + line: 1, + column: 10, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "function _foo() { if (a) return true; }", + errors: [ + { + messageId: "missingReturn", + data: { name: "function '_foo'" }, + type: "FunctionDeclaration", + line: 1, + column: 10, + endLine: 1, + endColumn: 14, + }, + ], + }, + { + code: "f(function foo() { if (a) return true; });", + errors: [ + { + messageId: "missingReturn", + data: { name: "function 'foo'" }, + type: "FunctionExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "f(function() { if (a) return true; });", + errors: [ + { + messageId: "missingReturn", + data: { name: "function" }, + type: "FunctionExpression", + line: 1, + column: 3, + endLine: 1, + endColumn: 11, + }, + ], + }, + { + code: "f(() => { if (a) return true; });", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + data: { name: "arrow function" }, + type: "ArrowFunctionExpression", + line: 1, + column: 6, + endLine: 1, + endColumn: 8, + }, + ], + }, + { + code: "var obj = {foo() { if (a) return true; }};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + data: { name: "method 'foo'" }, + type: "FunctionExpression", + line: 1, + column: 12, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "class A {foo() { if (a) return true; }};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + data: { name: "method 'foo'" }, + type: "FunctionExpression", + line: 1, + column: 10, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "if (a) return true;", + languageOptions: { + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + errors: [ + { + messageId: "missingReturn", + data: { name: "program" }, + type: "Program", + line: 1, + column: 1, + endLine: void 0, + endColumn: void 0, + }, + ], + }, + { + code: "class A { CapitalizedFunction() { if (a) return true; } }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + data: { name: "method 'CapitalizedFunction'" }, + type: "FunctionExpression", + line: 1, + column: 11, + endLine: 1, + endColumn: 30, + }, + ], + }, + { + code: "({ constructor() { if (a) return true; } });", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingReturn", + data: { name: "method 'constructor'" }, + type: "FunctionExpression", + line: 1, + column: 4, + endLine: 1, + endColumn: 15, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/consistent-this.js b/tests/lib/rules/consistent-this.js index c78b70be09e2..4513841ab2e3 100644 --- a/tests/lib/rules/consistent-this.js +++ b/tests/lib/rules/consistent-this.js @@ -9,7 +9,7 @@ // Requirements //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/consistent-this"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Helpers @@ -22,11 +22,11 @@ const rule = require("../../../lib/rules/consistent-this"), * @private */ function destructuringTest(code) { - return { - code, - options: ["self"], - languageOptions: { ecmaVersion: 6 } - }; + return { + code, + options: ["self"], + languageOptions: { ecmaVersion: 6 }, + }; } //------------------------------------------------------------------------------ @@ -34,40 +34,166 @@ function destructuringTest(code) { //------------------------------------------------------------------------------ const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, }); ruleTester.run("consistent-this", rule, { - valid: [ - "var foo = 42, that = this", - { code: "var foo = 42, self = this", options: ["self"] }, - { code: "var self = 42", options: ["that"] }, - { code: "var self", options: ["that"] }, - { code: "var self; self = this", options: ["self"] }, - { code: "var foo, self; self = this", options: ["self"] }, - { code: "var foo, self; foo = 42; self = this", options: ["self"] }, - { code: "self = 42", options: ["that"] }, - { code: "var foo = {}; foo.bar = this", options: ["self"] }, - { code: "var self = this; var vm = this;", options: ["self", "vm"] }, - destructuringTest("var {foo, bar} = this"), - destructuringTest("({foo, bar} = this)"), - destructuringTest("var [foo, bar] = this"), - destructuringTest("[foo, bar] = this") - ], - invalid: [ - { code: "var context = this", errors: [{ messageId: "unexpectedAlias", data: { name: "context" }, type: "VariableDeclarator" }] }, - { code: "var that = this", options: ["self"], errors: [{ messageId: "unexpectedAlias", data: { name: "that" }, type: "VariableDeclarator" }] }, - { code: "var foo = 42, self = this", options: ["that"], errors: [{ messageId: "unexpectedAlias", data: { name: "self" }, type: "VariableDeclarator" }] }, - { code: "var self = 42", options: ["self"], errors: [{ messageId: "aliasNotAssignedToThis", data: { name: "self" }, type: "VariableDeclarator" }] }, - { code: "var self", options: ["self"], errors: [{ messageId: "aliasNotAssignedToThis", data: { name: "self" }, type: "VariableDeclarator" }] }, - { code: "var self; self = 42", options: ["self"], errors: [{ messageId: "aliasNotAssignedToThis", data: { name: "self" }, type: "VariableDeclarator" }, { messageId: "aliasNotAssignedToThis", data: { name: "self" }, type: "AssignmentExpression" }] }, - { code: "context = this", options: ["that"], errors: [{ messageId: "unexpectedAlias", data: { name: "context" }, type: "AssignmentExpression" }] }, - { code: "that = this", options: ["self"], errors: [{ messageId: "unexpectedAlias", data: { name: "that" }, type: "AssignmentExpression" }] }, - { code: "self = this", options: ["that"], errors: [{ messageId: "unexpectedAlias", data: { name: "self" }, type: "AssignmentExpression" }] }, - { code: "self += this", options: ["self"], errors: [{ messageId: "aliasNotAssignedToThis", data: { name: "self" }, type: "AssignmentExpression" }] }, - { code: "var self; (function() { self = this; }())", options: ["self"], errors: [{ messageId: "aliasNotAssignedToThis", data: { name: "self" }, type: "VariableDeclarator" }] } - ] + valid: [ + "var foo = 42, that = this", + { code: "var foo = 42, self = this", options: ["self"] }, + { code: "var self = 42", options: ["that"] }, + { code: "var self", options: ["that"] }, + { code: "var self; self = this", options: ["self"] }, + { code: "var foo, self; self = this", options: ["self"] }, + { code: "var foo, self; foo = 42; self = this", options: ["self"] }, + { code: "self = 42", options: ["that"] }, + { code: "var foo = {}; foo.bar = this", options: ["self"] }, + { code: "var self = this; var vm = this;", options: ["self", "vm"] }, + destructuringTest("var {foo, bar} = this"), + destructuringTest("({foo, bar} = this)"), + destructuringTest("var [foo, bar] = this"), + destructuringTest("[foo, bar] = this"), + ], + invalid: [ + { + code: "var context = this", + errors: [ + { + messageId: "unexpectedAlias", + data: { name: "context" }, + type: "VariableDeclarator", + }, + ], + }, + { + code: "var that = this", + options: ["self"], + errors: [ + { + messageId: "unexpectedAlias", + data: { name: "that" }, + type: "VariableDeclarator", + }, + ], + }, + { + code: "var foo = 42, self = this", + options: ["that"], + errors: [ + { + messageId: "unexpectedAlias", + data: { name: "self" }, + type: "VariableDeclarator", + }, + ], + }, + { + code: "var self = 42", + options: ["self"], + errors: [ + { + messageId: "aliasNotAssignedToThis", + data: { name: "self" }, + type: "VariableDeclarator", + }, + ], + }, + { + code: "var self", + options: ["self"], + errors: [ + { + messageId: "aliasNotAssignedToThis", + data: { name: "self" }, + type: "VariableDeclarator", + }, + ], + }, + { + code: "var self; self = 42", + options: ["self"], + errors: [ + { + messageId: "aliasNotAssignedToThis", + data: { name: "self" }, + type: "VariableDeclarator", + }, + { + messageId: "aliasNotAssignedToThis", + data: { name: "self" }, + type: "AssignmentExpression", + }, + ], + }, + { + code: "context = this", + options: ["that"], + errors: [ + { + messageId: "unexpectedAlias", + data: { name: "context" }, + type: "AssignmentExpression", + }, + ], + }, + { + code: "that = this", + options: ["self"], + errors: [ + { + messageId: "unexpectedAlias", + data: { name: "that" }, + type: "AssignmentExpression", + }, + ], + }, + { + code: "self = this", + options: ["that"], + errors: [ + { + messageId: "unexpectedAlias", + data: { name: "self" }, + type: "AssignmentExpression", + }, + ], + }, + { + code: "self += this", + options: ["self"], + errors: [ + { + messageId: "aliasNotAssignedToThis", + data: { name: "self" }, + type: "AssignmentExpression", + }, + ], + }, + { + code: "var self; (function() { self = this; }())", + options: ["self"], + errors: [ + { + messageId: "aliasNotAssignedToThis", + data: { name: "self" }, + type: "VariableDeclarator", + }, + ], + }, + { + code: "var self; (function() { self = this; }())", + options: ["self"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "aliasNotAssignedToThis", + data: { name: "self" }, + type: "VariableDeclarator", + }, + ], + }, + ], }); diff --git a/tests/lib/rules/constructor-super.js b/tests/lib/rules/constructor-super.js index 1e0db7110202..3527979537ca 100644 --- a/tests/lib/rules/constructor-super.js +++ b/tests/lib/rules/constructor-super.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/constructor-super"); -const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); +const RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -19,75 +19,111 @@ const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2021 } }); ruleTester.run("constructor-super", rule, { - valid: [ - - // non derived classes. - "class A { }", - "class A { constructor() { } }", - - /* - * inherit from non constructors. - * those are valid if we don't define the constructor. - */ - "class A extends null { }", - - // derived classes. - "class A extends B { }", - "class A extends B { constructor() { super(); } }", - "class A extends B { constructor() { if (true) { super(); } else { super(); } } }", - "class A extends (class B {}) { constructor() { super(); } }", - "class A extends (B = C) { constructor() { super(); } }", - "class A extends (B &&= C) { constructor() { super(); } }", - "class A extends (B ||= C) { constructor() { super(); } }", - "class A extends (B ??= C) { constructor() { super(); } }", - "class A extends (B ||= 5) { constructor() { super(); } }", - "class A extends (B ??= 5) { constructor() { super(); } }", - "class A extends (B || C) { constructor() { super(); } }", - "class A extends (5 && B) { constructor() { super(); } }", - - // A future improvement could detect the left side as statically falsy, making this invalid. - "class A extends (false && B) { constructor() { super(); } }", - "class A extends (B || 5) { constructor() { super(); } }", - "class A extends (B ?? 5) { constructor() { super(); } }", - - "class A extends (a ? B : C) { constructor() { super(); } }", - "class A extends (B, C) { constructor() { super(); } }", - - // nested. - "class A { constructor() { class B extends C { constructor() { super(); } } } }", - "class A extends B { constructor() { super(); class C extends D { constructor() { super(); } } } }", - "class A extends B { constructor() { super(); class C { constructor() { } } } }", - - // multi code path. - "class A extends B { constructor() { a ? super() : super(); } }", - "class A extends B { constructor() { if (a) super(); else super(); } }", - "class A extends B { constructor() { switch (a) { case 0: super(); break; default: super(); } } }", - "class A extends B { constructor() { try {} finally { super(); } } }", - "class A extends B { constructor() { if (a) throw Error(); super(); } }", - - // returning value is a substitute of 'super()'. - "class A extends B { constructor() { if (true) return a; super(); } }", - "class A extends null { constructor() { return a; } }", - "class A { constructor() { return a; } }", - - // https://github.com/eslint/eslint/issues/5261 - "class A extends B { constructor(a) { super(); for (const b of a) { this.a(); } } }", - - // https://github.com/eslint/eslint/issues/5319 - "class Foo extends Object { constructor(method) { super(); this.method = method || function() {}; } }", - - // https://github.com/eslint/eslint/issues/5394 - [ - "class A extends Object {", - " constructor() {", - " super();", - " for (let i = 0; i < 0; i++);", - " }", - "}" - ].join("\n"), - - // https://github.com/eslint/eslint/issues/8848 - ` + valid: [ + // non derived classes. + "class A { }", + "class A { constructor() { } }", + + /* + * inherit from non constructors. + * those are valid if we don't define the constructor. + */ + "class A extends null { }", + + // derived classes. + "class A extends B { }", + "class A extends B { constructor() { super(); } }", + "class A extends B { constructor() { if (true) { super(); } else { super(); } } }", + "class A extends (class B {}) { constructor() { super(); } }", + "class A extends (B = C) { constructor() { super(); } }", + "class A extends (B &&= C) { constructor() { super(); } }", + "class A extends (B ||= C) { constructor() { super(); } }", + "class A extends (B ??= C) { constructor() { super(); } }", + "class A extends (B ||= 5) { constructor() { super(); } }", + "class A extends (B ??= 5) { constructor() { super(); } }", + "class A extends (B || C) { constructor() { super(); } }", + "class A extends (5 && B) { constructor() { super(); } }", + + // A future improvement could detect the left side as statically falsy, making this invalid. + "class A extends (false && B) { constructor() { super(); } }", + "class A extends (B || 5) { constructor() { super(); } }", + "class A extends (B ?? 5) { constructor() { super(); } }", + + "class A extends (a ? B : C) { constructor() { super(); } }", + "class A extends (B, C) { constructor() { super(); } }", + + // nested. + "class A { constructor() { class B extends C { constructor() { super(); } } } }", + "class A extends B { constructor() { super(); class C extends D { constructor() { super(); } } } }", + "class A extends B { constructor() { super(); class C { constructor() { } } } }", + + // multi code path. + "class A extends B { constructor() { a ? super() : super(); } }", + "class A extends B { constructor() { if (a) super(); else super(); } }", + "class A extends B { constructor() { switch (a) { case 0: super(); break; default: super(); } } }", + "class A extends B { constructor() { try {} finally { super(); } } }", + "class A extends B { constructor() { if (a) throw Error(); super(); } }", + + // returning value is a substitute of 'super()'. + "class A extends B { constructor() { if (true) return a; super(); } }", + "class A extends null { constructor() { return a; } }", + "class A { constructor() { return a; } }", + + // https://github.com/eslint/eslint/issues/5261 + "class A extends B { constructor(a) { super(); for (const b of a) { this.a(); } } }", + "class A extends B { constructor(a) { super(); for (b in a) ( foo(b) ); } }", + + // https://github.com/eslint/eslint/issues/5319 + "class Foo extends Object { constructor(method) { super(); this.method = method || function() {}; } }", + + // https://github.com/eslint/eslint/issues/5394 + [ + "class A extends Object {", + " constructor() {", + " super();", + " for (let i = 0; i < 0; i++);", + " }", + "}", + ].join("\n"), + [ + "class A extends Object {", + " constructor() {", + " super();", + " for (; i < 0; i++);", + " }", + "}", + ].join("\n"), + [ + "class A extends Object {", + " constructor() {", + " super();", + " for (let i = 0;; i++) {", + " if (foo) break;", + " }", + " }", + "}", + ].join("\n"), + [ + "class A extends Object {", + " constructor() {", + " super();", + " for (let i = 0; i < 0;);", + " }", + "}", + ].join("\n"), + [ + "class A extends Object {", + " constructor() {", + " super();", + " for (let i = 0;;) {", + " if (foo) break;", + " }", + " }", + "}", + ].join("\n"), + + // https://github.com/eslint/eslint/issues/8848 + ` class A extends B { constructor(props) { super(props); @@ -102,176 +138,274 @@ ruleTester.run("constructor-super", rule, { } `, - // Optional chaining - "class A extends obj?.prop { constructor() { super(); } }" - ], - invalid: [ - - // inherit from non constructors. - { - code: "class A extends null { constructor() { super(); } }", - errors: [{ messageId: "badSuper", type: "CallExpression" }] - }, - { - code: "class A extends null { constructor() { } }", - errors: [{ messageId: "missingAll", type: "MethodDefinition" }] - }, - { - code: "class A extends 100 { constructor() { super(); } }", - errors: [{ messageId: "badSuper", type: "CallExpression" }] - }, - { - code: "class A extends 'test' { constructor() { super(); } }", - errors: [{ messageId: "badSuper", type: "CallExpression" }] - }, - { - code: "class A extends (B = 5) { constructor() { super(); } }", - errors: [{ messageId: "badSuper", type: "CallExpression" }] - }, - { - code: "class A extends (B && 5) { constructor() { super(); } }", - errors: [{ messageId: "badSuper", type: "CallExpression" }] - }, - { - - // `B &&= 5` evaluates either to a falsy value of `B` (which, then, cannot be a constructor), or to '5' - code: "class A extends (B &&= 5) { constructor() { super(); } }", - errors: [{ messageId: "badSuper", type: "CallExpression" }] - }, - { - code: "class A extends (B += C) { constructor() { super(); } }", - errors: [{ messageId: "badSuper", type: "CallExpression" }] - }, - { - code: "class A extends (B -= C) { constructor() { super(); } }", - errors: [{ messageId: "badSuper", type: "CallExpression" }] - }, - { - code: "class A extends (B **= C) { constructor() { super(); } }", - errors: [{ messageId: "badSuper", type: "CallExpression" }] - }, - { - code: "class A extends (B |= C) { constructor() { super(); } }", - errors: [{ messageId: "badSuper", type: "CallExpression" }] - }, - { - code: "class A extends (B &= C) { constructor() { super(); } }", - errors: [{ messageId: "badSuper", type: "CallExpression" }] - }, - - // derived classes. - { - code: "class A extends B { constructor() { } }", - errors: [{ messageId: "missingAll", type: "MethodDefinition" }] - }, - { - code: "class A extends B { constructor() { for (var a of b) super.foo(); } }", - errors: [{ messageId: "missingAll", type: "MethodDefinition" }] - }, - - // nested execution scope. - { - code: "class A extends B { constructor() { class C extends D { constructor() { super(); } } } }", - errors: [{ messageId: "missingAll", type: "MethodDefinition" }] - }, - { - code: "class A extends B { constructor() { var c = class extends D { constructor() { super(); } } } }", - errors: [{ messageId: "missingAll", type: "MethodDefinition" }] - }, - { - code: "class A extends B { constructor() { var c = () => super(); } }", - errors: [{ messageId: "missingAll", type: "MethodDefinition" }] - }, - { - code: "class A extends B { constructor() { class C extends D { constructor() { super(); } } } }", - errors: [{ messageId: "missingAll", type: "MethodDefinition", column: 21 }] - }, - { - code: "class A extends B { constructor() { var C = class extends D { constructor() { super(); } } } }", - errors: [{ messageId: "missingAll", type: "MethodDefinition", column: 21 }] - }, - { - code: "class A extends B { constructor() { super(); class C extends D { constructor() { } } } }", - errors: [{ messageId: "missingAll", type: "MethodDefinition", column: 66 }] - }, - { - code: "class A extends B { constructor() { super(); var C = class extends D { constructor() { } } } }", - errors: [{ messageId: "missingAll", type: "MethodDefinition", column: 72 }] - }, - - // lacked in some code path. - { - code: "class A extends B { constructor() { if (a) super(); } }", - errors: [{ messageId: "missingSome", type: "MethodDefinition" }] - }, - { - code: "class A extends B { constructor() { if (a); else super(); } }", - errors: [{ messageId: "missingSome", type: "MethodDefinition" }] - }, - { - code: "class A extends B { constructor() { a && super(); } }", - errors: [{ messageId: "missingSome", type: "MethodDefinition" }] - }, - { - code: "class A extends B { constructor() { switch (a) { case 0: super(); } } }", - errors: [{ messageId: "missingSome", type: "MethodDefinition" }] - }, - { - code: "class A extends B { constructor() { switch (a) { case 0: break; default: super(); } } }", - errors: [{ messageId: "missingSome", type: "MethodDefinition" }] - }, - { - code: "class A extends B { constructor() { try { super(); } catch (err) {} } }", - errors: [{ messageId: "missingSome", type: "MethodDefinition" }] - }, - { - code: "class A extends B { constructor() { try { a; } catch (err) { super(); } } }", - errors: [{ messageId: "missingSome", type: "MethodDefinition" }] - }, - { - code: "class A extends B { constructor() { if (a) return; super(); } }", - errors: [{ messageId: "missingSome", type: "MethodDefinition" }] - }, - - // duplicate. - { - code: "class A extends B { constructor() { super(); super(); } }", - errors: [{ messageId: "duplicate", type: "CallExpression", column: 46 }] - }, - { - code: "class A extends B { constructor() { super() || super(); } }", - errors: [{ messageId: "duplicate", type: "CallExpression", column: 48 }] - }, - { - code: "class A extends B { constructor() { if (a) super(); super(); } }", - errors: [{ messageId: "duplicate", type: "CallExpression", column: 53 }] - }, - { - code: "class A extends B { constructor() { switch (a) { case 0: super(); default: super(); } } }", - errors: [{ messageId: "duplicate", type: "CallExpression", column: 76 }] - }, - { - code: "class A extends B { constructor(a) { while (a) super(); } }", - errors: [ - { messageId: "missingSome", type: "MethodDefinition" }, - { messageId: "duplicate", type: "CallExpression", column: 48 } - ] - }, - - // ignores `super()` on unreachable paths. - { - code: "class A extends B { constructor() { return; super(); } }", - errors: [{ messageId: "missingAll", type: "MethodDefinition" }] - }, - - // https://github.com/eslint/eslint/issues/8248 - { - code: `class Foo extends Bar { + // Optional chaining + "class A extends obj?.prop { constructor() { super(); } }", + + ` + class A extends Base { + constructor(list) { + for (const a of list) { + if (a.foo) { + super(a); + return; + } + } + super(); + } + } + `, + ], + invalid: [ + // inherit from non constructors. + { + code: "class A extends null { constructor() { super(); } }", + errors: [{ messageId: "badSuper", type: "CallExpression" }], + }, + { + code: "class A extends null { constructor() { } }", + errors: [{ messageId: "missingAll", type: "MethodDefinition" }], + }, + { + code: "class A extends 100 { constructor() { super(); } }", + errors: [{ messageId: "badSuper", type: "CallExpression" }], + }, + { + code: "class A extends 'test' { constructor() { super(); } }", + errors: [{ messageId: "badSuper", type: "CallExpression" }], + }, + { + code: "class A extends (B = 5) { constructor() { super(); } }", + errors: [{ messageId: "badSuper", type: "CallExpression" }], + }, + { + code: "class A extends (B && 5) { constructor() { super(); } }", + errors: [{ messageId: "badSuper", type: "CallExpression" }], + }, + { + // `B &&= 5` evaluates either to a falsy value of `B` (which, then, cannot be a constructor), or to '5' + code: "class A extends (B &&= 5) { constructor() { super(); } }", + errors: [{ messageId: "badSuper", type: "CallExpression" }], + }, + { + code: "class A extends (B += C) { constructor() { super(); } }", + errors: [{ messageId: "badSuper", type: "CallExpression" }], + }, + { + code: "class A extends (B -= C) { constructor() { super(); } }", + errors: [{ messageId: "badSuper", type: "CallExpression" }], + }, + { + code: "class A extends (B **= C) { constructor() { super(); } }", + errors: [{ messageId: "badSuper", type: "CallExpression" }], + }, + { + code: "class A extends (B |= C) { constructor() { super(); } }", + errors: [{ messageId: "badSuper", type: "CallExpression" }], + }, + { + code: "class A extends (B &= C) { constructor() { super(); } }", + errors: [{ messageId: "badSuper", type: "CallExpression" }], + }, + + // derived classes. + { + code: "class A extends B { constructor() { } }", + errors: [{ messageId: "missingAll", type: "MethodDefinition" }], + }, + { + code: "class A extends B { constructor() { for (var a of b) super.foo(); } }", + errors: [{ messageId: "missingAll", type: "MethodDefinition" }], + }, + { + code: "class A extends B { constructor() { for (var i = 1; i < 10; i++) super.foo(); } }", + errors: [{ messageId: "missingAll", type: "MethodDefinition" }], + }, + + // nested execution scope. + { + code: "class A extends B { constructor() { var c = class extends D { constructor() { super(); } } } }", + errors: [{ messageId: "missingAll", type: "MethodDefinition" }], + }, + { + code: "class A extends B { constructor() { var c = () => super(); } }", + errors: [{ messageId: "missingAll", type: "MethodDefinition" }], + }, + { + code: "class A extends B { constructor() { class C extends D { constructor() { super(); } } } }", + errors: [ + { + messageId: "missingAll", + type: "MethodDefinition", + column: 21, + }, + ], + }, + { + code: "class A extends B { constructor() { var C = class extends D { constructor() { super(); } } } }", + errors: [ + { + messageId: "missingAll", + type: "MethodDefinition", + column: 21, + }, + ], + }, + { + code: "class A extends B { constructor() { super(); class C extends D { constructor() { } } } }", + errors: [ + { + messageId: "missingAll", + type: "MethodDefinition", + column: 66, + }, + ], + }, + { + code: "class A extends B { constructor() { super(); var C = class extends D { constructor() { } } } }", + errors: [ + { + messageId: "missingAll", + type: "MethodDefinition", + column: 72, + }, + ], + }, + + // lacked in some code path. + { + code: "class A extends B { constructor() { if (a) super(); } }", + errors: [{ messageId: "missingSome", type: "MethodDefinition" }], + }, + { + code: "class A extends B { constructor() { if (a); else super(); } }", + errors: [{ messageId: "missingSome", type: "MethodDefinition" }], + }, + { + code: "class A extends B { constructor() { a && super(); } }", + errors: [{ messageId: "missingSome", type: "MethodDefinition" }], + }, + { + code: "class A extends B { constructor() { switch (a) { case 0: super(); } } }", + errors: [{ messageId: "missingSome", type: "MethodDefinition" }], + }, + { + code: "class A extends B { constructor() { switch (a) { case 0: break; default: super(); } } }", + errors: [{ messageId: "missingSome", type: "MethodDefinition" }], + }, + { + code: "class A extends B { constructor() { try { super(); } catch (err) {} } }", + errors: [{ messageId: "missingSome", type: "MethodDefinition" }], + }, + { + code: "class A extends B { constructor() { try { a; } catch (err) { super(); } } }", + errors: [{ messageId: "missingSome", type: "MethodDefinition" }], + }, + { + code: "class A extends B { constructor() { if (a) return; super(); } }", + errors: [{ messageId: "missingSome", type: "MethodDefinition" }], + }, + + // duplicate. + { + code: "class A extends B { constructor() { super(); super(); } }", + errors: [ + { messageId: "duplicate", type: "CallExpression", column: 46 }, + ], + }, + { + code: "class A extends B { constructor() { super() || super(); } }", + errors: [ + { messageId: "duplicate", type: "CallExpression", column: 48 }, + ], + }, + { + code: "class A extends B { constructor() { if (a) super(); super(); } }", + errors: [ + { messageId: "duplicate", type: "CallExpression", column: 53 }, + ], + }, + { + code: "class A extends B { constructor() { switch (a) { case 0: super(); default: super(); } } }", + errors: [ + { messageId: "duplicate", type: "CallExpression", column: 76 }, + ], + }, + { + code: "class A extends B { constructor(a) { while (a) super(); } }", + errors: [ + { messageId: "missingSome", type: "MethodDefinition" }, + { messageId: "duplicate", type: "CallExpression", column: 48 }, + ], + }, + + // ignores `super()` on unreachable paths. + { + code: "class A extends B { constructor() { return; super(); } }", + errors: [{ messageId: "missingAll", type: "MethodDefinition" }], + }, + + // https://github.com/eslint/eslint/issues/8248 + { + code: `class Foo extends Bar { constructor() { for (a in b) for (c in d); } }`, - errors: [{ messageId: "missingAll", type: "MethodDefinition" }] - } - ] + errors: [{ messageId: "missingAll", type: "MethodDefinition" }], + }, + + { + code: `class C extends D { + + constructor() { + do { + something(); + } while (foo); + } + + }`, + errors: [{ messageId: "missingAll", type: "MethodDefinition" }], + }, + { + code: `class C extends D { + + constructor() { + for (let i = 1;;i++) { + if (bar) { + break; + } + } + } + + }`, + errors: [{ messageId: "missingAll", type: "MethodDefinition" }], + }, + { + code: `class C extends D { + + constructor() { + do { + super(); + } while (foo); + } + + }`, + errors: [{ messageId: "duplicate", type: "CallExpression" }], + }, + { + code: `class C extends D { + + constructor() { + while (foo) { + if (bar) { + super(); + break; + } + } + } + + }`, + errors: [{ messageId: "missingSome", type: "MethodDefinition" }], + }, + ], }); diff --git a/tests/lib/rules/curly.js b/tests/lib/rules/curly.js index db23378917b9..0d4400390121 100644 --- a/tests/lib/rules/curly.js +++ b/tests/lib/rules/curly.js @@ -10,1841 +10,2075 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/curly"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, }); ruleTester.run("curly", rule, { - valid: [ - "if (foo) { bar() }", - "if (foo) { bar() } else if (foo2) { baz() }", - "while (foo) { bar() }", - "do { bar(); } while (foo)", - "for (;foo;) { bar() }", - "for (var foo in bar) { console.log(foo) }", - { - code: "for (var foo of bar) { console.log(foo) }", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "for (;foo;) bar()", - options: ["multi"] - }, - { - code: "if (foo) bar()", - options: ["multi"] - }, - { - code: "if (a) { b; c; }", - options: ["multi"] - }, - { - code: "for (var foo in bar) console.log(foo)", - options: ["multi"] - }, - { - code: "for (var foo in bar) { console.log(1); console.log(2) }", - options: ["multi"] - }, - { - code: "for (var foo of bar) console.log(foo)", - options: ["multi"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "for (var foo of bar) { console.log(1); console.log(2) }", - options: ["multi"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "if (foo) bar()", - options: ["multi-line"] - }, - { - code: "if (foo) bar() \n", - options: ["multi-line"] - }, - { - code: "if (foo) bar(); else baz()", - options: ["multi-line"] - }, - { - code: "if (foo) bar(); \n else baz()", - options: ["multi-line"] - }, - { - code: "if (foo) bar() \n else if (foo) bar() \n else baz()", - options: ["multi-line"] - }, - { - code: "do baz(); while (foo)", - options: ["multi-line"] - }, - { - code: "if (foo) { bar() }", - options: ["multi-line"] - }, - { - code: "for (var foo in bar) console.log(foo)", - options: ["multi-line"] - }, - { - code: "for (var foo in bar) { \n console.log(1); \n console.log(2); \n }", - options: ["multi-line"] - }, - { - code: "for (var foo of bar) console.log(foo)", - options: ["multi-line"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "for (var foo of bar) { \n console.log(1); \n console.log(2); \n }", - options: ["multi-line"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "if (foo) { \n bar(); \n baz(); \n }", - options: ["multi-line"] - }, - { - code: "do bar() \n while (foo)", - options: ["multi-line"] - }, - { - code: "if (foo) { \n quz = { \n bar: baz, \n qux: foo \n }; \n }", - options: ["multi-or-nest"] - }, - { - code: "while (true) { \n if (foo) \n doSomething(); \n else \n doSomethingElse(); \n }", - options: ["multi-or-nest"] - }, - { - code: "if (foo) \n quz = true;", - options: ["multi-or-nest"] - }, - { - code: "if (foo) { \n // line of comment \n quz = true; \n }", - options: ["multi-or-nest"] - }, - { - code: "// line of comment \n if (foo) \n quz = true; \n", - options: ["multi-or-nest"] - }, - { - code: "while (true) \n doSomething();", - options: ["multi-or-nest"] - }, - { - code: "for (var i = 0; foo; i++) \n doSomething();", - options: ["multi-or-nest"] - }, - { - code: "if (foo) { \n if(bar) \n doSomething(); \n } else \n doSomethingElse();", - options: ["multi-or-nest"] - }, - { - code: "for (var foo in bar) \n console.log(foo)", - options: ["multi-or-nest"] - }, - { - code: "for (var foo in bar) { \n if (foo) console.log(1); \n else console.log(2) \n }", - options: ["multi-or-nest"] - }, - { - code: "for (var foo of bar) \n console.log(foo)", - options: ["multi-or-nest"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "for (var foo of bar) { \n if (foo) console.log(1); \n else console.log(2) \n }", - options: ["multi-or-nest"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "if (foo) { const bar = 'baz'; }", - options: ["multi"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "while (foo) { let bar = 'baz'; }", - options: ["multi"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "for(;;) { function foo() {} }", - options: ["multi"] - }, - { - code: "for (foo in bar) { class Baz {} }", - options: ["multi"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "if (foo) { let bar; } else { baz(); }", - options: ["multi", "consistent"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "if (foo) { bar(); } else { const baz = 'quux'; }", - options: ["multi", "consistent"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "if (foo) { \n const bar = 'baz'; \n }", - options: ["multi-or-nest"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "if (foo) { \n let bar = 'baz'; \n }", - options: ["multi-or-nest"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "if (foo) { \n function bar() {} \n }", - options: ["multi-or-nest"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "if (foo) { \n class bar {} \n }", - options: ["multi-or-nest"], - languageOptions: { ecmaVersion: 6 } - }, + valid: [ + "if (foo) { bar() }", + "if (foo) { bar() } else if (foo2) { baz() }", + "while (foo) { bar() }", + "do { bar(); } while (foo)", + "for (;foo;) { bar() }", + "for (var foo in bar) { console.log(foo) }", + { + code: "for (var foo of bar) { console.log(foo) }", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "for (;foo;) bar()", + options: ["multi"], + }, + { + code: "if (foo) bar()", + options: ["multi"], + }, + { + code: "if (a) { b; c; }", + options: ["multi"], + }, + { + code: "for (var foo in bar) console.log(foo)", + options: ["multi"], + }, + { + code: "for (var foo in bar) { console.log(1); console.log(2) }", + options: ["multi"], + }, + { + code: "for (var foo of bar) console.log(foo)", + options: ["multi"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "for (var foo of bar) { console.log(1); console.log(2) }", + options: ["multi"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "if (foo) bar()", + options: ["multi-line"], + }, + { + code: "if (foo) bar() \n", + options: ["multi-line"], + }, + { + code: "if (foo) bar(); else baz()", + options: ["multi-line"], + }, + { + code: "if (foo) bar(); \n else baz()", + options: ["multi-line"], + }, + { + code: "if (foo) bar() \n else if (foo) bar() \n else baz()", + options: ["multi-line"], + }, + { + code: "do baz(); while (foo)", + options: ["multi-line"], + }, + { + code: "if (foo) { bar() }", + options: ["multi-line"], + }, + { + code: "for (var foo in bar) console.log(foo)", + options: ["multi-line"], + }, + { + code: "for (var foo in bar) { \n console.log(1); \n console.log(2); \n }", + options: ["multi-line"], + }, + { + code: "for (var foo of bar) console.log(foo)", + options: ["multi-line"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "for (var foo of bar) { \n console.log(1); \n console.log(2); \n }", + options: ["multi-line"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "if (foo) { \n bar(); \n baz(); \n }", + options: ["multi-line"], + }, + { + code: "do bar() \n while (foo)", + options: ["multi-line"], + }, + { + code: "if (foo) { \n quz = { \n bar: baz, \n qux: foo \n }; \n }", + options: ["multi-or-nest"], + }, + { + code: "while (true) { \n if (foo) \n doSomething(); \n else \n doSomethingElse(); \n }", + options: ["multi-or-nest"], + }, + { + code: "if (foo) \n quz = true;", + options: ["multi-or-nest"], + }, + { + code: "if (foo) { \n // line of comment \n quz = true; \n }", + options: ["multi-or-nest"], + }, + { + code: "// line of comment \n if (foo) \n quz = true; \n", + options: ["multi-or-nest"], + }, + { + code: "while (true) \n doSomething();", + options: ["multi-or-nest"], + }, + { + code: "for (var i = 0; foo; i++) \n doSomething();", + options: ["multi-or-nest"], + }, + { + code: "if (foo) { \n if(bar) \n doSomething(); \n } else \n doSomethingElse();", + options: ["multi-or-nest"], + }, + { + code: "for (var foo in bar) \n console.log(foo)", + options: ["multi-or-nest"], + }, + { + code: "for (var foo in bar) { \n if (foo) console.log(1); \n else console.log(2) \n }", + options: ["multi-or-nest"], + }, + { + code: "for (var foo of bar) \n console.log(foo)", + options: ["multi-or-nest"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "for (var foo of bar) { \n if (foo) console.log(1); \n else console.log(2) \n }", + options: ["multi-or-nest"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "if (foo) { const bar = 'baz'; }", + options: ["multi"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "while (foo) { let bar = 'baz'; }", + options: ["multi"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "for(;;) { function foo() {} }", + options: ["multi"], + }, + { + code: "for (foo in bar) { class Baz {} }", + options: ["multi"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "if (foo) { let bar; } else { baz(); }", + options: ["multi", "consistent"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "if (foo) { bar(); } else { const baz = 'quux'; }", + options: ["multi", "consistent"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "if (foo) { \n const bar = 'baz'; \n }", + options: ["multi-or-nest"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "if (foo) { \n let bar = 'baz'; \n }", + options: ["multi-or-nest"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "if (foo) { \n function bar() {} \n }", + options: ["multi-or-nest"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "if (foo) { \n class bar {} \n }", + options: ["multi-or-nest"], + languageOptions: { ecmaVersion: 6 }, + }, - // https://github.com/eslint/eslint/issues/12370 - { - code: "if (foo) doSomething() \n ;", - options: ["multi-or-nest"] - }, - { - code: "if (foo) doSomething(); \n else if (bar) doSomethingElse() \n ;", - options: ["multi-or-nest"] - }, - { - code: "if (foo) doSomething(); \n else doSomethingElse() \n ;", - options: ["multi-or-nest"] - }, - { - code: "if (foo) doSomething(); \n else if (bar) doSomethingElse(); \n else doAnotherThing() \n ;", - options: ["multi-or-nest"] - }, - { - code: "for (var i = 0; foo; i++) doSomething() \n ;", - options: ["multi-or-nest"] - }, - { - code: "for (var foo in bar) console.log(foo) \n ;", - options: ["multi-or-nest"] - }, - { - code: "for (var foo of bar) console.log(foo) \n ;", - options: ["multi-or-nest"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "while (foo) doSomething() \n ;", - options: ["multi-or-nest"] - }, - { - code: "do doSomething() \n ;while (foo)", - options: ["multi-or-nest"] - }, - { - code: "if (foo)\n;", - options: ["multi-or-nest"] - }, - { - code: "if (foo) doSomething(); \n else if (bar)\n;", - options: ["multi-or-nest"] - }, - { - code: "if (foo) doSomething(); \n else\n;", - options: ["multi-or-nest"] - }, - { - code: "if (foo) doSomething(); \n else if (bar) doSomethingElse(); \n else\n;", - options: ["multi-or-nest"] - }, - { - code: "for (var i = 0; foo; i++)\n;", - options: ["multi-or-nest"] - }, - { - code: "for (var foo in bar)\n;", - options: ["multi-or-nest"] - }, - { - code: "for (var foo of bar)\n;", - options: ["multi-or-nest"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "while (foo)\n;", - options: ["multi-or-nest"] - }, - { - code: "do\n;while (foo)", - options: ["multi-or-nest"] - }, + // https://github.com/eslint/eslint/issues/12370 + { + code: "if (foo) doSomething() \n ;", + options: ["multi-or-nest"], + }, + { + code: "if (foo) doSomething(); \n else if (bar) doSomethingElse() \n ;", + options: ["multi-or-nest"], + }, + { + code: "if (foo) doSomething(); \n else doSomethingElse() \n ;", + options: ["multi-or-nest"], + }, + { + code: "if (foo) doSomething(); \n else if (bar) doSomethingElse(); \n else doAnotherThing() \n ;", + options: ["multi-or-nest"], + }, + { + code: "for (var i = 0; foo; i++) doSomething() \n ;", + options: ["multi-or-nest"], + }, + { + code: "for (var foo in bar) console.log(foo) \n ;", + options: ["multi-or-nest"], + }, + { + code: "for (var foo of bar) console.log(foo) \n ;", + options: ["multi-or-nest"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "while (foo) doSomething() \n ;", + options: ["multi-or-nest"], + }, + { + code: "do doSomething() \n ;while (foo)", + options: ["multi-or-nest"], + }, + { + code: "if (foo)\n;", + options: ["multi-or-nest"], + }, + { + code: "if (foo) doSomething(); \n else if (bar)\n;", + options: ["multi-or-nest"], + }, + { + code: "if (foo) doSomething(); \n else\n;", + options: ["multi-or-nest"], + }, + { + code: "if (foo) doSomething(); \n else if (bar) doSomethingElse(); \n else\n;", + options: ["multi-or-nest"], + }, + { + code: "for (var i = 0; foo; i++)\n;", + options: ["multi-or-nest"], + }, + { + code: "for (var foo in bar)\n;", + options: ["multi-or-nest"], + }, + { + code: "for (var foo of bar)\n;", + options: ["multi-or-nest"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "while (foo)\n;", + options: ["multi-or-nest"], + }, + { + code: "do\n;while (foo)", + options: ["multi-or-nest"], + }, - // https://github.com/eslint/eslint/issues/3856 - { - code: "if (true) { if (false) console.log(1) } else console.log(2)", - options: ["multi"] - }, - { - code: "if (a) { if (b) console.log(1); else if (c) console.log(2) } else console.log(3)", - options: ["multi"] - }, - { - code: "if (true) { while(false) if (true); } else;", - options: ["multi"] - }, - { - code: "if (true) { label: if (false); } else;", - options: ["multi"] - }, - { - code: "if (true) { with(0) if (false); } else;", - options: ["multi"] - }, - { - code: "if (true) { while(a) if(b) while(c) if (d); else; } else;", - options: ["multi"] - }, - { - code: "if (true) foo(); else { bar(); baz(); }", - options: ["multi"] - }, - { - code: "if (true) { foo(); } else { bar(); baz(); }", - options: ["multi", "consistent"] - }, - { - code: "if (true) { foo(); } else if (true) { faa(); } else { bar(); baz(); }", - options: ["multi", "consistent"] - }, - { - code: "if (true) { foo(); faa(); } else { bar(); }", - options: ["multi", "consistent"] - }, - { + // https://github.com/eslint/eslint/issues/3856 + { + code: "if (true) { if (false) console.log(1) } else console.log(2)", + options: ["multi"], + }, + { + code: "if (a) { if (b) console.log(1); else if (c) console.log(2) } else console.log(3)", + options: ["multi"], + }, + { + code: "if (true) { while(false) if (true); } else;", + options: ["multi"], + }, + { + code: "if (true) { label: if (false); } else;", + options: ["multi"], + }, + { + code: "if (true) { with(0) if (false); } else;", + options: ["multi"], + }, + { + code: "if (true) { while(a) if(b) while(c) if (d); else; } else;", + options: ["multi"], + }, + { + code: "if (true) foo(); else { bar(); baz(); }", + options: ["multi"], + }, + { + code: "if (true) { foo(); } else { bar(); baz(); }", + options: ["multi", "consistent"], + }, + { + code: "if (true) { foo(); } else if (true) { faa(); } else { bar(); baz(); }", + options: ["multi", "consistent"], + }, + { + code: "if (true) { foo(); faa(); } else { bar(); }", + options: ["multi", "consistent"], + }, + { + // https://github.com/feross/standard/issues/664 + code: "if (true) foo()\n;[1, 2, 3].bar()", + options: ["multi-line"], + }, - // https://github.com/feross/standard/issues/664 - code: "if (true) foo()\n;[1, 2, 3].bar()", - options: ["multi-line"] - }, + // https://github.com/eslint/eslint/issues/12928 (also in invalid[]) + { + code: "if (x) for (var i in x) { if (i > 0) console.log(i); } else console.log('whoops');", + options: ["multi"], + }, + { + code: "if (a) { if (b) foo(); } else bar();", + options: ["multi"], + }, + { + code: "if (a) { if (b) foo(); } else bar();", + options: ["multi-or-nest"], + }, + { + code: "if (a) { if (b) foo(); } else { bar(); }", + options: ["multi", "consistent"], + }, + { + code: "if (a) { if (b) foo(); } else { bar(); }", + options: ["multi-or-nest", "consistent"], + }, + { + code: "if (a) { if (b) { foo(); bar(); } } else baz();", + options: ["multi"], + }, + { + code: "if (a) foo(); else if (b) { if (c) bar(); } else baz();", + options: ["multi"], + }, + { + code: "if (a) { if (b) foo(); else if (c) bar(); } else baz();", + options: ["multi"], + }, + { + code: "if (a) if (b) foo(); else { if (c) bar(); } else baz();", + options: ["multi"], + }, + { + code: "if (a) { lbl:if (b) foo(); } else bar();", + options: ["multi"], + }, + { + code: "if (a) { lbl1:lbl2:if (b) foo(); } else bar();", + options: ["multi"], + }, + { + code: "if (a) { for (;;) if (b) foo(); } else bar();", + options: ["multi"], + }, + { + code: "if (a) { for (key in obj) if (b) foo(); } else bar();", + options: ["multi"], + }, + { + code: "if (a) { for (elem of arr) if (b) foo(); } else bar();", + options: ["multi"], + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: "if (a) { with (obj) if (b) foo(); } else bar();", + options: ["multi"], + }, + { + code: "if (a) { while (cond) if (b) foo(); } else bar();", + options: ["multi"], + }, + { + code: "if (a) { while (cond) for (;;) for (key in obj) if (b) foo(); } else bar();", + options: ["multi"], + }, + { + code: "if (a) while (cond) { for (;;) for (key in obj) if (b) foo(); } else bar();", + options: ["multi"], + }, + { + code: "if (a) while (cond) for (;;) { for (key in obj) if (b) foo(); } else bar();", + options: ["multi"], + }, + { + code: "if (a) while (cond) for (;;) for (key in obj) { if (b) foo(); } else bar();", + options: ["multi"], + }, + ], + invalid: [ + { + code: "if (foo) bar()", + output: "if (foo) {bar()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 1, + column: 10, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "if (foo) \n bar()", + output: "if (foo) \n {bar()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 7, + }, + ], + }, + { + code: "if (foo) { bar() } else baz()", + output: "if (foo) { bar() } else {baz()}", + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) { bar() } else if (faa) baz()", + output: "if (foo) { bar() } else if (faa) {baz()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "while (foo) bar()", + output: "while (foo) {bar()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + line: 1, + column: 13, + endLine: 1, + endColumn: 18, + }, + ], + }, + { + code: "while (foo) \n bar()", + output: "while (foo) \n {bar()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 7, + }, + ], + }, + { + code: "do bar(); while (foo)", + output: "do {bar();} while (foo)", + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + line: 1, + column: 4, + endLine: 1, + endColumn: 10, + }, + ], + }, + { + code: "do \n bar(); while (foo)", + output: "do \n {bar();} while (foo)", + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 8, + }, + ], + }, + { + code: "for (;foo;) bar()", + output: "for (;foo;) {bar()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + }, + ], + }, + { + code: "for (var foo in bar) console.log(foo)", + output: "for (var foo in bar) {console.log(foo)}", + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "for-in" }, + type: "ForInStatement", + }, + ], + }, + { + code: "for (var foo of bar) console.log(foo)", + output: "for (var foo of bar) {console.log(foo)}", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "for-of" }, + type: "ForOfStatement", + line: 1, + column: 22, + endLine: 1, + endColumn: 38, + }, + ], + }, + { + code: "for (var foo of bar) \n console.log(foo)", + output: "for (var foo of bar) \n {console.log(foo)}", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "for-of" }, + type: "ForOfStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 18, + }, + ], + }, + { + code: "for (a;;) console.log(foo)", + output: "for (a;;) {console.log(foo)}", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + line: 1, + column: 11, + endLine: 1, + endColumn: 27, + }, + ], + }, + { + code: "for (a;;) \n console.log(foo)", + output: "for (a;;) \n {console.log(foo)}", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 18, + }, + ], + }, + { + code: "for (var foo of bar) {console.log(foo)}", + output: "for (var foo of bar) console.log(foo)", + options: ["multi"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "for-of" }, + type: "ForOfStatement", + line: 1, + column: 22, + endLine: 1, + endColumn: 40, + }, + ], + }, + { + code: "do{foo();} while(bar);", + output: "do foo(); while(bar);", + options: ["multi"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + line: 1, + column: 3, + endLine: 1, + endColumn: 11, + }, + ], + }, + { + code: "for (;foo;) { bar() }", + output: "for (;foo;) bar() ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + line: 1, + column: 13, + endLine: 1, + endColumn: 22, + }, + ], + }, + { + code: "for (;foo;) \n bar()", + output: "for (;foo;) \n {bar()}", + errors: [ + { + data: { name: "for" }, + type: "ForStatement", + messageId: "missingCurlyAfterCondition", + line: 2, + column: 2, + endLine: 2, + endColumn: 7, + }, + ], + }, + { + code: "if (foo) { bar() }", + output: "if (foo) bar() ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 1, + column: 10, + endLine: 1, + endColumn: 19, + }, + ], + }, + { + code: "if (foo) if (bar) { baz() }", + output: "if (foo) if (bar) baz() ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) if (bar) baz(); else if (quux) { quuux(); }", + output: "if (foo) if (bar) baz(); else if (quux) quuux(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "while (foo) { bar() }", + output: "while (foo) bar() ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + line: 1, + column: 13, + endLine: 1, + endColumn: 22, + }, + ], + }, + { + code: "if (foo) baz(); else { bar() }", + output: "if (foo) baz(); else bar() ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) if (bar); else { baz() }", + output: "if (foo) if (bar); else baz() ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (true) { if (false) console.log(1) }", + output: "if (true) if (false) console.log(1) ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (a) { if (b) console.log(1); else console.log(2) } else console.log(3)", + output: null, + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: [ + "if (0)", + " console.log(0)", + "else if (1) {", + " console.log(1)", + " console.log(1)", + "} else {", + " if (2)", + " console.log(2)", + " else", + " console.log(3)", + "}", + ].join("\n"), + output: [ + "if (0)", + " console.log(0)", + "else if (1) {", + " console.log(1)", + " console.log(1)", + "} else ", + " if (2)", + " console.log(2)", + " else", + " console.log(3)", + "", + ].join("\n"), + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + line: 6, + column: 8, + endLine: 11, + endColumn: 2, + }, + ], + }, + { + code: "for (var foo in bar) { console.log(foo) }", + output: "for (var foo in bar) console.log(foo) ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "for-in" }, + type: "ForInStatement", + line: 1, + column: 22, + endLine: 1, + endColumn: 42, + }, + ], + }, + { + code: "for (var foo of bar) { console.log(foo) }", + output: "for (var foo of bar) console.log(foo) ", + options: ["multi"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "for-of" }, + type: "ForOfStatement", + }, + ], + }, + { + code: "if (foo) \n baz()", + output: "if (foo) \n {baz()}", + options: ["multi-line"], + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 7, + }, + ], + }, + { + code: "if (foo) baz()", + output: "if (foo) {baz()}", + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 1, + column: 10, + endLine: 1, + endColumn: 15, + }, + ], + }, + { + code: "while (foo) \n baz()", + output: "while (foo) \n {baz()}", + options: ["multi-line"], + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + }, + ], + }, + { + code: "for (;foo;) \n bar()", + output: "for (;foo;) \n {bar()}", + options: ["multi-line"], + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + }, + ], + }, + { + code: "while (bar && \n baz) \n foo()", + output: "while (bar && \n baz) \n {foo()}", + options: ["multi-line"], + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + }, + ], + }, + { + code: "if (foo) bar(baz, \n baz)", + output: "if (foo) {bar(baz, \n baz)}", + options: ["multi-line"], + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "do foo(); while (bar)", + output: "do {foo();} while (bar)", + options: ["all"], + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + line: 1, + column: 4, + endLine: 1, + endColumn: 10, + }, + ], + }, + { + code: "do \n foo(); \n while (bar)", + output: "do \n {foo();} \n while (bar)", + options: ["multi-line"], + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 8, + }, + ], + }, + { + code: "for (var foo in bar) {console.log(foo)}", + output: "for (var foo in bar) console.log(foo)", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "for-in" }, + type: "ForInStatement", + line: 1, + column: 22, + endLine: 1, + endColumn: 40, + }, + ], + }, + { + code: "for (var foo in bar) \n console.log(foo)", + output: "for (var foo in bar) \n {console.log(foo)}", + options: ["multi-line"], + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "for-in" }, + type: "ForInStatement", + }, + ], + }, + { + code: "for (var foo in bar) \n console.log(1); \n console.log(2)", + output: "for (var foo in bar) \n {console.log(1);} \n console.log(2)", + options: ["multi-line"], + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "for-in" }, + type: "ForInStatement", + }, + ], + }, + { + code: "for (var foo of bar) \n console.log(foo)", + output: "for (var foo of bar) \n {console.log(foo)}", + options: ["multi-line"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "for-of" }, + type: "ForOfStatement", + line: 2, + column: 2, + endLine: 2, + endColumn: 18, + }, + ], + }, + { + code: "for (var foo of bar) \n console.log(1); \n console.log(2)", + output: "for (var foo of bar) \n {console.log(1);} \n console.log(2)", + options: ["multi-line"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "for-of" }, + type: "ForOfStatement", + }, + ], + }, + { + code: "if (foo) \n quz = { \n bar: baz, \n qux: foo \n };", + output: "if (foo) \n {quz = { \n bar: baz, \n qux: foo \n };}", + options: ["multi-or-nest"], + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "while (true) \n if (foo) \n doSomething(); \n else \n doSomethingElse(); \n", + output: "while (true) \n {if (foo) \n doSomething(); \n else \n doSomethingElse();} \n", + options: ["multi-or-nest"], + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + }, + ], + }, + { + code: "if (foo) { \n quz = true; \n }", + output: "if (foo) \n quz = true; \n ", + options: ["multi-or-nest"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) { var bar = 'baz'; }", + output: "if (foo) var bar = 'baz'; ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) { let bar; } else baz();", + output: "if (foo) { let bar; } else {baz();}", + options: ["multi", "consistent"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) bar(); else { const baz = 'quux' }", + output: "if (foo) {bar();} else { const baz = 'quux' }", + options: ["multi", "consistent"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) { \n var bar = 'baz'; \n }", + output: "if (foo) \n var bar = 'baz'; \n ", + options: ["multi-or-nest"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "while (true) { \n doSomething(); \n }", + output: "while (true) \n doSomething(); \n ", + options: ["multi-or-nest"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + }, + ], + }, + { + code: "for (var i = 0; foo; i++) { \n doSomething(); \n }", + output: "for (var i = 0; foo; i++) \n doSomething(); \n ", + options: ["multi-or-nest"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + line: 1, + column: 27, + endLine: 3, + endColumn: 3, + }, + ], + }, + { + code: "for (var foo in bar) if (foo) console.log(1); else console.log(2);", + output: "for (var foo in bar) {if (foo) console.log(1); else console.log(2);}", + options: ["all"], + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "for-in" }, + type: "ForInStatement", + line: 1, + column: 22, + endLine: 1, + endColumn: 67, + }, + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 1, + column: 31, + endLine: 1, + endColumn: 46, + }, + { + messageId: "missingCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + line: 1, + column: 52, + endLine: 1, + endColumn: 67, + }, + ], + }, + { + code: "for (var foo in bar) \n if (foo) console.log(1); \n else console.log(2);", + output: "for (var foo in bar) \n {if (foo) console.log(1); \n else console.log(2);}", + options: ["multi-or-nest"], + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "for-in" }, + type: "ForInStatement", + line: 2, + column: 2, + endLine: 3, + endColumn: 22, + }, + ], + }, + { + code: "for (var foo in bar) { if (foo) console.log(1) }", + output: "for (var foo in bar) if (foo) console.log(1) ", + options: ["multi-or-nest"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "for-in" }, + type: "ForInStatement", + }, + ], + }, + { + code: "for (var foo of bar) \n if (foo) console.log(1); \n else console.log(2);", + output: "for (var foo of bar) \n {if (foo) console.log(1); \n else console.log(2);}", + options: ["multi-or-nest"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "for-of" }, + type: "ForOfStatement", + }, + ], + }, + { + code: "for (var foo of bar) { if (foo) console.log(1) }", + output: "for (var foo of bar) if (foo) console.log(1) ", + options: ["multi-or-nest"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "for-of" }, + type: "ForOfStatement", + }, + ], + }, + { + code: "if (true) foo(); \n else { \n bar(); \n baz(); \n }", + output: "if (true) {foo();} \n else { \n bar(); \n baz(); \n }", + options: ["multi", "consistent"], + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (true) { foo(); faa(); }\n else bar();", + output: "if (true) { foo(); faa(); }\n else {bar();}", + options: ["multi", "consistent"], + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (true) foo(); else { baz(); }", + output: "if (true) foo(); else baz(); ", + options: ["multi", "consistent"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + line: 1, + column: 23, + endLine: 1, + endColumn: 33, + }, + ], + }, + { + code: "if (true) foo(); else if (true) faa(); else { bar(); baz(); }", + output: "if (true) {foo();} else if (true) {faa();} else { bar(); baz(); }", + options: ["multi", "consistent"], + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (true) if (true) foo(); else { bar(); baz(); }", + output: "if (true) if (true) {foo();} else { bar(); baz(); }", + options: ["multi", "consistent"], + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "do{foo();} while (bar)", + output: "do foo(); while (bar)", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + }, + ], + }, + { + code: "do\n{foo();} while (bar)", + output: "do\nfoo(); while (bar)", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + line: 2, + column: 1, + endLine: 2, + endColumn: 9, + }, + ], + }, + { + code: "while (bar) { foo(); }", + output: "while (bar) foo(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + line: 1, + column: 13, + endLine: 1, + endColumn: 23, + }, + ], + }, + { + code: "while (bar) \n{\n foo(); }", + output: "while (bar) \n\n foo(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + line: 2, + column: 1, + endLine: 3, + endColumn: 10, + }, + ], + }, + { + code: "for (;;) { foo(); }", + output: "for (;;) foo(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + line: 1, + column: 10, + endLine: 1, + endColumn: 20, + }, + ], + }, + { + code: "do{[1, 2, 3].map(bar);} while (bar)", + output: "do[1, 2, 3].map(bar); while (bar)", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + }, + ], + }, + { + code: "if (foo) {bar()} baz()", + output: null, + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "do {foo();} while (bar)", + output: "do foo(); while (bar)", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + }, + ], + }, - // https://github.com/eslint/eslint/issues/12928 (also in invalid[]) - { - code: "if (x) for (var i in x) { if (i > 0) console.log(i); } else console.log('whoops');", - options: ["multi"] - }, - { - code: "if (a) { if (b) foo(); } else bar();", - options: ["multi"] - }, - { - code: "if (a) { if (b) foo(); } else bar();", - options: ["multi-or-nest"] - }, - { - code: "if (a) { if (b) foo(); } else { bar(); }", - options: ["multi", "consistent"] - }, - { - code: "if (a) { if (b) foo(); } else { bar(); }", - options: ["multi-or-nest", "consistent"] - }, - { - code: "if (a) { if (b) { foo(); bar(); } } else baz();", - options: ["multi"] - }, - { - code: "if (a) foo(); else if (b) { if (c) bar(); } else baz();", - options: ["multi"] - }, - { - code: "if (a) { if (b) foo(); else if (c) bar(); } else baz();", - options: ["multi"] - }, - { - code: "if (a) if (b) foo(); else { if (c) bar(); } else baz();", - options: ["multi"] - }, - { - code: "if (a) { lbl:if (b) foo(); } else bar();", - options: ["multi"] - }, - { - code: "if (a) { lbl1:lbl2:if (b) foo(); } else bar();", - options: ["multi"] - }, - { - code: "if (a) { for (;;) if (b) foo(); } else bar();", - options: ["multi"] - }, - { - code: "if (a) { for (key in obj) if (b) foo(); } else bar();", - options: ["multi"] - }, - { - code: "if (a) { for (elem of arr) if (b) foo(); } else bar();", - options: ["multi"], - languageOptions: { ecmaVersion: 2015 } - }, - { - code: "if (a) { with (obj) if (b) foo(); } else bar();", - options: ["multi"] - }, - { - code: "if (a) { while (cond) if (b) foo(); } else bar();", - options: ["multi"] - }, - { - code: "if (a) { while (cond) for (;;) for (key in obj) if (b) foo(); } else bar();", - options: ["multi"] - }, - { - code: "if (a) while (cond) { for (;;) for (key in obj) if (b) foo(); } else bar();", - options: ["multi"] - }, - { - code: "if (a) while (cond) for (;;) { for (key in obj) if (b) foo(); } else bar();", - options: ["multi"] - }, - { - code: "if (a) while (cond) for (;;) for (key in obj) { if (b) foo(); } else bar();", - options: ["multi"] - } - ], - invalid: [ - { - code: "if (foo) bar()", - output: "if (foo) {bar()}", - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement", - line: 1, - column: 10, - endLine: 1, - endColumn: 15 - } - ] - }, - { - code: "if (foo) \n bar()", - output: "if (foo) \n {bar()}", - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement", - line: 2, - column: 2, - endLine: 2, - endColumn: 7 - } - ] - }, - { - code: "if (foo) { bar() } else baz()", - output: "if (foo) { bar() } else {baz()}", - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "else" }, - type: "IfStatement" - } - ] - }, - { - code: "if (foo) { bar() } else if (faa) baz()", - output: "if (foo) { bar() } else if (faa) {baz()}", - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: "while (foo) bar()", - output: "while (foo) {bar()}", - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "while" }, - type: "WhileStatement", - line: 1, - column: 13, - endLine: 1, - endColumn: 18 - } - ] - }, - { - code: "while (foo) \n bar()", - output: "while (foo) \n {bar()}", - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "while" }, - type: "WhileStatement", - line: 2, - column: 2, - endLine: 2, - endColumn: 7 - } - ] - }, - { - code: "do bar(); while (foo)", - output: "do {bar();} while (foo)", - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "do" }, - type: "DoWhileStatement", - line: 1, - column: 4, - endLine: 1, - endColumn: 10 - } - ] - }, - { - code: "do \n bar(); while (foo)", - output: "do \n {bar();} while (foo)", - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "do" }, - type: "DoWhileStatement", - line: 2, - column: 2, - endLine: 2, - endColumn: 8 - } - ] - }, - { - code: "for (;foo;) bar()", - output: "for (;foo;) {bar()}", - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "for" }, - type: "ForStatement" - } - ] - }, - { - code: "for (var foo in bar) console.log(foo)", - output: "for (var foo in bar) {console.log(foo)}", - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "for-in" }, - type: "ForInStatement" - } - ] - }, - { - code: "for (var foo of bar) console.log(foo)", - output: "for (var foo of bar) {console.log(foo)}", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "for-of" }, - type: "ForOfStatement", - line: 1, - column: 22, - endLine: 1, - endColumn: 38 - } - ] - }, - { - code: "for (var foo of bar) \n console.log(foo)", - output: "for (var foo of bar) \n {console.log(foo)}", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "for-of" }, - type: "ForOfStatement", - line: 2, - column: 2, - endLine: 2, - endColumn: 18 - } - ] - }, - { - code: "for (a;;) console.log(foo)", - output: "for (a;;) {console.log(foo)}", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "for" }, - type: "ForStatement", - line: 1, - column: 11, - endLine: 1, - endColumn: 27 - } - ] - }, - { - code: "for (a;;) \n console.log(foo)", - output: "for (a;;) \n {console.log(foo)}", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "for" }, - type: "ForStatement", - line: 2, - column: 2, - endLine: 2, - endColumn: 18 - } - ] - }, - { - code: "for (var foo of bar) {console.log(foo)}", - output: "for (var foo of bar) console.log(foo)", - options: ["multi"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "for-of" }, - type: "ForOfStatement", - line: 1, - column: 22, - endLine: 1, - endColumn: 40 - } - ] - }, - { - code: "do{foo();} while(bar);", - output: "do foo(); while(bar);", - options: ["multi"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "do" }, - type: "DoWhileStatement", - line: 1, - column: 3, - endLine: 1, - endColumn: 11 - } - ] - }, - { - code: "for (;foo;) { bar() }", - output: "for (;foo;) bar() ", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "for" }, - type: "ForStatement", - line: 1, - column: 13, - endLine: 1, - endColumn: 22 - } - ] - }, - { - code: "for (;foo;) \n bar()", - output: "for (;foo;) \n {bar()}", - errors: [ - { - data: { name: "for" }, - type: "ForStatement", - messageId: "missingCurlyAfterCondition", - line: 2, - column: 2, - endLine: 2, - endColumn: 7 - } - ] - }, - { - code: "if (foo) { bar() }", - output: "if (foo) bar() ", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement", - line: 1, - column: 10, - endLine: 1, - endColumn: 19 - } - ] - }, - { - code: "if (foo) if (bar) { baz() }", - output: "if (foo) if (bar) baz() ", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: "if (foo) if (bar) baz(); else if (quux) { quuux(); }", - output: "if (foo) if (bar) baz(); else if (quux) quuux(); ", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: "while (foo) { bar() }", - output: "while (foo) bar() ", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "while" }, - type: "WhileStatement", - line: 1, - column: 13, - endLine: 1, - endColumn: 22 - } - ] - }, - { - code: "if (foo) baz(); else { bar() }", - output: "if (foo) baz(); else bar() ", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "else" }, - type: "IfStatement" - } - ] - }, - { - code: "if (foo) if (bar); else { baz() }", - output: "if (foo) if (bar); else baz() ", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "else" }, - type: "IfStatement" - } - ] - }, - { - code: "if (true) { if (false) console.log(1) }", - output: "if (true) if (false) console.log(1) ", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: "if (a) { if (b) console.log(1); else console.log(2) } else console.log(3)", - output: null, - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: [ - "if (0)", - " console.log(0)", - "else if (1) {", - " console.log(1)", - " console.log(1)", - "} else {", - " if (2)", - " console.log(2)", - " else", - " console.log(3)", - "}" - ].join("\n"), - output: [ - "if (0)", - " console.log(0)", - "else if (1) {", - " console.log(1)", - " console.log(1)", - "} else ", - " if (2)", - " console.log(2)", - " else", - " console.log(3)", - "" - ].join("\n"), - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "else" }, - type: "IfStatement", - line: 6, - column: 8, - endLine: 11, - endColumn: 2 - } - ] - }, - { - code: "for (var foo in bar) { console.log(foo) }", - output: "for (var foo in bar) console.log(foo) ", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "for-in" }, - type: "ForInStatement", - line: 1, - column: 22, - endLine: 1, - endColumn: 42 - } - ] - }, - { - code: "for (var foo of bar) { console.log(foo) }", - output: "for (var foo of bar) console.log(foo) ", - options: ["multi"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "for-of" }, - type: "ForOfStatement" - } - ] - }, - { - code: "if (foo) \n baz()", - output: "if (foo) \n {baz()}", - options: ["multi-line"], - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement", - line: 2, - column: 2, - endLine: 2, - endColumn: 7 - } - ] - }, - { - code: "if (foo) baz()", - output: "if (foo) {baz()}", - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement", - line: 1, - column: 10, - endLine: 1, - endColumn: 15 - } - ] - }, - { - code: "while (foo) \n baz()", - output: "while (foo) \n {baz()}", - options: ["multi-line"], - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "while" }, - type: "WhileStatement" - } - ] - }, - { - code: "for (;foo;) \n bar()", - output: "for (;foo;) \n {bar()}", - options: ["multi-line"], - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "for" }, - type: "ForStatement" - } - ] - }, - { - code: "while (bar && \n baz) \n foo()", - output: "while (bar && \n baz) \n {foo()}", - options: ["multi-line"], - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "while" }, - type: "WhileStatement" - } - ] - }, - { - code: "if (foo) bar(baz, \n baz)", - output: "if (foo) {bar(baz, \n baz)}", - options: ["multi-line"], - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: "do foo(); while (bar)", - output: "do {foo();} while (bar)", - options: ["all"], - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "do" }, - type: "DoWhileStatement", - line: 1, - column: 4, - endLine: 1, - endColumn: 10 - } - ] - }, - { - code: "do \n foo(); \n while (bar)", - output: "do \n {foo();} \n while (bar)", - options: ["multi-line"], - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "do" }, - type: "DoWhileStatement", - line: 2, - column: 2, - endLine: 2, - endColumn: 8 - } - ] - }, - { - code: "for (var foo in bar) {console.log(foo)}", - output: "for (var foo in bar) console.log(foo)", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "for-in" }, - type: "ForInStatement", - line: 1, - column: 22, - endLine: 1, - endColumn: 40 - } - ] - }, - { - code: "for (var foo in bar) \n console.log(foo)", - output: "for (var foo in bar) \n {console.log(foo)}", - options: ["multi-line"], - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "for-in" }, - type: "ForInStatement" - } - ] - }, - { - code: "for (var foo in bar) \n console.log(1); \n console.log(2)", - output: "for (var foo in bar) \n {console.log(1);} \n console.log(2)", - options: ["multi-line"], - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "for-in" }, - type: "ForInStatement" - } - ] - }, - { - code: "for (var foo of bar) \n console.log(foo)", - output: "for (var foo of bar) \n {console.log(foo)}", - options: ["multi-line"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "for-of" }, - type: "ForOfStatement", - line: 2, - column: 2, - endLine: 2, - endColumn: 18 - } - ] - }, - { - code: "for (var foo of bar) \n console.log(1); \n console.log(2)", - output: "for (var foo of bar) \n {console.log(1);} \n console.log(2)", - options: ["multi-line"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "for-of" }, - type: "ForOfStatement" - } - ] - }, - { - code: "if (foo) \n quz = { \n bar: baz, \n qux: foo \n };", - output: "if (foo) \n {quz = { \n bar: baz, \n qux: foo \n };}", - options: ["multi-or-nest"], - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: "while (true) \n if (foo) \n doSomething(); \n else \n doSomethingElse(); \n", - output: "while (true) \n {if (foo) \n doSomething(); \n else \n doSomethingElse();} \n", - options: ["multi-or-nest"], - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "while" }, - type: "WhileStatement" - } - ] - }, - { - code: "if (foo) { \n quz = true; \n }", - output: "if (foo) \n quz = true; \n ", - options: ["multi-or-nest"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: "if (foo) { var bar = 'baz'; }", - output: "if (foo) var bar = 'baz'; ", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: "if (foo) { let bar; } else baz();", - output: "if (foo) { let bar; } else {baz();}", - options: ["multi", "consistent"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "else" }, - type: "IfStatement" - } - ] - }, - { - code: "if (foo) bar(); else { const baz = 'quux' }", - output: "if (foo) {bar();} else { const baz = 'quux' }", - options: ["multi", "consistent"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: "if (foo) { \n var bar = 'baz'; \n }", - output: "if (foo) \n var bar = 'baz'; \n ", - options: ["multi-or-nest"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: "while (true) { \n doSomething(); \n }", - output: "while (true) \n doSomething(); \n ", - options: ["multi-or-nest"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "while" }, - type: "WhileStatement" - } - ] - }, - { - code: "for (var i = 0; foo; i++) { \n doSomething(); \n }", - output: "for (var i = 0; foo; i++) \n doSomething(); \n ", - options: ["multi-or-nest"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "for" }, - type: "ForStatement", - line: 1, - column: 27, - endLine: 3, - endColumn: 3 - } - ] - }, - { - code: "for (var foo in bar) if (foo) console.log(1); else console.log(2);", - output: "for (var foo in bar) {if (foo) console.log(1); else console.log(2);}", - options: ["all"], - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "for-in" }, - type: "ForInStatement", - line: 1, - column: 22, - endLine: 1, - endColumn: 67 - }, - { - messageId: "missingCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement", - line: 1, - column: 31, - endLine: 1, - endColumn: 46 - }, - { - messageId: "missingCurlyAfter", - data: { name: "else" }, - type: "IfStatement", - line: 1, - column: 52, - endLine: 1, - endColumn: 67 - } - ] - }, - { - code: "for (var foo in bar) \n if (foo) console.log(1); \n else console.log(2);", - output: "for (var foo in bar) \n {if (foo) console.log(1); \n else console.log(2);}", - options: ["multi-or-nest"], - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "for-in" }, - type: "ForInStatement", - line: 2, - column: 2, - endLine: 3, - endColumn: 22 - } - ] - }, - { - code: "for (var foo in bar) { if (foo) console.log(1) }", - output: "for (var foo in bar) if (foo) console.log(1) ", - options: ["multi-or-nest"], - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "for-in" }, - type: "ForInStatement" - } - ] - }, - { - code: "for (var foo of bar) \n if (foo) console.log(1); \n else console.log(2);", - output: "for (var foo of bar) \n {if (foo) console.log(1); \n else console.log(2);}", - options: ["multi-or-nest"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "for-of" }, - type: "ForOfStatement" - } - ] - }, - { - code: "for (var foo of bar) { if (foo) console.log(1) }", - output: "for (var foo of bar) if (foo) console.log(1) ", - options: ["multi-or-nest"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "for-of" }, - type: "ForOfStatement" - } - ] - }, - { - code: "if (true) foo(); \n else { \n bar(); \n baz(); \n }", - output: "if (true) {foo();} \n else { \n bar(); \n baz(); \n }", - options: ["multi", "consistent"], - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: "if (true) { foo(); faa(); }\n else bar();", - output: "if (true) { foo(); faa(); }\n else {bar();}", - options: ["multi", "consistent"], - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "else" }, - type: "IfStatement" - } - ] - }, - { - code: "if (true) foo(); else { baz(); }", - output: "if (true) foo(); else baz(); ", - options: ["multi", "consistent"], - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "else" }, - type: "IfStatement", - line: 1, - column: 23, - endLine: 1, - endColumn: 33 - } - ] - }, - { - code: "if (true) foo(); else if (true) faa(); else { bar(); baz(); }", - output: "if (true) {foo();} else if (true) {faa();} else { bar(); baz(); }", - options: ["multi", "consistent"], - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - }, - { - messageId: "missingCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: "if (true) if (true) foo(); else { bar(); baz(); }", - output: "if (true) if (true) {foo();} else { bar(); baz(); }", - options: ["multi", "consistent"], - errors: [ - { - messageId: "missingCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: "do{foo();} while (bar)", - output: "do foo(); while (bar)", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "do" }, - type: "DoWhileStatement" - } - ] - }, - { - code: "do\n{foo();} while (bar)", - output: "do\nfoo(); while (bar)", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "do" }, - type: "DoWhileStatement", - line: 2, - column: 1, - endLine: 2, - endColumn: 9 - } - ] - }, - { - code: "while (bar) { foo(); }", - output: "while (bar) foo(); ", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "while" }, - type: "WhileStatement", - line: 1, - column: 13, - endLine: 1, - endColumn: 23 - } - ] - }, - { - code: "while (bar) \n{\n foo(); }", - output: "while (bar) \n\n foo(); ", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "while" }, - type: "WhileStatement", - line: 2, - column: 1, - endLine: 3, - endColumn: 10 - } - ] - }, - { - code: "for (;;) { foo(); }", - output: "for (;;) foo(); ", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "for" }, - type: "ForStatement", - line: 1, - column: 10, - endLine: 1, - endColumn: 20 - } - ] - }, - { - code: "do{[1, 2, 3].map(bar);} while (bar)", - output: "do[1, 2, 3].map(bar); while (bar)", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "do" }, - type: "DoWhileStatement" - } - ] - }, - { - code: "if (foo) {bar()} baz()", - output: null, - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement" - } - ] - }, - { - code: "do {foo();} while (bar)", - output: "do foo(); while (bar)", - options: ["multi"], - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "do" }, - type: "DoWhileStatement" - } - ] - }, + // Don't remove curly braces if it would cause issues due to ASI. + { + code: "if (foo) { bar }\n++baz;", + output: null, + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) { bar; }\n++baz;", + output: "if (foo) bar; \n++baz;", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) { bar++ }\nbaz;", + output: null, + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) { bar }\n[1, 2, 3].map(foo);", + output: null, + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) { bar }\n(1).toString();", + output: null, + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) { bar }\n/regex/.test('foo');", + output: null, + options: ["multi"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) { bar }\nBaz();", + output: "if (foo) bar \nBaz();", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: + "if (a) {\n" + + " while (b) {\n" + + " c();\n" + + " d();\n" + + " }\n" + + "} else e();", + output: + "if (a) \n" + + " while (b) {\n" + + " c();\n" + + " d();\n" + + " }\n" + + " else e();", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) { while (bar) {} } else {}", + output: "if (foo) while (bar) {} else {}", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) { var foo = () => {} } else {}", + output: null, + options: ["multi"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) { var foo = function() {} } else {}", + output: null, + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) { var foo = function*() {} } else {}", + output: null, + options: ["multi"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (true)\nfoo()\n;[1, 2, 3].bar()", + output: "if (true)\n{foo()\n;}[1, 2, 3].bar()", + options: ["multi-line"], + errors: [ + { + messageId: "missingCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, - // Don't remove curly braces if it would cause issues due to ASI. - { - code: "if (foo) { bar }\n++baz;", - output: null, - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (foo) { bar; }\n++baz;", - output: "if (foo) bar; \n++baz;", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (foo) { bar++ }\nbaz;", - output: null, - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (foo) { bar }\n[1, 2, 3].map(foo);", - output: null, - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (foo) { bar }\n(1).toString();", - output: null, - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (foo) { bar }\n/regex/.test('foo');", - output: null, - options: ["multi"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (foo) { bar }\nBaz();", - output: "if (foo) bar \nBaz();", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: - "if (a) {\n" + - " while (b) {\n" + - " c();\n" + - " d();\n" + - " }\n" + - "} else e();", - output: - "if (a) \n" + - " while (b) {\n" + - " c();\n" + - " d();\n" + - " }\n" + - " else e();", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (foo) { while (bar) {} } else {}", - output: "if (foo) while (bar) {} else {}", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (foo) { var foo = () => {} } else {}", - output: null, - options: ["multi"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (foo) { var foo = function() {} } else {}", - output: null, - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (foo) { var foo = function*() {} } else {}", - output: null, - options: ["multi"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (true)\nfoo()\n;[1, 2, 3].bar()", - output: "if (true)\n{foo()\n;}[1, 2, 3].bar()", - options: ["multi-line"], - errors: [{ messageId: "missingCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, + // https://github.com/eslint/eslint/issues/12370 + { + code: "if (foo) {\ndoSomething()\n;\n}", + output: "if (foo) \ndoSomething()\n;\n", + options: ["multi-or-nest"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) doSomething();\nelse if (bar) {\ndoSomethingElse()\n;\n}", + output: "if (foo) doSomething();\nelse if (bar) \ndoSomethingElse()\n;\n", + options: ["multi-or-nest"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (foo) doSomething();\nelse {\ndoSomethingElse()\n;\n}", + output: "if (foo) doSomething();\nelse \ndoSomethingElse()\n;\n", + options: ["multi-or-nest"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + }, + ], + }, + { + code: "for (var i = 0; foo; i++) {\ndoSomething()\n;\n}", + output: "for (var i = 0; foo; i++) \ndoSomething()\n;\n", + options: ["multi-or-nest"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "for" }, + type: "ForStatement", + }, + ], + }, + { + code: "for (var foo in bar) {\ndoSomething()\n;\n}", + output: "for (var foo in bar) \ndoSomething()\n;\n", + options: ["multi-or-nest"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "for-in" }, + type: "ForInStatement", + }, + ], + }, + { + code: "for (var foo of bar) {\ndoSomething()\n;\n}", + output: "for (var foo of bar) \ndoSomething()\n;\n", + options: ["multi-or-nest"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "for-of" }, + type: "ForOfStatement", + }, + ], + }, + { + code: "while (foo) {\ndoSomething()\n;\n}", + output: "while (foo) \ndoSomething()\n;\n", + options: ["multi-or-nest"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + }, + ], + }, + { + code: "do {\ndoSomething()\n;\n} while (foo)", + output: "do \ndoSomething()\n;\n while (foo)", + options: ["multi-or-nest"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + }, + ], + }, - // https://github.com/eslint/eslint/issues/12370 - { - code: "if (foo) {\ndoSomething()\n;\n}", - output: "if (foo) \ndoSomething()\n;\n", - options: ["multi-or-nest"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (foo) doSomething();\nelse if (bar) {\ndoSomethingElse()\n;\n}", - output: "if (foo) doSomething();\nelse if (bar) \ndoSomethingElse()\n;\n", - options: ["multi-or-nest"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (foo) doSomething();\nelse {\ndoSomethingElse()\n;\n}", - output: "if (foo) doSomething();\nelse \ndoSomethingElse()\n;\n", - options: ["multi-or-nest"], - errors: [{ messageId: "unexpectedCurlyAfter", data: { name: "else" }, type: "IfStatement" }] - }, - { - code: "for (var i = 0; foo; i++) {\ndoSomething()\n;\n}", - output: "for (var i = 0; foo; i++) \ndoSomething()\n;\n", - options: ["multi-or-nest"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "for" }, type: "ForStatement" }] - }, - { - code: "for (var foo in bar) {\ndoSomething()\n;\n}", - output: "for (var foo in bar) \ndoSomething()\n;\n", - options: ["multi-or-nest"], - errors: [{ messageId: "unexpectedCurlyAfter", data: { name: "for-in" }, type: "ForInStatement" }] - }, - { - code: "for (var foo of bar) {\ndoSomething()\n;\n}", - output: "for (var foo of bar) \ndoSomething()\n;\n", - options: ["multi-or-nest"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "unexpectedCurlyAfter", data: { name: "for-of" }, type: "ForOfStatement" }] - }, - { - code: "while (foo) {\ndoSomething()\n;\n}", - output: "while (foo) \ndoSomething()\n;\n", - options: ["multi-or-nest"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "while" }, type: "WhileStatement" }] - }, - { - code: "do {\ndoSomething()\n;\n} while (foo)", - output: "do \ndoSomething()\n;\n while (foo)", - options: ["multi-or-nest"], - errors: [{ messageId: "unexpectedCurlyAfter", data: { name: "do" }, type: "DoWhileStatement" }] - }, - - // https://github.com/eslint/eslint/issues/12928 (also in valid[]) - { - code: "if (a) { if (b) foo(); }", - output: "if (a) if (b) foo(); ", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (a) { if (b) foo(); else bar(); }", - output: "if (a) if (b) foo(); else bar(); ", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (a) { if (b) foo(); else bar(); } baz();", - output: "if (a) if (b) foo(); else bar(); baz();", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (a) { while (cond) if (b) foo(); }", - output: "if (a) while (cond) if (b) foo(); ", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (a) while (cond) { if (b) foo(); }", - output: "if (a) while (cond) if (b) foo(); ", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "while" }, type: "WhileStatement" }] - }, - { - code: "if (a) while (cond) { if (b) foo(); else bar(); }", - output: "if (a) while (cond) if (b) foo(); else bar(); ", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "while" }, type: "WhileStatement" }] - }, - { - code: "if (a) { while (cond) { if (b) foo(); } bar(); baz() } else quux();", - output: "if (a) { while (cond) if (b) foo(); bar(); baz() } else quux();", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "while" }, type: "WhileStatement" }] - }, - { - code: "if (a) { if (b) foo(); } bar();", - output: "if (a) if (b) foo(); bar();", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if(a) { if (b) foo(); } if (c) bar(); else baz();", - output: "if(a) if (b) foo(); if (c) bar(); else baz();", - options: ["multi-or-nest"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (a) { do if (b) foo(); while (cond); } else bar();", - output: "if (a) do if (b) foo(); while (cond); else bar();", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (a) do { if (b) foo(); } while (cond); else bar();", - output: "if (a) do if (b) foo(); while (cond); else bar();", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfter", data: { name: "do" }, type: "DoWhileStatement" }] - }, - { - code: "if (a) { if (b) foo(); else bar(); } else baz();", - output: "if (a) if (b) foo(); else bar(); else baz();", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (a) while (cond) { bar(); } else baz();", - output: "if (a) while (cond) bar(); else baz();", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "while" }, type: "WhileStatement" }] - }, - { - code: "if (a) { for (;;); } else bar();", - output: "if (a) for (;;); else bar();", - options: ["multi"], - errors: [{ messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }] - }, - { - code: "if (a) { while (cond) if (b) foo() } else bar();", - output: "if (a) { while (cond) if (b) foo() } else {bar();}", - options: ["multi", "consistent"], - errors: [ - { - messageId: "missingCurlyAfter", - data: { name: "else" }, - type: "IfStatement", - line: 1, - column: 43, - endLine: 1, - endColumn: 49 - } - ] - }, - { - code: "if (a) while (cond) if (b) foo() \nelse\n {bar();}", - output: "if (a) while (cond) if (b) foo() \nelse\n bar();", - options: ["multi", "consistent"], - errors: [ - { - messageId: "unexpectedCurlyAfter", - data: { name: "else" }, - type: "IfStatement", - line: 3, - column: 2, - endLine: 3, - endColumn: 10 - } - ] - }, - { - code: "if (a) foo() \nelse\n bar();", - output: "if (a) {foo()} \nelse\n {bar();}", - errors: [{ - type: "IfStatement", - messageId: "missingCurlyAfterCondition", - line: 1, - column: 8, - endLine: 1, - endColumn: 13 - }, - { - type: "IfStatement", - messageId: "missingCurlyAfter", - line: 3, - column: 2, - endLine: 3, - endColumn: 8 - }] - }, - { - code: "if (a) { while (cond) if (b) foo() } ", - output: "if (a) while (cond) if (b) foo() ", - options: ["multi", "consistent"], - errors: [{ - messageId: "unexpectedCurlyAfterCondition", - data: { name: "if" }, - type: "IfStatement", - line: 1, - column: 8, - endLine: 1, - endColumn: 37 - }] - }, - { - code: "if(a) { if (b) foo(); } if (c) bar(); else if(foo){bar();}", - output: "if(a) if (b) foo(); if (c) bar(); else if(foo)bar();", - options: ["multi-or-nest"], - errors: [{ - type: "IfStatement", - data: { name: "if" }, - messageId: "unexpectedCurlyAfterCondition", - line: 1, - column: 7, - endLine: 1, - endColumn: 24 - }, - { - type: "IfStatement", - data: { name: "if" }, - messageId: "unexpectedCurlyAfterCondition", - line: 1, - column: 51, - endLine: 1, - endColumn: 59 - }] - }, - { - code: "if (true) [1, 2, 3]\n.bar()", - output: "if (true) {[1, 2, 3]\n.bar()}", - options: ["multi-line"], - errors: [{ - data: { name: "if" }, - type: "IfStatement", - messageId: "missingCurlyAfterCondition", - line: 1, - column: 11, - endLine: 2, - endColumn: 7 - }] - }, - { - code: "for(\n;\n;\n) {foo()}", - output: "for(\n;\n;\n) foo()", - options: ["multi"], - errors: [{ - data: { name: "for" }, - type: "ForStatement", - messageId: "unexpectedCurlyAfterCondition", - line: 4, - column: 3, - endLine: 4, - endColumn: 10 - }] - }, - { - code: "for(\n;\n;\n) \nfoo()\n", - output: "for(\n;\n;\n) \n{foo()}\n", - options: ["multi-line"], - errors: [{ - data: { name: "for" }, - type: "ForStatement", - messageId: "missingCurlyAfterCondition", - line: 5, - column: 1, - endLine: 5, - endColumn: 6 - }] - }, - { - - /** - * Reports 2 errors, but one pair of braces is necessary if the other pair gets removed. - * Auto-fix will remove only outer braces in the first iteration. - * After that, the inner braces will become valid and won't be removed in the second iteration. - * If user manually removes inner braces first, the outer braces will become valid. - */ - code: "if (a) { while (cond) { if (b) foo(); } } else bar();", - output: "if (a) while (cond) { if (b) foo(); } else bar();", - options: ["multi"], - errors: [ - { messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" }, - { messageId: "unexpectedCurlyAfterCondition", data: { name: "while" }, type: "WhileStatement" } - ] - }, - { - code: "for(;;)foo()\n", - output: "for(;;){foo()}\n", - errors: [{ - data: { name: "for" }, - type: "ForStatement", - messageId: "missingCurlyAfterCondition", - line: 1, - column: 8, - endLine: 1, - endColumn: 13 - }] - }, - { - code: "for(var \ni \n in \n z)foo()\n", - output: "for(var \ni \n in \n z){foo()}\n", - errors: [{ - data: { name: "for-in" }, - type: "ForInStatement", - messageId: "missingCurlyAfter", - line: 4, - column: 4, - endLine: 4, - endColumn: 9 - }] - }, - { - code: "for(var i of \n z)\nfoo()\n", - output: "for(var i of \n z)\n{foo()}\n", - languageOptions: { ecmaVersion: 6 }, - errors: [{ - data: { name: "for-of" }, - type: "ForOfStatement", - messageId: "missingCurlyAfter", - line: 3, - column: 1, - endLine: 3, - endColumn: 6 - }] - } - ] + // https://github.com/eslint/eslint/issues/12928 (also in valid[]) + { + code: "if (a) { if (b) foo(); }", + output: "if (a) if (b) foo(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (a) { if (b) foo(); else bar(); }", + output: "if (a) if (b) foo(); else bar(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (a) { if (b) foo(); else bar(); } baz();", + output: "if (a) if (b) foo(); else bar(); baz();", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (a) { while (cond) if (b) foo(); }", + output: "if (a) while (cond) if (b) foo(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (a) while (cond) { if (b) foo(); }", + output: "if (a) while (cond) if (b) foo(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + }, + ], + }, + { + code: "if (a) while (cond) { if (b) foo(); else bar(); }", + output: "if (a) while (cond) if (b) foo(); else bar(); ", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + }, + ], + }, + { + code: "if (a) { while (cond) { if (b) foo(); } bar(); baz() } else quux();", + output: "if (a) { while (cond) if (b) foo(); bar(); baz() } else quux();", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + }, + ], + }, + { + code: "if (a) { if (b) foo(); } bar();", + output: "if (a) if (b) foo(); bar();", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if(a) { if (b) foo(); } if (c) bar(); else baz();", + output: "if(a) if (b) foo(); if (c) bar(); else baz();", + options: ["multi-or-nest"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (a) { do if (b) foo(); while (cond); } else bar();", + output: "if (a) do if (b) foo(); while (cond); else bar();", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (a) do { if (b) foo(); } while (cond); else bar();", + output: "if (a) do if (b) foo(); while (cond); else bar();", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "do" }, + type: "DoWhileStatement", + }, + ], + }, + { + code: "if (a) { if (b) foo(); else bar(); } else baz();", + output: "if (a) if (b) foo(); else bar(); else baz();", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (a) while (cond) { bar(); } else baz();", + output: "if (a) while (cond) bar(); else baz();", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + }, + ], + }, + { + code: "if (a) { for (;;); } else bar();", + output: "if (a) for (;;); else bar();", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + ], + }, + { + code: "if (a) { while (cond) if (b) foo() } else bar();", + output: "if (a) { while (cond) if (b) foo() } else {bar();}", + options: ["multi", "consistent"], + errors: [ + { + messageId: "missingCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + line: 1, + column: 43, + endLine: 1, + endColumn: 49, + }, + ], + }, + { + code: "if (a) while (cond) if (b) foo() \nelse\n {bar();}", + output: "if (a) while (cond) if (b) foo() \nelse\n bar();", + options: ["multi", "consistent"], + errors: [ + { + messageId: "unexpectedCurlyAfter", + data: { name: "else" }, + type: "IfStatement", + line: 3, + column: 2, + endLine: 3, + endColumn: 10, + }, + ], + }, + { + code: "if (a) foo() \nelse\n bar();", + output: "if (a) {foo()} \nelse\n {bar();}", + errors: [ + { + type: "IfStatement", + messageId: "missingCurlyAfterCondition", + line: 1, + column: 8, + endLine: 1, + endColumn: 13, + }, + { + type: "IfStatement", + messageId: "missingCurlyAfter", + line: 3, + column: 2, + endLine: 3, + endColumn: 8, + }, + ], + }, + { + code: "if (a) { while (cond) if (b) foo() } ", + output: "if (a) while (cond) if (b) foo() ", + options: ["multi", "consistent"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + line: 1, + column: 8, + endLine: 1, + endColumn: 37, + }, + ], + }, + { + code: "if(a) { if (b) foo(); } if (c) bar(); else if(foo){bar();}", + output: "if(a) if (b) foo(); if (c) bar(); else if(foo)bar();", + options: ["multi-or-nest"], + errors: [ + { + type: "IfStatement", + data: { name: "if" }, + messageId: "unexpectedCurlyAfterCondition", + line: 1, + column: 7, + endLine: 1, + endColumn: 24, + }, + { + type: "IfStatement", + data: { name: "if" }, + messageId: "unexpectedCurlyAfterCondition", + line: 1, + column: 51, + endLine: 1, + endColumn: 59, + }, + ], + }, + { + code: "if (true) [1, 2, 3]\n.bar()", + output: "if (true) {[1, 2, 3]\n.bar()}", + options: ["multi-line"], + errors: [ + { + data: { name: "if" }, + type: "IfStatement", + messageId: "missingCurlyAfterCondition", + line: 1, + column: 11, + endLine: 2, + endColumn: 7, + }, + ], + }, + { + code: "for(\n;\n;\n) {foo()}", + output: "for(\n;\n;\n) foo()", + options: ["multi"], + errors: [ + { + data: { name: "for" }, + type: "ForStatement", + messageId: "unexpectedCurlyAfterCondition", + line: 4, + column: 3, + endLine: 4, + endColumn: 10, + }, + ], + }, + { + code: "for(\n;\n;\n) \nfoo()\n", + output: "for(\n;\n;\n) \n{foo()}\n", + options: ["multi-line"], + errors: [ + { + data: { name: "for" }, + type: "ForStatement", + messageId: "missingCurlyAfterCondition", + line: 5, + column: 1, + endLine: 5, + endColumn: 6, + }, + ], + }, + { + /** + * Reports 2 errors, but one pair of braces is necessary if the other pair gets removed. + * Auto-fix will remove only outer braces in the first iteration. + * After that, the inner braces will become valid and won't be removed in the second iteration. + * If user manually removes inner braces first, the outer braces will become valid. + */ + code: "if (a) { while (cond) { if (b) foo(); } } else bar();", + output: "if (a) while (cond) { if (b) foo(); } else bar();", + options: ["multi"], + errors: [ + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "if" }, + type: "IfStatement", + }, + { + messageId: "unexpectedCurlyAfterCondition", + data: { name: "while" }, + type: "WhileStatement", + }, + ], + }, + { + code: "for(;;)foo()\n", + output: "for(;;){foo()}\n", + errors: [ + { + data: { name: "for" }, + type: "ForStatement", + messageId: "missingCurlyAfterCondition", + line: 1, + column: 8, + endLine: 1, + endColumn: 13, + }, + ], + }, + { + code: "for(var \ni \n in \n z)foo()\n", + output: "for(var \ni \n in \n z){foo()}\n", + errors: [ + { + data: { name: "for-in" }, + type: "ForInStatement", + messageId: "missingCurlyAfter", + line: 4, + column: 4, + endLine: 4, + endColumn: 9, + }, + ], + }, + { + code: "for(var i of \n z)\nfoo()\n", + output: "for(var i of \n z)\n{foo()}\n", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + data: { name: "for-of" }, + type: "ForOfStatement", + messageId: "missingCurlyAfter", + line: 3, + column: 1, + endLine: 3, + endColumn: 6, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/default-case-last.js b/tests/lib/rules/default-case-last.js index b0628c2e6654..d7828c52de19 100644 --- a/tests/lib/rules/default-case-last.js +++ b/tests/lib/rules/default-case-last.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/default-case-last"); -const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); +const RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Helpers @@ -22,16 +22,16 @@ const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); * @returns {Object} The error object. */ function error(column) { - const errorObject = { - messageId: "notLast", - type: "SwitchCase" - }; + const errorObject = { + messageId: "notLast", + type: "SwitchCase", + }; - if (column) { - errorObject.column = column; - } + if (column) { + errorObject.column = column; + } - return errorObject; + return errorObject; } //------------------------------------------------------------------------------ @@ -41,88 +41,88 @@ function error(column) { const ruleTester = new RuleTester(); ruleTester.run("default-case-last", rule, { - valid: [ - "switch (foo) {}", - "switch (foo) { case 1: bar(); break; }", - "switch (foo) { case 1: break; }", - "switch (foo) { case 1: }", - "switch (foo) { case 1: bar(); break; case 2: baz(); break; }", - "switch (foo) { case 1: break; case 2: break; }", - "switch (foo) { case 1: case 2: break; }", - "switch (foo) { case 1: case 2: }", - "switch (foo) { default: bar(); break; }", - "switch (foo) { default: bar(); }", - "switch (foo) { default: break; }", - "switch (foo) { default: }", - "switch (foo) { case 1: break; default: break; }", - "switch (foo) { case 1: break; default: }", - "switch (foo) { case 1: default: break; }", - "switch (foo) { case 1: default: }", - "switch (foo) { case 1: baz(); break; case 2: quux(); break; default: quuux(); break; }", - "switch (foo) { case 1: break; case 2: break; default: break; }", - "switch (foo) { case 1: break; case 2: break; default: }", - "switch (foo) { case 1: case 2: break; default: break; }", - "switch (foo) { case 1: break; case 2: default: break; }", - "switch (foo) { case 1: break; case 2: default: }", - "switch (foo) { case 1: case 2: default: }" - ], + valid: [ + "switch (foo) {}", + "switch (foo) { case 1: bar(); break; }", + "switch (foo) { case 1: break; }", + "switch (foo) { case 1: }", + "switch (foo) { case 1: bar(); break; case 2: baz(); break; }", + "switch (foo) { case 1: break; case 2: break; }", + "switch (foo) { case 1: case 2: break; }", + "switch (foo) { case 1: case 2: }", + "switch (foo) { default: bar(); break; }", + "switch (foo) { default: bar(); }", + "switch (foo) { default: break; }", + "switch (foo) { default: }", + "switch (foo) { case 1: break; default: break; }", + "switch (foo) { case 1: break; default: }", + "switch (foo) { case 1: default: break; }", + "switch (foo) { case 1: default: }", + "switch (foo) { case 1: baz(); break; case 2: quux(); break; default: quuux(); break; }", + "switch (foo) { case 1: break; case 2: break; default: break; }", + "switch (foo) { case 1: break; case 2: break; default: }", + "switch (foo) { case 1: case 2: break; default: break; }", + "switch (foo) { case 1: break; case 2: default: break; }", + "switch (foo) { case 1: break; case 2: default: }", + "switch (foo) { case 1: case 2: default: }", + ], - invalid: [ - { - code: "switch (foo) { default: bar(); break; case 1: baz(); break; }", - errors: [error(16)] - }, - { - code: "switch (foo) { default: break; case 1: break; }", - errors: [error(16)] - }, - { - code: "switch (foo) { default: break; case 1: }", - errors: [error(16)] - }, - { - code: "switch (foo) { default: case 1: break; }", - errors: [error(16)] - }, - { - code: "switch (foo) { default: case 1: }", - errors: [error(16)] - }, - { - code: "switch (foo) { default: break; case 1: break; case 2: break; }", - errors: [error(16)] - }, - { - code: "switch (foo) { default: case 1: break; case 2: break; }", - errors: [error(16)] - }, - { - code: "switch (foo) { default: case 1: case 2: break; }", - errors: [error(16)] - }, - { - code: "switch (foo) { default: case 1: case 2: }", - errors: [error(16)] - }, - { - code: "switch (foo) { case 1: break; default: break; case 2: break; }", - errors: [error(31)] - }, - { - code: "switch (foo) { case 1: default: break; case 2: break; }", - errors: [error(24)] - }, - { - code: "switch (foo) { case 1: break; default: case 2: break; }", - errors: [error(31)] - }, - { - code: "switch (foo) { case 1: default: case 2: break; }", - errors: [error(24)] - }, - { - code: "switch (foo) { case 1: default: case 2: }", - errors: [error(24)] - } - ] + invalid: [ + { + code: "switch (foo) { default: bar(); break; case 1: baz(); break; }", + errors: [error(16)], + }, + { + code: "switch (foo) { default: break; case 1: break; }", + errors: [error(16)], + }, + { + code: "switch (foo) { default: break; case 1: }", + errors: [error(16)], + }, + { + code: "switch (foo) { default: case 1: break; }", + errors: [error(16)], + }, + { + code: "switch (foo) { default: case 1: }", + errors: [error(16)], + }, + { + code: "switch (foo) { default: break; case 1: break; case 2: break; }", + errors: [error(16)], + }, + { + code: "switch (foo) { default: case 1: break; case 2: break; }", + errors: [error(16)], + }, + { + code: "switch (foo) { default: case 1: case 2: break; }", + errors: [error(16)], + }, + { + code: "switch (foo) { default: case 1: case 2: }", + errors: [error(16)], + }, + { + code: "switch (foo) { case 1: break; default: break; case 2: break; }", + errors: [error(31)], + }, + { + code: "switch (foo) { case 1: default: break; case 2: break; }", + errors: [error(24)], + }, + { + code: "switch (foo) { case 1: break; default: case 2: break; }", + errors: [error(31)], + }, + { + code: "switch (foo) { case 1: default: case 2: break; }", + errors: [error(24)], + }, + { + code: "switch (foo) { case 1: default: case 2: }", + errors: [error(24)], + }, + ], }); diff --git a/tests/lib/rules/default-case.js b/tests/lib/rules/default-case.js index d8b37506aad5..2f0ad8c7d5da 100644 --- a/tests/lib/rules/default-case.js +++ b/tests/lib/rules/default-case.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/default-case"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,98 +18,123 @@ const rule = require("../../../lib/rules/default-case"), const ruleTester = new RuleTester(); ruleTester.run("default-case", rule, { + valid: [ + "switch (a) { case 1: break; default: break; }", + "switch (a) { case 1: break; case 2: default: break; }", + "switch (a) { case 1: break; default: break; \n //no default \n }", + "switch (a) { \n case 1: break; \n\n//oh-oh \n // no default\n }", + "switch (a) { \n case 1: \n\n// no default\n }", + "switch (a) { \n case 1: \n\n// No default\n }", + "switch (a) { \n case 1: \n\n// no deFAUlt\n }", + "switch (a) { \n case 1: \n\n// NO DEFAULT\n }", + "switch (a) { \n case 1: a = 4; \n\n// no default\n }", + "switch (a) { \n case 1: a = 4; \n\n/* no default */\n }", + "switch (a) { \n case 1: a = 4; break; break; \n\n// no default\n }", + "switch (a) { // no default\n }", + "switch (a) { }", + { + code: "switch (a) { case 1: break; default: break; }", + options: [ + { + commentPattern: "default case omitted", + }, + ], + }, + { + code: "switch (a) { case 1: break; \n // skip default case \n }", + options: [ + { + commentPattern: "^skip default", + }, + ], + }, + { + code: "switch (a) { case 1: break; \n /*\nTODO:\n throw error in default case\n*/ \n }", + options: [ + { + commentPattern: "default", + }, + ], + }, + { + code: "switch (a) { case 1: break; \n// \n }", + options: [ + { + commentPattern: ".?", + }, + ], + }, + ], - valid: [ - "switch (a) { case 1: break; default: break; }", - "switch (a) { case 1: break; case 2: default: break; }", - "switch (a) { case 1: break; default: break; \n //no default \n }", - "switch (a) { \n case 1: break; \n\n//oh-oh \n // no default\n }", - "switch (a) { \n case 1: \n\n// no default\n }", - "switch (a) { \n case 1: \n\n// No default\n }", - "switch (a) { \n case 1: \n\n// no deFAUlt\n }", - "switch (a) { \n case 1: \n\n// NO DEFAULT\n }", - "switch (a) { \n case 1: a = 4; \n\n// no default\n }", - "switch (a) { \n case 1: a = 4; \n\n/* no default */\n }", - "switch (a) { \n case 1: a = 4; break; break; \n\n// no default\n }", - "switch (a) { // no default\n }", - "switch (a) { }", - { - code: "switch (a) { case 1: break; default: break; }", - options: [{ - commentPattern: "default case omitted" - }] - }, - { - code: "switch (a) { case 1: break; \n // skip default case \n }", - options: [{ - commentPattern: "^skip default" - }] - }, - { - code: "switch (a) { case 1: break; \n /*\nTODO:\n throw error in default case\n*/ \n }", - options: [{ - commentPattern: "default" - }] - }, - { - code: "switch (a) { case 1: break; \n// \n }", - options: [{ - commentPattern: ".?" - }] - } - ], - - invalid: [ - { - code: "switch (a) { case 1: break; }", - errors: [{ - messageId: "missingDefaultCase", - type: "SwitchStatement" - }] - }, - { - code: "switch (a) { \n // no default \n case 1: break; }", - errors: [{ - messageId: "missingDefaultCase", - type: "SwitchStatement" - }] - }, - { - code: "switch (a) { case 1: break; \n // no default \n // nope \n }", - errors: [{ - messageId: "missingDefaultCase", - type: "SwitchStatement" - }] - }, - { - code: "switch (a) { case 1: break; \n // no default \n }", - options: [{ - commentPattern: "skipped default case" - }], - errors: [{ - messageId: "missingDefaultCase", - type: "SwitchStatement" - }] - }, - { - code: "switch (a) {\ncase 1: break; \n// default omitted intentionally \n// TODO: add default case \n}", - options: [{ - commentPattern: "default omitted" - }], - errors: [{ - messageId: "missingDefaultCase", - type: "SwitchStatement" - }] - }, - { - code: "switch (a) {\ncase 1: break;\n}", - options: [{ - commentPattern: ".?" - }], - errors: [{ - messageId: "missingDefaultCase", - type: "SwitchStatement" - }] - } - ] + invalid: [ + { + code: "switch (a) { case 1: break; }", + errors: [ + { + messageId: "missingDefaultCase", + type: "SwitchStatement", + }, + ], + }, + { + code: "switch (a) { \n // no default \n case 1: break; }", + errors: [ + { + messageId: "missingDefaultCase", + type: "SwitchStatement", + }, + ], + }, + { + code: "switch (a) { case 1: break; \n // no default \n // nope \n }", + errors: [ + { + messageId: "missingDefaultCase", + type: "SwitchStatement", + }, + ], + }, + { + code: "switch (a) { case 1: break; \n // no default \n }", + options: [ + { + commentPattern: "skipped default case", + }, + ], + errors: [ + { + messageId: "missingDefaultCase", + type: "SwitchStatement", + }, + ], + }, + { + code: "switch (a) {\ncase 1: break; \n// default omitted intentionally \n// TODO: add default case \n}", + options: [ + { + commentPattern: "default omitted", + }, + ], + errors: [ + { + messageId: "missingDefaultCase", + type: "SwitchStatement", + }, + ], + }, + { + code: "switch (a) {\ncase 1: break;\n}", + options: [ + { + commentPattern: ".?", + }, + ], + errors: [ + { + messageId: "missingDefaultCase", + type: "SwitchStatement", + }, + ], + }, + ], }); diff --git a/tests/lib/rules/default-param-last.js b/tests/lib/rules/default-param-last.js index 8ff141f1036d..374d70901ed3 100644 --- a/tests/lib/rules/default-param-last.js +++ b/tests/lib/rules/default-param-last.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/default-param-last"); -const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); +const RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,97 +18,636 @@ const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); const SHOULD_BE_LAST = "shouldBeLast"; const ruleTester = new RuleTester({ - languageOptions: { ecmaVersion: 8 } + languageOptions: { ecmaVersion: 8 }, }); const cannedError = { - messageId: SHOULD_BE_LAST, - type: "AssignmentPattern" + messageId: SHOULD_BE_LAST, + type: "AssignmentPattern", }; ruleTester.run("default-param-last", rule, { - valid: [ - "function f() {}", - "function f(a) {}", - "function f(a = 5) {}", - "function f(a, b) {}", - "function f(a, b = 5) {}", - "function f(a, b = 5, c = 5) {}", - "function f(a, b = 5, ...c) {}", - "const f = () => {}", - "const f = (a) => {}", - "const f = (a = 5) => {}", - "const f = function f() {}", - "const f = function f(a) {}", - "const f = function f(a = 5) {}" - ], - invalid: [ - { - code: "function f(a = 5, b) {}", - errors: [ - { - messageId: SHOULD_BE_LAST, - column: 12, - endColumn: 17 - } - ] - }, - { - code: "function f(a = 5, b = 6, c) {}", - errors: [ - { - messageId: SHOULD_BE_LAST, - column: 12, - endColumn: 17 - }, - { - messageId: SHOULD_BE_LAST, - column: 19, - endColumn: 24 - } - ] - }, - { - code: "function f (a = 5, b, c = 6, d) {}", - errors: [cannedError, cannedError] - }, - { - code: "function f(a = 5, b, c = 5) {}", - errors: [ - { - messageId: SHOULD_BE_LAST, - column: 12, - endColumn: 17 - } - ] - }, - { - code: "const f = (a = 5, b, ...c) => {}", - errors: [cannedError] - }, - { - code: "const f = function f (a, b = 5, c) {}", - errors: [cannedError] - }, - { - code: "const f = (a = 5, { b }) => {}", - errors: [cannedError] - }, - { - code: "const f = ({ a } = {}, b) => {}", - errors: [cannedError] - }, - { - code: "const f = ({ a, b } = { a: 1, b: 2 }, c) => {}", - errors: [cannedError] - }, - { - code: "const f = ([a] = [], b) => {}", - errors: [cannedError] - }, - { - code: "const f = ([a, b] = [1, 2], c) => {}", - errors: [cannedError] - } - ] + valid: [ + "function f() {}", + "function f(a) {}", + "function f(a = 5) {}", + "function f(a, b) {}", + "function f(a, b = 5) {}", + "function f(a, b = 5, c = 5) {}", + "function f(a, b = 5, ...c) {}", + "const f = () => {}", + "const f = (a) => {}", + "const f = (a = 5) => {}", + "const f = function f() {}", + "const f = function f(a) {}", + "const f = function f(a = 5) {}", + ], + invalid: [ + { + code: "function f(a = 5, b) {}", + errors: [ + { + messageId: SHOULD_BE_LAST, + column: 12, + endColumn: 17, + }, + ], + }, + { + code: "function f(a = 5, b = 6, c) {}", + errors: [ + { + messageId: SHOULD_BE_LAST, + column: 12, + endColumn: 17, + }, + { + messageId: SHOULD_BE_LAST, + column: 19, + endColumn: 24, + }, + ], + }, + { + code: "function f (a = 5, b, c = 6, d) {}", + errors: [cannedError, cannedError], + }, + { + code: "function f(a = 5, b, c = 5) {}", + errors: [ + { + messageId: SHOULD_BE_LAST, + column: 12, + endColumn: 17, + }, + ], + }, + { + code: "const f = (a = 5, b, ...c) => {}", + errors: [cannedError], + }, + { + code: "const f = function f (a, b = 5, c) {}", + errors: [cannedError], + }, + { + code: "const f = (a = 5, { b }) => {}", + errors: [cannedError], + }, + { + code: "const f = ({ a } = {}, b) => {}", + errors: [cannedError], + }, + { + code: "const f = ({ a, b } = { a: 1, b: 2 }, c) => {}", + errors: [cannedError], + }, + { + code: "const f = ([a] = [], b) => {}", + errors: [cannedError], + }, + { + code: "const f = ([a, b] = [1, 2], c) => {}", + errors: [cannedError], + }, + ], +}); + +const ruleTesterTypeScript = new RuleTester({ + languageOptions: { + parser: require("@typescript-eslint/parser"), + }, +}); + +ruleTesterTypeScript.run("default-param-last", rule, { + valid: [ + "function foo() {}", + "function foo(a: number) {}", + "function foo(a = 1) {}", + "function foo(a?: number) {}", + "function foo(a: number, b: number) {}", + "function foo(a: number, b: number, c?: number) {}", + "function foo(a: number, b = 1) {}", + "function foo(a: number, b = 1, c = 1) {}", + "function foo(a: number, b = 1, c?: number) {}", + "function foo(a: number, b?: number, c = 1) {}", + "function foo(a: number, b = 1, ...c) {}", + + "const foo = function () {};", + "const foo = function (a: number) {};", + "const foo = function (a = 1) {};", + "const foo = function (a?: number) {};", + "const foo = function (a: number, b: number) {};", + "const foo = function (a: number, b: number, c?: number) {};", + "const foo = function (a: number, b = 1) {};", + "const foo = function (a: number, b = 1, c = 1) {};", + "const foo = function (a: number, b = 1, c?: number) {};", + "const foo = function (a: number, b?: number, c = 1) {};", + "const foo = function (a: number, b = 1, ...c) {};", + + "const foo = () => {};", + "const foo = (a: number) => {};", + "const foo = (a = 1) => {};", + "const foo = (a?: number) => {};", + "const foo = (a: number, b: number) => {};", + "const foo = (a: number, b: number, c?: number) => {};", + "const foo = (a: number, b = 1) => {};", + "const foo = (a: number, b = 1, c = 1) => {};", + "const foo = (a: number, b = 1, c?: number) => {};", + "const foo = (a: number, b?: number, c = 1) => {};", + "const foo = (a: number, b = 1, ...c) => {};", + ` + class Foo { + constructor(a: number, b: number, c: number) {} + } + `, + ` + class Foo { + constructor(a: number, b?: number, c = 1) {} + } + `, + ` + class Foo { + constructor(a: number, b = 1, c?: number) {} + } + `, + ` + class Foo { + constructor( + public a: number, + protected b: number, + private c: number, + ) {} + } + `, + ` + class Foo { + constructor( + public a: number, + protected b?: number, + private c = 10, + ) {} + } + `, + ` + class Foo { + constructor( + public a: number, + protected b = 10, + private c?: number, + ) {} + } + `, + ` + class Foo { + constructor( + a: number, + protected b?: number, + private c = 0, + ) {} + } + `, + ` + class Foo { + constructor( + a: number, + b?: number, + private c = 0, + ) {} + } + `, + ` + class Foo { + constructor( + a: number, + private b?: number, + c = 0, + ) {} + } + `, + ], + invalid: [ + { + code: "function foo(a = 1, b: number) {}", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 14, + endColumn: 19, + }, + ], + }, + { + code: "function foo(a = 1, b = 2, c: number) {}", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 14, + endColumn: 19, + }, + { + messageId: "shouldBeLast", + line: 1, + column: 21, + endColumn: 26, + }, + ], + }, + { + code: "function foo(a = 1, b: number, c = 2, d: number) {}", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 14, + endColumn: 19, + }, + { + messageId: "shouldBeLast", + line: 1, + column: 32, + endColumn: 37, + }, + ], + }, + { + code: "function foo(a = 1, b: number, c = 2) {}", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 14, + endColumn: 19, + }, + ], + }, + { + code: "function foo(a = 1, b: number, ...c) {}", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 14, + endColumn: 19, + }, + ], + }, + { + code: "function foo(a?: number, b: number) {}", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 14, + endColumn: 24, + }, + ], + }, + { + code: "function foo(a: number, b?: number, c: number) {}", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 25, + endColumn: 35, + }, + ], + }, + { + code: "function foo(a = 1, b?: number, c: number) {}", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 14, + endColumn: 19, + }, + { + messageId: "shouldBeLast", + line: 1, + column: 21, + endColumn: 31, + }, + ], + }, + { + code: "const foo = function (a = 1, b: number) {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 23, + endColumn: 28, + }, + ], + }, + { + code: "const foo = function (a = 1, b = 2, c: number) {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 23, + endColumn: 28, + }, + { + messageId: "shouldBeLast", + line: 1, + column: 30, + endColumn: 35, + }, + ], + }, + { + code: "const foo = function (a = 1, b: number, c = 2, d: number) {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 23, + endColumn: 28, + }, + { + messageId: "shouldBeLast", + line: 1, + column: 41, + endColumn: 46, + }, + ], + }, + { + code: "const foo = function (a = 1, b: number, c = 2) {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 23, + endColumn: 28, + }, + ], + }, + { + code: "const foo = function (a = 1, b: number, ...c) {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 23, + endColumn: 28, + }, + ], + }, + { + code: "const foo = function (a?: number, b: number) {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 23, + endColumn: 33, + }, + ], + }, + { + code: "const foo = function (a: number, b?: number, c: number) {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 34, + endColumn: 44, + }, + ], + }, + { + code: "const foo = function (a = 1, b?: number, c: number) {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 23, + endColumn: 28, + }, + { + messageId: "shouldBeLast", + line: 1, + column: 30, + endColumn: 40, + }, + ], + }, + { + code: "const foo = (a = 1, b: number) => {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 14, + endColumn: 19, + }, + ], + }, + { + code: "const foo = (a = 1, b = 2, c: number) => {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 14, + endColumn: 19, + }, + { + messageId: "shouldBeLast", + line: 1, + column: 21, + endColumn: 26, + }, + ], + }, + { + code: "const foo = (a = 1, b: number, c = 2, d: number) => {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 14, + endColumn: 19, + }, + { + messageId: "shouldBeLast", + line: 1, + column: 32, + endColumn: 37, + }, + ], + }, + { + code: "const foo = (a = 1, b: number, c = 2) => {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 14, + endColumn: 19, + }, + ], + }, + { + code: "const foo = (a = 1, b: number, ...c) => {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 14, + endColumn: 19, + }, + ], + }, + { + code: "const foo = (a?: number, b: number) => {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 14, + endColumn: 24, + }, + ], + }, + { + code: "const foo = (a: number, b?: number, c: number) => {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 25, + endColumn: 35, + }, + ], + }, + { + code: "const foo = (a = 1, b?: number, c: number) => {};", + errors: [ + { + messageId: "shouldBeLast", + line: 1, + column: 14, + endColumn: 19, + }, + { + messageId: "shouldBeLast", + line: 1, + column: 21, + endColumn: 31, + }, + ], + }, + { + code: ` + class Foo { + constructor( + public a: number, + protected b?: number, + private c: number, + ) {} + } + `, + errors: [ + { + messageId: "shouldBeLast", + line: 5, + column: 9, + endColumn: 29, + }, + ], + }, + { + code: ` + class Foo { + constructor( + public a: number, + protected b = 0, + private c: number, + ) {} + } + `, + errors: [ + { + messageId: "shouldBeLast", + line: 5, + column: 9, + endColumn: 24, + }, + ], + }, + { + code: ` + class Foo { + constructor( + public a?: number, + private b: number, + ) {} + } + `, + errors: [ + { + messageId: "shouldBeLast", + line: 4, + column: 9, + endColumn: 26, + }, + ], + }, + { + code: ` + class Foo { + constructor( + public a = 0, + private b: number, + ) {} + } + `, + errors: [ + { + messageId: "shouldBeLast", + line: 4, + column: 9, + endColumn: 21, + }, + ], + }, + { + code: ` + class Foo { + constructor(a = 0, b: number) {} + } + `, + errors: [ + { + messageId: "shouldBeLast", + line: 3, + column: 19, + endColumn: 24, + }, + ], + }, + { + code: ` + class Foo { + constructor(a?: number, b: number) {} + } + `, + errors: [ + { + messageId: "shouldBeLast", + line: 3, + column: 19, + endColumn: 29, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/dot-location.js b/tests/lib/rules/dot-location.js index d84fa00626ba..11aca5334ba3 100644 --- a/tests/lib/rules/dot-location.js +++ b/tests/lib/rules/dot-location.js @@ -10,415 +10,601 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/dot-location"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, }); ruleTester.run("dot-location", rule, { - valid: [ - "obj.\nprop", - "obj. \nprop", - "obj.\n prop", - "(obj).\nprop", - "obj\n['prop']", - "obj['prop']", - { - code: "obj.\nprop", - options: ["object"] - }, - { - code: "obj\n.prop", - options: ["property"] - }, - { - code: "(obj)\n.prop", - options: ["property"] - }, - { - code: "obj . prop", - options: ["object"] - }, - { - code: "obj /* a */ . prop", - options: ["object"] - }, - { - code: "obj . \nprop", - options: ["object"] - }, - { - code: "obj . prop", - options: ["property"] - }, - { - code: "obj . /* a */ prop", - options: ["property"] - }, - { - code: "obj\n. prop", - options: ["property"] - }, - { - code: "f(a\n).prop", - options: ["object"] - }, - { - code: "`\n`.prop", - options: ["object"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "obj[prop]", - options: ["object"] - }, - { - code: "obj\n[prop]", - options: ["object"] - }, - { - code: "obj[\nprop]", - options: ["object"] - }, - { - code: "obj\n[\nprop\n]", - options: ["object"] - }, - { - code: "obj[prop]", - options: ["property"] - }, - { - code: "obj\n[prop]", - options: ["property"] - }, - { - code: "obj[\nprop]", - options: ["property"] - }, - { - code: "obj\n[\nprop\n]", - options: ["property"] - }, + valid: [ + "obj.\nprop", + "obj. \nprop", + "obj.\n prop", + "(obj).\nprop", + "obj\n['prop']", + "obj['prop']", + { + code: "obj.\nprop", + options: ["object"], + }, + { + code: "obj\n.prop", + options: ["property"], + }, + { + code: "(obj)\n.prop", + options: ["property"], + }, + { + code: "obj . prop", + options: ["object"], + }, + { + code: "obj /* a */ . prop", + options: ["object"], + }, + { + code: "obj . \nprop", + options: ["object"], + }, + { + code: "obj . prop", + options: ["property"], + }, + { + code: "obj . /* a */ prop", + options: ["property"], + }, + { + code: "obj\n. prop", + options: ["property"], + }, + { + code: "f(a\n).prop", + options: ["object"], + }, + { + code: "`\n`.prop", + options: ["object"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "obj[prop]", + options: ["object"], + }, + { + code: "obj\n[prop]", + options: ["object"], + }, + { + code: "obj[\nprop]", + options: ["object"], + }, + { + code: "obj\n[\nprop\n]", + options: ["object"], + }, + { + code: "obj[prop]", + options: ["property"], + }, + { + code: "obj\n[prop]", + options: ["property"], + }, + { + code: "obj[\nprop]", + options: ["property"], + }, + { + code: "obj\n[\nprop\n]", + options: ["property"], + }, - // https://github.com/eslint/eslint/issues/11868 (also in invalid) - { - code: "(obj).prop", - options: ["object"] - }, - { - code: "(obj).\nprop", - options: ["object"] - }, - { - code: "(obj\n).\nprop", - options: ["object"] - }, - { - code: "(\nobj\n).\nprop", - options: ["object"] - }, - { - code: "((obj\n)).\nprop", - options: ["object"] - }, - { - code: "(f(a)\n).\nprop", - options: ["object"] - }, - { - code: "((obj\n)\n).\nprop", - options: ["object"] - }, - { - code: "(\na &&\nb()\n).toString()", - options: ["object"] - }, + // https://github.com/eslint/eslint/issues/11868 (also in invalid) + { + code: "(obj).prop", + options: ["object"], + }, + { + code: "(obj).\nprop", + options: ["object"], + }, + { + code: "(obj\n).\nprop", + options: ["object"], + }, + { + code: "(\nobj\n).\nprop", + options: ["object"], + }, + { + code: "((obj\n)).\nprop", + options: ["object"], + }, + { + code: "(f(a)\n).\nprop", + options: ["object"], + }, + { + code: "((obj\n)\n).\nprop", + options: ["object"], + }, + { + code: "(\na &&\nb()\n).toString()", + options: ["object"], + }, - // Optional chaining - { - code: "obj?.prop", - options: ["object"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "obj?.[key]", - options: ["object"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "obj?.\nprop", - options: ["object"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "obj\n?.[key]", - options: ["object"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "obj?.\n[key]", - options: ["object"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "obj?.[\nkey]", - options: ["object"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "obj?.prop", - options: ["property"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "obj?.[key]", - options: ["property"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "obj\n?.prop", - options: ["property"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "obj\n?.[key]", - options: ["property"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "obj?.\n[key]", - options: ["property"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "obj?.[\nkey]", - options: ["property"], - languageOptions: { ecmaVersion: 2020 } - }, + // Optional chaining + { + code: "obj?.prop", + options: ["object"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "obj?.[key]", + options: ["object"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "obj?.\nprop", + options: ["object"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "obj\n?.[key]", + options: ["object"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "obj?.\n[key]", + options: ["object"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "obj?.[\nkey]", + options: ["object"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "obj?.prop", + options: ["property"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "obj?.[key]", + options: ["property"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "obj\n?.prop", + options: ["property"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "obj\n?.[key]", + options: ["property"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "obj?.\n[key]", + options: ["property"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "obj?.[\nkey]", + options: ["property"], + languageOptions: { ecmaVersion: 2020 }, + }, - // Private properties - { - code: "class C { #a; foo() { this.\n#a; } }", - options: ["object"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #a; foo() { this\n.#a; } }", - options: ["property"], - languageOptions: { ecmaVersion: 2022 } - } - ], - invalid: [ - { - code: "obj\n.property", - output: "obj.\nproperty", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1, endLine: 2, endColumn: 2 }] - }, - { - code: "obj.\nproperty", - output: "obj\n.property", - options: ["property"], - errors: [{ messageId: "expectedDotBeforeProperty", type: "MemberExpression", line: 1, column: 4, endLine: 1, endColumn: 5 }] - }, - { - code: "(obj).\nproperty", - output: "(obj)\n.property", - options: ["property"], - errors: [{ messageId: "expectedDotBeforeProperty", type: "MemberExpression", line: 1, column: 6 }] - }, - { - code: "5\n.toExponential()", - output: "5 .\ntoExponential()", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }] - }, - { - code: "-5\n.toExponential()", - output: "-5 .\ntoExponential()", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }] - }, - { - code: "01\n.toExponential()", - output: "01.\ntoExponential()", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }] - }, - { - code: "08\n.toExponential()", - output: "08 .\ntoExponential()", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }] - }, - { - code: "0190\n.toExponential()", - output: "0190 .\ntoExponential()", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }] - }, - { - code: "5_000\n.toExponential()", - output: "5_000 .\ntoExponential()", - options: ["object"], - languageOptions: { ecmaVersion: 2021 }, - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }] - }, - { - code: "5_000_00\n.toExponential()", - output: "5_000_00 .\ntoExponential()", - options: ["object"], - languageOptions: { ecmaVersion: 2021 }, - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }] - }, - { - code: "5.000_000\n.toExponential()", - output: "5.000_000.\ntoExponential()", - options: ["object"], - languageOptions: { ecmaVersion: 2021 }, - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }] - }, - { - code: "0b1010_1010\n.toExponential()", - output: "0b1010_1010.\ntoExponential()", - options: ["object"], - languageOptions: { ecmaVersion: 2021 }, - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }] - }, - { - code: "foo /* a */ . /* b */ \n /* c */ bar", - output: "foo /* a */ /* b */ \n /* c */ .bar", - options: ["property"], - errors: [{ messageId: "expectedDotBeforeProperty", type: "MemberExpression", line: 1, column: 13 }] - }, - { - code: "foo /* a */ \n /* b */ . /* c */ bar", - output: "foo. /* a */ \n /* b */ /* c */ bar", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 10 }] - }, - { - code: "f(a\n)\n.prop", - output: "f(a\n).\nprop", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 3, column: 1 }] - }, - { - code: "`\n`\n.prop", - output: "`\n`.\nprop", - options: ["object"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 3, column: 1 }] - }, + // Private properties + { + code: "class C { #a; foo() { this.\n#a; } }", + options: ["object"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #a; foo() { this\n.#a; } }", + options: ["property"], + languageOptions: { ecmaVersion: 2022 }, + }, + ], + invalid: [ + { + code: "obj\n.property", + output: "obj.\nproperty", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 2, + column: 1, + endLine: 2, + endColumn: 2, + }, + ], + }, + { + code: "obj.\nproperty", + output: "obj\n.property", + options: ["property"], + errors: [ + { + messageId: "expectedDotBeforeProperty", + type: "MemberExpression", + line: 1, + column: 4, + endLine: 1, + endColumn: 5, + }, + ], + }, + { + code: "(obj).\nproperty", + output: "(obj)\n.property", + options: ["property"], + errors: [ + { + messageId: "expectedDotBeforeProperty", + type: "MemberExpression", + line: 1, + column: 6, + }, + ], + }, + { + code: "5\n.toExponential()", + output: "5 .\ntoExponential()", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "-5\n.toExponential()", + output: "-5 .\ntoExponential()", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "01\n.toExponential()", + output: "01.\ntoExponential()", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "08\n.toExponential()", + output: "08 .\ntoExponential()", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "0190\n.toExponential()", + output: "0190 .\ntoExponential()", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "5_000\n.toExponential()", + output: "5_000 .\ntoExponential()", + options: ["object"], + languageOptions: { ecmaVersion: 2021 }, + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "5_000_00\n.toExponential()", + output: "5_000_00 .\ntoExponential()", + options: ["object"], + languageOptions: { ecmaVersion: 2021 }, + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "5.000_000\n.toExponential()", + output: "5.000_000.\ntoExponential()", + options: ["object"], + languageOptions: { ecmaVersion: 2021 }, + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "0b1010_1010\n.toExponential()", + output: "0b1010_1010.\ntoExponential()", + options: ["object"], + languageOptions: { ecmaVersion: 2021 }, + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 2, + column: 1, + }, + ], + }, + { + code: "foo /* a */ . /* b */ \n /* c */ bar", + output: "foo /* a */ /* b */ \n /* c */ .bar", + options: ["property"], + errors: [ + { + messageId: "expectedDotBeforeProperty", + type: "MemberExpression", + line: 1, + column: 13, + }, + ], + }, + { + code: "foo /* a */ \n /* b */ . /* c */ bar", + output: "foo. /* a */ \n /* b */ /* c */ bar", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 2, + column: 10, + }, + ], + }, + { + code: "f(a\n)\n.prop", + output: "f(a\n).\nprop", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "`\n`\n.prop", + output: "`\n`.\nprop", + options: ["object"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 3, + column: 1, + }, + ], + }, - // https://github.com/eslint/eslint/issues/11868 (also in valid) - { - code: "(a\n)\n.prop", - output: "(a\n).\nprop", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 3, column: 1 }] - }, - { - code: "(a\n)\n.\nprop", - output: "(a\n).\n\nprop", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 3, column: 1 }] - }, - { - code: "(f(a)\n)\n.prop", - output: "(f(a)\n).\nprop", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 3, column: 1 }] - }, - { - code: "(f(a\n)\n)\n.prop", - output: "(f(a\n)\n).\nprop", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 4, column: 1 }] - }, - { - code: "((obj\n))\n.prop", - output: "((obj\n)).\nprop", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 3, column: 1 }] - }, - { - code: "((obj\n)\n)\n.prop", - output: "((obj\n)\n).\nprop", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 4, column: 1 }] - }, - { - code: "(a\n) /* a */ \n.prop", - output: "(a\n). /* a */ \nprop", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 3, column: 1 }] - }, - { - code: "(a\n)\n/* a */\n.prop", - output: "(a\n).\n/* a */\nprop", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 4, column: 1 }] - }, - { - code: "(a\n)\n/* a */.prop", - output: "(a\n).\n/* a */prop", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 3, column: 8 }] - }, - { - code: "(5)\n.toExponential()", - output: "(5).\ntoExponential()", - options: ["object"], - errors: [{ messageId: "expectedDotAfterObject", type: "MemberExpression", line: 2, column: 1 }] - }, + // https://github.com/eslint/eslint/issues/11868 (also in valid) + { + code: "(a\n)\n.prop", + output: "(a\n).\nprop", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "(a\n)\n.\nprop", + output: "(a\n).\n\nprop", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "(f(a)\n)\n.prop", + output: "(f(a)\n).\nprop", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "(f(a\n)\n)\n.prop", + output: "(f(a\n)\n).\nprop", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 4, + column: 1, + }, + ], + }, + { + code: "((obj\n))\n.prop", + output: "((obj\n)).\nprop", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "((obj\n)\n)\n.prop", + output: "((obj\n)\n).\nprop", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 4, + column: 1, + }, + ], + }, + { + code: "(a\n) /* a */ \n.prop", + output: "(a\n). /* a */ \nprop", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 3, + column: 1, + }, + ], + }, + { + code: "(a\n)\n/* a */\n.prop", + output: "(a\n).\n/* a */\nprop", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 4, + column: 1, + }, + ], + }, + { + code: "(a\n)\n/* a */.prop", + output: "(a\n).\n/* a */prop", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 3, + column: 8, + }, + ], + }, + { + code: "(5)\n.toExponential()", + output: "(5).\ntoExponential()", + options: ["object"], + errors: [ + { + messageId: "expectedDotAfterObject", + type: "MemberExpression", + line: 2, + column: 1, + }, + ], + }, - // Optional chaining - { - code: "obj\n?.prop", - output: "obj?.\nprop", - options: ["object"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "expectedDotAfterObject" }] - }, - { - code: "10\n?.prop", - output: "10?.\nprop", - options: ["object"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "expectedDotAfterObject" }] - }, - { - code: "obj?.\nprop", - output: "obj\n?.prop", - options: ["property"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "expectedDotBeforeProperty" }] - }, + // Optional chaining + { + code: "obj\n?.prop", + output: "obj?.\nprop", + options: ["object"], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "expectedDotAfterObject" }], + }, + { + code: "10\n?.prop", + output: "10?.\nprop", + options: ["object"], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "expectedDotAfterObject" }], + }, + { + code: "obj?.\nprop", + output: "obj\n?.prop", + options: ["property"], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "expectedDotBeforeProperty" }], + }, - // Private properties - { - code: "class C { #a; foo() { this\n.#a; } }", - output: "class C { #a; foo() { this.\n#a; } }", - options: ["object"], - languageOptions: { ecmaVersion: 2022 }, - errors: [{ messageId: "expectedDotAfterObject" }] - }, - { - code: "class C { #a; foo() { this.\n#a; } }", - output: "class C { #a; foo() { this\n.#a; } }", - options: ["property"], - languageOptions: { ecmaVersion: 2022 }, - errors: [{ messageId: "expectedDotBeforeProperty" }] - } - ] + // Private properties + { + code: "class C { #a; foo() { this\n.#a; } }", + output: "class C { #a; foo() { this.\n#a; } }", + options: ["object"], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedDotAfterObject" }], + }, + { + code: "class C { #a; foo() { this.\n#a; } }", + output: "class C { #a; foo() { this\n.#a; } }", + options: ["property"], + languageOptions: { ecmaVersion: 2022 }, + errors: [{ messageId: "expectedDotBeforeProperty" }], + }, + ], }); diff --git a/tests/lib/rules/dot-notation.js b/tests/lib/rules/dot-notation.js index c03cdc73ee9b..3fde2d863efc 100644 --- a/tests/lib/rules/dot-notation.js +++ b/tests/lib/rules/dot-notation.js @@ -10,17 +10,17 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/dot-notation"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, }); /** @@ -30,7 +30,7 @@ const ruleTester = new RuleTester({ * @returns {string} `"${str}"` */ function q(str) { - return `"${str}"`; + return `"${str}"`; } //------------------------------------------------------------------------------ @@ -38,288 +38,295 @@ function q(str) { //------------------------------------------------------------------------------ ruleTester.run("dot-notation", rule, { - valid: [ - "a.b;", - "a.b.c;", - "a['12'];", - "a[b];", - "a[0];", - { code: "a.b.c;", options: [{ allowKeywords: false }] }, - { code: "a.arguments;", options: [{ allowKeywords: false }] }, - { code: "a.let;", options: [{ allowKeywords: false }] }, - { code: "a.yield;", options: [{ allowKeywords: false }] }, - { code: "a.eval;", options: [{ allowKeywords: false }] }, - { code: "a[0];", options: [{ allowKeywords: false }] }, - { code: "a['while'];", options: [{ allowKeywords: false }] }, - { code: "a['true'];", options: [{ allowKeywords: false }] }, - { code: "a['null'];", options: [{ allowKeywords: false }] }, - { code: "a[true];", options: [{ allowKeywords: false }] }, - { code: "a[null];", options: [{ allowKeywords: false }] }, - { code: "a.true;", options: [{ allowKeywords: true }] }, - { code: "a.null;", options: [{ allowKeywords: true }] }, - { code: "a['snake_case'];", options: [{ allowPattern: "^[a-z]+(_[a-z]+)+$" }] }, - { code: "a['lots_of_snake_case'];", options: [{ allowPattern: "^[a-z]+(_[a-z]+)+$" }] }, - { code: "a[`time${range}`];", languageOptions: { ecmaVersion: 6 } }, - { code: "a[`while`];", options: [{ allowKeywords: false }], languageOptions: { ecmaVersion: 6 } }, - { code: "a[`time range`];", languageOptions: { ecmaVersion: 6 } }, - "a.true;", - "a.null;", - "a[undefined];", - "a[void 0];", - "a[b()];", - { code: "a[/(?0)/];", languageOptions: { ecmaVersion: 2018 } }, - { code: "class C { foo() { this['#a'] } }", languageOptions: { ecmaVersion: 2022 } }, - { - code: "class C { #in; foo() { this.#in; } }", - options: [{ allowKeywords: false }], - languageOptions: { ecmaVersion: 2022 } - } - ], - invalid: [ - { - code: "a.true;", - output: "a[\"true\"];", - options: [{ allowKeywords: false }], - errors: [{ messageId: "useBrackets", data: { key: "true" } }] - }, - { - code: "a['true'];", - output: "a.true;", - errors: [{ messageId: "useDot", data: { key: q("true") } }] - }, - { - code: "a[`time`];", - output: "a.time;", - languageOptions: { ecmaVersion: 6 }, - errors: [{ messageId: "useDot", data: { key: "`time`" } }] - }, - { - code: "a[null];", - output: "a.null;", - errors: [{ messageId: "useDot", data: { key: "null" } }] - }, - { - code: "a[true];", - output: "a.true;", - errors: [{ messageId: "useDot", data: { key: "true" } }] - }, - { - code: "a[false];", - output: "a.false;", - errors: [{ messageId: "useDot", data: { key: "false" } }] - }, - { - code: "a['b'];", - output: "a.b;", - errors: [{ messageId: "useDot", data: { key: q("b") } }] - }, - { - code: "a.b['c'];", - output: "a.b.c;", - errors: [{ messageId: "useDot", data: { key: q("c") } }] - }, - { - code: "a['_dangle'];", - output: "a._dangle;", - options: [{ allowPattern: "^[a-z]+(_[a-z]+)+$" }], - errors: [{ messageId: "useDot", data: { key: q("_dangle") } }] - }, - { - code: "a['SHOUT_CASE'];", - output: "a.SHOUT_CASE;", - options: [{ allowPattern: "^[a-z]+(_[a-z]+)+$" }], - errors: [{ messageId: "useDot", data: { key: q("SHOUT_CASE") } }] - }, - { - code: - "a\n" + - " ['SHOUT_CASE'];", - output: - "a\n" + - " .SHOUT_CASE;", - errors: [{ - messageId: "useDot", - data: { key: q("SHOUT_CASE") }, - line: 2, - column: 4 - }] - }, - { - code: - "getResource()\n" + - " .then(function(){})\n" + - " [\"catch\"](function(){})\n" + - " .then(function(){})\n" + - " [\"catch\"](function(){});", - output: - "getResource()\n" + - " .then(function(){})\n" + - " .catch(function(){})\n" + - " .then(function(){})\n" + - " .catch(function(){});", - errors: [ - { - messageId: "useDot", - data: { key: q("catch") }, - line: 3, - column: 6 - }, - { - messageId: "useDot", - data: { key: q("catch") }, - line: 5, - column: 6 - } - ] - }, - { - code: - "foo\n" + - " .while;", - output: - "foo\n" + - " [\"while\"];", - options: [{ allowKeywords: false }], - errors: [{ messageId: "useBrackets", data: { key: "while" } }] - }, - { - code: "foo[ /* comment */ 'bar' ]", - output: null, // Not fixed due to comment - errors: [{ messageId: "useDot", data: { key: q("bar") } }] - }, - { - code: "foo[ 'bar' /* comment */ ]", - output: null, // Not fixed due to comment - errors: [{ messageId: "useDot", data: { key: q("bar") } }] - }, - { - code: "foo[ 'bar' ];", - output: "foo.bar;", - errors: [{ messageId: "useDot", data: { key: q("bar") } }] - }, - { - code: "foo. /* comment */ while", - output: null, // Not fixed due to comment - options: [{ allowKeywords: false }], - errors: [{ messageId: "useBrackets", data: { key: "while" } }] - }, - { - code: "foo[('bar')]", - output: "foo.bar", - errors: [{ messageId: "useDot", data: { key: q("bar") } }] - }, - { - code: "foo[(null)]", - output: "foo.null", - errors: [{ messageId: "useDot", data: { key: "null" } }] - }, - { - code: "(foo)['bar']", - output: "(foo).bar", - errors: [{ messageId: "useDot", data: { key: q("bar") } }] - }, - { - code: "1['toString']", - output: "1 .toString", - errors: [{ messageId: "useDot", data: { key: q("toString") } }] - }, - { - code: "foo['bar']instanceof baz", - output: "foo.bar instanceof baz", - errors: [{ messageId: "useDot", data: { key: q("bar") } }] - }, - { - code: "let.if()", - output: null, // `let["if"]()` is a syntax error because `let[` indicates a destructuring variable declaration - options: [{ allowKeywords: false }], - errors: [{ messageId: "useBrackets", data: { key: "if" } }] - }, - { - code: "5['prop']", - output: "5 .prop", - errors: [{ messageId: "useDot", data: { key: q("prop") } }] - }, - { - code: "-5['prop']", - output: "-5 .prop", - errors: [{ messageId: "useDot", data: { key: q("prop") } }] - }, - { - code: "01['prop']", - output: "01.prop", - errors: [{ messageId: "useDot", data: { key: q("prop") } }] - }, - { - code: "01234567['prop']", - output: "01234567.prop", - errors: [{ messageId: "useDot", data: { key: q("prop") } }] - }, - { - code: "08['prop']", - output: "08 .prop", - errors: [{ messageId: "useDot", data: { key: q("prop") } }] - }, - { - code: "090['prop']", - output: "090 .prop", - errors: [{ messageId: "useDot", data: { key: q("prop") } }] - }, - { - code: "018['prop']", - output: "018 .prop", - errors: [{ messageId: "useDot", data: { key: q("prop") } }] - }, - { - code: "5_000['prop']", - output: "5_000 .prop", - languageOptions: { ecmaVersion: 2021 }, - errors: [{ messageId: "useDot", data: { key: q("prop") } }] - }, - { - code: "5_000_00['prop']", - output: "5_000_00 .prop", - languageOptions: { ecmaVersion: 2021 }, - errors: [{ messageId: "useDot", data: { key: q("prop") } }] - }, - { - code: "5.000_000['prop']", - output: "5.000_000.prop", - languageOptions: { ecmaVersion: 2021 }, - errors: [{ messageId: "useDot", data: { key: q("prop") } }] - }, - { - code: "0b1010_1010['prop']", - output: "0b1010_1010.prop", - languageOptions: { ecmaVersion: 2021 }, - errors: [{ messageId: "useDot", data: { key: q("prop") } }] - }, + valid: [ + "a.b;", + "a.b.c;", + "a['12'];", + "a[b];", + "a[0];", + { code: "a.b.c;", options: [{ allowKeywords: false }] }, + { code: "a.arguments;", options: [{ allowKeywords: false }] }, + { code: "a.let;", options: [{ allowKeywords: false }] }, + { code: "a.yield;", options: [{ allowKeywords: false }] }, + { code: "a.eval;", options: [{ allowKeywords: false }] }, + { code: "a[0];", options: [{ allowKeywords: false }] }, + { code: "a['while'];", options: [{ allowKeywords: false }] }, + { code: "a['true'];", options: [{ allowKeywords: false }] }, + { code: "a['null'];", options: [{ allowKeywords: false }] }, + { code: "a[true];", options: [{ allowKeywords: false }] }, + { code: "a[null];", options: [{ allowKeywords: false }] }, + { code: "a.true;", options: [{ allowKeywords: true }] }, + { code: "a.null;", options: [{ allowKeywords: true }] }, + { + code: "a['snake_case'];", + options: [{ allowPattern: "^[a-z]+(_[a-z]+)+$" }], + }, + { + code: "a['lots_of_snake_case'];", + options: [{ allowPattern: "^[a-z]+(_[a-z]+)+$" }], + }, + { code: "a[`time${range}`];", languageOptions: { ecmaVersion: 6 } }, + { + code: "a[`while`];", + options: [{ allowKeywords: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { code: "a[`time range`];", languageOptions: { ecmaVersion: 6 } }, + "a.true;", + "a.null;", + "a[undefined];", + "a[void 0];", + "a[b()];", + { code: "a[/(?0)/];", languageOptions: { ecmaVersion: 2018 } }, + { + code: "class C { foo() { this['#a'] } }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #in; foo() { this.#in; } }", + options: [{ allowKeywords: false }], + languageOptions: { ecmaVersion: 2022 }, + }, + ], + invalid: [ + { + code: "a.true;", + output: 'a["true"];', + options: [{ allowKeywords: false }], + errors: [{ messageId: "useBrackets", data: { key: "true" } }], + }, + { + code: "a['true'];", + output: "a.true;", + errors: [{ messageId: "useDot", data: { key: q("true") } }], + }, + { + code: "a[`time`];", + output: "a.time;", + languageOptions: { ecmaVersion: 6 }, + errors: [{ messageId: "useDot", data: { key: "`time`" } }], + }, + { + code: "a[null];", + output: "a.null;", + errors: [{ messageId: "useDot", data: { key: "null" } }], + }, + { + code: "a[true];", + output: "a.true;", + errors: [{ messageId: "useDot", data: { key: "true" } }], + }, + { + code: "a[false];", + output: "a.false;", + errors: [{ messageId: "useDot", data: { key: "false" } }], + }, + { + code: "a['b'];", + output: "a.b;", + errors: [{ messageId: "useDot", data: { key: q("b") } }], + }, + { + code: "a.b['c'];", + output: "a.b.c;", + errors: [{ messageId: "useDot", data: { key: q("c") } }], + }, + { + code: "a['_dangle'];", + output: "a._dangle;", + options: [{ allowPattern: "^[a-z]+(_[a-z]+)+$" }], + errors: [{ messageId: "useDot", data: { key: q("_dangle") } }], + }, + { + code: "a['SHOUT_CASE'];", + output: "a.SHOUT_CASE;", + options: [{ allowPattern: "^[a-z]+(_[a-z]+)+$" }], + errors: [{ messageId: "useDot", data: { key: q("SHOUT_CASE") } }], + }, + { + code: "a\n" + " ['SHOUT_CASE'];", + output: "a\n" + " .SHOUT_CASE;", + errors: [ + { + messageId: "useDot", + data: { key: q("SHOUT_CASE") }, + line: 2, + column: 4, + }, + ], + }, + { + code: + "getResource()\n" + + " .then(function(){})\n" + + ' ["catch"](function(){})\n' + + " .then(function(){})\n" + + ' ["catch"](function(){});', + output: + "getResource()\n" + + " .then(function(){})\n" + + " .catch(function(){})\n" + + " .then(function(){})\n" + + " .catch(function(){});", + errors: [ + { + messageId: "useDot", + data: { key: q("catch") }, + line: 3, + column: 6, + }, + { + messageId: "useDot", + data: { key: q("catch") }, + line: 5, + column: 6, + }, + ], + }, + { + code: "foo\n" + " .while;", + output: "foo\n" + ' ["while"];', + options: [{ allowKeywords: false }], + errors: [{ messageId: "useBrackets", data: { key: "while" } }], + }, + { + code: "foo[ /* comment */ 'bar' ]", + output: null, // Not fixed due to comment + errors: [{ messageId: "useDot", data: { key: q("bar") } }], + }, + { + code: "foo[ 'bar' /* comment */ ]", + output: null, // Not fixed due to comment + errors: [{ messageId: "useDot", data: { key: q("bar") } }], + }, + { + code: "foo[ 'bar' ];", + output: "foo.bar;", + errors: [{ messageId: "useDot", data: { key: q("bar") } }], + }, + { + code: "foo. /* comment */ while", + output: null, // Not fixed due to comment + options: [{ allowKeywords: false }], + errors: [{ messageId: "useBrackets", data: { key: "while" } }], + }, + { + code: "foo[('bar')]", + output: "foo.bar", + errors: [{ messageId: "useDot", data: { key: q("bar") } }], + }, + { + code: "foo[(null)]", + output: "foo.null", + errors: [{ messageId: "useDot", data: { key: "null" } }], + }, + { + code: "(foo)['bar']", + output: "(foo).bar", + errors: [{ messageId: "useDot", data: { key: q("bar") } }], + }, + { + code: "1['toString']", + output: "1 .toString", + errors: [{ messageId: "useDot", data: { key: q("toString") } }], + }, + { + code: "foo['bar']instanceof baz", + output: "foo.bar instanceof baz", + errors: [{ messageId: "useDot", data: { key: q("bar") } }], + }, + { + code: "let.if()", + output: null, // `let["if"]()` is a syntax error because `let[` indicates a destructuring variable declaration + options: [{ allowKeywords: false }], + errors: [{ messageId: "useBrackets", data: { key: "if" } }], + }, + { + code: "5['prop']", + output: "5 .prop", + errors: [{ messageId: "useDot", data: { key: q("prop") } }], + }, + { + code: "-5['prop']", + output: "-5 .prop", + errors: [{ messageId: "useDot", data: { key: q("prop") } }], + }, + { + code: "01['prop']", + output: "01.prop", + errors: [{ messageId: "useDot", data: { key: q("prop") } }], + }, + { + code: "01234567['prop']", + output: "01234567.prop", + errors: [{ messageId: "useDot", data: { key: q("prop") } }], + }, + { + code: "08['prop']", + output: "08 .prop", + errors: [{ messageId: "useDot", data: { key: q("prop") } }], + }, + { + code: "090['prop']", + output: "090 .prop", + errors: [{ messageId: "useDot", data: { key: q("prop") } }], + }, + { + code: "018['prop']", + output: "018 .prop", + errors: [{ messageId: "useDot", data: { key: q("prop") } }], + }, + { + code: "5_000['prop']", + output: "5_000 .prop", + languageOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "useDot", data: { key: q("prop") } }], + }, + { + code: "5_000_00['prop']", + output: "5_000_00 .prop", + languageOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "useDot", data: { key: q("prop") } }], + }, + { + code: "5.000_000['prop']", + output: "5.000_000.prop", + languageOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "useDot", data: { key: q("prop") } }], + }, + { + code: "0b1010_1010['prop']", + output: "0b1010_1010.prop", + languageOptions: { ecmaVersion: 2021 }, + errors: [{ messageId: "useDot", data: { key: q("prop") } }], + }, - // Optional chaining - { - code: "obj?.['prop']", - output: "obj?.prop", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "useDot", data: { key: q("prop") } }] - }, - { - code: "0?.['prop']", - output: "0?.prop", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "useDot", data: { key: q("prop") } }] - }, - { - code: "obj?.true", - output: "obj?.[\"true\"]", - options: [{ allowKeywords: false }], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "useBrackets", data: { key: "true" } }] - }, - { - code: "let?.true", - output: "let?.[\"true\"]", - options: [{ allowKeywords: false }], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "useBrackets", data: { key: "true" } }] - } - ] + // Optional chaining + { + code: "obj?.['prop']", + output: "obj?.prop", + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "useDot", data: { key: q("prop") } }], + }, + { + code: "0?.['prop']", + output: "0?.prop", + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "useDot", data: { key: q("prop") } }], + }, + { + code: "obj?.true", + output: 'obj?.["true"]', + options: [{ allowKeywords: false }], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "useBrackets", data: { key: "true" } }], + }, + { + code: "let?.true", + output: 'let?.["true"]', + options: [{ allowKeywords: false }], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "useBrackets", data: { key: "true" } }], + }, + ], }); diff --git a/tests/lib/rules/eol-last.js b/tests/lib/rules/eol-last.js index 84999344236a..c6442396fc99 100644 --- a/tests/lib/rules/eol-last.js +++ b/tests/lib/rules/eol-last.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/eol-last"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,197 +18,220 @@ const rule = require("../../../lib/rules/eol-last"), const ruleTester = new RuleTester(); ruleTester.run("eol-last", rule, { + valid: [ + "", + "\n", + "var a = 123;\n", + "var a = 123;\n\n", + "var a = 123;\n \n", - valid: [ - "", - "\n", - "var a = 123;\n", - "var a = 123;\n\n", - "var a = 123;\n \n", + "\r\n", + "var a = 123;\r\n", + "var a = 123;\r\n\r\n", + "var a = 123;\r\n \r\n", - "\r\n", - "var a = 123;\r\n", - "var a = 123;\r\n\r\n", - "var a = 123;\r\n \r\n", + { code: "var a = 123;", options: ["never"] }, + { code: "var a = 123;\nvar b = 456;", options: ["never"] }, + { code: "var a = 123;\r\nvar b = 456;", options: ["never"] }, - { code: "var a = 123;", options: ["never"] }, - { code: "var a = 123;\nvar b = 456;", options: ["never"] }, - { code: "var a = 123;\r\nvar b = 456;", options: ["never"] }, + // Deprecated: `"unix"` parameter + { code: "", options: ["unix"] }, + { code: "\n", options: ["unix"] }, + { code: "var a = 123;\n", options: ["unix"] }, + { code: "var a = 123;\n\n", options: ["unix"] }, + { code: "var a = 123;\n \n", options: ["unix"] }, - // Deprecated: `"unix"` parameter - { code: "", options: ["unix"] }, - { code: "\n", options: ["unix"] }, - { code: "var a = 123;\n", options: ["unix"] }, - { code: "var a = 123;\n\n", options: ["unix"] }, - { code: "var a = 123;\n \n", options: ["unix"] }, + // Deprecated: `"windows"` parameter + { code: "", options: ["windows"] }, + { code: "\n", options: ["windows"] }, + { code: "\r\n", options: ["windows"] }, + { code: "var a = 123;\r\n", options: ["windows"] }, + { code: "var a = 123;\r\n\r\n", options: ["windows"] }, + { code: "var a = 123;\r\n \r\n", options: ["windows"] }, + ], - // Deprecated: `"windows"` parameter - { code: "", options: ["windows"] }, - { code: "\n", options: ["windows"] }, - { code: "\r\n", options: ["windows"] }, - { code: "var a = 123;\r\n", options: ["windows"] }, - { code: "var a = 123;\r\n\r\n", options: ["windows"] }, - { code: "var a = 123;\r\n \r\n", options: ["windows"] } - ], + invalid: [ + { + code: "var a = 123;", + output: "var a = 123;\n", + errors: [ + { + messageId: "missing", + type: "Program", + line: 1, + column: 13, + endLine: void 0, + endColumn: void 0, + }, + ], + }, + { + code: "var a = 123;\n ", + output: "var a = 123;\n \n", + errors: [ + { + messageId: "missing", + type: "Program", + line: 2, + column: 4, + endLine: void 0, + endColumn: void 0, + }, + ], + }, + { + code: "var a = 123;\n", + output: "var a = 123;", + options: ["never"], + errors: [ + { + messageId: "unexpected", + type: "Program", + line: 1, + column: 13, + endLine: 2, + endColumn: 1, + }, + ], + }, + { + code: "var a = 123;\r\n", + output: "var a = 123;", + options: ["never"], + errors: [ + { + messageId: "unexpected", + type: "Program", + line: 1, + column: 13, + endLine: 2, + endColumn: 1, + }, + ], + }, + { + code: "var a = 123;\r\n\r\n", + output: "var a = 123;", + options: ["never"], + errors: [ + { + messageId: "unexpected", + type: "Program", + line: 2, + column: 1, + endLine: 3, + endColumn: 1, + }, + ], + }, + { + code: "var a = 123;\nvar b = 456;\n", + output: "var a = 123;\nvar b = 456;", + options: ["never"], + errors: [ + { + messageId: "unexpected", + type: "Program", + line: 2, + column: 13, + endLine: 3, + endColumn: 1, + }, + ], + }, + { + code: "var a = 123;\r\nvar b = 456;\r\n", + output: "var a = 123;\r\nvar b = 456;", + options: ["never"], + errors: [ + { + messageId: "unexpected", + type: "Program", + line: 2, + column: 13, + endLine: 3, + endColumn: 1, + }, + ], + }, + { + code: "var a = 123;\n\n", + output: "var a = 123;", + options: ["never"], + errors: [ + { + messageId: "unexpected", + type: "Program", + line: 2, + column: 1, + endLine: 3, + endColumn: 1, + }, + ], + }, - invalid: [ - { - code: "var a = 123;", - output: "var a = 123;\n", - errors: [{ - messageId: "missing", - type: "Program", - line: 1, - column: 13, - endLine: void 0, - endColumn: void 0 - }] - }, - { - code: "var a = 123;\n ", - output: "var a = 123;\n \n", - errors: [{ - messageId: "missing", - type: "Program", - line: 2, - column: 4, - endLine: void 0, - endColumn: void 0 - }] - }, - { - code: "var a = 123;\n", - output: "var a = 123;", - options: ["never"], - errors: [{ - messageId: "unexpected", - type: "Program", - line: 1, - column: 13, - endLine: 2, - endColumn: 1 - }] - }, - { - code: "var a = 123;\r\n", - output: "var a = 123;", - options: ["never"], - errors: [{ - messageId: "unexpected", - type: "Program", - line: 1, - column: 13, - endLine: 2, - endColumn: 1 - }] - }, - { - code: "var a = 123;\r\n\r\n", - output: "var a = 123;", - options: ["never"], - errors: [{ - messageId: "unexpected", - type: "Program", - line: 2, - column: 1, - endLine: 3, - endColumn: 1 - }] - }, - { - code: "var a = 123;\nvar b = 456;\n", - output: "var a = 123;\nvar b = 456;", - options: ["never"], - errors: [{ - messageId: "unexpected", - type: "Program", - line: 2, - column: 13, - endLine: 3, - endColumn: 1 - }] - }, - { - code: "var a = 123;\r\nvar b = 456;\r\n", - output: "var a = 123;\r\nvar b = 456;", - options: ["never"], - errors: [{ - messageId: "unexpected", - type: "Program", - line: 2, - column: 13, - endLine: 3, - endColumn: 1 - }] - }, - { - code: "var a = 123;\n\n", - output: "var a = 123;", - options: ["never"], - errors: [{ - messageId: "unexpected", - type: "Program", - line: 2, - column: 1, - endLine: 3, - endColumn: 1 - }] - }, + // Deprecated: `"unix"` parameter + { + code: "var a = 123;", + output: "var a = 123;\n", + options: ["unix"], + errors: [ + { + messageId: "missing", + type: "Program", + line: 1, + column: 13, + endLine: void 0, + endColumn: void 0, + }, + ], + }, + { + code: "var a = 123;\n ", + output: "var a = 123;\n \n", + options: ["unix"], + errors: [ + { + messageId: "missing", + type: "Program", + line: 2, + column: 4, + endLine: void 0, + endColumn: void 0, + }, + ], + }, - // Deprecated: `"unix"` parameter - { - code: "var a = 123;", - output: "var a = 123;\n", - options: ["unix"], - errors: [{ - messageId: "missing", - type: "Program", - line: 1, - column: 13, - endLine: void 0, - endColumn: void 0 - }] - }, - { - code: "var a = 123;\n ", - output: "var a = 123;\n \n", - options: ["unix"], - errors: [{ - messageId: "missing", - type: "Program", - line: 2, - column: 4, - endLine: void 0, - endColumn: void 0 - }] - }, - - // Deprecated: `"windows"` parameter - { - code: "var a = 123;", - output: "var a = 123;\r\n", - options: ["windows"], - errors: [{ - messageId: "missing", - type: "Program", - line: 1, - column: 13, - endLine: void 0, - endColumn: void 0 - }] - }, - { - code: "var a = 123;\r\n ", - output: "var a = 123;\r\n \r\n", - options: ["windows"], - errors: [{ - messageId: "missing", - type: "Program", - line: 2, - column: 4, - endLine: void 0, - endColumn: void 0 - }] - } - ] + // Deprecated: `"windows"` parameter + { + code: "var a = 123;", + output: "var a = 123;\r\n", + options: ["windows"], + errors: [ + { + messageId: "missing", + type: "Program", + line: 1, + column: 13, + endLine: void 0, + endColumn: void 0, + }, + ], + }, + { + code: "var a = 123;\r\n ", + output: "var a = 123;\r\n \r\n", + options: ["windows"], + errors: [ + { + messageId: "missing", + type: "Program", + line: 2, + column: 4, + endLine: void 0, + endColumn: void 0, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/eqeqeq.js b/tests/lib/rules/eqeqeq.js index c2628bf4616c..8ae84b8958f9 100644 --- a/tests/lib/rules/eqeqeq.js +++ b/tests/lib/rules/eqeqeq.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/eqeqeq"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -24,152 +24,737 @@ const wantedEqEq = { expectedOperator: "==", actualOperator: "===" }; const wantedNotEq = { expectedOperator: "!=", actualOperator: "!==" }; ruleTester.run("eqeqeq", rule, { - valid: [ - "a === b", - "a !== b", - { code: "a === b", options: ["always"] }, - { code: "typeof a == 'number'", options: ["smart"] }, - { code: "'string' != typeof a", options: ["smart"] }, - { code: "'hello' != 'world'", options: ["smart"] }, - { code: "2 == 3", options: ["smart"] }, - { code: "true == true", options: ["smart"] }, - { code: "null == a", options: ["smart"] }, - { code: "a == null", options: ["smart"] }, - { code: "null == a", options: ["allow-null"] }, - { code: "a == null", options: ["allow-null"] }, - { code: "a == null", options: ["always", { null: "ignore" }] }, - { code: "a != null", options: ["always", { null: "ignore" }] }, - { code: "a !== null", options: ["always", { null: "ignore" }] }, - { code: "a === null", options: ["always", { null: "always" }] }, - { code: "a !== null", options: ["always", { null: "always" }] }, - { code: "null === null", options: ["always", { null: "always" }] }, - { code: "null !== null", options: ["always", { null: "always" }] }, - { code: "a == null", options: ["always", { null: "never" }] }, - { code: "a != null", options: ["always", { null: "never" }] }, - { code: "null == null", options: ["always", { null: "never" }] }, - { code: "null != null", options: ["always", { null: "never" }] }, + valid: [ + "a === b", + "a !== b", + { code: "a === b", options: ["always"] }, + { code: "typeof a == 'number'", options: ["smart"] }, + { code: "'string' != typeof a", options: ["smart"] }, + { code: "'hello' != 'world'", options: ["smart"] }, + { code: "2 == 3", options: ["smart"] }, + { code: "true == true", options: ["smart"] }, + { code: "null == a", options: ["smart"] }, + { code: "a == null", options: ["smart"] }, + { code: "null == a", options: ["allow-null"] }, + { code: "a == null", options: ["allow-null"] }, + { code: "a == null", options: ["always", { null: "ignore" }] }, + { code: "a != null", options: ["always", { null: "ignore" }] }, + { code: "a !== null", options: ["always", { null: "ignore" }] }, + { code: "a === null", options: ["always", { null: "always" }] }, + { code: "a !== null", options: ["always", { null: "always" }] }, + { code: "null === null", options: ["always", { null: "always" }] }, + { code: "null !== null", options: ["always", { null: "always" }] }, + { code: "a == null", options: ["always", { null: "never" }] }, + { code: "a != null", options: ["always", { null: "never" }] }, + { code: "null == null", options: ["always", { null: "never" }] }, + { code: "null != null", options: ["always", { null: "never" }] }, - // https://github.com/eslint/eslint/issues/8020 - { code: "foo === /abc/u", options: ["always", { null: "never" }], languageOptions: { ecmaVersion: 2015 } }, + // https://github.com/eslint/eslint/issues/8020 + { + code: "foo === /abc/u", + options: ["always", { null: "never" }], + languageOptions: { ecmaVersion: 2015 }, + }, - // bigint - { code: "foo === 1n", options: ["always", { null: "never" }], languageOptions: { ecmaVersion: 2020 } } - ], - invalid: [ - { code: "a == b", errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "a != b", errors: [{ messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression" }] }, - { code: "typeof a == 'number'", output: "typeof a === 'number'", errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "typeof a == 'number'", output: "typeof a === 'number'", options: ["always"], errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "'string' != typeof a", output: "'string' !== typeof a", errors: [{ messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression" }] }, - { code: "true == true", output: "true === true", errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "2 == 3", output: "2 === 3", errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "2 == 3", output: "2 === 3", options: ["always"], errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "'hello' != 'world'", output: "'hello' !== 'world'", errors: [{ messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression" }] }, - { code: "'hello' != 'world'", output: "'hello' !== 'world'", options: ["always"], errors: [{ messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression" }] }, - { code: "a == null", errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "a == null", options: ["always"], errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "null != a", errors: [{ messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression" }] }, - { code: "true == 1", options: ["smart"], errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "0 != '1'", options: ["smart"], errors: [{ messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression" }] }, - { code: "'wee' == /wee/", options: ["smart"], errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "typeof a == 'number'", output: "typeof a === 'number'", options: ["allow-null"], errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "'string' != typeof a", output: "'string' !== typeof a", options: ["allow-null"], errors: [{ messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression" }] }, - { code: "'hello' != 'world'", output: "'hello' !== 'world'", options: ["allow-null"], errors: [{ messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression" }] }, - { code: "2 == 3", output: "2 === 3", options: ["allow-null"], errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "true == true", output: "true === true", options: ["allow-null"], errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "true == null", options: ["always", { null: "always" }], errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "true != null", options: ["always", { null: "always" }], errors: [{ messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression" }] }, - { code: "null == null", output: "null === null", options: ["always", { null: "always" }], errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression" }] }, - { code: "null != null", output: "null !== null", options: ["always", { null: "always" }], errors: [{ messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression" }] }, - { code: "true === null", options: ["always", { null: "never" }], errors: [{ messageId: "unexpected", data: wantedEqEq, type: "BinaryExpression" }] }, - { code: "true !== null", options: ["always", { null: "never" }], errors: [{ messageId: "unexpected", data: wantedNotEq, type: "BinaryExpression" }] }, - { code: "null === null", output: "null == null", options: ["always", { null: "never" }], errors: [{ messageId: "unexpected", data: wantedEqEq, type: "BinaryExpression" }] }, - { code: "null !== null", output: "null != null", options: ["always", { null: "never" }], errors: [{ messageId: "unexpected", data: wantedNotEq, type: "BinaryExpression" }] }, - { code: "a\n==\nb", errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression", line: 2 }] }, - { code: "(a) == b", errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression", line: 1 }] }, - { code: "(a) != b", errors: [{ messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression", line: 1 }] }, - { code: "a == (b)", errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression", line: 1 }] }, - { code: "a != (b)", errors: [{ messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression", line: 1 }] }, - { code: "(a) == (b)", errors: [{ messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression", line: 1 }] }, - { code: "(a) != (b)", errors: [{ messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression", line: 1 }] }, - { - code: "(a == b) == (c)", - errors: [ - { messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression", line: 1 }, - { messageId: "unexpected", data: wantedEqEqEq, type: "BinaryExpression", line: 1 } - ] - }, - { - code: "(a != b) != (c)", - errors: [ - { messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression", line: 1 }, - { messageId: "unexpected", data: wantedNotEqEq, type: "BinaryExpression", line: 1 } - ] - }, + // bigint + { + code: "foo === 1n", + options: ["always", { null: "never" }], + languageOptions: { ecmaVersion: 2020 }, + }, + ], + invalid: [ + { + code: "a == b", + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEqEq, + output: "a === b", + }, + ], + }, + ], + }, + { + code: "a != b", + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + suggestions: [ + { + messageId: "replaceOperator", + data: wantedNotEqEq, + output: "a !== b", + }, + ], + }, + ], + }, + { + code: "typeof a == 'number'", + output: "typeof a === 'number'", + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "typeof a == 'number'", + output: "typeof a === 'number'", + options: ["always"], + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "'string' != typeof a", + output: "'string' !== typeof a", + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "true == true", + output: "true === true", + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "2 == 3", + output: "2 === 3", + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "2 == 3", + output: "2 === 3", + options: ["always"], + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "'hello' != 'world'", + output: "'hello' !== 'world'", + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "'hello' != 'world'", + output: "'hello' !== 'world'", + options: ["always"], + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "a == null", + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEqEq, + output: "a === null", + }, + ], + }, + ], + }, + { + code: "a == null", + options: ["always"], + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEqEq, + output: "a === null", + }, + ], + }, + ], + }, + { + code: "null != a", + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + suggestions: [ + { + messageId: "replaceOperator", + data: wantedNotEqEq, + output: "null !== a", + }, + ], + }, + ], + }, + { + code: "true == 1", + options: ["smart"], + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEqEq, + output: "true === 1", + }, + ], + }, + ], + }, + { + code: "0 != '1'", + options: ["smart"], + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + suggestions: [ + { + messageId: "replaceOperator", + data: wantedNotEqEq, + output: "0 !== '1'", + }, + ], + }, + ], + }, + { + code: "'wee' == /wee/", + options: ["smart"], + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEqEq, + output: "'wee' === /wee/", + }, + ], + }, + ], + }, + { + code: "typeof a == 'number'", + output: "typeof a === 'number'", + options: ["allow-null"], + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "'string' != typeof a", + output: "'string' !== typeof a", + options: ["allow-null"], + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "'hello' != 'world'", + output: "'hello' !== 'world'", + options: ["allow-null"], + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "2 == 3", + output: "2 === 3", + options: ["allow-null"], + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "true == true", + output: "true === true", + options: ["allow-null"], + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "true == null", + options: ["always", { null: "always" }], + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEqEq, + output: "true === null", + }, + ], + }, + ], + }, + { + code: "true != null", + options: ["always", { null: "always" }], + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + suggestions: [ + { + messageId: "replaceOperator", + data: wantedNotEqEq, + output: "true !== null", + }, + ], + }, + ], + }, + { + code: "null == null", + output: "null === null", + options: ["always", { null: "always" }], + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "null != null", + output: "null !== null", + options: ["always", { null: "always" }], + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "true === null", + options: ["always", { null: "never" }], + errors: [ + { + messageId: "unexpected", + data: wantedEqEq, + type: "BinaryExpression", + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEq, + output: "true == null", + }, + ], + }, + ], + }, + { + code: "true !== null", + options: ["always", { null: "never" }], + errors: [ + { + messageId: "unexpected", + data: wantedNotEq, + type: "BinaryExpression", + suggestions: [ + { + messageId: "replaceOperator", + data: wantedNotEq, + output: "true != null", + }, + ], + }, + ], + }, + { + code: "null === null", + output: "null == null", + options: ["always", { null: "never" }], + errors: [ + { + messageId: "unexpected", + data: wantedEqEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "null !== null", + output: "null != null", + options: ["always", { null: "never" }], + errors: [ + { + messageId: "unexpected", + data: wantedNotEq, + type: "BinaryExpression", + }, + ], + }, + { + code: "a\n==\nb", + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + line: 2, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEqEq, + output: "a\n===\nb", + }, + ], + }, + ], + }, + { + code: "(a) == b", + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + line: 1, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEqEq, + output: "(a) === b", + }, + ], + }, + ], + }, + { + code: "(a) != b", + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + line: 1, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedNotEqEq, + output: "(a) !== b", + }, + ], + }, + ], + }, + { + code: "a == (b)", + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + line: 1, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEqEq, + output: "a === (b)", + }, + ], + }, + ], + }, + { + code: "a != (b)", + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + line: 1, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedNotEqEq, + output: "a !== (b)", + }, + ], + }, + ], + }, + { + code: "(a) == (b)", + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + line: 1, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEqEq, + output: "(a) === (b)", + }, + ], + }, + ], + }, + { + code: "(a) != (b)", + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + line: 1, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedNotEqEq, + output: "(a) !== (b)", + }, + ], + }, + ], + }, + { + code: "(a == b) == (c)", + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + line: 1, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEqEq, + output: "(a === b) == (c)", + }, + ], + }, + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + line: 1, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEqEq, + output: "(a == b) === (c)", + }, + ], + }, + ], + }, + { + code: "(a != b) != (c)", + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + line: 1, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedNotEqEq, + output: "(a !== b) != (c)", + }, + ], + }, + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + line: 1, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedNotEqEq, + output: "(a != b) !== (c)", + }, + ], + }, + ], + }, - // location tests - { - code: "a == b;", - errors: [ - { - messageId: "unexpected", - data: wantedEqEqEq, - type: "BinaryExpression", - column: 3, - endColumn: 5 - } - ] - }, - { - code: "a!=b;", - errors: [ - { - messageId: "unexpected", - data: wantedNotEqEq, - type: "BinaryExpression", - column: 2, - endColumn: 4 - } - ] - }, - { - code: "(a + b) == c;", - errors: [ - { - messageId: "unexpected", - data: wantedEqEqEq, - type: "BinaryExpression", - column: 9, - endColumn: 11 - } - ] - }, - { - code: "(a + b) != c;", - errors: [ - { - messageId: "unexpected", - data: wantedNotEqEq, - type: "BinaryExpression", - column: 10, - endColumn: 12 - } - ] - }, - { - code: "((1) ) == (2);", - output: "((1) ) === (2);", - errors: [ - { - messageId: "unexpected", - data: wantedEqEqEq, - type: "BinaryExpression", - column: 9, - endColumn: 11 - } - ] - } + // location tests + { + code: "a == b;", + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + column: 3, + endColumn: 5, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEqEq, + output: "a === b;", + }, + ], + }, + ], + }, + { + code: "a!=b;", + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + column: 2, + endColumn: 4, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedNotEqEq, + output: "a!==b;", + }, + ], + }, + ], + }, + { + code: "(a + b) == c;", + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + column: 9, + endColumn: 11, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedEqEqEq, + output: "(a + b) === c;", + }, + ], + }, + ], + }, + { + code: "(a + b) != c;", + errors: [ + { + messageId: "unexpected", + data: wantedNotEqEq, + type: "BinaryExpression", + column: 10, + endColumn: 12, + suggestions: [ + { + messageId: "replaceOperator", + data: wantedNotEqEq, + output: "(a + b) !== c;", + }, + ], + }, + ], + }, + { + code: "((1) ) == (2);", + output: "((1) ) === (2);", + errors: [ + { + messageId: "unexpected", + data: wantedEqEqEq, + type: "BinaryExpression", + column: 9, + endColumn: 11, + }, + ], + }, - // If no output is provided, assert that no output is produced. - ].map(invalidCase => Object.assign({ output: null }, invalidCase)) + // If no output is provided, assert that no output is produced. + ].map(invalidCase => Object.assign({ output: null }, invalidCase)), }); diff --git a/tests/lib/rules/for-direction.js b/tests/lib/rules/for-direction.js index e23142305ad9..2f7d6a33a8b1 100644 --- a/tests/lib/rules/for-direction.js +++ b/tests/lib/rules/for-direction.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/for-direction"); -const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); +const RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -20,100 +20,161 @@ const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2020 } }); const incorrectDirection = { messageId: "incorrectDirection" }; ruleTester.run("for-direction", rule, { - valid: [ - - // test if '++', '--' - "for(var i = 0; i < 10; i++){}", - "for(var i = 0; i <= 10; i++){}", - "for(var i = 10; i > 0; i--){}", - "for(var i = 10; i >= 0; i--){}", - - // test if '++', '--' with counter 'i' on the right side of test condition - "for(var i = 0; 10 > i; i++){}", - "for(var i = 0; 10 >= i; i++){}", - "for(var i = 10; 0 < i; i--){}", - "for(var i = 10; 0 <= i; i--){}", - - // test if '+=', '-=', - "for(var i = 0; i < 10; i+=1){}", - "for(var i = 0; i <= 10; i+=1){}", - "for(var i = 0; i < 10; i-=-1){}", - "for(var i = 0; i <= 10; i-=-1){}", - "for(var i = 10; i > 0; i-=1){}", - "for(var i = 10; i >= 0; i-=1){}", - "for(var i = 10; i > 0; i+=-1){}", - "for(var i = 10; i >= 0; i+=-1){}", - "for(var i = 0n; i > l; i-=1n){}", - "for(var i = 0n; i < l; i-=-1n){}", - "for(var i = MIN; i <= MAX; i+=true){}", - "for(var i = 0; i < 10; i+=+5e-7){}", - "for(var i = 0; i < MAX; i -= ~2);", - "for(var i = 0, n = -1; i < MAX; i += -n);", - - // test if '+=', '-=' with counter 'i' on the right side of test condition - "for(var i = 0; 10 > i; i+=1){}", - - // test if no update. - "for(var i = 10; i > 0;){}", - "for(var i = 10; i >= 0;){}", - "for(var i = 10; i < 0;){}", - "for(var i = 10; i <= 0;){}", - "for(var i = 10; i <= 0; j++){}", - "for(var i = 10; i <= 0; j--){}", - "for(var i = 10; i >= 0; j++){}", - "for(var i = 10; i >= 0; j--){}", - "for(var i = 10; i >= 0; j += 2){}", - "for(var i = 10; i >= 0; j -= 2){}", - "for(var i = 10; i >= 0; i |= 2){}", - "for(var i = 10; i >= 0; i %= 2){}", - "for(var i = 0; i < MAX; i += STEP_SIZE);", - "for(var i = 0; i < MAX; i -= STEP_SIZE);", - "for(var i = 10; i > 0; i += STEP_SIZE);", - "for(var i = 10; i >= 0; i += 0);", - "for(var i = 10n; i >= 0n; i += 0n);", - "for(var i = 10; i >= 0; i += this.step);", - "for(var i = 10; i >= 0; i += 'foo');", - "for(var i = 10; i > 0; i += !foo);", - "for(var i = MIN; i <= MAX; i -= false);", - "for(var i = MIN; i <= MAX; i -= 0/0);", - - // other cond-expressions. - "for(var i = 0; i !== 10; i+=1){}", - "for(var i = 0; i === 10; i+=1){}", - "for(var i = 0; i == 10; i+=1){}", - "for(var i = 0; i != 10; i+=1){}" - ], - invalid: [ - - // test if '++', '--' - { code: "for(var i = 0; i < 10; i--){}", errors: [incorrectDirection] }, - { code: "for(var i = 0; i <= 10; i--){}", errors: [incorrectDirection] }, - { code: "for(var i = 10; i > 10; i++){}", errors: [incorrectDirection] }, - { code: "for(var i = 10; i >= 0; i++){}", errors: [incorrectDirection] }, - - // test if '++', '--' with counter 'i' on the right side of test condition - { code: "for(var i = 0; 10 > i; i--){}", errors: [incorrectDirection] }, - { code: "for(var i = 0; 10 >= i; i--){}", errors: [incorrectDirection] }, - { code: "for(var i = 10; 10 < i; i++){}", errors: [incorrectDirection] }, - { code: "for(var i = 10; 0 <= i; i++){}", errors: [incorrectDirection] }, - - // test if '+=', '-=' - { code: "for(var i = 0; i < 10; i-=1){}", errors: [incorrectDirection] }, - { code: "for(var i = 0; i <= 10; i-=1){}", errors: [incorrectDirection] }, - { code: "for(var i = 10; i > 10; i+=1){}", errors: [incorrectDirection] }, - { code: "for(var i = 10; i >= 0; i+=1){}", errors: [incorrectDirection] }, - { code: "for(var i = 0; i < 10; i+=-1){}", errors: [incorrectDirection] }, - { code: "for(var i = 0; i <= 10; i+=-1){}", errors: [incorrectDirection] }, - { code: "for(var i = 10; i > 10; i-=-1){}", errors: [incorrectDirection] }, - { code: "for(var i = 10; i >= 0; i-=-1){}", errors: [incorrectDirection] }, - { code: "for(var i = 0n; i > l; i+=1n){}", errors: [incorrectDirection] }, - { code: "for(var i = 0n; i < l; i+=-1n){}", errors: [incorrectDirection] }, - { code: "for(var i = MIN; i <= MAX; i-=true){}", errors: [incorrectDirection] }, - { code: "for(var i = 0; i < 10; i-=+5e-7){}", errors: [incorrectDirection] }, - { code: "for(var i = 0; i < MAX; i += (2 - 3));", errors: [incorrectDirection] }, - { code: "var n = -2; for(var i = 0; i < 10; i += n);", errors: [incorrectDirection] }, - - // test if '+=', '-=' with counter 'i' on the right side of test condition - { code: "for(var i = 0; 10 > i; i-=1){}", errors: [incorrectDirection] } - ] + valid: [ + // test if '++', '--' + "for(var i = 0; i < 10; i++){}", + "for(var i = 0; i <= 10; i++){}", + "for(var i = 10; i > 0; i--){}", + "for(var i = 10; i >= 0; i--){}", + + // test if '++', '--' with counter 'i' on the right side of test condition + "for(var i = 0; 10 > i; i++){}", + "for(var i = 0; 10 >= i; i++){}", + "for(var i = 10; 0 < i; i--){}", + "for(var i = 10; 0 <= i; i--){}", + + // test if '+=', '-=', + "for(var i = 0; i < 10; i+=1){}", + "for(var i = 0; i <= 10; i+=1){}", + "for(var i = 0; i < 10; i-=-1){}", + "for(var i = 0; i <= 10; i-=-1){}", + "for(var i = 10; i > 0; i-=1){}", + "for(var i = 10; i >= 0; i-=1){}", + "for(var i = 10; i > 0; i+=-1){}", + "for(var i = 10; i >= 0; i+=-1){}", + "for(var i = 0n; i > l; i-=1n){}", + "for(var i = 0n; i < l; i-=-1n){}", + "for(var i = MIN; i <= MAX; i+=true){}", + "for(var i = 0; i < 10; i+=+5e-7){}", + "for(var i = 0; i < MAX; i -= ~2);", + "for(var i = 0, n = -1; i < MAX; i += -n);", + + // test if '+=', '-=' with counter 'i' on the right side of test condition + "for(var i = 0; 10 > i; i+=1){}", + + // test if no update. + "for(var i = 10; i > 0;){}", + "for(var i = 10; i >= 0;){}", + "for(var i = 10; i < 0;){}", + "for(var i = 10; i <= 0;){}", + "for(var i = 10; i <= 0; j++){}", + "for(var i = 10; i <= 0; j--){}", + "for(var i = 10; i >= 0; j++){}", + "for(var i = 10; i >= 0; j--){}", + "for(var i = 10; i >= 0; j += 2){}", + "for(var i = 10; i >= 0; j -= 2){}", + "for(var i = 10; i >= 0; i |= 2){}", + "for(var i = 10; i >= 0; i %= 2){}", + "for(var i = 0; i < MAX; i += STEP_SIZE);", + "for(var i = 0; i < MAX; i -= STEP_SIZE);", + "for(var i = 10; i > 0; i += STEP_SIZE);", + "for(var i = 10; i >= 0; i += 0);", + "for(var i = 10n; i >= 0n; i += 0n);", + "for(var i = 10; i >= 0; i += this.step);", + "for(var i = 10; i >= 0; i += 'foo');", + "for(var i = 10; i > 0; i += !foo);", + "for(var i = MIN; i <= MAX; i -= false);", + "for(var i = MIN; i <= MAX; i -= 0/0);", + + // other cond-expressions. + "for(var i = 0; i !== 10; i+=1){}", + "for(var i = 0; i === 10; i+=1){}", + "for(var i = 0; i == 10; i+=1){}", + "for(var i = 0; i != 10; i+=1){}", + ], + invalid: [ + // test if '++', '--' + { code: "for(var i = 0; i < 10; i--){}", errors: [incorrectDirection] }, + { + code: "for(var i = 0; i <= 10; i--){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 10; i > 10; i++){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 10; i >= 0; i++){}", + errors: [incorrectDirection], + }, + + // test if '++', '--' with counter 'i' on the right side of test condition + { code: "for(var i = 0; 10 > i; i--){}", errors: [incorrectDirection] }, + { + code: "for(var i = 0; 10 >= i; i--){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 10; 10 < i; i++){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 10; 0 <= i; i++){}", + errors: [incorrectDirection], + }, + + // test if '+=', '-=' + { + code: "for(var i = 0; i < 10; i-=1){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 0; i <= 10; i-=1){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 10; i > 10; i+=1){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 10; i >= 0; i+=1){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 0; i < 10; i+=-1){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 0; i <= 10; i+=-1){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 10; i > 10; i-=-1){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 10; i >= 0; i-=-1){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 0n; i > l; i+=1n){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 0n; i < l; i+=-1n){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = MIN; i <= MAX; i-=true){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 0; i < 10; i-=+5e-7){}", + errors: [incorrectDirection], + }, + { + code: "for(var i = 0; i < MAX; i += (2 - 3));", + errors: [incorrectDirection], + }, + { + code: "var n = -2; for(var i = 0; i < 10; i += n);", + errors: [incorrectDirection], + }, + + // test if '+=', '-=' with counter 'i' on the right side of test condition + { + code: "for(var i = 0; 10 > i; i-=1){}", + errors: [incorrectDirection], + }, + ], }); diff --git a/tests/lib/rules/func-call-spacing.js b/tests/lib/rules/func-call-spacing.js index e4c7c0c13727..387643c0e11d 100644 --- a/tests/lib/rules/func-call-spacing.js +++ b/tests/lib/rules/func-call-spacing.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/func-call-spacing"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -19,952 +19,1009 @@ const rule = require("../../../lib/rules/func-call-spacing"), const ruleTester = new RuleTester(); ruleTester.run("func-call-spacing", rule, { - valid: [ + valid: [ + // default ("never") + "f();", + "f(a, b);", + "f.b();", + "f.b().c();", + "f()()", + "(function() {}())", + "var f = new Foo()", + "var f = new Foo", + "f( (0) )", + "( f )( 0 )", + "( (f) )( (0) )", + "( f()() )(0)", + "(function(){ if (foo) { bar(); } }());", + "f(0, (1))", + "describe/**/('foo', function () {});", + "new (foo())", + { + code: "import(source)", + languageOptions: { ecmaVersion: 2020 }, + }, - // default ("never") - "f();", - "f(a, b);", - "f.b();", - "f.b().c();", - "f()()", - "(function() {}())", - "var f = new Foo()", - "var f = new Foo", - "f( (0) )", - "( f )( 0 )", - "( (f) )( (0) )", - "( f()() )(0)", - "(function(){ if (foo) { bar(); } }());", - "f(0, (1))", - "describe/**/('foo', function () {});", - "new (foo())", - { - code: "import(source)", - languageOptions: { ecmaVersion: 2020 } - }, + // "never" + { + code: "f();", + options: ["never"], + }, + { + code: "f(a, b);", + options: ["never"], + }, + { + code: "f.b();", + options: ["never"], + }, + { + code: "f.b().c();", + options: ["never"], + }, + { + code: "f()()", + options: ["never"], + }, + { + code: "(function() {}())", + options: ["never"], + }, + { + code: "var f = new Foo()", + options: ["never"], + }, + { + code: "var f = new Foo", + options: ["never"], + }, + { + code: "f( (0) )", + options: ["never"], + }, + { + code: "( f )( 0 )", + options: ["never"], + }, + { + code: "( (f) )( (0) )", + options: ["never"], + }, + { + code: "( f()() )(0)", + options: ["never"], + }, + { + code: "(function(){ if (foo) { bar(); } }());", + options: ["never"], + }, + { + code: "f(0, (1))", + options: ["never"], + }, + { + code: "describe/**/('foo', function () {});", + options: ["never"], + }, + { + code: "new (foo())", + options: ["never"], + }, + { + code: "import(source)", + options: ["never"], + languageOptions: { ecmaVersion: 2020 }, + }, - // "never" - { - code: "f();", - options: ["never"] - }, - { - code: "f(a, b);", - options: ["never"] - }, - { - code: "f.b();", - options: ["never"] - }, - { - code: "f.b().c();", - options: ["never"] - }, - { - code: "f()()", - options: ["never"] - }, - { - code: "(function() {}())", - options: ["never"] - }, - { - code: "var f = new Foo()", - options: ["never"] - }, - { - code: "var f = new Foo", - options: ["never"] - }, - { - code: "f( (0) )", - options: ["never"] - }, - { - code: "( f )( 0 )", - options: ["never"] - }, - { - code: "( (f) )( (0) )", - options: ["never"] - }, - { - code: "( f()() )(0)", - options: ["never"] - }, - { - code: "(function(){ if (foo) { bar(); } }());", - options: ["never"] - }, - { - code: "f(0, (1))", - options: ["never"] - }, - { - code: "describe/**/('foo', function () {});", - options: ["never"] - }, - { - code: "new (foo())", - options: ["never"] - }, - { - code: "import(source)", - options: ["never"], - languageOptions: { ecmaVersion: 2020 } - }, + // "always" + { + code: "f ();", + options: ["always"], + }, + { + code: "f (a, b);", + options: ["always"], + }, + { + code: "f.b ();", + options: ["always"], + }, + { + code: "f.b ().c ();", + options: ["always"], + }, + { + code: "f () ()", + options: ["always"], + }, + { + code: "(function() {} ())", + options: ["always"], + }, + { + code: "var f = new Foo ()", + options: ["always"], + }, + { + code: "var f = new Foo", + options: ["always"], + }, + { + code: "f ( (0) )", + options: ["always"], + }, + { + code: "f (0) (1)", + options: ["always"], + }, + { + code: "(f) (0)", + options: ["always"], + }, + { + code: "f ();\n t ();", + options: ["always"], + }, + { + code: "import (source)", + options: ["always"], + languageOptions: { ecmaVersion: 2020 }, + }, - // "always" - { - code: "f ();", - options: ["always"] - }, - { - code: "f (a, b);", - options: ["always"] - }, - { - code: "f.b ();", - options: ["always"] - }, - { - code: "f.b ().c ();", - options: ["always"] - }, - { - code: "f () ()", - options: ["always"] - }, - { - code: "(function() {} ())", - options: ["always"] - }, - { - code: "var f = new Foo ()", - options: ["always"] - }, - { - code: "var f = new Foo", - options: ["always"] - }, - { - code: "f ( (0) )", - options: ["always"] - }, - { - code: "f (0) (1)", - options: ["always"] - }, - { - code: "(f) (0)", - options: ["always"] - }, - { - code: "f ();\n t ();", - options: ["always"] - }, - { - code: "import (source)", - options: ["always"], - languageOptions: { ecmaVersion: 2020 } - }, + // "always", "allowNewlines": true + { + code: "f\n();", + options: ["always", { allowNewlines: true }], + }, + { + code: "f.b \n ();", + options: ["always", { allowNewlines: true }], + }, + { + code: "f\n() ().b \n()\n ()", + options: ["always", { allowNewlines: true }], + }, + { + code: "var f = new Foo\n();", + options: ["always", { allowNewlines: true }], + }, + { + code: "f// comment\n()", + options: ["always", { allowNewlines: true }], + }, + { + code: "f // comment\n ()", + options: ["always", { allowNewlines: true }], + }, + { + code: "f\n/*\n*/\n()", + options: ["always", { allowNewlines: true }], + }, + { + code: "f\r();", + options: ["always", { allowNewlines: true }], + }, + { + code: "f\u2028();", + options: ["always", { allowNewlines: true }], + }, + { + code: "f\u2029();", + options: ["always", { allowNewlines: true }], + }, + { + code: "f\r\n();", + options: ["always", { allowNewlines: true }], + }, + { + code: "import\n(source)", + options: ["always", { allowNewlines: true }], + languageOptions: { ecmaVersion: 2020 }, + }, - // "always", "allowNewlines": true - { - code: "f\n();", - options: ["always", { allowNewlines: true }] - }, - { - code: "f.b \n ();", - options: ["always", { allowNewlines: true }] - }, - { - code: "f\n() ().b \n()\n ()", - options: ["always", { allowNewlines: true }] - }, - { - code: "var f = new Foo\n();", - options: ["always", { allowNewlines: true }] - }, - { - code: "f// comment\n()", - options: ["always", { allowNewlines: true }] - }, - { - code: "f // comment\n ()", - options: ["always", { allowNewlines: true }] - }, - { - code: "f\n/*\n*/\n()", - options: ["always", { allowNewlines: true }] - }, - { - code: "f\r();", - options: ["always", { allowNewlines: true }] - }, - { - code: "f\u2028();", - options: ["always", { allowNewlines: true }] - }, - { - code: "f\u2029();", - options: ["always", { allowNewlines: true }] - }, - { - code: "f\r\n();", - options: ["always", { allowNewlines: true }] - }, - { - code: "import\n(source)", - options: ["always", { allowNewlines: true }], - languageOptions: { ecmaVersion: 2020 } - }, + // Optional chaining + { + code: "func?.()", + options: ["never"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "func ?.()", + options: ["always"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "func?. ()", + options: ["always"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "func ?. ()", + options: ["always"], + languageOptions: { ecmaVersion: 2020 }, + }, + ], + invalid: [ + // default ("never") + { + code: "f ();", + output: "f();", + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "f (a, b);", + output: "f(a, b);", + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "f.b ();", + output: "f.b();", + errors: [ + { + messageId: "unexpectedWhitespace", + type: "CallExpression", + column: 4, + line: 1, + endColumn: 4, + endLine: 1, + }, + ], + }, + { + code: "f.b().c ();", + output: "f.b().c();", + errors: [ + { + messageId: "unexpectedWhitespace", + type: "CallExpression", + column: 8, + line: 1, + endColumn: 8, + endLine: 1, + }, + ], + }, + { + code: "f() ()", + output: "f()()", + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "(function() {} ())", + output: "(function() {}())", + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "var f = new Foo ()", + output: "var f = new Foo()", + errors: [ + { messageId: "unexpectedWhitespace", type: "NewExpression" }, + ], + }, + { + code: "f ( (0) )", + output: "f( (0) )", + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "f(0) (1)", + output: "f(0)(1)", + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "(f) (0)", + output: "(f)(0)", + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "f ();\n t ();", + output: "f();\n t();", + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "import (source);", + output: "import(source);", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { messageId: "unexpectedWhitespace", type: "ImportExpression" }, + ], + }, - // Optional chaining - { - code: "func?.()", - options: ["never"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "func ?.()", - options: ["always"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "func?. ()", - options: ["always"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "func ?. ()", - options: ["always"], - languageOptions: { ecmaVersion: 2020 } - } - ], - invalid: [ + // https://github.com/eslint/eslint/issues/7787 + { + code: "f\n();", + output: null, // no change + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "f\r();", + output: null, // no change + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "f\u2028();", + output: null, // no change + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "f\u2029();", + output: null, // no change + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "f\r\n();", + output: null, // no change + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "import\n(source);", + output: null, + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { messageId: "unexpectedWhitespace", type: "ImportExpression" }, + ], + }, - // default ("never") - { - code: "f ();", - output: "f();", - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "f (a, b);", - output: "f(a, b);", - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "f.b ();", - output: "f.b();", - errors: [ - { - messageId: "unexpectedWhitespace", - type: "CallExpression", - column: 4, - line: 1, - endColumn: 4, - endLine: 1 - } - ] - }, - { - code: "f.b().c ();", - output: "f.b().c();", - errors: [ - { - messageId: "unexpectedWhitespace", - type: "CallExpression", - column: 8, - line: 1, - endColumn: 8, - endLine: 1 - } - ] - }, - { - code: "f() ()", - output: "f()()", - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "(function() {} ())", - output: "(function() {}())", - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "var f = new Foo ()", - output: "var f = new Foo()", - errors: [{ messageId: "unexpectedWhitespace", type: "NewExpression" }] - }, - { - code: "f ( (0) )", - output: "f( (0) )", - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "f(0) (1)", - output: "f(0)(1)", - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "(f) (0)", - output: "(f)(0)", - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "f ();\n t ();", - output: "f();\n t();", - errors: [ - { messageId: "unexpectedWhitespace", type: "CallExpression" }, - { messageId: "unexpectedWhitespace", type: "CallExpression" } - ] - }, - { - code: "import (source);", - output: "import(source);", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "unexpectedWhitespace", type: "ImportExpression" }] - }, + // "never" + { + code: "f ();", + output: "f();", + options: ["never"], + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "f (a, b);", + output: "f(a, b);", + options: ["never"], + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "f.b ();", + output: "f.b();", + options: ["never"], + errors: [ + { + messageId: "unexpectedWhitespace", + type: "CallExpression", + column: 4, + line: 1, + endColumn: 5, + endLine: 1, + }, + ], + }, + { + code: "f.b().c ();", + output: "f.b().c();", + options: ["never"], + errors: [ + { + messageId: "unexpectedWhitespace", + type: "CallExpression", + column: 8, + line: 1, + endColumn: 8, + endLine: 1, + }, + ], + }, + { + code: "f() ()", + output: "f()()", + options: ["never"], + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "(function() {} ())", + output: "(function() {}())", + options: ["never"], + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "var f = new Foo ()", + output: "var f = new Foo()", + options: ["never"], + errors: [ + { messageId: "unexpectedWhitespace", type: "NewExpression" }, + ], + }, + { + code: "f ( (0) )", + output: "f( (0) )", + options: ["never"], + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "f(0) (1)", + output: "f(0)(1)", + options: ["never"], + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "(f) (0)", + output: "(f)(0)", + options: ["never"], + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "f ();\n t ();", + output: "f();\n t();", + options: ["never"], + errors: [ + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + { messageId: "unexpectedWhitespace", type: "CallExpression" }, + ], + }, + { + code: "import (source);", + output: "import(source);", + options: ["never"], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { messageId: "unexpectedWhitespace", type: "ImportExpression" }, + ], + }, - // https://github.com/eslint/eslint/issues/7787 - { - code: "f\n();", - output: null, // no change - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "f\r();", - output: null, // no change - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "f\u2028();", - output: null, // no change - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "f\u2029();", - output: null, // no change - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "f\r\n();", - output: null, // no change - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "import\n(source);", - output: null, - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "unexpectedWhitespace", type: "ImportExpression" }] - }, + // https://github.com/eslint/eslint/issues/7787 + { + code: "f\n();", + output: null, // no change + options: ["never"], + errors: [ + { + messageId: "unexpectedWhitespace", + type: "CallExpression", + line: 1, + column: 2, + endLine: 2, + endColumn: 0, + }, + ], + }, + { + code: [ + "this.cancelled.add(request)", + "this.decrement(request)", + "(0, request.reject)(new api.Cancel())", + ].join("\n"), + output: null, // no change + options: ["never"], + errors: [ + { + messageId: "unexpectedWhitespace", + type: "CallExpression", + line: 2, + column: 24, + endLine: 3, + endColumn: 0, + }, + ], + }, + { + code: ["var a = foo", "(function(global) {}(this));"].join("\n"), + output: null, // no change + options: ["never"], + errors: [ + { + messageId: "unexpectedWhitespace", + type: "CallExpression", + line: 1, + column: 12, + endLine: 2, + endColumn: 0, + }, + ], + }, + { + code: ["var a = foo", "(0, baz())"].join("\n"), + output: null, // no change + options: ["never"], + errors: [ + { + messageId: "unexpectedWhitespace", + type: "CallExpression", + line: 1, + column: 12, + endColumn: 0, + endLine: 2, + }, + ], + }, + { + code: "f\r();", + output: null, // no change + options: ["never"], + errors: [ + { + messageId: "unexpectedWhitespace", + type: "CallExpression", + }, + ], + }, + { + code: "f\u2028();", + output: null, // no change + options: ["never"], + errors: [ + { + messageId: "unexpectedWhitespace", + type: "CallExpression", + }, + ], + }, + { + code: "f\u2029();", + output: null, // no change + options: ["never"], + errors: [ + { + messageId: "unexpectedWhitespace", + type: "CallExpression", + }, + ], + }, + { + code: "f\r\n();", + output: null, // no change + options: ["never"], + errors: [ + { + messageId: "unexpectedWhitespace", + type: "CallExpression", + }, + ], + }, - // "never" - { - code: "f ();", - output: "f();", - options: ["never"], - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "f (a, b);", - output: "f(a, b);", - options: ["never"], - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "f.b ();", - output: "f.b();", - options: ["never"], - errors: [ - { - messageId: "unexpectedWhitespace", - type: "CallExpression", - column: 4, - line: 1, - endColumn: 5, - endLine: 1 - } - ] - }, - { - code: "f.b().c ();", - output: "f.b().c();", - options: ["never"], - errors: [ - { - messageId: "unexpectedWhitespace", - type: "CallExpression", - column: 8, - line: 1, - endColumn: 8, - endLine: 1 - } - ] - }, - { - code: "f() ()", - output: "f()()", - options: ["never"], - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "(function() {} ())", - output: "(function() {}())", - options: ["never"], - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "var f = new Foo ()", - output: "var f = new Foo()", - options: ["never"], - errors: [{ messageId: "unexpectedWhitespace", type: "NewExpression" }] - }, - { - code: "f ( (0) )", - output: "f( (0) )", - options: ["never"], - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "f(0) (1)", - output: "f(0)(1)", - options: ["never"], - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "(f) (0)", - output: "(f)(0)", - options: ["never"], - errors: [{ messageId: "unexpectedWhitespace", type: "CallExpression" }] - }, - { - code: "f ();\n t ();", - output: "f();\n t();", - options: ["never"], - errors: [ - { messageId: "unexpectedWhitespace", type: "CallExpression" }, - { messageId: "unexpectedWhitespace", type: "CallExpression" } - ] - }, - { - code: "import (source);", - output: "import(source);", - options: ["never"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "unexpectedWhitespace", type: "ImportExpression" }] - }, + // "always" + { + code: "f();", + output: "f ();", + options: ["always"], + errors: [{ messageId: "missing", type: "CallExpression" }], + }, + { + code: "f\n();", + output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) + options: ["always"], + errors: [ + { messageId: "unexpectedNewline", type: "CallExpression" }, + ], + }, + { + code: "f(a, b);", + output: "f (a, b);", + options: ["always"], + errors: [{ messageId: "missing", type: "CallExpression" }], + }, + { + code: "f\n(a, b);", + output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) + options: ["always"], + errors: [ + { messageId: "unexpectedNewline", type: "CallExpression" }, + ], + }, + { + code: "f.b();", + output: "f.b ();", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "CallExpression", + column: 3, + line: 1, + endLine: 1, + endColumn: 4, + }, + ], + }, + { + code: "f.b\n();", + output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) + options: ["always"], + errors: [ + { + messageId: "unexpectedNewline", + type: "CallExpression", + column: 4, + line: 1, + endColumn: 1, + endLine: 2, + }, + ], + }, + { + code: "f.b().c ();", + output: "f.b ().c ();", + options: ["always"], + errors: [ + { messageId: "missing", type: "CallExpression", column: 3 }, + ], + }, + { + code: "f.b\n().c ();", + output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) + options: ["always"], + errors: [ + { + messageId: "unexpectedNewline", + type: "CallExpression", + column: 4, + line: 1, + endColumn: 1, + endLine: 2, + }, + ], + }, + { + code: "f() ()", + output: "f () ()", + options: ["always"], + errors: [{ messageId: "missing", type: "CallExpression" }], + }, + { + code: "f\n() ()", + output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) + options: ["always"], + errors: [ + { messageId: "unexpectedNewline", type: "CallExpression" }, + ], + }, + { + code: "f\n()()", + output: "f\n() ()", // Don't fix the first error to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) + options: ["always"], + errors: [ + { messageId: "unexpectedNewline", type: "CallExpression" }, + { messageId: "missing", type: "CallExpression" }, + ], + }, + { + code: "(function() {}())", + output: "(function() {} ())", + options: ["always"], + errors: [{ messageId: "missing", type: "CallExpression" }], + }, + { + code: "var f = new Foo()", + output: "var f = new Foo ()", + options: ["always"], + errors: [{ messageId: "missing", type: "NewExpression" }], + }, + { + code: "f( (0) )", + output: "f ( (0) )", + options: ["always"], + errors: [{ messageId: "missing", type: "CallExpression" }], + }, + { + code: "f(0) (1)", + output: "f (0) (1)", + options: ["always"], + errors: [{ messageId: "missing", type: "CallExpression" }], + }, + { + code: "(f)(0)", + output: "(f) (0)", + options: ["always"], + errors: [{ messageId: "missing", type: "CallExpression" }], + }, + { + code: "import(source);", + output: "import (source);", + options: ["always"], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "missing", type: "ImportExpression" }], + }, + { + code: "f();\n t();", + output: "f ();\n t ();", + options: ["always"], + errors: [ + { messageId: "missing", type: "CallExpression" }, + { messageId: "missing", type: "CallExpression" }, + ], + }, + { + code: "f\r();", + output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) + options: ["always"], + errors: [ + { messageId: "unexpectedNewline", type: "CallExpression" }, + ], + }, + { + code: "f\u2028();", + output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) + options: ["always"], + errors: [ + { messageId: "unexpectedNewline", type: "CallExpression" }, + ], + }, + { + code: "f\u2029();", + output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) + options: ["always"], + errors: [ + { messageId: "unexpectedNewline", type: "CallExpression" }, + ], + }, + { + code: "f\r\n();", + output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) + options: ["always"], + errors: [ + { messageId: "unexpectedNewline", type: "CallExpression" }, + ], + }, - // https://github.com/eslint/eslint/issues/7787 - { - code: "f\n();", - output: null, // no change - options: ["never"], - errors: [ - { - messageId: "unexpectedWhitespace", - type: "CallExpression", - line: 1, - column: 2, - endLine: 2, - endColumn: 0 - } - ] - }, - { - code: [ - "this.cancelled.add(request)", - "this.decrement(request)", - "(0, request.reject)(new api.Cancel())" - ].join("\n"), - output: null, // no change - options: ["never"], - errors: [ - { - messageId: "unexpectedWhitespace", - type: "CallExpression", - line: 2, - column: 24, - endLine: 3, - endColumn: 0 - } - ] - }, - { - code: [ - "var a = foo", - "(function(global) {}(this));" - ].join("\n"), - output: null, // no change - options: ["never"], - errors: [ - { - messageId: "unexpectedWhitespace", - type: "CallExpression", - line: 1, - column: 12, - endLine: 2, - endColumn: 0 - } - ] - }, - { - code: [ - "var a = foo", - "(0, baz())" - ].join("\n"), - output: null, // no change - options: ["never"], - errors: [ - { - messageId: "unexpectedWhitespace", - type: "CallExpression", - line: 1, - column: 12, - endColumn: 0, - endLine: 2 - } - ] - }, - { - code: "f\r();", - output: null, // no change - options: ["never"], - errors: [ - { - messageId: "unexpectedWhitespace", - type: "CallExpression" - } - ] - }, - { - code: "f\u2028();", - output: null, // no change - options: ["never"], - errors: [ - { - messageId: "unexpectedWhitespace", - type: "CallExpression" - } - ] - }, - { - code: "f\u2029();", - output: null, // no change - options: ["never"], - errors: [ - { - messageId: "unexpectedWhitespace", - type: "CallExpression" - } - ] - }, - { - code: "f\r\n();", - output: null, // no change - options: ["never"], - errors: [ - { - messageId: "unexpectedWhitespace", - type: "CallExpression" - } - ] - }, + // "always", "allowNewlines": true + { + code: "f();", + output: "f ();", + options: ["always", { allowNewlines: true }], + errors: [{ messageId: "missing", type: "CallExpression" }], + }, + { + code: "f(a, b);", + output: "f (a, b);", + options: ["always", { allowNewlines: true }], + errors: [{ messageId: "missing", type: "CallExpression" }], + }, + { + code: "f.b();", + output: "f.b ();", + options: ["always", { allowNewlines: true }], + errors: [ + { + messageId: "missing", + type: "CallExpression", + column: 3, + }, + ], + }, + { + code: "f.b().c ();", + output: "f.b ().c ();", + options: ["always", { allowNewlines: true }], + errors: [ + { messageId: "missing", type: "CallExpression", column: 3 }, + ], + }, + { + code: "f() ()", + output: "f () ()", + options: ["always", { allowNewlines: true }], + errors: [{ messageId: "missing", type: "CallExpression" }], + }, + { + code: "(function() {}())", + output: "(function() {} ())", + options: ["always", { allowNewlines: true }], + errors: [{ messageId: "missing", type: "CallExpression" }], + }, + { + code: "var f = new Foo()", + output: "var f = new Foo ()", + options: ["always", { allowNewlines: true }], + errors: [{ messageId: "missing", type: "NewExpression" }], + }, + { + code: "f( (0) )", + output: "f ( (0) )", + options: ["always", { allowNewlines: true }], + errors: [{ messageId: "missing", type: "CallExpression" }], + }, + { + code: "f(0) (1)", + output: "f (0) (1)", + options: ["always", { allowNewlines: true }], + errors: [{ messageId: "missing", type: "CallExpression" }], + }, + { + code: "(f)(0)", + output: "(f) (0)", + options: ["always", { allowNewlines: true }], + errors: [{ messageId: "missing", type: "CallExpression" }], + }, + { + code: "f();\n t();", + output: "f ();\n t ();", + options: ["always", { allowNewlines: true }], + errors: [ + { messageId: "missing", type: "CallExpression" }, + { messageId: "missing", type: "CallExpression" }, + ], + }, + { + code: "f ();", + output: "f();", + errors: [ + { + messageId: "unexpectedWhitespace", + type: "CallExpression", + line: 1, + column: 2, + endLine: 1, + endColumn: 5, + }, + ], + }, + { + code: "f\n ();", + output: null, + errors: [ + { + messageId: "unexpectedWhitespace", + type: "CallExpression", + line: 1, + column: 2, + endLine: 2, + endColumn: 1, + }, + ], + }, + { + code: "fn();", + output: "fn ();", + options: ["always"], + errors: [ + { + messageId: "missing", + type: "CallExpression", + line: 1, + column: 2, + endLine: 1, + endColumn: 3, + }, + ], + }, + { + code: "fnn\n (a, b);", + output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) + options: ["always"], + errors: [ + { + messageId: "unexpectedNewline", + type: "CallExpression", + line: 1, + column: 4, + endLine: 2, + endColumn: 2, + }, + ], + }, + { + code: "f /*comment*/ ()", + output: null, // Don't remove comments + options: ["never"], + errors: [{ messageId: "unexpectedWhitespace" }], + }, + { + code: "f /*\n*/ ()", + output: null, // Don't remove comments + options: ["never"], + errors: [{ messageId: "unexpectedWhitespace" }], + }, + { + code: "f/*comment*/()", + output: "f/*comment*/ ()", + options: ["always"], + errors: [{ messageId: "missing" }], + }, - // "always" - { - code: "f();", - output: "f ();", - options: ["always"], - errors: [{ messageId: "missing", type: "CallExpression" }] - }, - { - code: "f\n();", - output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) - options: ["always"], - errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }] - }, - { - code: "f(a, b);", - output: "f (a, b);", - options: ["always"], - errors: [{ messageId: "missing", type: "CallExpression" }] - }, - { - code: "f\n(a, b);", - output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) - options: ["always"], - errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }] - }, - { - code: "f.b();", - output: "f.b ();", - options: ["always"], - errors: [ - { - messageId: "missing", - type: "CallExpression", - column: 3, - line: 1, - endLine: 1, - endColumn: 4 - } - ] - }, - { - code: "f.b\n();", - output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) - options: ["always"], - errors: [ - { - messageId: "unexpectedNewline", - type: "CallExpression", - column: 4, - line: 1, - endColumn: 1, - endLine: 2 - } - ] - }, - { - code: "f.b().c ();", - output: "f.b ().c ();", - options: ["always"], - errors: [{ messageId: "missing", type: "CallExpression", column: 3 }] - }, - { - code: "f.b\n().c ();", - output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) - options: ["always"], - errors: [ - { - messageId: "unexpectedNewline", - type: "CallExpression", - column: 4, - line: 1, - endColumn: 1, - endLine: 2 - } - ] - }, - { - code: "f() ()", - output: "f () ()", - options: ["always"], - errors: [{ messageId: "missing", type: "CallExpression" }] - }, - { - code: "f\n() ()", - output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) - options: ["always"], - errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }] - }, - { - code: "f\n()()", - output: "f\n() ()", // Don't fix the first error to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) - options: ["always"], - errors: [ - { messageId: "unexpectedNewline", type: "CallExpression" }, - { messageId: "missing", type: "CallExpression" } - ] - }, - { - code: "(function() {}())", - output: "(function() {} ())", - options: ["always"], - errors: [{ messageId: "missing", type: "CallExpression" }] - }, - { - code: "var f = new Foo()", - output: "var f = new Foo ()", - options: ["always"], - errors: [{ messageId: "missing", type: "NewExpression" }] - }, - { - code: "f( (0) )", - output: "f ( (0) )", - options: ["always"], - errors: [{ messageId: "missing", type: "CallExpression" }] - }, - { - code: "f(0) (1)", - output: "f (0) (1)", - options: ["always"], - errors: [{ messageId: "missing", type: "CallExpression" }] - }, - { - code: "(f)(0)", - output: "(f) (0)", - options: ["always"], - errors: [{ messageId: "missing", type: "CallExpression" }] - }, - { - code: "import(source);", - output: "import (source);", - options: ["always"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "missing", type: "ImportExpression" }] - }, - { - code: "f();\n t();", - output: "f ();\n t ();", - options: ["always"], - errors: [ - { messageId: "missing", type: "CallExpression" }, - { messageId: "missing", type: "CallExpression" } - ] - }, - { - code: "f\r();", - output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) - options: ["always"], - errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }] - }, - { - code: "f\u2028();", - output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) - options: ["always"], - errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }] - }, - { - code: "f\u2029();", - output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) - options: ["always"], - errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }] - }, - { - code: "f\r\n();", - output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) - options: ["always"], - errors: [{ messageId: "unexpectedNewline", type: "CallExpression" }] - }, - - // "always", "allowNewlines": true - { - code: "f();", - output: "f ();", - options: ["always", { allowNewlines: true }], - errors: [ - { messageId: "missing", type: "CallExpression" }] - }, - { - code: "f(a, b);", - output: "f (a, b);", - options: ["always", { allowNewlines: true }], - errors: [{ messageId: "missing", type: "CallExpression" }] - }, - { - code: "f.b();", - output: "f.b ();", - options: ["always", { allowNewlines: true }], - errors: [ - { - messageId: "missing", - type: "CallExpression", - column: 3 - } - ] - }, - { - code: "f.b().c ();", - output: "f.b ().c ();", - options: ["always", { allowNewlines: true }], - errors: [{ messageId: "missing", type: "CallExpression", column: 3 }] - }, - { - code: "f() ()", - output: "f () ()", - options: ["always", { allowNewlines: true }], - errors: [{ messageId: "missing", type: "CallExpression" }] - }, - { - code: "(function() {}())", - output: "(function() {} ())", - options: ["always", { allowNewlines: true }], - errors: [{ messageId: "missing", type: "CallExpression" }] - }, - { - code: "var f = new Foo()", - output: "var f = new Foo ()", - options: ["always", { allowNewlines: true }], - errors: [{ messageId: "missing", type: "NewExpression" }] - }, - { - code: "f( (0) )", - output: "f ( (0) )", - options: ["always", { allowNewlines: true }], - errors: [{ messageId: "missing", type: "CallExpression" }] - }, - { - code: "f(0) (1)", - output: "f (0) (1)", - options: ["always", { allowNewlines: true }], - errors: [{ messageId: "missing", type: "CallExpression" }] - }, - { - code: "(f)(0)", - output: "(f) (0)", - options: ["always", { allowNewlines: true }], - errors: [{ messageId: "missing", type: "CallExpression" }] - }, - { - code: "f();\n t();", - output: "f ();\n t ();", - options: ["always", { allowNewlines: true }], - errors: [ - { messageId: "missing", type: "CallExpression" }, - { messageId: "missing", type: "CallExpression" } - ] - }, - { - code: "f ();", - output: "f();", - errors: [ - { - messageId: "unexpectedWhitespace", - type: "CallExpression", - line: 1, - column: 2, - endLine: 1, - endColumn: 5 - } - ] - }, - { - code: "f\n ();", - output: null, - errors: [ - { - messageId: "unexpectedWhitespace", - type: "CallExpression", - line: 1, - column: 2, - endLine: 2, - endColumn: 1 - } - ] - }, - { - code: "fn();", - output: "fn ();", - options: ["always"], - errors: [ - { - messageId: "missing", - type: "CallExpression", - line: 1, - column: 2, - endLine: 1, - endColumn: 3 - } - ] - }, - { - code: "fnn\n (a, b);", - output: null, // Don't fix to avoid hiding no-unexpected-multiline (https://github.com/eslint/eslint/issues/7787) - options: ["always"], - errors: [ - { - messageId: "unexpectedNewline", - type: "CallExpression", - line: 1, - column: 4, - endLine: 2, - endColumn: 2 - } - ] - }, - { - code: "f /*comment*/ ()", - output: null, // Don't remove comments - options: ["never"], - errors: [{ messageId: "unexpectedWhitespace" }] - }, - { - code: "f /*\n*/ ()", - output: null, // Don't remove comments - options: ["never"], - errors: [{ messageId: "unexpectedWhitespace" }] - }, - { - code: "f/*comment*/()", - output: "f/*comment*/ ()", - options: ["always"], - errors: [{ messageId: "missing" }] - }, - - // Optional chaining - { - code: "func ?.()", - output: "func?.()", - options: ["never"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "unexpectedWhitespace" }] - }, - { - code: "func?. ()", - output: "func?.()", - options: ["never"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "unexpectedWhitespace" }] - }, - { - code: "func ?. ()", - output: "func?.()", - options: ["never"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "unexpectedWhitespace" }] - }, - { - code: "func\n?.()", - output: "func?.()", - options: ["never"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "unexpectedWhitespace" }] - }, - { - code: "func\n//comment\n?.()", - output: null, // Don't remove comments - options: ["never"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "unexpectedWhitespace" }] - }, - { - code: "func?.()", - output: null, // Not sure inserting a space into either before/after `?.`. - options: ["always"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "missing" }] - }, - { - code: "func\n ?.()", - output: "func ?.()", - options: ["always"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "unexpectedNewline" }] - }, - { - code: "func?.\n ()", - output: "func?. ()", - options: ["always"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "unexpectedNewline" }] - }, - { - code: "func ?.\n ()", - output: "func ?. ()", - options: ["always"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "unexpectedNewline" }] - }, - { - code: "func\n /*comment*/ ?.()", - output: null, // Don't remove comments - options: ["always"], - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "unexpectedNewline" }] - } - ] + // Optional chaining + { + code: "func ?.()", + output: "func?.()", + options: ["never"], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpectedWhitespace" }], + }, + { + code: "func?. ()", + output: "func?.()", + options: ["never"], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpectedWhitespace" }], + }, + { + code: "func ?. ()", + output: "func?.()", + options: ["never"], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpectedWhitespace" }], + }, + { + code: "func\n?.()", + output: "func?.()", + options: ["never"], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpectedWhitespace" }], + }, + { + code: "func\n//comment\n?.()", + output: null, // Don't remove comments + options: ["never"], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpectedWhitespace" }], + }, + { + code: "func?.()", + output: null, // Not sure inserting a space into either before/after `?.`. + options: ["always"], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "missing" }], + }, + { + code: "func\n ?.()", + output: "func ?.()", + options: ["always"], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpectedNewline" }], + }, + { + code: "func?.\n ()", + output: "func?. ()", + options: ["always"], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpectedNewline" }], + }, + { + code: "func ?.\n ()", + output: "func ?. ()", + options: ["always"], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpectedNewline" }], + }, + { + code: "func\n /*comment*/ ?.()", + output: null, // Don't remove comments + options: ["always"], + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "unexpectedNewline" }], + }, + ], }); diff --git a/tests/lib/rules/func-name-matching.js b/tests/lib/rules/func-name-matching.js index 14b0d72ed8fd..f2f8d0bdecd2 100644 --- a/tests/lib/rules/func-name-matching.js +++ b/tests/lib/rules/func-name-matching.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/func-name-matching"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -19,880 +19,1056 @@ const rule = require("../../../lib/rules/func-name-matching"), const ruleTester = new RuleTester(); ruleTester.run("func-name-matching", rule, { - valid: [ - "var foo;", - "var foo = function foo() {};", - { code: "var foo = function foo() {};", options: ["always"] }, - { code: "var foo = function bar() {};", options: ["never"] }, - "var foo = function() {}", - { code: "var foo = () => {}", languageOptions: { ecmaVersion: 6 } }, - "foo = function foo() {};", - { code: "foo = function foo() {};", options: ["always"] }, - { code: "foo = function bar() {};", options: ["never"] }, - { code: "foo &&= function foo() {};", languageOptions: { ecmaVersion: 2021 } }, - { code: "obj.foo ||= function foo() {};", languageOptions: { ecmaVersion: 2021 } }, - { code: "obj['foo'] ??= function foo() {};", languageOptions: { ecmaVersion: 2021 } }, - "obj.foo = function foo() {};", - { code: "obj.foo = function foo() {};", options: ["always"] }, - { code: "obj.foo = function bar() {};", options: ["never"] }, - "obj.foo = function() {};", - { code: "obj.foo = function() {};", options: ["always"] }, - { code: "obj.foo = function() {};", options: ["never"] }, - "obj.bar.foo = function foo() {};", - { code: "obj.bar.foo = function foo() {};", options: ["always"] }, - { code: "obj.bar.foo = function baz() {};", options: ["never"] }, - "obj['foo'] = function foo() {};", - { code: "obj['foo'] = function foo() {};", options: ["always"] }, - { code: "obj['foo'] = function bar() {};", options: ["never"] }, - "obj['foo//bar'] = function foo() {};", - { code: "obj['foo//bar'] = function foo() {};", options: ["always"] }, - { code: "obj['foo//bar'] = function foo() {};", options: ["never"] }, - "obj[foo] = function bar() {};", - { code: "obj[foo] = function bar() {};", options: ["always"] }, - { code: "obj[foo] = function bar() {};", options: ["never"] }, - "var obj = {foo: function foo() {}};", - { code: "var obj = {foo: function foo() {}};", options: ["always"] }, - { code: "var obj = {foo: function bar() {}};", options: ["never"] }, - "var obj = {'foo': function foo() {}};", - { code: "var obj = {'foo': function foo() {}};", options: ["always"] }, - { code: "var obj = {'foo': function bar() {}};", options: ["never"] }, - "var obj = {'foo//bar': function foo() {}};", - { code: "var obj = {'foo//bar': function foo() {}};", options: ["always"] }, - { code: "var obj = {'foo//bar': function foo() {}};", options: ["never"] }, - "var obj = {foo: function() {}};", - { code: "var obj = {foo: function() {}};", options: ["always"] }, - { code: "var obj = {foo: function() {}};", options: ["never"] }, - { code: "var obj = {[foo]: function bar() {}} ", languageOptions: { ecmaVersion: 6 } }, - { code: "var obj = {['x' + 2]: function bar(){}};", languageOptions: { ecmaVersion: 6 } }, - "obj['x' + 2] = function bar(){};", - { code: "var [ bar ] = [ function bar(){} ];", languageOptions: { ecmaVersion: 6 } }, - { code: "function a(foo = function bar() {}) {}", languageOptions: { ecmaVersion: 6 } }, - "module.exports = function foo(name) {};", - "module['exports'] = function foo(name) {};", - { - code: "module.exports = function foo(name) {};", - options: [{ includeCommonJSModuleExports: false }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "module.exports = function foo(name) {};", - options: ["always", { includeCommonJSModuleExports: false }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "module.exports = function foo(name) {};", - options: ["never", { includeCommonJSModuleExports: false }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "module['exports'] = function foo(name) {};", - options: [{ includeCommonJSModuleExports: false }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "module['exports'] = function foo(name) {};", - options: ["always", { includeCommonJSModuleExports: false }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "module['exports'] = function foo(name) {};", - options: ["never", { includeCommonJSModuleExports: false }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({['foo']: function foo() {}})", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({['foo']: function foo() {}})", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({['foo']: function bar() {}})", - options: ["never"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({['❤']: function foo() {}})", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({[foo]: function bar() {}})", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({[null]: function foo() {}})", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({[1]: function foo() {}})", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({[true]: function foo() {}})", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({[`x`]: function foo() {}})", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({[/abc/]: function foo() {}})", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({[[1, 2, 3]]: function foo() {}})", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({[{x: 1}]: function foo() {}})", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "[] = function foo() {}", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({} = function foo() {})", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "[a] = function foo() {}", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({a} = function foo() {})", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [] = function foo() {}", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var {} = function foo() {}", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var [a] = function foo() {}", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var {a} = function foo() {}", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({ value: function value() {} })", - options: [{ considerPropertyDescriptor: true }] - }, - { - code: "obj.foo = function foo() {};", - options: ["always", { considerPropertyDescriptor: true }] - }, - { - code: "obj.bar.foo = function foo() {};", - options: ["always", { considerPropertyDescriptor: true }] - }, - { - code: "var obj = {foo: function foo() {}};", - options: ["always", { considerPropertyDescriptor: true }] - }, - { - code: "var obj = {foo: function() {}};", - options: ["always", { considerPropertyDescriptor: true }] - }, - { - code: "var obj = { value: function value() {} }", - options: ["always", { considerPropertyDescriptor: true }] - }, - { - code: "Object.defineProperty(foo, 'bar', { value: function bar() {} })", - options: ["always", { considerPropertyDescriptor: true }] - }, - { - code: "Object.defineProperties(foo, { bar: { value: function bar() {} } })", - options: ["always", { considerPropertyDescriptor: true }] - }, - { - code: "Object.create(proto, { bar: { value: function bar() {} } })", - options: ["always", { considerPropertyDescriptor: true }] - }, - { - code: "Object.defineProperty(foo, 'b' + 'ar', { value: function bar() {} })", - options: ["always", { considerPropertyDescriptor: true }] - }, - { - code: "Object.defineProperties(foo, { ['bar']: { value: function bar() {} } })", - options: ["always", { considerPropertyDescriptor: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "Object.create(proto, { ['bar']: { value: function bar() {} } })", - options: ["always", { considerPropertyDescriptor: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "Object.defineProperty(foo, 'bar', { value() {} })", - options: ["never", { considerPropertyDescriptor: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "Object.defineProperties(foo, { bar: { value() {} } })", - options: ["never", { considerPropertyDescriptor: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "Object.create(proto, { bar: { value() {} } })", - options: ["never", { considerPropertyDescriptor: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "Reflect.defineProperty(foo, 'bar', { value: function bar() {} })", - options: ["always", { considerPropertyDescriptor: true }] - }, - { - code: "Reflect.defineProperty(foo, 'b' + 'ar', { value: function baz() {} })", - options: ["always", { considerPropertyDescriptor: true }] - }, - { - code: "Reflect.defineProperty(foo, 'bar', { value() {} })", - options: ["never", { considerPropertyDescriptor: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "foo({ value: function value() {} })", - options: ["always", { considerPropertyDescriptor: true }] - }, + valid: [ + "var foo;", + "var foo = function foo() {};", + { code: "var foo = function foo() {};", options: ["always"] }, + { code: "var foo = function bar() {};", options: ["never"] }, + "var foo = function() {}", + { code: "var foo = () => {}", languageOptions: { ecmaVersion: 6 } }, + "foo = function foo() {};", + { code: "foo = function foo() {};", options: ["always"] }, + { code: "foo = function bar() {};", options: ["never"] }, + { + code: "foo &&= function foo() {};", + languageOptions: { ecmaVersion: 2021 }, + }, + { + code: "obj.foo ||= function foo() {};", + languageOptions: { ecmaVersion: 2021 }, + }, + { + code: "obj['foo'] ??= function foo() {};", + languageOptions: { ecmaVersion: 2021 }, + }, + "obj.foo = function foo() {};", + { code: "obj.foo = function foo() {};", options: ["always"] }, + { code: "obj.foo = function bar() {};", options: ["never"] }, + "obj.foo = function() {};", + { code: "obj.foo = function() {};", options: ["always"] }, + { code: "obj.foo = function() {};", options: ["never"] }, + "obj.bar.foo = function foo() {};", + { code: "obj.bar.foo = function foo() {};", options: ["always"] }, + { code: "obj.bar.foo = function baz() {};", options: ["never"] }, + "obj['foo'] = function foo() {};", + { code: "obj['foo'] = function foo() {};", options: ["always"] }, + { code: "obj['foo'] = function bar() {};", options: ["never"] }, + "obj['foo//bar'] = function foo() {};", + { code: "obj['foo//bar'] = function foo() {};", options: ["always"] }, + { code: "obj['foo//bar'] = function foo() {};", options: ["never"] }, + "obj[foo] = function bar() {};", + { code: "obj[foo] = function bar() {};", options: ["always"] }, + { code: "obj[foo] = function bar() {};", options: ["never"] }, + "var obj = {foo: function foo() {}};", + { code: "var obj = {foo: function foo() {}};", options: ["always"] }, + { code: "var obj = {foo: function bar() {}};", options: ["never"] }, + "var obj = {'foo': function foo() {}};", + { code: "var obj = {'foo': function foo() {}};", options: ["always"] }, + { code: "var obj = {'foo': function bar() {}};", options: ["never"] }, + "var obj = {'foo//bar': function foo() {}};", + { + code: "var obj = {'foo//bar': function foo() {}};", + options: ["always"], + }, + { + code: "var obj = {'foo//bar': function foo() {}};", + options: ["never"], + }, + "var obj = {foo: function() {}};", + { code: "var obj = {foo: function() {}};", options: ["always"] }, + { code: "var obj = {foo: function() {}};", options: ["never"] }, + { + code: "var obj = {[foo]: function bar() {}} ", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var obj = {['x' + 2]: function bar(){}};", + languageOptions: { ecmaVersion: 6 }, + }, + "obj['x' + 2] = function bar(){};", + { + code: "var [ bar ] = [ function bar(){} ];", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function a(foo = function bar() {}) {}", + languageOptions: { ecmaVersion: 6 }, + }, + "module.exports = function foo(name) {};", + "module['exports'] = function foo(name) {};", + { + code: "module.exports = function foo(name) {};", + options: [{ includeCommonJSModuleExports: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "module.exports = function foo(name) {};", + options: ["always", { includeCommonJSModuleExports: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "module.exports = function foo(name) {};", + options: ["never", { includeCommonJSModuleExports: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "module['exports'] = function foo(name) {};", + options: [{ includeCommonJSModuleExports: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "module['exports'] = function foo(name) {};", + options: ["always", { includeCommonJSModuleExports: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "module['exports'] = function foo(name) {};", + options: ["never", { includeCommonJSModuleExports: false }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({['foo']: function foo() {}})", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({['foo']: function foo() {}})", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({['foo']: function bar() {}})", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({['❤']: function foo() {}})", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({[foo]: function bar() {}})", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({[null]: function foo() {}})", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({[1]: function foo() {}})", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({[true]: function foo() {}})", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({[`x`]: function foo() {}})", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({[/abc/]: function foo() {}})", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({[[1, 2, 3]]: function foo() {}})", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({[{x: 1}]: function foo() {}})", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "[] = function foo() {}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({} = function foo() {})", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "[a] = function foo() {}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({a} = function foo() {})", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [] = function foo() {}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var {} = function foo() {}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var [a] = function foo() {}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var {a} = function foo() {}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ value: function value() {} })", + options: [{ considerPropertyDescriptor: true }], + }, + { + code: "obj.foo = function foo() {};", + options: ["always", { considerPropertyDescriptor: true }], + }, + { + code: "obj.bar.foo = function foo() {};", + options: ["always", { considerPropertyDescriptor: true }], + }, + { + code: "var obj = {foo: function foo() {}};", + options: ["always", { considerPropertyDescriptor: true }], + }, + { + code: "var obj = {foo: function() {}};", + options: ["always", { considerPropertyDescriptor: true }], + }, + { + code: "var obj = { value: function value() {} }", + options: ["always", { considerPropertyDescriptor: true }], + }, + { + code: "Object.defineProperty(foo, 'bar', { value: function bar() {} })", + options: ["always", { considerPropertyDescriptor: true }], + }, + { + code: "Object.defineProperties(foo, { bar: { value: function bar() {} } })", + options: ["always", { considerPropertyDescriptor: true }], + }, + { + code: "Object.create(proto, { bar: { value: function bar() {} } })", + options: ["always", { considerPropertyDescriptor: true }], + }, + { + code: "Object.defineProperty(foo, 'b' + 'ar', { value: function bar() {} })", + options: ["always", { considerPropertyDescriptor: true }], + }, + { + code: "Object.defineProperties(foo, { ['bar']: { value: function bar() {} } })", + options: ["always", { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "Object.create(proto, { ['bar']: { value: function bar() {} } })", + options: ["always", { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "Object.defineProperty(foo, 'bar', { value() {} })", + options: ["never", { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "Object.defineProperties(foo, { bar: { value() {} } })", + options: ["never", { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "Object.create(proto, { bar: { value() {} } })", + options: ["never", { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "Reflect.defineProperty(foo, 'bar', { value: function bar() {} })", + options: ["always", { considerPropertyDescriptor: true }], + }, + { + code: "Reflect.defineProperty(foo, 'b' + 'ar', { value: function baz() {} })", + options: ["always", { considerPropertyDescriptor: true }], + }, + { + code: "Reflect.defineProperty(foo, 'bar', { value() {} })", + options: ["never", { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "foo({ value: function value() {} })", + options: ["always", { considerPropertyDescriptor: true }], + }, - // class fields, private names are ignored - { - code: "class C { x = function () {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { x = function () {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { 'x' = function () {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { 'x' = function () {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #x = function () {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #x = function () {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { [x] = function () {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { [x] = function () {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { ['x'] = function () {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { ['x'] = function () {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { x = function x() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { x = function y() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { 'x' = function x() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { 'x' = function y() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #x = function x() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #x = function x() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #x = function y() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #x = function y() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { [x] = function x() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { [x] = function x() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { [x] = function y() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { [x] = function y() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { ['x'] = function x() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { ['x'] = function y() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { 'xy ' = function foo() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { 'xy ' = function xy() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { ['xy '] = function foo() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { ['xy '] = function xy() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { 1 = function x0() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { 1 = function x1() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { [1] = function x0() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { [1] = function x1() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { [f()] = function g() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { [f()] = function f() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { static x = function x() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { static x = function y() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { x = (function y() {})(); }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { x = (function x() {})(); }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "(class { x = function x() {}; })", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "(class { x = function y() {}; })", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #x; foo() { this.#x = function x() {}; } }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #x; foo() { this.#x = function x() {}; } }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #x; foo() { this.#x = function y() {}; } }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #x; foo() { this.#x = function y() {}; } }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #x; foo() { a.b.#x = function x() {}; } }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #x; foo() { a.b.#x = function x() {}; } }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #x; foo() { a.b.#x = function y() {}; } }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #x; foo() { a.b.#x = function y() {}; } }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "var obj = { '\\u1885': function foo() {} };", // not a valid identifier in es5 - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } - } + // class fields, private names are ignored + { + code: "class C { x = function () {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { x = function () {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { 'x' = function () {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { 'x' = function () {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #x = function () {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #x = function () {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { [x] = function () {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { [x] = function () {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { ['x'] = function () {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { ['x'] = function () {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { x = function x() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { x = function y() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { 'x' = function x() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { 'x' = function y() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #x = function x() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #x = function x() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #x = function y() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #x = function y() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { [x] = function x() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { [x] = function x() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { [x] = function y() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { [x] = function y() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { ['x'] = function x() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { ['x'] = function y() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { 'xy ' = function foo() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { 'xy ' = function xy() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { ['xy '] = function foo() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { ['xy '] = function xy() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { 1 = function x0() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { 1 = function x1() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { [1] = function x0() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { [1] = function x1() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { [f()] = function g() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { [f()] = function f() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static x = function x() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { static x = function y() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { x = (function y() {})(); }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { x = (function x() {})(); }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "(class { x = function x() {}; })", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "(class { x = function y() {}; })", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #x; foo() { this.#x = function x() {}; } }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #x; foo() { this.#x = function x() {}; } }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #x; foo() { this.#x = function y() {}; } }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #x; foo() { this.#x = function y() {}; } }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #x; foo() { a.b.#x = function x() {}; } }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #x; foo() { a.b.#x = function x() {}; } }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #x; foo() { a.b.#x = function y() {}; } }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #x; foo() { a.b.#x = function y() {}; } }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "var obj = { '\\u1885': function foo() {} };", // not a valid identifier in es5 + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, + }, + ], + invalid: [ + { + code: "let foo = function bar() {};", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "matchVariable", + data: { funcName: "bar", name: "foo" }, + }, + ], + }, + { + code: "let foo = function bar() {};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "matchVariable", + data: { funcName: "bar", name: "foo" }, + }, + ], + }, + { + code: "foo = function bar() {};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "matchVariable", + data: { funcName: "bar", name: "foo" }, + }, + ], + }, + { + code: "foo &&= function bar() {};", + languageOptions: { ecmaVersion: 2021 }, + errors: [ + { + messageId: "matchVariable", + data: { funcName: "bar", name: "foo" }, + }, + ], + }, + { + code: "obj.foo ||= function bar() {};", + languageOptions: { ecmaVersion: 2021 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "bar", name: "foo" }, + }, + ], + }, + { + code: "obj['foo'] ??= function bar() {};", + languageOptions: { ecmaVersion: 2021 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "bar", name: "foo" }, + }, + ], + }, + { + code: "obj.foo = function bar() {};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "bar", name: "foo" }, + }, + ], + }, + { + code: "obj.bar.foo = function bar() {};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "bar", name: "foo" }, + }, + ], + }, + { + code: "obj['foo'] = function bar() {};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "bar", name: "foo" }, + }, + ], + }, + { + code: "let obj = {foo: function bar() {}};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "bar", name: "foo" }, + }, + ], + }, + { + code: "let obj = {'foo': function bar() {}};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "bar", name: "foo" }, + }, + ], + }, + { + code: "({['foo']: function bar() {}})", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "bar", name: "foo" }, + }, + ], + }, + { + code: "module.exports = function foo(name) {};", + options: [{ includeCommonJSModuleExports: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "foo", name: "exports" }, + }, + ], + }, + { + code: "module.exports = function foo(name) {};", + options: ["always", { includeCommonJSModuleExports: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "foo", name: "exports" }, + }, + ], + }, + { + code: "module.exports = function exports(name) {};", + options: ["never", { includeCommonJSModuleExports: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "exports", name: "exports" }, + }, + ], + }, + { + code: "module['exports'] = function foo(name) {};", + options: [{ includeCommonJSModuleExports: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "foo", name: "exports" }, + }, + ], + }, + { + code: "module['exports'] = function foo(name) {};", + options: ["always", { includeCommonJSModuleExports: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "foo", name: "exports" }, + }, + ], + }, + { + code: "module['exports'] = function exports(name) {};", + options: ["never", { includeCommonJSModuleExports: true }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "exports", name: "exports" }, + }, + ], + }, + { + code: "var foo = function foo(name) {};", + options: ["never"], + errors: [ + { + messageId: "notMatchVariable", + data: { funcName: "foo", name: "foo" }, + }, + ], + }, + { + code: "obj.foo = function foo(name) {};", + options: ["never"], + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "foo", name: "foo" }, + }, + ], + }, + { + code: "Object.defineProperty(foo, 'bar', { value: function baz() {} })", + options: ["always", { considerPropertyDescriptor: true }], + errors: [ + { + messageId: "matchProperty", + data: { funcName: "baz", name: "bar" }, + }, + ], + }, + { + code: "Object.defineProperties(foo, { bar: { value: function baz() {} } })", + options: ["always", { considerPropertyDescriptor: true }], + errors: [ + { + messageId: "matchProperty", + data: { funcName: "baz", name: "bar" }, + }, + ], + }, + { + code: "Object.create(proto, { bar: { value: function baz() {} } })", + options: ["always", { considerPropertyDescriptor: true }], + errors: [ + { + messageId: "matchProperty", + data: { funcName: "baz", name: "bar" }, + }, + ], + }, + { + code: "var obj = { value: function foo(name) {} }", + options: ["always", { considerPropertyDescriptor: true }], + errors: [ + { + messageId: "matchProperty", + data: { funcName: "foo", name: "value" }, + }, + ], + }, + { + code: "Object.defineProperty(foo, 'bar', { value: function bar() {} })", + options: ["never", { considerPropertyDescriptor: true }], + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "bar", name: "bar" }, + }, + ], + }, + { + code: "Object.defineProperties(foo, { bar: { value: function bar() {} } })", + options: ["never", { considerPropertyDescriptor: true }], + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "bar", name: "bar" }, + }, + ], + }, + { + code: "Object.create(proto, { bar: { value: function bar() {} } })", + options: ["never", { considerPropertyDescriptor: true }], + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "bar", name: "bar" }, + }, + ], + }, + { + code: "Reflect.defineProperty(foo, 'bar', { value: function baz() {} })", + options: ["always", { considerPropertyDescriptor: true }], + errors: [ + { + messageId: "matchProperty", + data: { funcName: "baz", name: "bar" }, + }, + ], + }, + { + code: "Reflect.defineProperty(foo, 'bar', { value: function bar() {} })", + options: ["never", { considerPropertyDescriptor: true }], + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "bar", name: "bar" }, + }, + ], + }, + { + code: "foo({ value: function bar() {} })", + options: ["always", { considerPropertyDescriptor: true }], + errors: [ + { + messageId: "matchProperty", + data: { funcName: "bar", name: "value" }, + }, + ], + }, - ], - invalid: [ - { - code: "let foo = function bar() {};", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "matchVariable", data: { funcName: "bar", name: "foo" } } - ] - }, - { - code: "let foo = function bar() {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "matchVariable", data: { funcName: "bar", name: "foo" } } - ] - }, - { - code: "foo = function bar() {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "matchVariable", data: { funcName: "bar", name: "foo" } } - ] - }, - { - code: "foo &&= function bar() {};", - languageOptions: { ecmaVersion: 2021 }, - errors: [ - { messageId: "matchVariable", data: { funcName: "bar", name: "foo" } } - ] - }, - { - code: "obj.foo ||= function bar() {};", - languageOptions: { ecmaVersion: 2021 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } } - ] - }, - { - code: "obj['foo'] ??= function bar() {};", - languageOptions: { ecmaVersion: 2021 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } } - ] - }, - { - code: "obj.foo = function bar() {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } } - ] - }, - { - code: "obj.bar.foo = function bar() {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } } - ] - }, - { - code: "obj['foo'] = function bar() {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } } - ] - }, - { - code: "let obj = {foo: function bar() {}};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } } - ] - }, - { - code: "let obj = {'foo': function bar() {}};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } } - ] - }, - { - code: "({['foo']: function bar() {}})", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } } - ] - }, - { - code: "module.exports = function foo(name) {};", - options: [{ includeCommonJSModuleExports: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "foo", name: "exports" } } - ] - }, - { - code: "module.exports = function foo(name) {};", - options: ["always", { includeCommonJSModuleExports: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "foo", name: "exports" } } - ] - }, - { - code: "module.exports = function exports(name) {};", - options: ["never", { includeCommonJSModuleExports: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "notMatchProperty", data: { funcName: "exports", name: "exports" } } - ] - }, - { - code: "module['exports'] = function foo(name) {};", - options: [{ includeCommonJSModuleExports: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "foo", name: "exports" } } - ] - }, - { - code: "module['exports'] = function foo(name) {};", - options: ["always", { includeCommonJSModuleExports: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "foo", name: "exports" } } - ] - }, - { - code: "module['exports'] = function exports(name) {};", - options: ["never", { includeCommonJSModuleExports: true }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "notMatchProperty", data: { funcName: "exports", name: "exports" } } - ] - }, - { - code: "var foo = function foo(name) {};", - options: ["never"], - errors: [ - { messageId: "notMatchVariable", data: { funcName: "foo", name: "foo" } } - ] - }, - { - code: "obj.foo = function foo(name) {};", - options: ["never"], - errors: [ - { messageId: "notMatchProperty", data: { funcName: "foo", name: "foo" } } - ] - }, - { - code: "Object.defineProperty(foo, 'bar', { value: function baz() {} })", - options: ["always", { considerPropertyDescriptor: true }], - errors: [ - { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } } - ] - }, - { - code: "Object.defineProperties(foo, { bar: { value: function baz() {} } })", - options: ["always", { considerPropertyDescriptor: true }], - errors: [ - { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } } - ] - }, - { - code: "Object.create(proto, { bar: { value: function baz() {} } })", - options: ["always", { considerPropertyDescriptor: true }], - errors: [ - { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } } - ] - }, - { - code: "var obj = { value: function foo(name) {} }", - options: ["always", { considerPropertyDescriptor: true }], - errors: [ - { messageId: "matchProperty", data: { funcName: "foo", name: "value" } } - ] - }, - { - code: "Object.defineProperty(foo, 'bar', { value: function bar() {} })", - options: ["never", { considerPropertyDescriptor: true }], - errors: [ - { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } } - ] - }, - { - code: "Object.defineProperties(foo, { bar: { value: function bar() {} } })", - options: ["never", { considerPropertyDescriptor: true }], - errors: [ - { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } } - ] - }, - { - code: "Object.create(proto, { bar: { value: function bar() {} } })", - options: ["never", { considerPropertyDescriptor: true }], - errors: [ - { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } } - ] - }, - { - code: "Reflect.defineProperty(foo, 'bar', { value: function baz() {} })", - options: ["always", { considerPropertyDescriptor: true }], - errors: [ - { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } } - ] - }, - { - code: "Reflect.defineProperty(foo, 'bar', { value: function bar() {} })", - options: ["never", { considerPropertyDescriptor: true }], - errors: [ - { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } } - ] - }, - { - code: "foo({ value: function bar() {} })", - options: ["always", { considerPropertyDescriptor: true }], - errors: [ - { messageId: "matchProperty", data: { funcName: "bar", name: "value" } } - ] - }, + // Optional chaining + { + code: "(obj?.aaa).foo = function bar() {};", + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "bar", name: "foo" }, + }, + ], + }, + { + code: "Object?.defineProperty(foo, 'bar', { value: function baz() {} })", + options: ["always", { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "baz", name: "bar" }, + }, + ], + }, + { + code: "(Object?.defineProperty)(foo, 'bar', { value: function baz() {} })", + options: ["always", { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "baz", name: "bar" }, + }, + ], + }, + { + code: "Object?.defineProperty(foo, 'bar', { value: function bar() {} })", + options: ["never", { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "bar", name: "bar" }, + }, + ], + }, + { + code: "(Object?.defineProperty)(foo, 'bar', { value: function bar() {} })", + options: ["never", { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "bar", name: "bar" }, + }, + ], + }, + { + code: "Object?.defineProperties(foo, { bar: { value: function baz() {} } })", + options: ["always", { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "baz", name: "bar" }, + }, + ], + }, + { + code: "(Object?.defineProperties)(foo, { bar: { value: function baz() {} } })", + options: ["always", { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "baz", name: "bar" }, + }, + ], + }, + { + code: "Object?.defineProperties(foo, { bar: { value: function bar() {} } })", + options: ["never", { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "bar", name: "bar" }, + }, + ], + }, + { + code: "(Object?.defineProperties)(foo, { bar: { value: function bar() {} } })", + options: ["never", { considerPropertyDescriptor: true }], + languageOptions: { ecmaVersion: 2020 }, + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "bar", name: "bar" }, + }, + ], + }, - // Optional chaining - { - code: "(obj?.aaa).foo = function bar() {};", - languageOptions: { ecmaVersion: 2020 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "bar", name: "foo" } } - ] - }, - { - code: "Object?.defineProperty(foo, 'bar', { value: function baz() {} })", - options: ["always", { considerPropertyDescriptor: true }], - languageOptions: { ecmaVersion: 2020 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } } - ] - }, - { - code: "(Object?.defineProperty)(foo, 'bar', { value: function baz() {} })", - options: ["always", { considerPropertyDescriptor: true }], - languageOptions: { ecmaVersion: 2020 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } } - ] - }, - { - code: "Object?.defineProperty(foo, 'bar', { value: function bar() {} })", - options: ["never", { considerPropertyDescriptor: true }], - languageOptions: { ecmaVersion: 2020 }, - errors: [ - { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } } - ] - }, - { - code: "(Object?.defineProperty)(foo, 'bar', { value: function bar() {} })", - options: ["never", { considerPropertyDescriptor: true }], - languageOptions: { ecmaVersion: 2020 }, - errors: [ - { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } } - ] - }, - { - code: "Object?.defineProperties(foo, { bar: { value: function baz() {} } })", - options: ["always", { considerPropertyDescriptor: true }], - languageOptions: { ecmaVersion: 2020 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } } - ] - }, - { - code: "(Object?.defineProperties)(foo, { bar: { value: function baz() {} } })", - options: ["always", { considerPropertyDescriptor: true }], - languageOptions: { ecmaVersion: 2020 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "baz", name: "bar" } } - ] - }, - { - code: "Object?.defineProperties(foo, { bar: { value: function bar() {} } })", - options: ["never", { considerPropertyDescriptor: true }], - languageOptions: { ecmaVersion: 2020 }, - errors: [ - { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } } - ] - }, - { - code: "(Object?.defineProperties)(foo, { bar: { value: function bar() {} } })", - options: ["never", { considerPropertyDescriptor: true }], - languageOptions: { ecmaVersion: 2020 }, - errors: [ - { messageId: "notMatchProperty", data: { funcName: "bar", name: "bar" } } - ] - }, - - // class fields - { - code: "class C { x = function y() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "y", name: "x" } } - ] - }, - { - code: "class C { x = function x() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } } - ] - }, - { - code: "class C { 'x' = function y() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "y", name: "x" } } - ] - }, - { - code: "class C { 'x' = function x() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } } - ] - }, - { - code: "class C { ['x'] = function y() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "y", name: "x" } } - ] - }, - { - code: "class C { ['x'] = function x() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } } - ] - }, - { - code: "class C { static x = function y() {}; }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "y", name: "x" } } - ] - }, - { - code: "class C { static x = function x() {}; }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } } - ] - }, - { - code: "(class { x = function y() {}; })", - options: ["always"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "y", name: "x" } } - ] - }, - { - code: "(class { x = function x() {}; })", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { messageId: "notMatchProperty", data: { funcName: "x", name: "x" } } - ] - }, - { - code: "var obj = { '\\u1885': function foo() {} };", // valid identifier in es2015 - languageOptions: { ecmaVersion: 6 }, - errors: [ - { messageId: "matchProperty", data: { funcName: "foo", name: "\u1885" } } - ] - } - ] + // class fields + { + code: "class C { x = function y() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "y", name: "x" }, + }, + ], + }, + { + code: "class C { x = function x() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "x", name: "x" }, + }, + ], + }, + { + code: "class C { 'x' = function y() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "y", name: "x" }, + }, + ], + }, + { + code: "class C { 'x' = function x() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "x", name: "x" }, + }, + ], + }, + { + code: "class C { ['x'] = function y() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "y", name: "x" }, + }, + ], + }, + { + code: "class C { ['x'] = function x() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "x", name: "x" }, + }, + ], + }, + { + code: "class C { static x = function y() {}; }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "y", name: "x" }, + }, + ], + }, + { + code: "class C { static x = function x() {}; }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "x", name: "x" }, + }, + ], + }, + { + code: "(class { x = function y() {}; })", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "y", name: "x" }, + }, + ], + }, + { + code: "(class { x = function x() {}; })", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "notMatchProperty", + data: { funcName: "x", name: "x" }, + }, + ], + }, + { + code: "var obj = { '\\u1885': function foo() {} };", // valid identifier in es2015 + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "matchProperty", + data: { funcName: "foo", name: "\u1885" }, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/func-names.js b/tests/lib/rules/func-names.js index c296c61eb0dc..c7ea49b705a9 100644 --- a/tests/lib/rules/func-names.js +++ b/tests/lib/rules/func-names.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/func-names"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -19,866 +19,971 @@ const rule = require("../../../lib/rules/func-names"), const ruleTester = new RuleTester(); ruleTester.run("func-names", rule, { - valid: [ - "Foo.prototype.bar = function bar(){};", - { code: "Foo.prototype.bar = () => {}", languageOptions: { ecmaVersion: 6 } }, - "function foo(){}", - "function test(d, e, f) {}", - "new function bar(){}", - "exports = { get foo() { return 1; }, set bar(val) { return val; } };", - { - code: "({ foo() { return 1; } });", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "class A { constructor(){} foo(){} get bar(){} set baz(value){} static qux(){}}", - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo() {}", - options: ["always"] - }, - { - code: "var a = function foo() {};", - options: ["always"] - }, - { - code: "class A { constructor(){} foo(){} get bar(){} set baz(value){} static qux(){}}", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({ foo() {} });", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = function(){};", - options: ["as-needed"] - }, - { - code: "({foo: function(){}});", - options: ["as-needed"] - }, - { - code: "(foo = function(){});", - options: ["as-needed"] - }, - { - code: "({foo = function(){}} = {});", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({key: foo = function(){}} = {});", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "[foo = function(){}] = [];", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function fn(foo = function(){}) {}", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo() {}", - options: ["never"] - }, - { - code: "var a = function() {};", - options: ["never"] - }, - { - code: "var a = function foo() { foo(); };", - options: ["never"] - }, - { - code: "var foo = {bar: function() {}};", - options: ["never"] - }, - { - code: "$('#foo').click(function() {});", - options: ["never"] - }, - { - code: "Foo.prototype.bar = function() {};", - options: ["never"] - }, - { - code: "class A { constructor(){} foo(){} get bar(){} set baz(value){} static qux(){}}", - options: ["never"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({ foo() {} });", - options: ["never"], - languageOptions: { ecmaVersion: 6 } - }, + valid: [ + "Foo.prototype.bar = function bar(){};", + { + code: "Foo.prototype.bar = () => {}", + languageOptions: { ecmaVersion: 6 }, + }, + "function foo(){}", + "function test(d, e, f) {}", + "new function bar(){}", + "exports = { get foo() { return 1; }, set bar(val) { return val; } };", + { + code: "({ foo() { return 1; } });", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class A { constructor(){} foo(){} get bar(){} set baz(value){} static qux(){}}", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo() {}", + options: ["always"], + }, + { + code: "var a = function foo() {};", + options: ["always"], + }, + { + code: "class A { constructor(){} foo(){} get bar(){} set baz(value){} static qux(){}}", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ foo() {} });", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = function(){};", + options: ["as-needed"], + }, + { + code: "({foo: function(){}});", + options: ["as-needed"], + }, + { + code: "(foo = function(){});", + options: ["as-needed"], + }, + { + code: "({foo = function(){}} = {});", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({key: foo = function(){}} = {});", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "[foo = function(){}] = [];", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function fn(foo = function(){}) {}", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo() {}", + options: ["never"], + }, + { + code: "var a = function() {};", + options: ["never"], + }, + { + code: "var a = function foo() { foo(); };", + options: ["never"], + }, + { + code: "var foo = {bar: function() {}};", + options: ["never"], + }, + { + code: "$('#foo').click(function() {});", + options: ["never"], + }, + { + code: "Foo.prototype.bar = function() {};", + options: ["never"], + }, + { + code: "class A { constructor(){} foo(){} get bar(){} set baz(value){} static qux(){}}", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ foo() {} });", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, - // export default - { - code: "export default function foo() {}", - options: ["always"], - languageOptions: { sourceType: "module", ecmaVersion: 6 } - }, - { - code: "export default function foo() {}", - options: ["as-needed"], - languageOptions: { sourceType: "module", ecmaVersion: 6 } - }, - { - code: "export default function foo() {}", - options: ["never"], - languageOptions: { sourceType: "module", ecmaVersion: 6 } - }, - { - code: "export default function() {}", - options: ["never"], - languageOptions: { sourceType: "module", ecmaVersion: 6 } - }, + // export default + { + code: "export default function foo() {}", + options: ["always"], + languageOptions: { sourceType: "module", ecmaVersion: 6 }, + }, + { + code: "export default function foo() {}", + options: ["as-needed"], + languageOptions: { sourceType: "module", ecmaVersion: 6 }, + }, + { + code: "export default function foo() {}", + options: ["never"], + languageOptions: { sourceType: "module", ecmaVersion: 6 }, + }, + { + code: "export default function() {}", + options: ["never"], + languageOptions: { sourceType: "module", ecmaVersion: 6 }, + }, - // generators - { - code: "var foo = bar(function *baz() {});", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = bar(function *baz() {});", - options: ["always", { generators: "always" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = bar(function *baz() {});", - options: ["always", { generators: "as-needed" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = function*() {};", - options: ["always", { generators: "as-needed" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = bar(function *baz() {});", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = function*() {};", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = bar(function *baz() {});", - options: ["as-needed", { generators: "always" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = bar(function *baz() {});", - options: ["as-needed", { generators: "as-needed" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = function*() {};", - options: ["as-needed", { generators: "as-needed" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = bar(function *baz() {});", - options: ["never", { generators: "always" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = bar(function *baz() {});", - options: ["never", { generators: "as-needed" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = function*() {};", - options: ["never", { generators: "as-needed" }], - languageOptions: { ecmaVersion: 6 } - }, + // generators + { + code: "var foo = bar(function *baz() {});", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = bar(function *baz() {});", + options: ["always", { generators: "always" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = bar(function *baz() {});", + options: ["always", { generators: "as-needed" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = function*() {};", + options: ["always", { generators: "as-needed" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = bar(function *baz() {});", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = function*() {};", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = bar(function *baz() {});", + options: ["as-needed", { generators: "always" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = bar(function *baz() {});", + options: ["as-needed", { generators: "as-needed" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = function*() {};", + options: ["as-needed", { generators: "as-needed" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = bar(function *baz() {});", + options: ["never", { generators: "always" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = bar(function *baz() {});", + options: ["never", { generators: "as-needed" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = function*() {};", + options: ["never", { generators: "as-needed" }], + languageOptions: { ecmaVersion: 6 }, + }, - { - code: "var foo = bar(function *() {});", - options: ["never"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = function*() {};", - options: ["never"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "(function*() {}())", - options: ["never"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = bar(function *() {});", - options: ["never", { generators: "never" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = function*() {};", - options: ["never", { generators: "never" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "(function*() {}())", - options: ["never", { generators: "never" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = bar(function *() {});", - options: ["always", { generators: "never" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = function*() {};", - options: ["always", { generators: "never" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "(function*() {}())", - options: ["always", { generators: "never" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = bar(function *() {});", - options: ["as-needed", { generators: "never" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = function*() {};", - options: ["as-needed", { generators: "never" }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "(function*() {}())", - options: ["as-needed", { generators: "never" }], - languageOptions: { ecmaVersion: 6 } - }, + { + code: "var foo = bar(function *() {});", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = function*() {};", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "(function*() {}())", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = bar(function *() {});", + options: ["never", { generators: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = function*() {};", + options: ["never", { generators: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "(function*() {}())", + options: ["never", { generators: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = bar(function *() {});", + options: ["always", { generators: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = function*() {};", + options: ["always", { generators: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "(function*() {}())", + options: ["always", { generators: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = bar(function *() {});", + options: ["as-needed", { generators: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = function*() {};", + options: ["as-needed", { generators: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "(function*() {}())", + options: ["as-needed", { generators: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, - // class fields - { - code: "class C { foo = function() {}; }", - options: ["as-needed"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { [foo] = function() {}; }", - options: ["as-needed"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { #foo = function() {}; }", - options: ["as-needed"], - languageOptions: { ecmaVersion: 2022 } - } - ], - invalid: [ - { - code: "Foo.prototype.bar = function() {};", - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 21, - endColumn: 29 - }] - }, - { - code: "(function(){}())", - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 2, - endColumn: 10 - }] - }, - { - code: "f(function(){})", - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 3, - endColumn: 11 - }] - }, - { - code: "var a = new Date(function() {});", - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 18, - endColumn: 26 - }] - }, - { - code: "var test = function(d, e, f) {};", - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 12, - endColumn: 20 - }] - }, - { - code: "new function() {}", - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 5, - endColumn: 13 - }] - }, - { - code: "Foo.prototype.bar = function() {};", - options: ["as-needed"], - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 21, - endColumn: 29 - }] - }, - { - code: "(function(){}())", - options: ["as-needed"], - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 2, - endColumn: 10 - }] - }, - { - code: "f(function(){})", - options: ["as-needed"], - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 3, - endColumn: 11 - }] - }, - { - code: "var a = new Date(function() {});", - options: ["as-needed"], - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 18, - endColumn: 26 - }] - }, - { - code: "new function() {}", - options: ["as-needed"], - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 5, - endColumn: 13 - }] - }, - { - code: "var {foo} = function(){};", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 13, - endColumn: 21 - }] - }, - { - code: "({ a: obj.prop = function(){} } = foo);", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 18, - endColumn: 26 - }] - }, - { - code: "[obj.prop = function(){}] = foo;", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 13, - endColumn: 21 - }] - }, - { - code: "var { a: [b] = function(){} } = foo;", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 16, - endColumn: 24 - }] - }, - { - code: "function foo({ a } = function(){}) {};", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 22, - endColumn: 30 - }] - }, - { - code: "var x = function foo() {};", - options: ["never"], - errors: [{ - messageId: "named", - data: { name: "function 'foo'" }, - type: "FunctionExpression", - line: 1, - column: 9, - endColumn: 21 - }] - }, - { - code: "Foo.prototype.bar = function foo() {};", - options: ["never"], - errors: [{ - messageId: "named", - data: { name: "function 'foo'" }, - type: "FunctionExpression", - line: 1, - column: 21, - endColumn: 33 - }] - }, - { - code: "({foo: function foo() {}})", - options: ["never"], - errors: [{ - messageId: "named", - data: { name: "method 'foo'" }, - type: "FunctionExpression", - line: 1, - column: 3, - endColumn: 20 - }] - }, + // class fields + { + code: "class C { foo = function() {}; }", + options: ["as-needed"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { [foo] = function() {}; }", + options: ["as-needed"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { #foo = function() {}; }", + options: ["as-needed"], + languageOptions: { ecmaVersion: 2022 }, + }, + ], + invalid: [ + { + code: "Foo.prototype.bar = function() {};", + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 21, + endColumn: 29, + }, + ], + }, + { + code: "(function(){}())", + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 2, + endColumn: 10, + }, + ], + }, + { + code: "f(function(){})", + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 3, + endColumn: 11, + }, + ], + }, + { + code: "var a = new Date(function() {});", + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 18, + endColumn: 26, + }, + ], + }, + { + code: "var test = function(d, e, f) {};", + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 12, + endColumn: 20, + }, + ], + }, + { + code: "new function() {}", + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 5, + endColumn: 13, + }, + ], + }, + { + code: "Foo.prototype.bar = function() {};", + options: ["as-needed"], + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 21, + endColumn: 29, + }, + ], + }, + { + code: "(function(){}())", + options: ["as-needed"], + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 2, + endColumn: 10, + }, + ], + }, + { + code: "f(function(){})", + options: ["as-needed"], + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 3, + endColumn: 11, + }, + ], + }, + { + code: "var a = new Date(function() {});", + options: ["as-needed"], + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 18, + endColumn: 26, + }, + ], + }, + { + code: "new function() {}", + options: ["as-needed"], + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 5, + endColumn: 13, + }, + ], + }, + { + code: "var {foo} = function(){};", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 13, + endColumn: 21, + }, + ], + }, + { + code: "({ a: obj.prop = function(){} } = foo);", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 18, + endColumn: 26, + }, + ], + }, + { + code: "[obj.prop = function(){}] = foo;", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 13, + endColumn: 21, + }, + ], + }, + { + code: "var { a: [b] = function(){} } = foo;", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 16, + endColumn: 24, + }, + ], + }, + { + code: "function foo({ a } = function(){}) {};", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 22, + endColumn: 30, + }, + ], + }, + { + code: "var x = function foo() {};", + options: ["never"], + errors: [ + { + messageId: "named", + data: { name: "function 'foo'" }, + type: "FunctionExpression", + line: 1, + column: 9, + endColumn: 21, + }, + ], + }, + { + code: "Foo.prototype.bar = function foo() {};", + options: ["never"], + errors: [ + { + messageId: "named", + data: { name: "function 'foo'" }, + type: "FunctionExpression", + line: 1, + column: 21, + endColumn: 33, + }, + ], + }, + { + code: "({foo: function foo() {}})", + options: ["never"], + errors: [ + { + messageId: "named", + data: { name: "method 'foo'" }, + type: "FunctionExpression", + line: 1, + column: 3, + endColumn: 20, + }, + ], + }, - // export default - { - code: "export default function() {}", - options: ["always"], - languageOptions: { sourceType: "module", ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionDeclaration", - column: 16, - endColumn: 24 - }] - }, - { - code: "export default function() {}", - options: ["as-needed"], - languageOptions: { sourceType: "module", ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionDeclaration", - column: 16, - endColumn: 24 - }] - }, - { - code: "export default (function(){});", - options: ["as-needed"], - languageOptions: { sourceType: "module", ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - column: 17, - endColumn: 25 - }] - }, + // export default + { + code: "export default function() {}", + options: ["always"], + languageOptions: { sourceType: "module", ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionDeclaration", + column: 16, + endColumn: 24, + }, + ], + }, + { + code: "export default function() {}", + options: ["as-needed"], + languageOptions: { sourceType: "module", ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionDeclaration", + column: 16, + endColumn: 24, + }, + ], + }, + { + code: "export default (function(){});", + options: ["as-needed"], + languageOptions: { sourceType: "module", ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + column: 17, + endColumn: 25, + }, + ], + }, - // generators - { - code: "var foo = bar(function *() {});", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 15, - endColumn: 25 - }] - }, - { - code: "var foo = function*() {};", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 11, - endColumn: 20 - }] - }, - { - code: "(function*() {}())", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 2, - endColumn: 11 - }] - }, - { - code: "var foo = bar(function *() {});", - options: ["always", { generators: "always" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 15, - endColumn: 25 - }] - }, - { - code: "var foo = function*() {};", - options: ["always", { generators: "always" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 11, - endColumn: 20 - }] - }, - { - code: "(function*() {}())", - options: ["always", { generators: "always" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 2, - endColumn: 11 - }] - }, - { - code: "var foo = bar(function *() {});", - options: ["always", { generators: "as-needed" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 15, - endColumn: 25 - }] - }, - { - code: "(function*() {}())", - options: ["always", { generators: "as-needed" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 2, - endColumn: 11 - }] - }, - { - code: "var foo = bar(function *() {});", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 15, - endColumn: 25 - }] - }, - { - code: "(function*() {}())", - options: ["as-needed"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 2, - endColumn: 11 - }] - }, - { - code: "var foo = bar(function *() {});", - options: ["as-needed", { generators: "always" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 15, - endColumn: 25 - }] - }, - { - code: "var foo = function*() {};", - options: ["as-needed", { generators: "always" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 11, - endColumn: 20 - }] - }, - { - code: "(function*() {}())", - options: ["as-needed", { generators: "always" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 2, - endColumn: 11 - }] - }, - { - code: "var foo = bar(function *() {});", - options: ["as-needed", { generators: "as-needed" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 15, - endColumn: 25 - }] - }, - { - code: "(function*() {}())", - options: ["as-needed", { generators: "as-needed" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 2, - endColumn: 11 - }] - }, - { - code: "var foo = bar(function *() {});", - options: ["never", { generators: "always" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 15, - endColumn: 25 - }] - }, - { - code: "var foo = function*() {};", - options: ["never", { generators: "always" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 11, - endColumn: 20 - }] - }, - { - code: "(function*() {}())", - options: ["never", { generators: "always" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 2, - endColumn: 11 - }] - }, - { - code: "var foo = bar(function *() {});", - options: ["never", { generators: "as-needed" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 15, - endColumn: 25 - }] - }, - { - code: "(function*() {}())", - options: ["never", { generators: "as-needed" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "unnamed", - type: "FunctionExpression", - line: 1, - column: 2, - endColumn: 11 - }] - }, + // generators + { + code: "var foo = bar(function *() {});", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 15, + endColumn: 25, + }, + ], + }, + { + code: "var foo = function*() {};", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 11, + endColumn: 20, + }, + ], + }, + { + code: "(function*() {}())", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 2, + endColumn: 11, + }, + ], + }, + { + code: "var foo = bar(function *() {});", + options: ["always", { generators: "always" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 15, + endColumn: 25, + }, + ], + }, + { + code: "var foo = function*() {};", + options: ["always", { generators: "always" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 11, + endColumn: 20, + }, + ], + }, + { + code: "(function*() {}())", + options: ["always", { generators: "always" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 2, + endColumn: 11, + }, + ], + }, + { + code: "var foo = bar(function *() {});", + options: ["always", { generators: "as-needed" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 15, + endColumn: 25, + }, + ], + }, + { + code: "(function*() {}())", + options: ["always", { generators: "as-needed" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 2, + endColumn: 11, + }, + ], + }, + { + code: "var foo = bar(function *() {});", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 15, + endColumn: 25, + }, + ], + }, + { + code: "(function*() {}())", + options: ["as-needed"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 2, + endColumn: 11, + }, + ], + }, + { + code: "var foo = bar(function *() {});", + options: ["as-needed", { generators: "always" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 15, + endColumn: 25, + }, + ], + }, + { + code: "var foo = function*() {};", + options: ["as-needed", { generators: "always" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 11, + endColumn: 20, + }, + ], + }, + { + code: "(function*() {}())", + options: ["as-needed", { generators: "always" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 2, + endColumn: 11, + }, + ], + }, + { + code: "var foo = bar(function *() {});", + options: ["as-needed", { generators: "as-needed" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 15, + endColumn: 25, + }, + ], + }, + { + code: "(function*() {}())", + options: ["as-needed", { generators: "as-needed" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 2, + endColumn: 11, + }, + ], + }, + { + code: "var foo = bar(function *() {});", + options: ["never", { generators: "always" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 15, + endColumn: 25, + }, + ], + }, + { + code: "var foo = function*() {};", + options: ["never", { generators: "always" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 11, + endColumn: 20, + }, + ], + }, + { + code: "(function*() {}())", + options: ["never", { generators: "always" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 2, + endColumn: 11, + }, + ], + }, + { + code: "var foo = bar(function *() {});", + options: ["never", { generators: "as-needed" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 15, + endColumn: 25, + }, + ], + }, + { + code: "(function*() {}())", + options: ["never", { generators: "as-needed" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unnamed", + type: "FunctionExpression", + line: 1, + column: 2, + endColumn: 11, + }, + ], + }, - { - code: "var foo = bar(function *baz() {});", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "named", - data: { name: "generator function 'baz'" }, - type: "FunctionExpression", - line: 1, - column: 15, - endColumn: 28 - }] - }, - { - code: "var foo = bar(function *baz() {});", - options: ["never", { generators: "never" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "named", - data: { name: "generator function 'baz'" }, - type: "FunctionExpression", - line: 1, - column: 15, - endColumn: 28 - }] - }, - { - code: "var foo = bar(function *baz() {});", - options: ["always", { generators: "never" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "named", - data: { name: "generator function 'baz'" }, - type: "FunctionExpression", - line: 1, - column: 15, - endColumn: 28 - }] - }, - { - code: "var foo = bar(function *baz() {});", - options: ["as-needed", { generators: "never" }], - languageOptions: { ecmaVersion: 6 }, - errors: [{ - messageId: "named", - data: { name: "generator function 'baz'" }, - type: "FunctionExpression", - line: 1, - column: 15, - endColumn: 28 - }] - }, + { + code: "var foo = bar(function *baz() {});", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "named", + data: { name: "generator function 'baz'" }, + type: "FunctionExpression", + line: 1, + column: 15, + endColumn: 28, + }, + ], + }, + { + code: "var foo = bar(function *baz() {});", + options: ["never", { generators: "never" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "named", + data: { name: "generator function 'baz'" }, + type: "FunctionExpression", + line: 1, + column: 15, + endColumn: 28, + }, + ], + }, + { + code: "var foo = bar(function *baz() {});", + options: ["always", { generators: "never" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "named", + data: { name: "generator function 'baz'" }, + type: "FunctionExpression", + line: 1, + column: 15, + endColumn: 28, + }, + ], + }, + { + code: "var foo = bar(function *baz() {});", + options: ["as-needed", { generators: "never" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "named", + data: { name: "generator function 'baz'" }, + type: "FunctionExpression", + line: 1, + column: 15, + endColumn: 28, + }, + ], + }, - // class fields - { - code: "class C { foo = function() {} }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "unnamed", - data: { name: "method 'foo'" }, - column: 11, - endColumn: 25 - }] - }, - { - code: "class C { [foo] = function() {} }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "unnamed", - data: { name: "method" }, - column: 11, - endColumn: 27 - }] - }, - { - code: "class C { #foo = function() {} }", - options: ["always"], - languageOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "unnamed", - data: { name: "private method #foo" }, - column: 11, - endColumn: 26 - }] - }, - { - code: "class C { foo = bar(function() {}) }", - options: ["as-needed"], - languageOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "unnamed", - data: { name: "function" }, - column: 21, - endColumn: 29 - }] - }, - { - code: "class C { foo = function bar() {} }", - options: ["never"], - languageOptions: { ecmaVersion: 2022 }, - errors: [{ - messageId: "named", - data: { name: "method 'foo'" }, - column: 11, - endColumn: 29 - }] - } - ] + // class fields + { + code: "class C { foo = function() {} }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "unnamed", + data: { name: "method 'foo'" }, + column: 11, + endColumn: 25, + }, + ], + }, + { + code: "class C { [foo] = function() {} }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "unnamed", + data: { name: "method" }, + column: 11, + endColumn: 27, + }, + ], + }, + { + code: "class C { #foo = function() {} }", + options: ["always"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "unnamed", + data: { name: "private method #foo" }, + column: 11, + endColumn: 26, + }, + ], + }, + { + code: "class C { foo = bar(function() {}) }", + options: ["as-needed"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "unnamed", + data: { name: "function" }, + column: 21, + endColumn: 29, + }, + ], + }, + { + code: "class C { foo = function bar() {} }", + options: ["never"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "named", + data: { name: "method 'foo'" }, + column: 11, + endColumn: 29, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/func-style.js b/tests/lib/rules/func-style.js index 8ee8b481f78e..626cddcd2fe0 100644 --- a/tests/lib/rules/func-style.js +++ b/tests/lib/rules/func-style.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/func-style"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -19,113 +19,992 @@ const rule = require("../../../lib/rules/func-style"), const ruleTester = new RuleTester(); ruleTester.run("func-style", rule, { - valid: [ - { - code: "function foo(){}\n function bar(){}", - options: ["declaration"] - }, - { - code: "foo.bar = function(){};", - options: ["declaration"] - }, - { - code: "(function() { /* code */ }());", - options: ["declaration"] - }, - { - code: "var module = (function() { return {}; }());", - options: ["declaration"] - }, - { - code: "var object = { foo: function(){} };", - options: ["declaration"] - }, - { - code: "Array.prototype.foo = function(){};", - options: ["declaration"] - }, - { - code: "foo.bar = function(){};", - options: ["expression"] - }, - { - code: "var foo = function(){};\n var bar = function(){};", - options: ["expression"] - }, - { - code: "var foo = () => {};\n var bar = () => {}", - options: ["expression"], - languageOptions: { ecmaVersion: 6 } - }, + valid: [ + { + code: "function foo(){}\n function bar(){}", + options: ["declaration"], + }, + { + code: "foo.bar = function(){};", + options: ["declaration"], + }, + { + code: "(function() { /* code */ }());", + options: ["declaration"], + }, + { + code: "var module = (function() { return {}; }());", + options: ["declaration"], + }, + { + code: "var object = { foo: function(){} };", + options: ["declaration"], + }, + { + code: "Array.prototype.foo = function(){};", + options: ["declaration"], + }, + { + code: "foo.bar = function(){};", + options: ["expression"], + }, + { + code: "var foo = function(){};\n var bar = function(){};", + options: ["expression"], + }, + { + code: "var foo = () => {};\n var bar = () => {}", + options: ["expression"], + languageOptions: { ecmaVersion: 6 }, + }, - // https://github.com/eslint/eslint/issues/3819 - { - code: "var foo = function() { this; }.bind(this);", - options: ["declaration"] - }, - { - code: "var foo = () => { this; };", - options: ["declaration"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "export default function () {};", - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "var foo = () => {};", - options: ["declaration", { allowArrowFunctions: true }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var foo = () => { function foo() { this; } };", - options: ["declaration", { allowArrowFunctions: true }], - languageOptions: { ecmaVersion: 6 } - } - ], + // https://github.com/eslint/eslint/issues/3819 + { + code: "var foo = function() { this; }.bind(this);", + options: ["declaration"], + }, + { + code: "var foo = () => { this; };", + options: ["declaration"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class C extends D { foo() { var bar = () => { super.baz(); }; } }", + options: ["declaration"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var obj = { foo() { var bar = () => super.baz; } }", + options: ["declaration"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "export default function () {};", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "var foo = () => {};", + options: ["declaration", { allowArrowFunctions: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = () => { function foo() { this; } };", + options: ["declaration", { allowArrowFunctions: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = () => ({ bar() { super.baz(); } });", + options: ["declaration", { allowArrowFunctions: true }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "export function foo() {};", + options: ["declaration"], + }, + { + code: "export function foo() {};", + options: [ + "expression", + { overrides: { namedExports: "declaration" } }, + ], + }, + { + code: "export function foo() {};", + options: [ + "declaration", + { overrides: { namedExports: "declaration" } }, + ], + }, + { + code: "export function foo() {};", + options: ["expression", { overrides: { namedExports: "ignore" } }], + }, + { + code: "export function foo() {};", + options: ["declaration", { overrides: { namedExports: "ignore" } }], + }, + { + code: "export var foo = function(){};", + options: ["expression"], + }, + { + code: "export var foo = function(){};", + options: [ + "declaration", + { overrides: { namedExports: "expression" } }, + ], + }, + { + code: "export var foo = function(){};", + options: [ + "expression", + { overrides: { namedExports: "expression" } }, + ], + }, + { + code: "export var foo = function(){};", + options: ["declaration", { overrides: { namedExports: "ignore" } }], + }, + { + code: "export var foo = function(){};", + options: ["expression", { overrides: { namedExports: "ignore" } }], + }, + { + code: "export var foo = () => {};", + options: [ + "expression", + { overrides: { namedExports: "expression" } }, + ], + }, + { + code: "export var foo = () => {};", + options: [ + "declaration", + { overrides: { namedExports: "expression" } }, + ], + }, + { + code: "export var foo = () => {};", + options: ["declaration", { overrides: { namedExports: "ignore" } }], + }, + { + code: "export var foo = () => {};", + options: ["expression", { overrides: { namedExports: "ignore" } }], + }, + { + code: "export var foo = () => {};", + options: [ + "declaration", + { + allowArrowFunctions: true, + overrides: { namedExports: "expression" }, + }, + ], + }, + { + code: "export var foo = () => {};", + options: [ + "expression", + { + allowArrowFunctions: true, + overrides: { namedExports: "expression" }, + }, + ], + }, + { + code: "export var foo = () => {};", + options: [ + "declaration", + { + allowArrowFunctions: true, + overrides: { namedExports: "ignore" }, + }, + ], + }, + { + code: "$1: function $2() { }", + options: ["declaration"], + languageOptions: { sourceType: "script" }, + }, + { + code: "switch ($0) { case $1: function $2() { } }", + options: ["declaration"], + }, + ], - invalid: [ - { - code: "var foo = function(){};", - options: ["declaration"], - errors: [ - { - messageId: "declaration", - type: "VariableDeclarator" - } - ] - }, - { - code: "var foo = () => {};", - options: ["declaration"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "declaration", - type: "VariableDeclarator" - } - ] - }, - { - code: "var foo = () => { function foo() { this; } };", - options: ["declaration"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "declaration", - type: "VariableDeclarator" - } - ] - }, - { - code: "function foo(){}", - options: ["expression"], - errors: [ - { - messageId: "expression", - type: "FunctionDeclaration" - } - ] - } - ] + invalid: [ + { + code: "var foo = function(){};", + options: ["declaration"], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "var foo = () => {};", + options: ["declaration"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "var foo = () => { function foo() { this; } };", + options: ["declaration"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "var foo = () => ({ bar() { super.baz(); } });", + options: ["declaration"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "function foo(){}", + options: ["expression"], + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: "export function foo(){}", + options: ["expression"], + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: "export function foo() {};", + options: [ + "declaration", + { overrides: { namedExports: "expression" } }, + ], + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: "export function foo() {};", + options: [ + "expression", + { overrides: { namedExports: "expression" } }, + ], + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: "export var foo = function(){};", + options: ["declaration"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "export var foo = function(){};", + options: [ + "expression", + { overrides: { namedExports: "declaration" } }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "export var foo = function(){};", + options: [ + "declaration", + { overrides: { namedExports: "declaration" } }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "export var foo = () => {};", + options: ["declaration"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "export var b = () => {};", + options: [ + "expression", + { overrides: { namedExports: "declaration" } }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "export var c = () => {};", + options: [ + "declaration", + { overrides: { namedExports: "declaration" } }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "function foo() {};", + options: [ + "expression", + { overrides: { namedExports: "declaration" } }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: "var foo = function() {};", + options: [ + "declaration", + { overrides: { namedExports: "expression" } }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "var foo = () => {};", + options: [ + "declaration", + { overrides: { namedExports: "expression" } }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "const foo = function() {};", + options: ["declaration", { allowTypeAnnotation: true }], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "$1: function $2() { }", + languageOptions: { sourceType: "script" }, + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: "const foo = () => {};", + options: ["declaration", { allowTypeAnnotation: true }], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "export const foo = function() {};", + options: [ + "expression", + { + allowTypeAnnotation: true, + overrides: { namedExports: "declaration" }, + }, + ], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "export const foo = () => {};", + options: [ + "expression", + { + allowTypeAnnotation: true, + overrides: { namedExports: "declaration" }, + }, + ], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "if (foo) function bar() {}", + languageOptions: { sourceType: "script" }, + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + ], +}); + +const ruleTesterTypeScript = new RuleTester({ + languageOptions: { + parser: require("@typescript-eslint/parser"), + }, +}); + +ruleTesterTypeScript.run("func-style", rule, { + valid: [ + { + code: "function foo(): void {}\n function bar(): void {}", + options: ["declaration"], + }, + { + code: "(function(): void { /* code */ }());", + options: ["declaration"], + }, + { + code: "const module = (function(): { [key: string]: any } { return {}; }());", + options: ["declaration"], + }, + { + code: "const object: { foo: () => void } = { foo: function(): void {} };", + options: ["declaration"], + }, + { + code: "Array.prototype.foo = function(): void {};", + options: ["declaration"], + }, + { + code: "const foo: () => void = function(): void {};\n const bar: () => void = function(): void {};", + options: ["expression"], + }, + { + code: "const foo: () => void = (): void => {};\n const bar: () => void = (): void => {}", + options: ["expression"], + }, + { + code: "const foo: () => void = function(): void { this; }.bind(this);", + options: ["declaration"], + }, + { + code: "const foo: () => void = (): void => { this; };", + options: ["declaration"], + }, + { + code: "class C extends D { foo(): void { const bar: () => void = (): void => { super.baz(); }; } }", + options: ["declaration"], + }, + { + code: "const obj: { foo(): void } = { foo(): void { const bar: () => void = (): void => super.baz; } }", + options: ["declaration"], + }, + { + code: "const foo: () => void = (): void => {};", + options: ["declaration", { allowArrowFunctions: true }], + }, + { + code: "const foo: () => void = (): void => { function foo(): void { this; } };", + options: ["declaration", { allowArrowFunctions: true }], + }, + { + code: "const foo: () => { bar(): void } = (): { bar(): void } => ({ bar(): void { super.baz(); } });", + options: ["declaration", { allowArrowFunctions: true }], + }, + { + code: "export function foo(): void {};", + options: ["declaration"], + }, + { + code: "export function foo(): void {};", + options: [ + "expression", + { overrides: { namedExports: "declaration" } }, + ], + }, + { + code: "export function foo(): void {};", + options: [ + "declaration", + { overrides: { namedExports: "declaration" } }, + ], + }, + { + code: "export function foo(): void {};", + options: ["expression", { overrides: { namedExports: "ignore" } }], + }, + { + code: "export function foo(): void {};", + options: ["declaration", { overrides: { namedExports: "ignore" } }], + }, + { + code: "export const foo: () => void = function(): void {};", + options: ["expression"], + }, + { + code: "export const foo: () => void = function(): void {};", + options: [ + "declaration", + { overrides: { namedExports: "expression" } }, + ], + }, + { + code: "export const foo: () => void = function(): void {};", + options: [ + "expression", + { overrides: { namedExports: "expression" } }, + ], + }, + { + code: "export const foo: () => void = function(): void {};", + options: ["declaration", { overrides: { namedExports: "ignore" } }], + }, + { + code: "export const foo: () => void = function(): void {};", + options: ["expression", { overrides: { namedExports: "ignore" } }], + }, + { + code: "const expression: Fn = function () {}", + options: ["declaration", { allowTypeAnnotation: true }], + }, + { + code: "const arrow: Fn = () => {}", + options: ["declaration", { allowTypeAnnotation: true }], + }, + { + code: "export const expression: Fn = function () {}", + options: ["declaration", { allowTypeAnnotation: true }], + }, + { + code: "export const arrow: Fn = () => {}", + options: ["declaration", { allowTypeAnnotation: true }], + }, + { + code: "export const expression: Fn = function () {}", + options: [ + "expression", + { + allowTypeAnnotation: true, + overrides: { namedExports: "declaration" }, + }, + ], + }, + { + code: "export const arrow: Fn = () => {}", + options: [ + "expression", + { + allowTypeAnnotation: true, + overrides: { namedExports: "declaration" }, + }, + ], + }, + { + code: "$1: function $2(): void { }", + options: ["declaration"], + }, + { + code: "switch ($0) { case $1: function $2(): void { } }", + options: ["declaration"], + }, + ` + function test(a: string): string; + function test(a: number): number; + function test(a: unknown) { + return a; + } + `, + ` + export function test(a: string): string; + export function test(a: number): number; + export function test(a: unknown) { + return a; + } + `, + { + code: ` + export function test(a: string): string; + export function test(a: number): number; + export function test(a: unknown) { + return a; + } + `, + options: [ + "expression", + { overrides: { namedExports: "expression" } }, + ], + }, + ` + switch ($0) { + case $1: + function test(a: string): string; + function test(a: number): number; + function test(a: unknown) { + return a; + } + } + `, + ` + switch ($0) { + case $1: + function test(a: string): string; + break; + case $2: + function test(a: unknown) { + return a; + } + } + `, + ], + invalid: [ + { + code: "const foo: () => void = function(): void {};", + options: ["declaration"], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "const foo: () => void = (): void => {};", + options: ["declaration"], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "const foo: () => void = (): void => { function foo(): void { this; } };", + options: ["declaration"], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "const foo: () => { bar(): void } = (): { bar(): void } => ({ bar(): void { super.baz(); } });", + options: ["declaration"], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "function foo(): void {}", + options: ["expression"], + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: "export function foo(): void {}", + options: ["expression"], + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: "export function foo(): void {};", + options: [ + "declaration", + { overrides: { namedExports: "expression" } }, + ], + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: "export function foo(): void {};", + options: [ + "expression", + { overrides: { namedExports: "expression" } }, + ], + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: "export const foo: () => void = function(): void {};", + options: ["declaration"], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "export const foo: () => void = function(): void {};", + options: [ + "expression", + { overrides: { namedExports: "declaration" } }, + ], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "export const foo: () => void = function(): void {};", + options: [ + "declaration", + { overrides: { namedExports: "declaration" } }, + ], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "export const foo: () => void = (): void => {};", + options: ["declaration"], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "export const b: () => void = (): void => {};", + options: [ + "expression", + { overrides: { namedExports: "declaration" } }, + ], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "export const c: () => void = (): void => {};", + options: [ + "declaration", + { overrides: { namedExports: "declaration" } }, + ], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "function foo(): void {};", + options: [ + "expression", + { overrides: { namedExports: "declaration" } }, + ], + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: "const foo: () => void = function(): void {};", + options: [ + "declaration", + { overrides: { namedExports: "expression" } }, + ], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "const foo: () => void = (): void => {};", + options: [ + "declaration", + { overrides: { namedExports: "expression" } }, + ], + errors: [ + { + messageId: "declaration", + type: "VariableDeclarator", + }, + ], + }, + { + code: "$1: function $2(): void { }", + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: "if (foo) function bar(): string {}", + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: ` + function test1(a: string): string; + function test2(a: number): number; + function test3(a: unknown) { + return a; + }`, + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: ` + export function test1(a: string): string; + export function test2(a: number): number; + export function test3(a: unknown) { + return a; + }`, + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: ` + export function test1(a: string): string; + export function test2(a: number): number; + export function test3(a: unknown) { + return a; + } + `, + options: [ + "expression", + { overrides: { namedExports: "expression" } }, + ], + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: ` + switch ($0) { + case $1: + function test1(a: string): string; + function test2(a: number): number; + function test3(a: unknown) { + return a; + } + } + `, + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + { + code: ` + switch ($0) { + case $1: + function test1(a: string): string; + break; + case $2: + function test2(a: unknown) { + return a; + } + } + `, + errors: [ + { + messageId: "expression", + type: "FunctionDeclaration", + }, + ], + }, + ], }); diff --git a/tests/lib/rules/function-call-argument-newline.js b/tests/lib/rules/function-call-argument-newline.js index 2e09ae63c297..7620bab47b8c 100644 --- a/tests/lib/rules/function-call-argument-newline.js +++ b/tests/lib/rules/function-call-argument-newline.js @@ -5,7 +5,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/function-call-argument-newline"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -14,556 +14,570 @@ const rule = require("../../../lib/rules/function-call-argument-newline"), const ruleTester = new RuleTester(); ruleTester.run("function-call-argument-newline", rule, { - valid: [ + valid: [ + /* early return */ + "fn()", + "fn(a)", + "new Foo()", + "new Foo(b)", - /* early return */ - "fn()", - "fn(a)", - "new Foo()", - "new Foo(b)", + /* default ("always") */ + "fn(a,\n\tb)", - /* default ("always") */ - "fn(a,\n\tb)", + /* "always" */ + { code: "fn(a,\n\tb)", options: ["always"] }, + { code: "fn(\n\ta,\n\tb\n)", options: ["always"] }, + { code: "fn(\n\ta,\n\tb,\n\tc\n)", options: ["always"] }, + { + code: "fn(\n\ta,\n\tb,\n\t[\n\t\t1,\n\t\t2\n\t]\n)", + options: ["always"], + }, + { + code: "fn(\n\ta,\n\tb,\n\t{\n\t\ta: 1,\n\t\tb: 2\n\t}\n)", + options: ["always"], + }, + { + code: "fn(\n\ta,\n\tb,\n\tfunction (x) {\n\t\tx()\n\t}\n)", + options: ["always"], + }, + { + code: "fn(\n\ta,\n\tb,\n\tx => {\n\t\tx()\n\t}\n)", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, + { code: "fn({\n\ta: 1\n},\n\tb,\n\tc)", options: ["always"] }, + { + code: "fn(`\n`,\n\ta)", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + }, - /* "always" */ - { code: "fn(a,\n\tb)", options: ["always"] }, - { code: "fn(\n\ta,\n\tb\n)", options: ["always"] }, - { code: "fn(\n\ta,\n\tb,\n\tc\n)", options: ["always"] }, - { - code: "fn(\n\ta,\n\tb,\n\t[\n\t\t1,\n\t\t2\n\t]\n)", - options: ["always"] - }, - { - code: "fn(\n\ta,\n\tb,\n\t{\n\t\ta: 1,\n\t\tb: 2\n\t}\n)", - options: ["always"] - }, - { - code: "fn(\n\ta,\n\tb,\n\tfunction (x) {\n\t\tx()\n\t}\n)", - options: ["always"] - }, - { - code: "fn(\n\ta,\n\tb,\n\tx => {\n\t\tx()\n\t}\n)", - options: ["always"], - languageOptions: { ecmaVersion: 6 } - }, - { code: "fn({\n\ta: 1\n},\n\tb,\n\tc)", options: ["always"] }, - { code: "fn(`\n`,\n\ta)", options: ["always"], languageOptions: { ecmaVersion: 6 } }, + /* "never" */ + { code: "fn(a, b)", options: ["never"] }, + { code: "fn(\n\ta, b\n)", options: ["never"] }, + { code: "fn(a, b, c)", options: ["never"] }, + { code: "fn(a, b, [\n\t1,\n\t2\n])", options: ["never"] }, + { code: "fn(a, b, {\n\ta: 1,\n\tb: 2\n})", options: ["never"] }, + { code: "fn(a, b, function (x) {\n\tx()\n})", options: ["never"] }, + { + code: "fn(a, b, x => {\n\tx()\n})", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, + { code: "fn({\n\ta: 1\n}, b)", options: ["never"] }, + { + code: "fn(`\n`, a)", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + }, - /* "never" */ - { code: "fn(a, b)", options: ["never"] }, - { code: "fn(\n\ta, b\n)", options: ["never"] }, - { code: "fn(a, b, c)", options: ["never"] }, - { code: "fn(a, b, [\n\t1,\n\t2\n])", options: ["never"] }, - { code: "fn(a, b, {\n\ta: 1,\n\tb: 2\n})", options: ["never"] }, - { code: "fn(a, b, function (x) {\n\tx()\n})", options: ["never"] }, - { - code: "fn(a, b, x => {\n\tx()\n})", - options: ["never"], - languageOptions: { ecmaVersion: 6 } - }, - { code: "fn({\n\ta: 1\n}, b)", options: ["never"] }, - { code: "fn(`\n`, a)", options: ["never"], languageOptions: { ecmaVersion: 6 } }, + /* "consistent" */ + { code: "fn(a, b, c)", options: ["consistent"] }, + { code: "fn(a,\n\tb,\n\tc)", options: ["consistent"] }, + { code: "fn({\n\ta: 1\n}, b, c)", options: ["consistent"] }, + { code: "fn({\n\ta: 1\n},\n\tb,\n\tc)", options: ["consistent"] }, + { + code: "fn(`\n`, b, c)", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "fn(`\n`,\n\tb,\n\tc)", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + }, + ], + invalid: [ + /* default ("always") */ + { + code: "fn(a, b)", + output: "fn(a,\nb)", + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + ], + }, - /* "consistent" */ - { code: "fn(a, b, c)", options: ["consistent"] }, - { code: "fn(a,\n\tb,\n\tc)", options: ["consistent"] }, - { code: "fn({\n\ta: 1\n}, b, c)", options: ["consistent"] }, - { code: "fn({\n\ta: 1\n},\n\tb,\n\tc)", options: ["consistent"] }, - { code: "fn(`\n`, b, c)", options: ["consistent"], languageOptions: { ecmaVersion: 6 } }, - { code: "fn(`\n`,\n\tb,\n\tc)", options: ["consistent"], languageOptions: { ecmaVersion: 6 } } - ], - invalid: [ + /* "always" */ + { + code: "fn(a, b)", + output: "fn(a,\nb)", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + ], + }, + { + code: "fn(a, b, c)", + output: "fn(a,\nb,\nc)", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + ], + }, + { + code: "fn(a, b, [\n\t1,\n\t2\n])", + output: "fn(a,\nb,\n[\n\t1,\n\t2\n])", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + ], + }, + { + code: "fn(a, b, {\n\ta: 1,\n\tb: 2\n})", + output: "fn(a,\nb,\n{\n\ta: 1,\n\tb: 2\n})", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + ], + }, + { + code: "fn(a, b, function (x) {\n\tx()\n})", + output: "fn(a,\nb,\nfunction (x) {\n\tx()\n})", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + ], + }, + { + code: "fn(a, b, x => {\n\tx()\n})", + output: "fn(a,\nb,\nx => {\n\tx()\n})", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingLineBreak", + line: 1, + column: 6, + endLine: 1, + endColumn: 7, + }, + { + messageId: "missingLineBreak", + line: 1, + column: 9, + endLine: 1, + endColumn: 10, + }, + ], + }, + { + code: "fn({\n\ta: 1\n}, b)", + output: "fn({\n\ta: 1\n},\nb)", + options: ["always"], + errors: [ + { + messageId: "missingLineBreak", + line: 3, + column: 3, + endLine: 3, + endColumn: 4, + }, + ], + }, + { + code: "fn(`\n`, b)", + output: "fn(`\n`,\nb)", + options: ["always"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingLineBreak", + line: 2, + column: 3, + endLine: 2, + endColumn: 4, + }, + ], + }, - /* default ("always") */ - { - code: "fn(a, b)", - output: "fn(a,\nb)", - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 6, - endLine: 1, - endColumn: 7 - } - ] - }, + /* "never" */ + { + code: "fn(a,\n\tb)", + output: "fn(a, b)", + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 6, + endLine: 2, + endColumn: 2, + }, + ], + }, + { + code: "fn(a,\n\tb,\n\tc)", + output: "fn(a, b, c)", + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 6, + endLine: 2, + endColumn: 2, + }, + { + messageId: "unexpectedLineBreak", + line: 2, + column: 4, + endLine: 3, + endColumn: 2, + }, + ], + }, + { + code: "fn(a,\n\tb,\n\t[\n\t\t1,\n\t\t2\n])", + output: "fn(a, b, [\n\t\t1,\n\t\t2\n])", + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 6, + endLine: 2, + endColumn: 2, + }, + { + messageId: "unexpectedLineBreak", + line: 2, + column: 4, + endLine: 3, + endColumn: 2, + }, + ], + }, + { + code: "fn(a,\n\tb,\n\t{\n\t\ta: 1,\n\t\tb: 2\n})", + output: "fn(a, b, {\n\t\ta: 1,\n\t\tb: 2\n})", + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 6, + endLine: 2, + endColumn: 2, + }, + { + messageId: "unexpectedLineBreak", + line: 2, + column: 4, + endLine: 3, + endColumn: 2, + }, + ], + }, + { + code: "fn(a,\n\tb,\n\tfunction (x) {\n\t\tx()\n})", + output: "fn(a, b, function (x) {\n\t\tx()\n})", + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 6, + endLine: 2, + endColumn: 2, + }, + { + messageId: "unexpectedLineBreak", + line: 2, + column: 4, + endLine: 3, + endColumn: 2, + }, + ], + }, + { + code: "fn(a,\n\tb,\n\tx => {\n\t\tx()\n})", + output: "fn(a, b, x => {\n\t\tx()\n})", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 6, + endLine: 2, + endColumn: 2, + }, + { + messageId: "unexpectedLineBreak", + line: 2, + column: 4, + endLine: 3, + endColumn: 2, + }, + ], + }, + { + code: "fn({\n\ta: 1\n},\nb)", + output: "fn({\n\ta: 1\n}, b)", + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 3, + column: 3, + endLine: 4, + endColumn: 1, + }, + ], + }, + { + code: "fn(`\n`,\nb)", + output: "fn(`\n`, b)", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedLineBreak", + line: 2, + column: 3, + endLine: 3, + endColumn: 1, + }, + ], + }, + { + code: "fn(a,/* comment */\nb)", + output: "fn(a,/* comment */ b)", + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 19, + endLine: 2, + endColumn: 1, + }, + ], + }, - /* "always" */ - { - code: "fn(a, b)", - output: "fn(a,\nb)", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 6, - endLine: 1, - endColumn: 7 - } - ] - }, - { - code: "fn(a, b, c)", - output: "fn(a,\nb,\nc)", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 6, - endLine: 1, - endColumn: 7 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 9, - endLine: 1, - endColumn: 10 - } - ] - }, - { - code: "fn(a, b, [\n\t1,\n\t2\n])", - output: "fn(a,\nb,\n[\n\t1,\n\t2\n])", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 6, - endLine: 1, - endColumn: 7 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 9, - endLine: 1, - endColumn: 10 - } - ] - }, - { - code: "fn(a, b, {\n\ta: 1,\n\tb: 2\n})", - output: "fn(a,\nb,\n{\n\ta: 1,\n\tb: 2\n})", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 6, - endLine: 1, - endColumn: 7 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 9, - endLine: 1, - endColumn: 10 - } - ] - }, - { - code: "fn(a, b, function (x) {\n\tx()\n})", - output: "fn(a,\nb,\nfunction (x) {\n\tx()\n})", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 6, - endLine: 1, - endColumn: 7 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 9, - endLine: 1, - endColumn: 10 - } - ] - }, - { - code: "fn(a, b, x => {\n\tx()\n})", - output: "fn(a,\nb,\nx => {\n\tx()\n})", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingLineBreak", - line: 1, - column: 6, - endLine: 1, - endColumn: 7 - }, - { - messageId: "missingLineBreak", - line: 1, - column: 9, - endLine: 1, - endColumn: 10 - } - ] - }, - { - code: "fn({\n\ta: 1\n}, b)", - output: "fn({\n\ta: 1\n},\nb)", - options: ["always"], - errors: [ - { - messageId: "missingLineBreak", - line: 3, - column: 3, - endLine: 3, - endColumn: 4 - } - ] - }, - { - code: "fn(`\n`, b)", - output: "fn(`\n`,\nb)", - options: ["always"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingLineBreak", - line: 2, - column: 3, - endLine: 2, - endColumn: 4 - } - ] - }, - - /* "never" */ - { - code: "fn(a,\n\tb)", - output: "fn(a, b)", - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 6, - endLine: 2, - endColumn: 2 - } - ] - }, - { - code: "fn(a,\n\tb,\n\tc)", - output: "fn(a, b, c)", - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 6, - endLine: 2, - endColumn: 2 - }, - { - messageId: "unexpectedLineBreak", - line: 2, - column: 4, - endLine: 3, - endColumn: 2 - } - ] - }, - { - code: "fn(a,\n\tb,\n\t[\n\t\t1,\n\t\t2\n])", - output: "fn(a, b, [\n\t\t1,\n\t\t2\n])", - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 6, - endLine: 2, - endColumn: 2 - }, - { - messageId: "unexpectedLineBreak", - line: 2, - column: 4, - endLine: 3, - endColumn: 2 - } - ] - }, - { - code: "fn(a,\n\tb,\n\t{\n\t\ta: 1,\n\t\tb: 2\n})", - output: "fn(a, b, {\n\t\ta: 1,\n\t\tb: 2\n})", - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 6, - endLine: 2, - endColumn: 2 - }, - { - messageId: "unexpectedLineBreak", - line: 2, - column: 4, - endLine: 3, - endColumn: 2 - } - ] - }, - { - code: "fn(a,\n\tb,\n\tfunction (x) {\n\t\tx()\n})", - output: "fn(a, b, function (x) {\n\t\tx()\n})", - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 6, - endLine: 2, - endColumn: 2 - }, - { - messageId: "unexpectedLineBreak", - line: 2, - column: 4, - endLine: 3, - endColumn: 2 - } - ] - }, - { - code: "fn(a,\n\tb,\n\tx => {\n\t\tx()\n})", - output: "fn(a, b, x => {\n\t\tx()\n})", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 6, - endLine: 2, - endColumn: 2 - }, - { - messageId: "unexpectedLineBreak", - line: 2, - column: 4, - endLine: 3, - endColumn: 2 - } - ] - }, - { - code: "fn({\n\ta: 1\n},\nb)", - output: "fn({\n\ta: 1\n}, b)", - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 3, - column: 3, - endLine: 4, - endColumn: 1 - } - ] - }, - { - code: "fn(`\n`,\nb)", - output: "fn(`\n`, b)", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedLineBreak", - line: 2, - column: 3, - endLine: 3, - endColumn: 1 - } - ] - }, - { - code: "fn(a,/* comment */\nb)", - output: "fn(a,/* comment */ b)", - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 19, - endLine: 2, - endColumn: 1 - } - ] - }, - - /* "consistent" */ - { - code: "fn(a, b,\n\tc)", - output: "fn(a, b, c)", - options: ["consistent"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 9, - endLine: 2, - endColumn: 2 - } - ] - }, - { - code: "fn(a,\n\tb, c)", - output: "fn(a,\n\tb,\nc)", - options: ["consistent"], - errors: [ - { - messageId: "missingLineBreak", - line: 2, - column: 4, - endLine: 2, - endColumn: 5 - } - ] - }, - { - code: "fn(a,\n\tb /* comment */, c)", - output: "fn(a,\n\tb /* comment */,\nc)", - options: ["consistent"], - errors: [ - { - messageId: "missingLineBreak", - line: 2, - column: 18, - endLine: 2, - endColumn: 19 - } - ] - }, - { - code: "fn(a,\n\tb, /* comment */ c)", - output: "fn(a,\n\tb, /* comment */\nc)", - options: ["consistent"], - errors: [ - { - messageId: "missingLineBreak", - line: 2, - column: 18, - endLine: 2, - endColumn: 19 - } - ] - }, - { - code: "fn({\n\ta: 1\n},\nb, c)", - output: "fn({\n\ta: 1\n},\nb,\nc)", - options: ["consistent"], - errors: [ - { - messageId: "missingLineBreak", - line: 4, - column: 3, - endLine: 4, - endColumn: 4 - } - ] - }, - { - code: "fn({\n\ta: 1\n}, b,\nc)", - output: "fn({\n\ta: 1\n}, b, c)", - options: ["consistent"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 3, - column: 6, - endLine: 4, - endColumn: 1 - } - ] - }, - { - code: "fn(`\n`,\nb, c)", - output: "fn(`\n`,\nb,\nc)", - options: ["consistent"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "missingLineBreak", - line: 3, - column: 3, - endLine: 3, - endColumn: 4 - } - ] - }, - { - code: "fn(`\n`, b,\nc)", - output: "fn(`\n`, b, c)", - options: ["consistent"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedLineBreak", - line: 2, - column: 6, - endLine: 3, - endColumn: 1 - } - ] - }, - { - code: "fn(a,// comment\n{b, c})", - output: null, - options: ["never"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 16, - endLine: 2, - endColumn: 1 - } - ] - }, - { - code: "fn(a, // comment\nb)", - output: null, - options: ["never"], - errors: [ - { - messageId: "unexpectedLineBreak", - line: 1, - column: 17, - endLine: 2, - endColumn: 1 - } - ] - }, - { - code: "fn(`\n`, b, // comment\nc)", - output: null, - options: ["consistent"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "unexpectedLineBreak", - line: 2, - column: 17, - endLine: 3, - endColumn: 1 - } - ] - } - ] + /* "consistent" */ + { + code: "fn(a, b,\n\tc)", + output: "fn(a, b, c)", + options: ["consistent"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 9, + endLine: 2, + endColumn: 2, + }, + ], + }, + { + code: "fn(a,\n\tb, c)", + output: "fn(a,\n\tb,\nc)", + options: ["consistent"], + errors: [ + { + messageId: "missingLineBreak", + line: 2, + column: 4, + endLine: 2, + endColumn: 5, + }, + ], + }, + { + code: "fn(a,\n\tb /* comment */, c)", + output: "fn(a,\n\tb /* comment */,\nc)", + options: ["consistent"], + errors: [ + { + messageId: "missingLineBreak", + line: 2, + column: 18, + endLine: 2, + endColumn: 19, + }, + ], + }, + { + code: "fn(a,\n\tb, /* comment */ c)", + output: "fn(a,\n\tb, /* comment */\nc)", + options: ["consistent"], + errors: [ + { + messageId: "missingLineBreak", + line: 2, + column: 18, + endLine: 2, + endColumn: 19, + }, + ], + }, + { + code: "fn({\n\ta: 1\n},\nb, c)", + output: "fn({\n\ta: 1\n},\nb,\nc)", + options: ["consistent"], + errors: [ + { + messageId: "missingLineBreak", + line: 4, + column: 3, + endLine: 4, + endColumn: 4, + }, + ], + }, + { + code: "fn({\n\ta: 1\n}, b,\nc)", + output: "fn({\n\ta: 1\n}, b, c)", + options: ["consistent"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 3, + column: 6, + endLine: 4, + endColumn: 1, + }, + ], + }, + { + code: "fn(`\n`,\nb, c)", + output: "fn(`\n`,\nb,\nc)", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "missingLineBreak", + line: 3, + column: 3, + endLine: 3, + endColumn: 4, + }, + ], + }, + { + code: "fn(`\n`, b,\nc)", + output: "fn(`\n`, b, c)", + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedLineBreak", + line: 2, + column: 6, + endLine: 3, + endColumn: 1, + }, + ], + }, + { + code: "fn(a,// comment\n{b, c})", + output: null, + options: ["never"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 16, + endLine: 2, + endColumn: 1, + }, + ], + }, + { + code: "fn(a, // comment\nb)", + output: null, + options: ["never"], + errors: [ + { + messageId: "unexpectedLineBreak", + line: 1, + column: 17, + endLine: 2, + endColumn: 1, + }, + ], + }, + { + code: "fn(`\n`, b, // comment\nc)", + output: null, + options: ["consistent"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "unexpectedLineBreak", + line: 2, + column: 17, + endLine: 3, + endColumn: 1, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/function-paren-newline.js b/tests/lib/rules/function-paren-newline.js index 322dcab87c7f..0865ab6cf074 100644 --- a/tests/lib/rules/function-paren-newline.js +++ b/tests/lib/rules/function-paren-newline.js @@ -9,589 +9,595 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/function-paren-newline"); -const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); +const RuleTester = require("../../../lib/rule-tester/rule-tester"); const { unIndent } = require("../../_utils"); const fixtureParser = require("../../fixtures/fixture-parser"); - //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ const LEFT_MISSING_ERROR = { messageId: "expectedAfter", type: "Punctuator" }; -const LEFT_UNEXPECTED_ERROR = { messageId: "unexpectedAfter", type: "Punctuator" }; +const LEFT_UNEXPECTED_ERROR = { + messageId: "unexpectedAfter", + type: "Punctuator", +}; const RIGHT_MISSING_ERROR = { messageId: "expectedBefore", type: "Punctuator" }; -const RIGHT_UNEXPECTED_ERROR = { messageId: "unexpectedBefore", type: "Punctuator" }; +const RIGHT_UNEXPECTED_ERROR = { + messageId: "unexpectedBefore", + type: "Punctuator", +}; const EXPECTED_BETWEEN = { messageId: "expectedBetween", type: "Identifier" }; -const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 6, sourceType: "script" } }); +const ruleTester = new RuleTester({ + languageOptions: { ecmaVersion: 6, sourceType: "script" }, +}); ruleTester.run("function-paren-newline", rule, { + valid: [ + "new new Foo();", - valid: [ - "new new Foo();", - - // multiline option (default) - "function baz(foo, bar) {}", - "(function(foo, bar) {});", - "(function baz(foo, bar) {});", - "(foo, bar) => {};", - "foo => {};", - "baz(foo, bar);", - "function baz() {}", - ` + // multiline option (default) + "function baz(foo, bar) {}", + "(function(foo, bar) {});", + "(function baz(foo, bar) {});", + "(foo, bar) => {};", + "foo => {};", + "baz(foo, bar);", + "function baz() {}", + ` function baz( foo, bar ) {} `, - ` + ` (function( foo, bar ) {}); `, - ` + ` (function baz( foo, bar ) {}); `, - ` + ` ( foo, bar ) => {}; `, - ` + ` baz( foo, bar ); `, - ` + ` baz(\`foo bar\`) `, - "new Foo(bar, baz)", - "new Foo", - "new (Foo)", + "new Foo(bar, baz)", + "new Foo", + "new (Foo)", - ` + ` (foo) (bar) `, - ` + ` foo.map(value => { return value; }) `, - { - code: "function baz(foo, bar) {}", - options: ["multiline"] - }, - { - code: "async (foo, bar) => {};", - languageOptions: { ecmaVersion: 2017 } - }, - { - code: ` + { + code: "function baz(foo, bar) {}", + options: ["multiline"], + }, + { + code: "async (foo, bar) => {};", + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: ` async ( foo, bar ) => {}; `, - languageOptions: { ecmaVersion: 2017 } - }, - { - code: "async foo => {};", - languageOptions: { ecmaVersion: 2017 } - }, - { - code: "import(source)", - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "import(source\n + ext)", - languageOptions: { ecmaVersion: 2020 } - }, + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: "async foo => {};", + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: "import(source)", + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "import(source\n + ext)", + languageOptions: { ecmaVersion: 2020 }, + }, - // multiline-arguments - { - code: "function baz(foo, bar) {}", - options: ["multiline-arguments"] - }, - { - code: "function baz(foo) {}", - options: ["multiline-arguments"] - }, - { - code: "(function(foo, bar) {});", - options: ["multiline-arguments"] - }, - { - code: "(function(foo) {});", - options: ["multiline-arguments"] - }, - { - code: "(function baz(foo, bar) {});", - options: ["multiline-arguments"] - }, - { - code: "(function baz(foo) {});", - options: ["multiline-arguments"] - }, - { - code: "(foo, bar) => {};", - options: ["multiline-arguments"] - }, - { - code: "foo => {};", - options: ["multiline-arguments"] - }, - { - code: "baz(foo, bar);", - options: ["multiline-arguments"] - }, - { - code: "baz(foo);", - options: ["multiline-arguments"] - }, - { - code: "function baz() {}", - options: ["multiline-arguments"] - }, - { - code: ` + // multiline-arguments + { + code: "function baz(foo, bar) {}", + options: ["multiline-arguments"], + }, + { + code: "function baz(foo) {}", + options: ["multiline-arguments"], + }, + { + code: "(function(foo, bar) {});", + options: ["multiline-arguments"], + }, + { + code: "(function(foo) {});", + options: ["multiline-arguments"], + }, + { + code: "(function baz(foo, bar) {});", + options: ["multiline-arguments"], + }, + { + code: "(function baz(foo) {});", + options: ["multiline-arguments"], + }, + { + code: "(foo, bar) => {};", + options: ["multiline-arguments"], + }, + { + code: "foo => {};", + options: ["multiline-arguments"], + }, + { + code: "baz(foo, bar);", + options: ["multiline-arguments"], + }, + { + code: "baz(foo);", + options: ["multiline-arguments"], + }, + { + code: "function baz() {}", + options: ["multiline-arguments"], + }, + { + code: ` function baz( foo, bar ) {} `, - options: ["multiline-arguments"] - }, - { - code: ` + options: ["multiline-arguments"], + }, + { + code: ` function baz( foo ) {} `, - options: ["multiline-arguments"] - }, - { - code: ` + options: ["multiline-arguments"], + }, + { + code: ` (function( foo, bar ) {}); `, - options: ["multiline-arguments"] - }, - { - code: ` + options: ["multiline-arguments"], + }, + { + code: ` (function( foo ) {}); `, - options: ["multiline-arguments"] - }, - { - code: ` + options: ["multiline-arguments"], + }, + { + code: ` (function baz( foo, bar ) {}); `, - options: ["multiline-arguments"] - }, - { - code: ` + options: ["multiline-arguments"], + }, + { + code: ` (function baz( foo ) {}); `, - options: ["multiline-arguments"] - }, - { - code: ` + options: ["multiline-arguments"], + }, + { + code: ` ( foo, bar ) => {}; `, - options: ["multiline-arguments"] - }, - { - code: ` + options: ["multiline-arguments"], + }, + { + code: ` ( foo ) => {}; `, - options: ["multiline-arguments"] - }, - { - code: ` + options: ["multiline-arguments"], + }, + { + code: ` baz( foo, bar ); `, - options: ["multiline-arguments"] - }, - { - code: ` + options: ["multiline-arguments"], + }, + { + code: ` baz( foo ); `, - options: ["multiline-arguments"] - }, - { - code: ` + options: ["multiline-arguments"], + }, + { + code: ` baz(\`foo bar\`) `, - options: ["multiline-arguments"] - }, - { - code: "new Foo(bar, baz)", - options: ["multiline-arguments"] - }, - { - code: "new Foo(bar)", - options: ["multiline-arguments"] - }, - { - code: "new Foo", - options: ["multiline-arguments"] - }, - { - code: "new (Foo)", - options: ["multiline-arguments"] - }, - { - code: "async (foo, bar) => {};", - options: ["multiline-arguments"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: "async (foo) => {};", - options: ["multiline-arguments"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: ` + options: ["multiline-arguments"], + }, + { + code: "new Foo(bar, baz)", + options: ["multiline-arguments"], + }, + { + code: "new Foo(bar)", + options: ["multiline-arguments"], + }, + { + code: "new Foo", + options: ["multiline-arguments"], + }, + { + code: "new (Foo)", + options: ["multiline-arguments"], + }, + { + code: "async (foo, bar) => {};", + options: ["multiline-arguments"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: "async (foo) => {};", + options: ["multiline-arguments"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: ` async ( foo ) => {}; `, - options: ["multiline-arguments"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: ` + options: ["multiline-arguments"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: ` async ( foo, bar ) => {}; `, - options: ["multiline-arguments"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: "async foo => {};", - options: ["multiline-arguments"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: "import(source)", - options: ["multiline-arguments"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "import(source\n + ext)", - options: ["multiline-arguments"], - languageOptions: { ecmaVersion: 2020 } - }, + options: ["multiline-arguments"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: "async foo => {};", + options: ["multiline-arguments"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: "import(source)", + options: ["multiline-arguments"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "import(source\n + ext)", + options: ["multiline-arguments"], + languageOptions: { ecmaVersion: 2020 }, + }, - { - code: ` + { + code: ` (foo) (bar) `, - options: ["multiline-arguments"] - }, - { - code: ` + options: ["multiline-arguments"], + }, + { + code: ` foo.map(value => { return value; }) `, - options: ["multiline-arguments"] - }, + options: ["multiline-arguments"], + }, - // always option - { - code: ` + // always option + { + code: ` function baz( foo, bar ) {} `, - options: ["always"] - }, - { - code: ` + options: ["always"], + }, + { + code: ` (function( foo, bar ) {}); `, - options: ["always"] - }, - { - code: ` + options: ["always"], + }, + { + code: ` (function baz( foo, bar ) {}); `, - options: ["always"] - }, - { - code: ` + options: ["always"], + }, + { + code: ` ( foo, bar ) => {}; `, - options: ["always"] - }, - { - code: ` + options: ["always"], + }, + { + code: ` baz( foo, bar ); `, - options: ["always"] - }, - { - code: ` + options: ["always"], + }, + { + code: ` function baz( ) {} `, - options: ["always"] - }, - { - code: ` + options: ["always"], + }, + { + code: ` async ( foo ) => {}; `, - options: ["always"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: ` + options: ["always"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: ` async ( foo, bar ) => {}; `, - options: ["always"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: "async foo => {};", - options: ["always"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: "import(\n source\n)", - options: ["always"], - languageOptions: { ecmaVersion: 2020 } - }, + options: ["always"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: "async foo => {};", + options: ["always"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: "import(\n source\n)", + options: ["always"], + languageOptions: { ecmaVersion: 2020 }, + }, - // never option - { - code: "function baz(foo, bar) {}", - options: ["never"] - }, - { - code: "(function(foo, bar) {});", - options: ["never"] - }, - { - code: "(function baz(foo, bar) {});", - options: ["never"] - }, - { - code: "(foo, bar) => {};", - options: ["never"] - }, - { - code: "baz(foo, bar);", - options: ["never"] - }, - { - code: "function baz() {}", - options: ["never"] - }, - { - code: "async (foo, bar) => {};", - options: ["never"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: "async foo => {};", - options: ["never"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: "import(source)", - options: ["never"], - languageOptions: { ecmaVersion: 2020 } - }, + // never option + { + code: "function baz(foo, bar) {}", + options: ["never"], + }, + { + code: "(function(foo, bar) {});", + options: ["never"], + }, + { + code: "(function baz(foo, bar) {});", + options: ["never"], + }, + { + code: "(foo, bar) => {};", + options: ["never"], + }, + { + code: "baz(foo, bar);", + options: ["never"], + }, + { + code: "function baz() {}", + options: ["never"], + }, + { + code: "async (foo, bar) => {};", + options: ["never"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: "async foo => {};", + options: ["never"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: "import(source)", + options: ["never"], + languageOptions: { ecmaVersion: 2020 }, + }, - // minItems option - { - code: "function baz(foo, bar) {}", - options: [{ minItems: 3 }] - }, - { - code: ` + // minItems option + { + code: "function baz(foo, bar) {}", + options: [{ minItems: 3 }], + }, + { + code: ` function baz( foo, bar, qux ) {} `, - options: [{ minItems: 3 }] - }, - { - code: ` + options: [{ minItems: 3 }], + }, + { + code: ` baz( foo, bar, qux ); `, - options: [{ minItems: 3 }] - }, - { - code: "baz(foo, bar);", - options: [{ minItems: 3 }] - }, - { - code: "async (foo, bar) => {};", - options: [{ minItems: 3 }], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: ` + options: [{ minItems: 3 }], + }, + { + code: "baz(foo, bar);", + options: [{ minItems: 3 }], + }, + { + code: "async (foo, bar) => {};", + options: [{ minItems: 3 }], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: ` async ( foo, bar, baz ) => {}; `, - options: [{ minItems: 3 }], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: "async foo => {};", - options: [{ minItems: 3 }], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: "import(source)", - options: [{ minItems: 3 }], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "import(\n source\n)", - options: [{ minItems: 1 }], - languageOptions: { ecmaVersion: 2020 } - }, + options: [{ minItems: 3 }], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: "async foo => {};", + options: [{ minItems: 3 }], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: "import(source)", + options: [{ minItems: 3 }], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "import(\n source\n)", + options: [{ minItems: 1 }], + languageOptions: { ecmaVersion: 2020 }, + }, - // consistent option - { - code: "foo(bar, baz)", - options: ["consistent"] - }, - { - code: ` + // consistent option + { + code: "foo(bar, baz)", + options: ["consistent"], + }, + { + code: ` foo(bar, baz) `, - options: ["consistent"] - }, - { - code: ` + options: ["consistent"], + }, + { + code: ` foo( bar, baz ) `, - options: ["consistent"] - }, - { - code: ` + options: ["consistent"], + }, + { + code: ` foo( bar, baz ) `, - options: ["consistent"] - }, - { - code: "async (foo, bar) => {};", - options: ["consistent"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: "async foo => {};", - options: ["consistent"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: ` + options: ["consistent"], + }, + { + code: "async (foo, bar) => {};", + options: ["consistent"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: "async foo => {};", + options: ["consistent"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: ` async (foo, bar) => {}; `, - options: ["consistent"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: ` + options: ["consistent"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: ` async ( foo, bar ) => {}; `, - options: ["consistent"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: ` + options: ["consistent"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: ` async ( foo, bar ) => {}; `, - options: ["consistent"], - languageOptions: { ecmaVersion: 2017 } - }, - { - code: "import(source)", - options: ["consistent"], - languageOptions: { ecmaVersion: 2020 } - }, - { - code: "import(\n source\n)", - options: ["consistent"], - languageOptions: { ecmaVersion: 2020 } - }, + options: ["consistent"], + languageOptions: { ecmaVersion: 2017 }, + }, + { + code: "import(source)", + options: ["consistent"], + languageOptions: { ecmaVersion: 2020 }, + }, + { + code: "import(\n source\n)", + options: ["consistent"], + languageOptions: { ecmaVersion: 2020 }, + }, - // https://github.com/eslint/eslint/issues/15091#issuecomment-975605821 - { - code: unIndent` + // https://github.com/eslint/eslint/issues/15091#issuecomment-975605821 + { + code: unIndent` const method6 = ( abc: number, def: () => void, @@ -601,881 +607,885 @@ ruleTester.run("function-paren-newline", rule, { ] => [\`a\${abc}\`, def]; method6(3, () => {}); `, - options: ["multiline"], - languageOptions: { - parser: require(fixtureParser("function-paren-newline", "arrow-function-return-type")) - } - } - ], - - invalid: [ + options: ["multiline"], + languageOptions: { + parser: require( + fixtureParser( + "function-paren-newline", + "arrow-function-return-type", + ), + ), + }, + }, + ], - // multiline option (default) - { - code: ` + invalid: [ + // multiline option (default) + { + code: ` function baz(foo, bar ) {} `, - output: ` + output: ` function baz(\nfoo, bar ) {} `, - errors: [LEFT_MISSING_ERROR] - }, - { - code: ` + errors: [LEFT_MISSING_ERROR], + }, + { + code: ` (function( foo, bar) {}) `, - output: ` + output: ` (function( foo, bar\n) {}) `, - errors: [RIGHT_MISSING_ERROR] - }, - { - code: ` + errors: [RIGHT_MISSING_ERROR], + }, + { + code: ` (function baz(foo, bar) {}) `, - output: ` + output: ` (function baz(\nfoo, bar\n) {}) `, - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: ` + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: ` baz( foo, bar); `, - output: ` + output: ` baz(foo, bar); `, - errors: [LEFT_UNEXPECTED_ERROR] - }, - { - code: ` + errors: [LEFT_UNEXPECTED_ERROR], + }, + { + code: ` (foo, bar ) => {}; `, - output: ` + output: ` (foo, bar) => {}; `, - errors: [RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + errors: [RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` function baz( foo, bar ) {} `, - output: ` + output: ` function baz(foo, bar) {} `, - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` function baz( foo = 1 ) {} `, - output: ` + output: ` function baz(foo = 1) {} `, - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` function baz( ) {} `, - output: ` + output: ` function baz() {} `, - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` new Foo(bar, baz); `, - output: ` + output: ` new Foo(\nbar, baz\n); `, - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: ` + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: ` function baz(/* not fixed due to comment */ foo) {} `, - output: null, - errors: [LEFT_UNEXPECTED_ERROR] - }, - { - code: ` + output: null, + errors: [LEFT_UNEXPECTED_ERROR], + }, + { + code: ` function baz(foo /* not fixed due to comment */) {} `, - output: null, - errors: [RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + output: null, + errors: [RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` async ( foo, bar ) => {}; `, - output: ` + output: ` async (foo, bar) => {}; `, - languageOptions: { ecmaVersion: 2017 }, - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + languageOptions: { ecmaVersion: 2017 }, + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` async (foo, bar ) => {}; `, - output: ` + output: ` async (foo, bar) => {}; `, - languageOptions: { ecmaVersion: 2017 }, - errors: [RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + languageOptions: { ecmaVersion: 2017 }, + errors: [RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` async (foo, bar) => {}; `, - output: ` + output: ` async (\nfoo, bar\n) => {}; `, - languageOptions: { ecmaVersion: 2017 }, - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: ` + languageOptions: { ecmaVersion: 2017 }, + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: ` async ( foo, bar) => {}; `, - output: ` + output: ` async ( foo, bar\n) => {}; `, - languageOptions: { ecmaVersion: 2017 }, - errors: [RIGHT_MISSING_ERROR] - }, - { - code: "import(\n source\n)", - output: "import(source)", - languageOptions: { ecmaVersion: 2020 }, - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, + languageOptions: { ecmaVersion: 2017 }, + errors: [RIGHT_MISSING_ERROR], + }, + { + code: "import(\n source\n)", + output: "import(source)", + languageOptions: { ecmaVersion: 2020 }, + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, - // multiline-arguments - { - code: ` + // multiline-arguments + { + code: ` function baz(foo, bar ) {} `, - output: ` + output: ` function baz(\nfoo, bar ) {} `, - options: ["multiline-arguments"], - errors: [LEFT_MISSING_ERROR] - }, - { - code: ` + options: ["multiline-arguments"], + errors: [LEFT_MISSING_ERROR], + }, + { + code: ` (function( foo, bar) {}) `, - output: ` + output: ` (function( foo, bar\n) {}) `, - options: ["multiline-arguments"], - errors: [RIGHT_MISSING_ERROR] - }, - { - code: ` + options: ["multiline-arguments"], + errors: [RIGHT_MISSING_ERROR], + }, + { + code: ` (function baz(foo, bar) {}) `, - output: ` + output: ` (function baz(\nfoo, bar\n) {}) `, - options: ["multiline-arguments"], - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: ` + options: ["multiline-arguments"], + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: ` baz( foo, bar); `, - output: ` + output: ` baz(foo, bar); `, - options: ["multiline-arguments"], - errors: [LEFT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["multiline-arguments"], + errors: [LEFT_UNEXPECTED_ERROR], + }, + { + code: ` (foo, bar ) => {}; `, - output: ` + output: ` (foo, bar) => {}; `, - options: ["multiline-arguments"], - errors: [RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["multiline-arguments"], + errors: [RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` function baz( foo, bar ) {} `, - output: ` + output: ` function baz(foo, bar) {} `, - options: ["multiline-arguments"], - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["multiline-arguments"], + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` function baz( ) {} `, - output: ` + output: ` function baz() {} `, - options: ["multiline-arguments"], - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["multiline-arguments"], + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` new Foo(bar, baz); `, - output: ` + output: ` new Foo(\nbar, baz\n); `, - options: ["multiline-arguments"], - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: ` + options: ["multiline-arguments"], + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: ` function baz(/* not fixed due to comment */ foo) {} `, - output: ` + output: ` function baz(/* not fixed due to comment */ foo\n) {} `, - options: ["multiline-arguments"], - errors: [RIGHT_MISSING_ERROR] - }, - { - code: ` + options: ["multiline-arguments"], + errors: [RIGHT_MISSING_ERROR], + }, + { + code: ` function baz(foo /* not fixed due to comment */) {} `, - output: null, - options: ["multiline-arguments"], - errors: [RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + output: null, + options: ["multiline-arguments"], + errors: [RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` function baz( qwe, foo, bar ) {} `, - output: ` + output: ` function baz( qwe, foo, \nbar ) {} `, - options: ["multiline-arguments"], - errors: [EXPECTED_BETWEEN] - }, - { - code: ` + options: ["multiline-arguments"], + errors: [EXPECTED_BETWEEN], + }, + { + code: ` function baz( qwe, foo, bar ) {} `, - output: ` + output: ` function baz( qwe, \nfoo, bar ) {} `, - options: ["multiline-arguments"], - errors: [EXPECTED_BETWEEN] - }, - { - code: ` + options: ["multiline-arguments"], + errors: [EXPECTED_BETWEEN], + }, + { + code: ` function baz(qwe, foo, bar) {} `, - output: ` + output: ` function baz(\nqwe, \nfoo, bar\n) {} `, - options: ["multiline-arguments"], - errors: [LEFT_MISSING_ERROR, EXPECTED_BETWEEN, RIGHT_MISSING_ERROR] - }, - { - code: ` + options: ["multiline-arguments"], + errors: [LEFT_MISSING_ERROR, EXPECTED_BETWEEN, RIGHT_MISSING_ERROR], + }, + { + code: ` baz( foo); `, - output: ` + output: ` baz( foo\n); `, - options: ["multiline-arguments"], - errors: [RIGHT_MISSING_ERROR] - }, - { - code: ` + options: ["multiline-arguments"], + errors: [RIGHT_MISSING_ERROR], + }, + { + code: ` baz(foo ); `, - output: ` + output: ` baz(foo); `, - options: ["multiline-arguments"], - errors: [RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["multiline-arguments"], + errors: [RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` async (foo, bar ) => {}; `, - output: ` + output: ` async (foo, bar) => {}; `, - options: ["multiline-arguments"], - languageOptions: { ecmaVersion: 2017 }, - errors: [RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["multiline-arguments"], + languageOptions: { ecmaVersion: 2017 }, + errors: [RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` async (foo, bar) => {}; `, - output: ` + output: ` async (\nfoo, bar\n) => {}; `, - options: ["multiline-arguments"], - languageOptions: { ecmaVersion: 2017 }, - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: ` + options: ["multiline-arguments"], + languageOptions: { ecmaVersion: 2017 }, + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: ` async ( foo, bar) => {}; `, - output: ` + output: ` async ( foo, bar\n) => {}; `, - options: ["multiline-arguments"], - languageOptions: { ecmaVersion: 2017 }, - errors: [RIGHT_MISSING_ERROR] - }, - { - code: "import(source\n)", - output: "import(source)", - options: ["multiline-arguments"], - languageOptions: { ecmaVersion: 2020 }, - errors: [RIGHT_UNEXPECTED_ERROR] - }, - { - code: "import(\n source)", - output: "import(\n source\n)", - options: ["multiline-arguments"], - languageOptions: { ecmaVersion: 2020 }, - errors: [RIGHT_MISSING_ERROR] - }, + options: ["multiline-arguments"], + languageOptions: { ecmaVersion: 2017 }, + errors: [RIGHT_MISSING_ERROR], + }, + { + code: "import(source\n)", + output: "import(source)", + options: ["multiline-arguments"], + languageOptions: { ecmaVersion: 2020 }, + errors: [RIGHT_UNEXPECTED_ERROR], + }, + { + code: "import(\n source)", + output: "import(\n source\n)", + options: ["multiline-arguments"], + languageOptions: { ecmaVersion: 2020 }, + errors: [RIGHT_MISSING_ERROR], + }, - // always option - { - code: ` + // always option + { + code: ` function baz(foo, bar ) {} `, - output: ` + output: ` function baz(\nfoo, bar ) {} `, - options: ["always"], - errors: [LEFT_MISSING_ERROR] - }, - { - code: ` + options: ["always"], + errors: [LEFT_MISSING_ERROR], + }, + { + code: ` (function( foo, bar) {}) `, - output: ` + output: ` (function( foo, bar\n) {}) `, - options: ["always"], - errors: [RIGHT_MISSING_ERROR] - }, - { - code: ` + options: ["always"], + errors: [RIGHT_MISSING_ERROR], + }, + { + code: ` (function baz(foo, bar) {}) `, - output: ` + output: ` (function baz(\nfoo, bar\n) {}) `, - options: ["always"], - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: "function baz(foo, bar) {}", - output: "function baz(\nfoo, bar\n) {}", - options: ["always"], - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: "(function(foo, bar) {});", - output: "(function(\nfoo, bar\n) {});", - options: ["always"], - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: "(function baz(foo, bar) {});", - output: "(function baz(\nfoo, bar\n) {});", - options: ["always"], - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: "(foo, bar) => {};", - output: "(\nfoo, bar\n) => {};", - options: ["always"], - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: "baz(foo, bar);", - output: "baz(\nfoo, bar\n);", - options: ["always"], - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: "function baz() {}", - output: "function baz(\n) {}", - options: ["always"], - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: ` + options: ["always"], + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: "function baz(foo, bar) {}", + output: "function baz(\nfoo, bar\n) {}", + options: ["always"], + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: "(function(foo, bar) {});", + output: "(function(\nfoo, bar\n) {});", + options: ["always"], + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: "(function baz(foo, bar) {});", + output: "(function baz(\nfoo, bar\n) {});", + options: ["always"], + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: "(foo, bar) => {};", + output: "(\nfoo, bar\n) => {};", + options: ["always"], + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: "baz(foo, bar);", + output: "baz(\nfoo, bar\n);", + options: ["always"], + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: "function baz() {}", + output: "function baz(\n) {}", + options: ["always"], + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: ` async (foo, bar) => {}; `, - output: ` + output: ` async (\nfoo, bar\n) => {}; `, - options: ["always"], - languageOptions: { ecmaVersion: 2017 }, - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: ` + options: ["always"], + languageOptions: { ecmaVersion: 2017 }, + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: ` async (foo, bar) => {}; `, - output: ` + output: ` async (\nfoo, bar\n) => {}; `, - options: ["always"], - languageOptions: { ecmaVersion: 2017 }, - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: ` + options: ["always"], + languageOptions: { ecmaVersion: 2017 }, + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: ` async ( foo, bar) => {}; `, - output: ` + output: ` async ( foo, bar\n) => {}; `, - options: ["always"], - languageOptions: { ecmaVersion: 2017 }, - errors: [RIGHT_MISSING_ERROR] - }, - { - code: "import(source)", - output: "import(\nsource\n)", - options: ["always"], - languageOptions: { ecmaVersion: 2020 }, - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, + options: ["always"], + languageOptions: { ecmaVersion: 2017 }, + errors: [RIGHT_MISSING_ERROR], + }, + { + code: "import(source)", + output: "import(\nsource\n)", + options: ["always"], + languageOptions: { ecmaVersion: 2020 }, + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, - // never option - { - code: ` + // never option + { + code: ` function baz(foo, bar ) {} `, - output: ` + output: ` function baz(foo, bar) {} `, - options: ["never"], - errors: [RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["never"], + errors: [RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` (function( foo, bar) {}) `, - output: ` + output: ` (function(foo, bar) {}) `, - options: ["never"], - errors: [LEFT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["never"], + errors: [LEFT_UNEXPECTED_ERROR], + }, + { + code: ` new new C()( ); `, - output: ` + output: ` new new C()(); `, - options: ["never"], - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, + options: ["never"], + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, - { - code: ` + { + code: ` function baz( foo, bar ) {} `, - output: ` + output: ` function baz(foo, bar) {} `, - options: ["never"], - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["never"], + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` (function( foo, bar ) {}); `, - output: ` + output: ` (function(foo, bar) {}); `, - options: ["never"], - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["never"], + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` (function baz( foo, bar ) {}); `, - output: ` + output: ` (function baz(foo, bar) {}); `, - options: ["never"], - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["never"], + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` ( foo, bar ) => {}; `, - output: ` + output: ` (foo, bar) => {}; `, - options: ["never"], - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["never"], + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` baz( foo, bar ); `, - output: ` + output: ` baz(foo, bar); `, - options: ["never"], - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["never"], + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` function baz( ) {} `, - output: ` + output: ` function baz() {} `, - options: ["never"], - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["never"], + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` async ( foo, bar ) => {}; `, - output: ` + output: ` async (foo, bar) => {}; `, - options: ["never"], - languageOptions: { ecmaVersion: 2017 }, - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["never"], + languageOptions: { ecmaVersion: 2017 }, + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` async ( foo, bar) => {}; `, - output: ` + output: ` async (foo, bar) => {}; `, - options: ["never"], - languageOptions: { ecmaVersion: 2017 }, - errors: [LEFT_UNEXPECTED_ERROR] - }, - { - code: "import(\n source\n)", - output: "import(source)", - options: ["never"], - languageOptions: { ecmaVersion: 2020 }, - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, + options: ["never"], + languageOptions: { ecmaVersion: 2017 }, + errors: [LEFT_UNEXPECTED_ERROR], + }, + { + code: "import(\n source\n)", + output: "import(source)", + options: ["never"], + languageOptions: { ecmaVersion: 2020 }, + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, - // minItems option - { - code: "function baz(foo, bar, qux) {}", - output: "function baz(\nfoo, bar, qux\n) {}", - options: [{ minItems: 3 }], - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: ` + // minItems option + { + code: "function baz(foo, bar, qux) {}", + output: "function baz(\nfoo, bar, qux\n) {}", + options: [{ minItems: 3 }], + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: ` function baz( foo, bar ) {} `, - output: ` + output: ` function baz(foo, bar) {} `, - options: [{ minItems: 3 }], - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: "baz(foo, bar, qux);", - output: "baz(\nfoo, bar, qux\n);", - options: [{ minItems: 3 }], - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: ` + options: [{ minItems: 3 }], + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: "baz(foo, bar, qux);", + output: "baz(\nfoo, bar, qux\n);", + options: [{ minItems: 3 }], + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: ` baz( foo, bar ); `, - output: ` + output: ` baz(foo, bar); `, - options: [{ minItems: 3 }], - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: [{ minItems: 3 }], + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` async ( foo, bar ) => {}; `, - output: ` + output: ` async (foo, bar) => {}; `, - options: [{ minItems: 3 }], - languageOptions: { ecmaVersion: 2017 }, - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: [{ minItems: 3 }], + languageOptions: { ecmaVersion: 2017 }, + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` async ( foo, bar) => {}; `, - output: ` + output: ` async (foo, bar) => {}; `, - options: [{ minItems: 3 }], - languageOptions: { ecmaVersion: 2017 }, - errors: [LEFT_UNEXPECTED_ERROR] - }, - { - code: ` + options: [{ minItems: 3 }], + languageOptions: { ecmaVersion: 2017 }, + errors: [LEFT_UNEXPECTED_ERROR], + }, + { + code: ` async (foo, bar, baz) => {}; `, - output: ` + output: ` async (\nfoo, bar, baz\n) => {}; `, - options: [{ minItems: 3 }], - languageOptions: { ecmaVersion: 2017 }, - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, - { - code: "import(\n source\n)", - output: "import(source)", - options: [{ minItems: 3 }], - languageOptions: { ecmaVersion: 2020 }, - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - }, - { - code: "import(source)", - output: "import(\nsource\n)", - options: [{ minItems: 1 }], - languageOptions: { ecmaVersion: 2020 }, - errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] - }, + options: [{ minItems: 3 }], + languageOptions: { ecmaVersion: 2017 }, + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, + { + code: "import(\n source\n)", + output: "import(source)", + options: [{ minItems: 3 }], + languageOptions: { ecmaVersion: 2020 }, + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + { + code: "import(source)", + output: "import(\nsource\n)", + options: [{ minItems: 1 }], + languageOptions: { ecmaVersion: 2020 }, + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR], + }, - // consistent option - { - code: ` + // consistent option + { + code: ` foo( bar, baz) `, - output: ` + output: ` foo( bar, baz\n) `, - options: ["consistent"], - errors: [RIGHT_MISSING_ERROR] - }, - { - code: ` + options: ["consistent"], + errors: [RIGHT_MISSING_ERROR], + }, + { + code: ` foo(bar, baz ) `, - output: ` + output: ` foo(bar, baz) `, - options: ["consistent"], - errors: [RIGHT_UNEXPECTED_ERROR] - }, - { - code: ` + options: ["consistent"], + errors: [RIGHT_UNEXPECTED_ERROR], + }, + { + code: ` async ( foo, bar) => {}; `, - output: ` + output: ` async ( foo, bar\n) => {}; `, - options: ["consistent"], - languageOptions: { ecmaVersion: 2017 }, - errors: [RIGHT_MISSING_ERROR] - }, - { - code: ` + options: ["consistent"], + languageOptions: { ecmaVersion: 2017 }, + errors: [RIGHT_MISSING_ERROR], + }, + { + code: ` async (foo, bar ) => {}; `, - output: ` + output: ` async (foo, bar) => {}; `, - options: ["consistent"], - languageOptions: { ecmaVersion: 2017 }, - errors: [RIGHT_UNEXPECTED_ERROR] - }, - { - code: "import(source\n)", - output: "import(source)", - options: ["consistent"], - languageOptions: { ecmaVersion: 2020 }, - errors: [RIGHT_UNEXPECTED_ERROR] - }, - { - code: "import(\n source)", - output: "import(\n source\n)", - options: ["consistent"], - languageOptions: { ecmaVersion: 2020 }, - errors: [RIGHT_MISSING_ERROR] - }, + options: ["consistent"], + languageOptions: { ecmaVersion: 2017 }, + errors: [RIGHT_UNEXPECTED_ERROR], + }, + { + code: "import(source\n)", + output: "import(source)", + options: ["consistent"], + languageOptions: { ecmaVersion: 2020 }, + errors: [RIGHT_UNEXPECTED_ERROR], + }, + { + code: "import(\n source)", + output: "import(\n source\n)", + options: ["consistent"], + languageOptions: { ecmaVersion: 2020 }, + errors: [RIGHT_MISSING_ERROR], + }, - // https://github.com/eslint/eslint/issues/15091#issuecomment-975605821 - { - code: unIndent` + // https://github.com/eslint/eslint/issues/15091#issuecomment-975605821 + { + code: unIndent` const method6 = ( abc: number, def: () => void, @@ -1485,7 +1495,7 @@ ruleTester.run("function-paren-newline", rule, { ] => [\`a\${abc}\`, def]; method6(3, () => {}); `, - output: unIndent` + output: unIndent` const method6 = (abc: number, def: () => void,): [ string, @@ -1493,11 +1503,16 @@ ruleTester.run("function-paren-newline", rule, { ] => [\`a\${abc}\`, def]; method6(3, () => {}); `, - options: ["never"], - languageOptions: { - parser: require(fixtureParser("function-paren-newline", "arrow-function-return-type")) - }, - errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] - } - ] + options: ["never"], + languageOptions: { + parser: require( + fixtureParser( + "function-paren-newline", + "arrow-function-return-type", + ), + ), + }, + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR], + }, + ], }); diff --git a/tests/lib/rules/generator-star-spacing.js b/tests/lib/rules/generator-star-spacing.js index f89ff6eb636e..f2d31ca102da 100644 --- a/tests/lib/rules/generator-star-spacing.js +++ b/tests/lib/rules/generator-star-spacing.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/generator-star-spacing"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -20,1007 +20,1020 @@ const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2018 } }); const missingBeforeError = { messageId: "missingBefore", type: "Punctuator" }; const missingAfterError = { messageId: "missingAfter", type: "Punctuator" }; -const unexpectedBeforeError = { messageId: "unexpectedBefore", type: "Punctuator" }; -const unexpectedAfterError = { messageId: "unexpectedAfter", type: "Punctuator" }; +const unexpectedBeforeError = { + messageId: "unexpectedBefore", + type: "Punctuator", +}; +const unexpectedAfterError = { + messageId: "unexpectedAfter", + type: "Punctuator", +}; ruleTester.run("generator-star-spacing", rule, { + valid: [ + // Default ("before") + "function foo(){}", + "function *foo(){}", + "function *foo(arg1, arg2){}", + "var foo = function *foo(){};", + "var foo = function *(){};", + "var foo = { *foo(){} };", + "var foo = {*foo(){} };", + "class Foo { *foo(){} }", + "class Foo {*foo(){} }", + "class Foo { static *foo(){} }", + "var foo = {*[ foo ](){} };", + "class Foo {*[ foo ](){} }", - valid: [ + // "before" + { + code: "function foo(){}", + options: ["before"], + }, + { + code: "function *foo(){}", + options: ["before"], + }, + { + code: "function *foo(arg1, arg2){}", + options: ["before"], + }, + { + code: "var foo = function *foo(){};", + options: ["before"], + }, + { + code: "var foo = function *(){};", + options: ["before"], + }, + { + code: "var foo = { *foo(){} };", + options: ["before"], + }, + { + code: "var foo = {*foo(){} };", + options: ["before"], + }, + { + code: "class Foo { *foo(){} }", + options: ["before"], + }, + { + code: "class Foo {*foo(){} }", + options: ["before"], + }, + { + code: "class Foo { static *foo(){} }", + options: ["before"], + }, + { + code: "class Foo {*[ foo ](){} }", + options: ["before"], + }, + { + code: "var foo = {*[ foo ](){} };", + options: ["before"], + }, - // Default ("before") - "function foo(){}", - "function *foo(){}", - "function *foo(arg1, arg2){}", - "var foo = function *foo(){};", - "var foo = function *(){};", - "var foo = { *foo(){} };", - "var foo = {*foo(){} };", - "class Foo { *foo(){} }", - "class Foo {*foo(){} }", - "class Foo { static *foo(){} }", - "var foo = {*[ foo ](){} };", - "class Foo {*[ foo ](){} }", + // "after" + { + code: "function foo(){}", + options: ["after"], + }, + { + code: "function* foo(){}", + options: ["after"], + }, + { + code: "function* foo(arg1, arg2){}", + options: ["after"], + }, + { + code: "var foo = function* foo(){};", + options: ["after"], + }, + { + code: "var foo = function* (){};", + options: ["after"], + }, + { + code: "var foo = {* foo(){} };", + options: ["after"], + }, + { + code: "var foo = { * foo(){} };", + options: ["after"], + }, + { + code: "class Foo {* foo(){} }", + options: ["after"], + }, + { + code: "class Foo { * foo(){} }", + options: ["after"], + }, + { + code: "class Foo { static* foo(){} }", + options: ["after"], + }, + { + code: "var foo = {* [foo](){} };", + options: ["after"], + }, + { + code: "class Foo {* [foo](){} }", + options: ["after"], + }, - // "before" - { - code: "function foo(){}", - options: ["before"] - }, - { - code: "function *foo(){}", - options: ["before"] - }, - { - code: "function *foo(arg1, arg2){}", - options: ["before"] - }, - { - code: "var foo = function *foo(){};", - options: ["before"] - }, - { - code: "var foo = function *(){};", - options: ["before"] - }, - { - code: "var foo = { *foo(){} };", - options: ["before"] - }, - { - code: "var foo = {*foo(){} };", - options: ["before"] - }, - { - code: "class Foo { *foo(){} }", - options: ["before"] - }, - { - code: "class Foo {*foo(){} }", - options: ["before"] - }, - { - code: "class Foo { static *foo(){} }", - options: ["before"] - }, - { - code: "class Foo {*[ foo ](){} }", - options: ["before"] - }, - { - code: "var foo = {*[ foo ](){} };", - options: ["before"] - }, + // "both" + { + code: "function foo(){}", + options: ["both"], + }, + { + code: "function * foo(){}", + options: ["both"], + }, + { + code: "function * foo(arg1, arg2){}", + options: ["both"], + }, + { + code: "var foo = function * foo(){};", + options: ["both"], + }, + { + code: "var foo = function * (){};", + options: ["both"], + }, + { + code: "var foo = { * foo(){} };", + options: ["both"], + }, + { + code: "var foo = {* foo(){} };", + options: ["both"], + }, + { + code: "class Foo { * foo(){} }", + options: ["both"], + }, + { + code: "class Foo {* foo(){} }", + options: ["both"], + }, + { + code: "class Foo { static * foo(){} }", + options: ["both"], + }, + { + code: "var foo = {* [foo](){} };", + options: ["both"], + }, + { + code: "class Foo {* [foo](){} }", + options: ["both"], + }, - // "after" - { - code: "function foo(){}", - options: ["after"] - }, - { - code: "function* foo(){}", - options: ["after"] - }, - { - code: "function* foo(arg1, arg2){}", - options: ["after"] - }, - { - code: "var foo = function* foo(){};", - options: ["after"] - }, - { - code: "var foo = function* (){};", - options: ["after"] - }, - { - code: "var foo = {* foo(){} };", - options: ["after"] - }, - { - code: "var foo = { * foo(){} };", - options: ["after"] - }, - { - code: "class Foo {* foo(){} }", - options: ["after"] - }, - { - code: "class Foo { * foo(){} }", - options: ["after"] - }, - { - code: "class Foo { static* foo(){} }", - options: ["after"] - }, - { - code: "var foo = {* [foo](){} };", - options: ["after"] - }, - { - code: "class Foo {* [foo](){} }", - options: ["after"] - }, + // "neither" + { + code: "function foo(){}", + options: ["neither"], + }, + { + code: "function*foo(){}", + options: ["neither"], + }, + { + code: "function*foo(arg1, arg2){}", + options: ["neither"], + }, + { + code: "var foo = function*foo(){};", + options: ["neither"], + }, + { + code: "var foo = function*(){};", + options: ["neither"], + }, + { + code: "var foo = {*foo(){} };", + options: ["neither"], + }, + { + code: "var foo = { *foo(){} };", + options: ["neither"], + }, + { + code: "class Foo {*foo(){} }", + options: ["neither"], + }, + { + code: "class Foo { *foo(){} }", + options: ["neither"], + }, + { + code: "class Foo { static*foo(){} }", + options: ["neither"], + }, + { + code: "var foo = {*[ foo ](){} };", + options: ["neither"], + }, + { + code: "class Foo {*[ foo ](){} }", + options: ["neither"], + }, - // "both" - { - code: "function foo(){}", - options: ["both"] - }, - { - code: "function * foo(){}", - options: ["both"] - }, - { - code: "function * foo(arg1, arg2){}", - options: ["both"] - }, - { - code: "var foo = function * foo(){};", - options: ["both"] - }, - { - code: "var foo = function * (){};", - options: ["both"] - }, - { - code: "var foo = { * foo(){} };", - options: ["both"] - }, - { - code: "var foo = {* foo(){} };", - options: ["both"] - }, - { - code: "class Foo { * foo(){} }", - options: ["both"] - }, - { - code: "class Foo {* foo(){} }", - options: ["both"] - }, - { - code: "class Foo { static * foo(){} }", - options: ["both"] - }, - { - code: "var foo = {* [foo](){} };", - options: ["both"] - }, - { - code: "class Foo {* [foo](){} }", - options: ["both"] - }, + // {"before": true, "after": false} + { + code: "function foo(){}", + options: [{ before: true, after: false }], + }, + { + code: "function *foo(){}", + options: [{ before: true, after: false }], + }, + { + code: "function *foo(arg1, arg2){}", + options: [{ before: true, after: false }], + }, + { + code: "var foo = function *foo(){};", + options: [{ before: true, after: false }], + }, + { + code: "var foo = function *(){};", + options: [{ before: true, after: false }], + }, + { + code: "var foo = { *foo(){} };", + options: [{ before: true, after: false }], + }, + { + code: "var foo = {*foo(){} };", + options: [{ before: true, after: false }], + }, + { + code: "class Foo { *foo(){} }", + options: [{ before: true, after: false }], + }, + { + code: "class Foo {*foo(){} }", + options: [{ before: true, after: false }], + }, + { + code: "class Foo { static *foo(){} }", + options: [{ before: true, after: false }], + }, - // "neither" - { - code: "function foo(){}", - options: ["neither"] - }, - { - code: "function*foo(){}", - options: ["neither"] - }, - { - code: "function*foo(arg1, arg2){}", - options: ["neither"] - }, - { - code: "var foo = function*foo(){};", - options: ["neither"] - }, - { - code: "var foo = function*(){};", - options: ["neither"] - }, - { - code: "var foo = {*foo(){} };", - options: ["neither"] - }, - { - code: "var foo = { *foo(){} };", - options: ["neither"] - }, - { - code: "class Foo {*foo(){} }", - options: ["neither"] - }, - { - code: "class Foo { *foo(){} }", - options: ["neither"] - }, - { - code: "class Foo { static*foo(){} }", - options: ["neither"] - }, - { - code: "var foo = {*[ foo ](){} };", - options: ["neither"] - }, - { - code: "class Foo {*[ foo ](){} }", - options: ["neither"] - }, + // {"before": false, "after": true} + { + code: "function foo(){}", + options: [{ before: false, after: true }], + }, + { + code: "function* foo(){}", + options: [{ before: false, after: true }], + }, + { + code: "function* foo(arg1, arg2){}", + options: [{ before: false, after: true }], + }, + { + code: "var foo = function* foo(){};", + options: [{ before: false, after: true }], + }, + { + code: "var foo = function* (){};", + options: [{ before: false, after: true }], + }, + { + code: "var foo = {* foo(){} };", + options: [{ before: false, after: true }], + }, + { + code: "var foo = { * foo(){} };", + options: [{ before: false, after: true }], + }, + { + code: "class Foo {* foo(){} }", + options: [{ before: false, after: true }], + }, + { + code: "class Foo { * foo(){} }", + options: [{ before: false, after: true }], + }, + { + code: "class Foo { static* foo(){} }", + options: [{ before: false, after: true }], + }, - // {"before": true, "after": false} - { - code: "function foo(){}", - options: [{ before: true, after: false }] - }, - { - code: "function *foo(){}", - options: [{ before: true, after: false }] - }, - { - code: "function *foo(arg1, arg2){}", - options: [{ before: true, after: false }] - }, - { - code: "var foo = function *foo(){};", - options: [{ before: true, after: false }] - }, - { - code: "var foo = function *(){};", - options: [{ before: true, after: false }] - }, - { - code: "var foo = { *foo(){} };", - options: [{ before: true, after: false }] - }, - { - code: "var foo = {*foo(){} };", - options: [{ before: true, after: false }] - }, - { - code: "class Foo { *foo(){} }", - options: [{ before: true, after: false }] - }, - { - code: "class Foo {*foo(){} }", - options: [{ before: true, after: false }] - }, - { - code: "class Foo { static *foo(){} }", - options: [{ before: true, after: false }] - }, + // {"before": true, "after": true} + { + code: "function foo(){}", + options: [{ before: true, after: true }], + }, + { + code: "function * foo(){}", + options: [{ before: true, after: true }], + }, + { + code: "function * foo(arg1, arg2){}", + options: [{ before: true, after: true }], + }, + { + code: "var foo = function * foo(){};", + options: [{ before: true, after: true }], + }, + { + code: "var foo = function * (){};", + options: [{ before: true, after: true }], + }, + { + code: "var foo = { * foo(){} };", + options: [{ before: true, after: true }], + }, + { + code: "var foo = {* foo(){} };", + options: [{ before: true, after: true }], + }, + { + code: "class Foo { * foo(){} }", + options: [{ before: true, after: true }], + }, + { + code: "class Foo {* foo(){} }", + options: [{ before: true, after: true }], + }, + { + code: "class Foo { static * foo(){} }", + options: [{ before: true, after: true }], + }, - // {"before": false, "after": true} - { - code: "function foo(){}", - options: [{ before: false, after: true }] - }, - { - code: "function* foo(){}", - options: [{ before: false, after: true }] - }, - { - code: "function* foo(arg1, arg2){}", - options: [{ before: false, after: true }] - }, - { - code: "var foo = function* foo(){};", - options: [{ before: false, after: true }] - }, - { - code: "var foo = function* (){};", - options: [{ before: false, after: true }] - }, - { - code: "var foo = {* foo(){} };", - options: [{ before: false, after: true }] - }, - { - code: "var foo = { * foo(){} };", - options: [{ before: false, after: true }] - }, - { - code: "class Foo {* foo(){} }", - options: [{ before: false, after: true }] - }, - { - code: "class Foo { * foo(){} }", - options: [{ before: false, after: true }] - }, - { - code: "class Foo { static* foo(){} }", - options: [{ before: false, after: true }] - }, + // {"before": false, "after": false} + { + code: "function foo(){}", + options: [{ before: false, after: false }], + }, + { + code: "function*foo(){}", + options: [{ before: false, after: false }], + }, + { + code: "function*foo(arg1, arg2){}", + options: [{ before: false, after: false }], + }, + { + code: "var foo = function*foo(){};", + options: [{ before: false, after: false }], + }, + { + code: "var foo = function*(){};", + options: [{ before: false, after: false }], + }, + { + code: "var foo = {*foo(){} };", + options: [{ before: false, after: false }], + }, + { + code: "var foo = { *foo(){} };", + options: [{ before: false, after: false }], + }, + { + code: "class Foo {*foo(){} }", + options: [{ before: false, after: false }], + }, + { + code: "class Foo { *foo(){} }", + options: [{ before: false, after: false }], + }, + { + code: "class Foo { static*foo(){} }", + options: [{ before: false, after: false }], + }, - // {"before": true, "after": true} - { - code: "function foo(){}", - options: [{ before: true, after: true }] - }, - { - code: "function * foo(){}", - options: [{ before: true, after: true }] - }, - { - code: "function * foo(arg1, arg2){}", - options: [{ before: true, after: true }] - }, - { - code: "var foo = function * foo(){};", - options: [{ before: true, after: true }] - }, - { - code: "var foo = function * (){};", - options: [{ before: true, after: true }] - }, - { - code: "var foo = { * foo(){} };", - options: [{ before: true, after: true }] - }, - { - code: "var foo = {* foo(){} };", - options: [{ before: true, after: true }] - }, - { - code: "class Foo { * foo(){} }", - options: [{ before: true, after: true }] - }, - { - code: "class Foo {* foo(){} }", - options: [{ before: true, after: true }] - }, - { - code: "class Foo { static * foo(){} }", - options: [{ before: true, after: true }] - }, + // full configurability + { + code: "function * foo(){}", + options: [{ before: false, after: false, named: "both" }], + }, + { + code: "var foo = function * (){};", + options: [{ before: false, after: false, anonymous: "both" }], + }, + { + code: "class Foo { * foo(){} }", + options: [{ before: false, after: false, method: "both" }], + }, + { + code: "var foo = { * foo(){} }", + options: [{ before: false, after: false, method: "both" }], + }, + { + code: "var foo = { bar: function * () {} }", + options: [{ before: false, after: false, anonymous: "both" }], + }, + { + code: "class Foo { static * foo(){} }", + options: [{ before: false, after: false, method: "both" }], + }, - // {"before": false, "after": false} - { - code: "function foo(){}", - options: [{ before: false, after: false }] - }, - { - code: "function*foo(){}", - options: [{ before: false, after: false }] - }, - { - code: "function*foo(arg1, arg2){}", - options: [{ before: false, after: false }] - }, - { - code: "var foo = function*foo(){};", - options: [{ before: false, after: false }] - }, - { - code: "var foo = function*(){};", - options: [{ before: false, after: false }] - }, - { - code: "var foo = {*foo(){} };", - options: [{ before: false, after: false }] - }, - { - code: "var foo = { *foo(){} };", - options: [{ before: false, after: false }] - }, - { - code: "class Foo {*foo(){} }", - options: [{ before: false, after: false }] - }, - { - code: "class Foo { *foo(){} }", - options: [{ before: false, after: false }] - }, - { - code: "class Foo { static*foo(){} }", - options: [{ before: false, after: false }] - }, + // default to top level "before" + { + code: "function *foo(){}", + options: [{ method: "both" }], + }, - // full configurability - { - code: "function * foo(){}", - options: [{ before: false, after: false, named: "both" }] - }, - { - code: "var foo = function * (){};", - options: [{ before: false, after: false, anonymous: "both" }] - }, - { - code: "class Foo { * foo(){} }", - options: [{ before: false, after: false, method: "both" }] - }, - { - code: "var foo = { * foo(){} }", - options: [{ before: false, after: false, method: "both" }] - }, - { - code: "var foo = { bar: function * () {} }", - options: [{ before: false, after: false, anonymous: "both" }] - }, - { - code: "class Foo { static * foo(){} }", - options: [{ before: false, after: false, method: "both" }] - }, + // don't apply unrelated override + { + code: "function*foo(){}", + options: [{ before: false, after: false, method: "both" }], + }, - // default to top level "before" - { - code: "function *foo(){}", - options: [{ method: "both" }] - }, + // ensure using object-type override works + { + code: "function * foo(){}", + options: [ + { + before: false, + after: false, + named: { before: true, after: true }, + }, + ], + }, - // don't apply unrelated override - { - code: "function*foo(){}", - options: [{ before: false, after: false, method: "both" }] - }, + // unspecified option uses default + { + code: "function *foo(){}", + options: [{ before: false, after: false, named: { before: true } }], + }, - // ensure using object-type override works - { - code: "function * foo(){}", - options: [{ before: false, after: false, named: { before: true, after: true } }] - }, + // https://github.com/eslint/eslint/issues/7101#issuecomment-246080531 + { + code: "async function foo() { }", + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "(async function() { })", + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "async () => { }", + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "({async foo() { }})", + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "class A {async foo() { }}", + languageOptions: { ecmaVersion: 8 }, + }, + { + code: "(class {async foo() { }})", + languageOptions: { ecmaVersion: 8 }, + }, + ], - // unspecified option uses default - { - code: "function *foo(){}", - options: [{ before: false, after: false, named: { before: true } }] - }, + invalid: [ + // Default ("before") + { + code: "function*foo(){}", + output: "function *foo(){}", + errors: [missingBeforeError], + }, + { + code: "function* foo(arg1, arg2){}", + output: "function *foo(arg1, arg2){}", + errors: [missingBeforeError, unexpectedAfterError], + }, + { + code: "var foo = function*foo(){};", + output: "var foo = function *foo(){};", + errors: [missingBeforeError], + }, + { + code: "var foo = function* (){};", + output: "var foo = function *(){};", + errors: [missingBeforeError, unexpectedAfterError], + }, + { + code: "var foo = {* foo(){} };", + output: "var foo = {*foo(){} };", + errors: [unexpectedAfterError], + }, + { + code: "class Foo {* foo(){} }", + output: "class Foo {*foo(){} }", + errors: [unexpectedAfterError], + }, + { + code: "class Foo { static* foo(){} }", + output: "class Foo { static *foo(){} }", + errors: [missingBeforeError, unexpectedAfterError], + }, - // https://github.com/eslint/eslint/issues/7101#issuecomment-246080531 - { - code: "async function foo() { }", - languageOptions: { ecmaVersion: 8 } - }, - { - code: "(async function() { })", - languageOptions: { ecmaVersion: 8 } - }, - { - code: "async () => { }", - languageOptions: { ecmaVersion: 8 } - }, - { - code: "({async foo() { }})", - languageOptions: { ecmaVersion: 8 } - }, - { - code: "class A {async foo() { }}", - languageOptions: { ecmaVersion: 8 } - }, - { - code: "(class {async foo() { }})", - languageOptions: { ecmaVersion: 8 } - } - ], + // "before" + { + code: "function*foo(){}", + output: "function *foo(){}", + options: ["before"], + errors: [missingBeforeError], + }, + { + code: "function* foo(arg1, arg2){}", + output: "function *foo(arg1, arg2){}", + options: ["before"], + errors: [missingBeforeError, unexpectedAfterError], + }, + { + code: "var foo = function*foo(){};", + output: "var foo = function *foo(){};", + options: ["before"], + errors: [missingBeforeError], + }, + { + code: "var foo = function* (){};", + output: "var foo = function *(){};", + options: ["before"], + errors: [missingBeforeError, unexpectedAfterError], + }, + { + code: "var foo = {* foo(){} };", + output: "var foo = {*foo(){} };", + options: ["before"], + errors: [unexpectedAfterError], + }, + { + code: "class Foo {* foo(){} }", + output: "class Foo {*foo(){} }", + options: ["before"], + errors: [unexpectedAfterError], + }, + { + code: "var foo = {* [ foo ](){} };", + output: "var foo = {*[ foo ](){} };", + options: ["before"], + errors: [unexpectedAfterError], + }, + { + code: "class Foo {* [ foo ](){} }", + output: "class Foo {*[ foo ](){} }", + options: ["before"], + errors: [unexpectedAfterError], + }, - invalid: [ + // "after" + { + code: "function*foo(){}", + output: "function* foo(){}", + options: ["after"], + errors: [missingAfterError], + }, + { + code: "function *foo(arg1, arg2){}", + output: "function* foo(arg1, arg2){}", + options: ["after"], + errors: [unexpectedBeforeError, missingAfterError], + }, + { + code: "var foo = function *foo(){};", + output: "var foo = function* foo(){};", + options: ["after"], + errors: [unexpectedBeforeError, missingAfterError], + }, + { + code: "var foo = function *(){};", + output: "var foo = function* (){};", + options: ["after"], + errors: [unexpectedBeforeError, missingAfterError], + }, + { + code: "var foo = { *foo(){} };", + output: "var foo = { * foo(){} };", + options: ["after"], + errors: [missingAfterError], + }, + { + code: "class Foo { *foo(){} }", + output: "class Foo { * foo(){} }", + options: ["after"], + errors: [missingAfterError], + }, + { + code: "class Foo { static *foo(){} }", + output: "class Foo { static* foo(){} }", + options: ["after"], + errors: [unexpectedBeforeError, missingAfterError], + }, + { + code: "var foo = { *[foo](){} };", + output: "var foo = { * [foo](){} };", + options: ["after"], + errors: [missingAfterError], + }, + { + code: "class Foo { *[foo](){} }", + output: "class Foo { * [foo](){} }", + options: ["after"], + errors: [missingAfterError], + }, - // Default ("before") - { - code: "function*foo(){}", - output: "function *foo(){}", - errors: [missingBeforeError] - }, - { - code: "function* foo(arg1, arg2){}", - output: "function *foo(arg1, arg2){}", - errors: [missingBeforeError, unexpectedAfterError] - }, - { - code: "var foo = function*foo(){};", - output: "var foo = function *foo(){};", - errors: [missingBeforeError] - }, - { - code: "var foo = function* (){};", - output: "var foo = function *(){};", - errors: [missingBeforeError, unexpectedAfterError] - }, - { - code: "var foo = {* foo(){} };", - output: "var foo = {*foo(){} };", - errors: [unexpectedAfterError] - }, - { - code: "class Foo {* foo(){} }", - output: "class Foo {*foo(){} }", - errors: [unexpectedAfterError] - }, - { - code: "class Foo { static* foo(){} }", - output: "class Foo { static *foo(){} }", - errors: [missingBeforeError, unexpectedAfterError] - }, + // "both" + { + code: "function*foo(){}", + output: "function * foo(){}", + options: ["both"], + errors: [missingBeforeError, missingAfterError], + }, + { + code: "function*foo(arg1, arg2){}", + output: "function * foo(arg1, arg2){}", + options: ["both"], + errors: [missingBeforeError, missingAfterError], + }, + { + code: "var foo = function*foo(){};", + output: "var foo = function * foo(){};", + options: ["both"], + errors: [missingBeforeError, missingAfterError], + }, + { + code: "var foo = function*(){};", + output: "var foo = function * (){};", + options: ["both"], + errors: [missingBeforeError, missingAfterError], + }, + { + code: "var foo = {*foo(){} };", + output: "var foo = {* foo(){} };", + options: ["both"], + errors: [missingAfterError], + }, + { + code: "class Foo {*foo(){} }", + output: "class Foo {* foo(){} }", + options: ["both"], + errors: [missingAfterError], + }, + { + code: "class Foo { static*foo(){} }", + output: "class Foo { static * foo(){} }", + options: ["both"], + errors: [missingBeforeError, missingAfterError], + }, + { + code: "var foo = {*[foo](){} };", + output: "var foo = {* [foo](){} };", + options: ["both"], + errors: [missingAfterError], + }, + { + code: "class Foo {*[foo](){} }", + output: "class Foo {* [foo](){} }", + options: ["both"], + errors: [missingAfterError], + }, - // "before" - { - code: "function*foo(){}", - output: "function *foo(){}", - options: ["before"], - errors: [missingBeforeError] - }, - { - code: "function* foo(arg1, arg2){}", - output: "function *foo(arg1, arg2){}", - options: ["before"], - errors: [missingBeforeError, unexpectedAfterError] - }, - { - code: "var foo = function*foo(){};", - output: "var foo = function *foo(){};", - options: ["before"], - errors: [missingBeforeError] - }, - { - code: "var foo = function* (){};", - output: "var foo = function *(){};", - options: ["before"], - errors: [missingBeforeError, unexpectedAfterError] - }, - { - code: "var foo = {* foo(){} };", - output: "var foo = {*foo(){} };", - options: ["before"], - errors: [unexpectedAfterError] - }, - { - code: "class Foo {* foo(){} }", - output: "class Foo {*foo(){} }", - options: ["before"], - errors: [unexpectedAfterError] - }, - { - code: "var foo = {* [ foo ](){} };", - output: "var foo = {*[ foo ](){} };", - options: ["before"], - errors: [unexpectedAfterError] - }, - { - code: "class Foo {* [ foo ](){} }", - output: "class Foo {*[ foo ](){} }", - options: ["before"], - errors: [unexpectedAfterError] - }, + // "neither" + { + code: "function * foo(){}", + output: "function*foo(){}", + options: ["neither"], + errors: [unexpectedBeforeError, unexpectedAfterError], + }, + { + code: "function * foo(arg1, arg2){}", + output: "function*foo(arg1, arg2){}", + options: ["neither"], + errors: [unexpectedBeforeError, unexpectedAfterError], + }, + { + code: "var foo = function * foo(){};", + output: "var foo = function*foo(){};", + options: ["neither"], + errors: [unexpectedBeforeError, unexpectedAfterError], + }, + { + code: "var foo = function * (){};", + output: "var foo = function*(){};", + options: ["neither"], + errors: [unexpectedBeforeError, unexpectedAfterError], + }, + { + code: "var foo = { * foo(){} };", + output: "var foo = { *foo(){} };", + options: ["neither"], + errors: [unexpectedAfterError], + }, + { + code: "class Foo { * foo(){} }", + output: "class Foo { *foo(){} }", + options: ["neither"], + errors: [unexpectedAfterError], + }, + { + code: "class Foo { static * foo(){} }", + output: "class Foo { static*foo(){} }", + options: ["neither"], + errors: [unexpectedBeforeError, unexpectedAfterError], + }, + { + code: "var foo = { * [ foo ](){} };", + output: "var foo = { *[ foo ](){} };", + options: ["neither"], + errors: [unexpectedAfterError], + }, + { + code: "class Foo { * [ foo ](){} }", + output: "class Foo { *[ foo ](){} }", + options: ["neither"], + errors: [unexpectedAfterError], + }, - // "after" - { - code: "function*foo(){}", - output: "function* foo(){}", - options: ["after"], - errors: [missingAfterError] - }, - { - code: "function *foo(arg1, arg2){}", - output: "function* foo(arg1, arg2){}", - options: ["after"], - errors: [unexpectedBeforeError, missingAfterError] - }, - { - code: "var foo = function *foo(){};", - output: "var foo = function* foo(){};", - options: ["after"], - errors: [unexpectedBeforeError, missingAfterError] - }, - { - code: "var foo = function *(){};", - output: "var foo = function* (){};", - options: ["after"], - errors: [unexpectedBeforeError, missingAfterError] - }, - { - code: "var foo = { *foo(){} };", - output: "var foo = { * foo(){} };", - options: ["after"], - errors: [missingAfterError] - }, - { - code: "class Foo { *foo(){} }", - output: "class Foo { * foo(){} }", - options: ["after"], - errors: [missingAfterError] - }, - { - code: "class Foo { static *foo(){} }", - output: "class Foo { static* foo(){} }", - options: ["after"], - errors: [unexpectedBeforeError, missingAfterError] - }, - { - code: "var foo = { *[foo](){} };", - output: "var foo = { * [foo](){} };", - options: ["after"], - errors: [missingAfterError] - }, - { - code: "class Foo { *[foo](){} }", - output: "class Foo { * [foo](){} }", - options: ["after"], - errors: [missingAfterError] - }, + // {"before": true, "after": false} + { + code: "function*foo(){}", + output: "function *foo(){}", + options: [{ before: true, after: false }], + errors: [missingBeforeError], + }, + { + code: "function* foo(arg1, arg2){}", + output: "function *foo(arg1, arg2){}", + options: [{ before: true, after: false }], + errors: [missingBeforeError, unexpectedAfterError], + }, + { + code: "var foo = function*foo(){};", + output: "var foo = function *foo(){};", + options: [{ before: true, after: false }], + errors: [missingBeforeError], + }, + { + code: "var foo = function* (){};", + output: "var foo = function *(){};", + options: [{ before: true, after: false }], + errors: [missingBeforeError, unexpectedAfterError], + }, + { + code: "var foo = {* foo(){} };", + output: "var foo = {*foo(){} };", + options: [{ before: true, after: false }], + errors: [unexpectedAfterError], + }, + { + code: "class Foo {* foo(){} }", + output: "class Foo {*foo(){} }", + options: [{ before: true, after: false }], + errors: [unexpectedAfterError], + }, - // "both" - { - code: "function*foo(){}", - output: "function * foo(){}", - options: ["both"], - errors: [missingBeforeError, missingAfterError] - }, - { - code: "function*foo(arg1, arg2){}", - output: "function * foo(arg1, arg2){}", - options: ["both"], - errors: [missingBeforeError, missingAfterError] - }, - { - code: "var foo = function*foo(){};", - output: "var foo = function * foo(){};", - options: ["both"], - errors: [missingBeforeError, missingAfterError] - }, - { - code: "var foo = function*(){};", - output: "var foo = function * (){};", - options: ["both"], - errors: [missingBeforeError, missingAfterError] - }, - { - code: "var foo = {*foo(){} };", - output: "var foo = {* foo(){} };", - options: ["both"], - errors: [missingAfterError] - }, - { - code: "class Foo {*foo(){} }", - output: "class Foo {* foo(){} }", - options: ["both"], - errors: [missingAfterError] - }, - { - code: "class Foo { static*foo(){} }", - output: "class Foo { static * foo(){} }", - options: ["both"], - errors: [missingBeforeError, missingAfterError] - }, - { - code: "var foo = {*[foo](){} };", - output: "var foo = {* [foo](){} };", - options: ["both"], - errors: [missingAfterError] - }, - { - code: "class Foo {*[foo](){} }", - output: "class Foo {* [foo](){} }", - options: ["both"], - errors: [missingAfterError] - }, + // {"before": false, "after": true} + { + code: "function*foo(){}", + output: "function* foo(){}", + options: [{ before: false, after: true }], + errors: [missingAfterError], + }, + { + code: "function *foo(arg1, arg2){}", + output: "function* foo(arg1, arg2){}", + options: [{ before: false, after: true }], + errors: [unexpectedBeforeError, missingAfterError], + }, + { + code: "var foo = function *foo(){};", + output: "var foo = function* foo(){};", + options: [{ before: false, after: true }], + errors: [unexpectedBeforeError, missingAfterError], + }, + { + code: "var foo = function *(){};", + output: "var foo = function* (){};", + options: [{ before: false, after: true }], + errors: [unexpectedBeforeError, missingAfterError], + }, + { + code: "var foo = { *foo(){} };", + output: "var foo = { * foo(){} };", + options: [{ before: false, after: true }], + errors: [missingAfterError], + }, + { + code: "class Foo { *foo(){} }", + output: "class Foo { * foo(){} }", + options: [{ before: false, after: true }], + errors: [missingAfterError], + }, + { + code: "class Foo { static *foo(){} }", + output: "class Foo { static* foo(){} }", + options: [{ before: false, after: true }], + errors: [unexpectedBeforeError, missingAfterError], + }, - // "neither" - { - code: "function * foo(){}", - output: "function*foo(){}", - options: ["neither"], - errors: [unexpectedBeforeError, unexpectedAfterError] - }, - { - code: "function * foo(arg1, arg2){}", - output: "function*foo(arg1, arg2){}", - options: ["neither"], - errors: [unexpectedBeforeError, unexpectedAfterError] - }, - { - code: "var foo = function * foo(){};", - output: "var foo = function*foo(){};", - options: ["neither"], - errors: [unexpectedBeforeError, unexpectedAfterError] - }, - { - code: "var foo = function * (){};", - output: "var foo = function*(){};", - options: ["neither"], - errors: [unexpectedBeforeError, unexpectedAfterError] - }, - { - code: "var foo = { * foo(){} };", - output: "var foo = { *foo(){} };", - options: ["neither"], - errors: [unexpectedAfterError] - }, - { - code: "class Foo { * foo(){} }", - output: "class Foo { *foo(){} }", - options: ["neither"], - errors: [unexpectedAfterError] - }, - { - code: "class Foo { static * foo(){} }", - output: "class Foo { static*foo(){} }", - options: ["neither"], - errors: [unexpectedBeforeError, unexpectedAfterError] - }, - { - code: "var foo = { * [ foo ](){} };", - output: "var foo = { *[ foo ](){} };", - options: ["neither"], - errors: [unexpectedAfterError] - }, - { - code: "class Foo { * [ foo ](){} }", - output: "class Foo { *[ foo ](){} }", - options: ["neither"], - errors: [unexpectedAfterError] - }, + // {"before": true, "after": true} + { + code: "function*foo(){}", + output: "function * foo(){}", + options: [{ before: true, after: true }], + errors: [missingBeforeError, missingAfterError], + }, + { + code: "function*foo(arg1, arg2){}", + output: "function * foo(arg1, arg2){}", + options: [{ before: true, after: true }], + errors: [missingBeforeError, missingAfterError], + }, + { + code: "var foo = function*foo(){};", + output: "var foo = function * foo(){};", + options: [{ before: true, after: true }], + errors: [missingBeforeError, missingAfterError], + }, + { + code: "var foo = function*(){};", + output: "var foo = function * (){};", + options: [{ before: true, after: true }], + errors: [missingBeforeError, missingAfterError], + }, + { + code: "var foo = {*foo(){} };", + output: "var foo = {* foo(){} };", + options: [{ before: true, after: true }], + errors: [missingAfterError], + }, + { + code: "class Foo {*foo(){} }", + output: "class Foo {* foo(){} }", + options: [{ before: true, after: true }], + errors: [missingAfterError], + }, + { + code: "class Foo { static*foo(){} }", + output: "class Foo { static * foo(){} }", + options: [{ before: true, after: true }], + errors: [missingBeforeError, missingAfterError], + }, - // {"before": true, "after": false} - { - code: "function*foo(){}", - output: "function *foo(){}", - options: [{ before: true, after: false }], - errors: [missingBeforeError] - }, - { - code: "function* foo(arg1, arg2){}", - output: "function *foo(arg1, arg2){}", - options: [{ before: true, after: false }], - errors: [missingBeforeError, unexpectedAfterError] - }, - { - code: "var foo = function*foo(){};", - output: "var foo = function *foo(){};", - options: [{ before: true, after: false }], - errors: [missingBeforeError] - }, - { - code: "var foo = function* (){};", - output: "var foo = function *(){};", - options: [{ before: true, after: false }], - errors: [missingBeforeError, unexpectedAfterError] - }, - { - code: "var foo = {* foo(){} };", - output: "var foo = {*foo(){} };", - options: [{ before: true, after: false }], - errors: [unexpectedAfterError] - }, - { - code: "class Foo {* foo(){} }", - output: "class Foo {*foo(){} }", - options: [{ before: true, after: false }], - errors: [unexpectedAfterError] - }, + // {"before": false, "after": false} + { + code: "function * foo(){}", + output: "function*foo(){}", + options: [{ before: false, after: false }], + errors: [unexpectedBeforeError, unexpectedAfterError], + }, + { + code: "function * foo(arg1, arg2){}", + output: "function*foo(arg1, arg2){}", + options: [{ before: false, after: false }], + errors: [unexpectedBeforeError, unexpectedAfterError], + }, + { + code: "var foo = function * foo(){};", + output: "var foo = function*foo(){};", + options: [{ before: false, after: false }], + errors: [unexpectedBeforeError, unexpectedAfterError], + }, + { + code: "var foo = function * (){};", + output: "var foo = function*(){};", + options: [{ before: false, after: false }], + errors: [unexpectedBeforeError, unexpectedAfterError], + }, + { + code: "var foo = { * foo(){} };", + output: "var foo = { *foo(){} };", + options: [{ before: false, after: false }], + errors: [unexpectedAfterError], + }, + { + code: "class Foo { * foo(){} }", + output: "class Foo { *foo(){} }", + options: [{ before: false, after: false }], + errors: [unexpectedAfterError], + }, + { + code: "class Foo { static * foo(){} }", + output: "class Foo { static*foo(){} }", + options: [{ before: false, after: false }], + errors: [unexpectedBeforeError, unexpectedAfterError], + }, - // {"before": false, "after": true} - { - code: "function*foo(){}", - output: "function* foo(){}", - options: [{ before: false, after: true }], - errors: [missingAfterError] - }, - { - code: "function *foo(arg1, arg2){}", - output: "function* foo(arg1, arg2){}", - options: [{ before: false, after: true }], - errors: [unexpectedBeforeError, missingAfterError] - }, - { - code: "var foo = function *foo(){};", - output: "var foo = function* foo(){};", - options: [{ before: false, after: true }], - errors: [unexpectedBeforeError, missingAfterError] - }, - { - code: "var foo = function *(){};", - output: "var foo = function* (){};", - options: [{ before: false, after: true }], - errors: [unexpectedBeforeError, missingAfterError] - }, - { - code: "var foo = { *foo(){} };", - output: "var foo = { * foo(){} };", - options: [{ before: false, after: true }], - errors: [missingAfterError] - }, - { - code: "class Foo { *foo(){} }", - output: "class Foo { * foo(){} }", - options: [{ before: false, after: true }], - errors: [missingAfterError] - }, - { - code: "class Foo { static *foo(){} }", - output: "class Foo { static* foo(){} }", - options: [{ before: false, after: true }], - errors: [unexpectedBeforeError, missingAfterError] - }, + // full configurability + { + code: "function*foo(){}", + output: "function * foo(){}", + options: [{ before: false, after: false, named: "both" }], + errors: [missingBeforeError, missingAfterError], + }, + { + code: "var foo = function*(){};", + output: "var foo = function * (){};", + options: [{ before: false, after: false, anonymous: "both" }], + errors: [missingBeforeError, missingAfterError], + }, + { + code: "class Foo { *foo(){} }", + output: "class Foo { * foo(){} }", + options: [{ before: false, after: false, method: "both" }], + errors: [missingAfterError], + }, + { + code: "var foo = { *foo(){} }", + output: "var foo = { * foo(){} }", + options: [{ before: false, after: false, method: "both" }], + errors: [missingAfterError], + }, + { + code: "var foo = { bar: function*() {} }", + output: "var foo = { bar: function * () {} }", + options: [{ before: false, after: false, anonymous: "both" }], + errors: [missingBeforeError, missingAfterError], + }, + { + code: "class Foo { static*foo(){} }", + output: "class Foo { static * foo(){} }", + options: [{ before: false, after: false, method: "both" }], + errors: [missingBeforeError, missingAfterError], + }, - // {"before": true, "after": true} - { - code: "function*foo(){}", - output: "function * foo(){}", - options: [{ before: true, after: true }], - errors: [missingBeforeError, missingAfterError] - }, - { - code: "function*foo(arg1, arg2){}", - output: "function * foo(arg1, arg2){}", - options: [{ before: true, after: true }], - errors: [missingBeforeError, missingAfterError] - }, - { - code: "var foo = function*foo(){};", - output: "var foo = function * foo(){};", - options: [{ before: true, after: true }], - errors: [missingBeforeError, missingAfterError] - }, - { - code: "var foo = function*(){};", - output: "var foo = function * (){};", - options: [{ before: true, after: true }], - errors: [missingBeforeError, missingAfterError] - }, - { - code: "var foo = {*foo(){} };", - output: "var foo = {* foo(){} };", - options: [{ before: true, after: true }], - errors: [missingAfterError] - }, - { - code: "class Foo {*foo(){} }", - output: "class Foo {* foo(){} }", - options: [{ before: true, after: true }], - errors: [missingAfterError] - }, - { - code: "class Foo { static*foo(){} }", - output: "class Foo { static * foo(){} }", - options: [{ before: true, after: true }], - errors: [missingBeforeError, missingAfterError] - }, + // default to top level "before" + { + code: "function*foo(){}", + output: "function *foo(){}", + options: [{ method: "both" }], + errors: [missingBeforeError], + }, - // {"before": false, "after": false} - { - code: "function * foo(){}", - output: "function*foo(){}", - options: [{ before: false, after: false }], - errors: [unexpectedBeforeError, unexpectedAfterError] - }, - { - code: "function * foo(arg1, arg2){}", - output: "function*foo(arg1, arg2){}", - options: [{ before: false, after: false }], - errors: [unexpectedBeforeError, unexpectedAfterError] - }, - { - code: "var foo = function * foo(){};", - output: "var foo = function*foo(){};", - options: [{ before: false, after: false }], - errors: [unexpectedBeforeError, unexpectedAfterError] - }, - { - code: "var foo = function * (){};", - output: "var foo = function*(){};", - options: [{ before: false, after: false }], - errors: [unexpectedBeforeError, unexpectedAfterError] - }, - { - code: "var foo = { * foo(){} };", - output: "var foo = { *foo(){} };", - options: [{ before: false, after: false }], - errors: [unexpectedAfterError] - }, - { - code: "class Foo { * foo(){} }", - output: "class Foo { *foo(){} }", - options: [{ before: false, after: false }], - errors: [unexpectedAfterError] - }, - { - code: "class Foo { static * foo(){} }", - output: "class Foo { static*foo(){} }", - options: [{ before: false, after: false }], - errors: [unexpectedBeforeError, unexpectedAfterError] - }, + // don't apply unrelated override + { + code: "function * foo(){}", + output: "function*foo(){}", + options: [{ before: false, after: false, method: "both" }], + errors: [unexpectedBeforeError, unexpectedAfterError], + }, - // full configurability - { - code: "function*foo(){}", - output: "function * foo(){}", - options: [{ before: false, after: false, named: "both" }], - errors: [missingBeforeError, missingAfterError] - }, - { - code: "var foo = function*(){};", - output: "var foo = function * (){};", - options: [{ before: false, after: false, anonymous: "both" }], - errors: [missingBeforeError, missingAfterError] - }, - { - code: "class Foo { *foo(){} }", - output: "class Foo { * foo(){} }", - options: [{ before: false, after: false, method: "both" }], - errors: [missingAfterError] - }, - { - code: "var foo = { *foo(){} }", - output: "var foo = { * foo(){} }", - options: [{ before: false, after: false, method: "both" }], - errors: [missingAfterError] - }, - { - code: "var foo = { bar: function*() {} }", - output: "var foo = { bar: function * () {} }", - options: [{ before: false, after: false, anonymous: "both" }], - errors: [missingBeforeError, missingAfterError] - }, - { - code: "class Foo { static*foo(){} }", - output: "class Foo { static * foo(){} }", - options: [{ before: false, after: false, method: "both" }], - errors: [missingBeforeError, missingAfterError] - }, + // ensure using object-type override works + { + code: "function*foo(){}", + output: "function * foo(){}", + options: [ + { + before: false, + after: false, + named: { before: true, after: true }, + }, + ], + errors: [missingBeforeError, missingAfterError], + }, - // default to top level "before" - { - code: "function*foo(){}", - output: "function *foo(){}", - options: [{ method: "both" }], - errors: [missingBeforeError] - }, - - // don't apply unrelated override - { - code: "function * foo(){}", - output: "function*foo(){}", - options: [{ before: false, after: false, method: "both" }], - errors: [unexpectedBeforeError, unexpectedAfterError] - }, - - // ensure using object-type override works - { - code: "function*foo(){}", - output: "function * foo(){}", - options: [{ before: false, after: false, named: { before: true, after: true } }], - errors: [missingBeforeError, missingAfterError] - }, - - // unspecified option uses default - { - code: "function*foo(){}", - output: "function *foo(){}", - options: [{ before: false, after: false, named: { before: true } }], - errors: [missingBeforeError] - }, - - // async generators - { - code: "({ async * foo(){} })", - output: "({ async*foo(){} })", - options: [{ before: false, after: false }], - errors: [unexpectedBeforeError, unexpectedAfterError] - }, - { - code: "({ async*foo(){} })", - output: "({ async * foo(){} })", - options: [{ before: true, after: true }], - errors: [missingBeforeError, missingAfterError] - }, - { - code: "class Foo { async * foo(){} }", - output: "class Foo { async*foo(){} }", - options: [{ before: false, after: false }], - errors: [unexpectedBeforeError, unexpectedAfterError] - }, - { - code: "class Foo { async*foo(){} }", - output: "class Foo { async * foo(){} }", - options: [{ before: true, after: true }], - errors: [missingBeforeError, missingAfterError] - }, - { - code: "class Foo { static async * foo(){} }", - output: "class Foo { static async*foo(){} }", - options: [{ before: false, after: false }], - errors: [unexpectedBeforeError, unexpectedAfterError] - }, - { - code: "class Foo { static async*foo(){} }", - output: "class Foo { static async * foo(){} }", - options: [{ before: true, after: true }], - errors: [missingBeforeError, missingAfterError] - } - - ] + // unspecified option uses default + { + code: "function*foo(){}", + output: "function *foo(){}", + options: [{ before: false, after: false, named: { before: true } }], + errors: [missingBeforeError], + }, + // async generators + { + code: "({ async * foo(){} })", + output: "({ async*foo(){} })", + options: [{ before: false, after: false }], + errors: [unexpectedBeforeError, unexpectedAfterError], + }, + { + code: "({ async*foo(){} })", + output: "({ async * foo(){} })", + options: [{ before: true, after: true }], + errors: [missingBeforeError, missingAfterError], + }, + { + code: "class Foo { async * foo(){} }", + output: "class Foo { async*foo(){} }", + options: [{ before: false, after: false }], + errors: [unexpectedBeforeError, unexpectedAfterError], + }, + { + code: "class Foo { async*foo(){} }", + output: "class Foo { async * foo(){} }", + options: [{ before: true, after: true }], + errors: [missingBeforeError, missingAfterError], + }, + { + code: "class Foo { static async * foo(){} }", + output: "class Foo { static async*foo(){} }", + options: [{ before: false, after: false }], + errors: [unexpectedBeforeError, unexpectedAfterError], + }, + { + code: "class Foo { static async*foo(){} }", + output: "class Foo { static async * foo(){} }", + options: [{ before: true, after: true }], + errors: [missingBeforeError, missingAfterError], + }, + ], }); diff --git a/tests/lib/rules/getter-return.js b/tests/lib/rules/getter-return.js index 189a9f870736..1e624ecafc88 100644 --- a/tests/lib/rules/getter-return.js +++ b/tests/lib/rules/getter-return.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/getter-return"); -const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); +const RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,317 +18,413 @@ const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2022 } }); const expectedError = { messageId: "expected", data: { name: "getter 'bar'" } }; -const expectedAlwaysError = { messageId: "expectedAlways", data: { name: "getter 'bar'" } }; +const expectedAlwaysError = { + messageId: "expectedAlways", + data: { name: "getter 'bar'" }, +}; const options = [{ allowImplicit: true }]; ruleTester.run("getter-return", rule, { + valid: [ + /* + * test obj: get + * option: {allowImplicit: false} + */ + "var foo = { get bar(){return true;} };", - valid: [ + // option: {allowImplicit: true} + { code: "var foo = { get bar() {return;} };", options }, + { code: "var foo = { get bar(){return true;} };", options }, + { + code: "var foo = { get bar(){if(bar) {return;} return true;} };", + options, + }, - /* - * test obj: get - * option: {allowImplicit: false} - */ - "var foo = { get bar(){return true;} };", + /* + * test class: get + * option: {allowImplicit: false} + */ + "class foo { get bar(){return true;} }", + "class foo { get bar(){if(baz){return true;} else {return false;} } }", + "class foo { get(){return true;} }", - // option: {allowImplicit: true} - { code: "var foo = { get bar() {return;} };", options }, - { code: "var foo = { get bar(){return true;} };", options }, - { code: "var foo = { get bar(){if(bar) {return;} return true;} };", options }, + // option: {allowImplicit: true} + { code: "class foo { get bar(){return true;} }", options }, + { code: "class foo { get bar(){return;} }", options }, - /* - * test class: get - * option: {allowImplicit: false} - */ - "class foo { get bar(){return true;} }", - "class foo { get bar(){if(baz){return true;} else {return false;} } }", - "class foo { get(){return true;} }", + /* + * test object.defineProperty(s) + * option: {allowImplicit: false} + */ + 'Object.defineProperty(foo, "bar", { get: function () {return true;}});', + 'Object.defineProperty(foo, "bar", { get: function () { ~function (){ return true; }();return true;}});', + "Object.defineProperties(foo, { bar: { get: function () {return true;}} });", + "Object.defineProperties(foo, { bar: { get: function () { ~function (){ return true; }(); return true;}} });", - // option: {allowImplicit: true} - { code: "class foo { get bar(){return true;} }", options }, - { code: "class foo { get bar(){return;} }", options }, + /* + * test reflect.defineProperty(s) + * option: {allowImplicit: false} + */ + 'Reflect.defineProperty(foo, "bar", { get: function () {return true;}});', + 'Reflect.defineProperty(foo, "bar", { get: function () { ~function (){ return true; }();return true;}});', - /* - * test object.defineProperty(s) - * option: {allowImplicit: false} - */ - "Object.defineProperty(foo, \"bar\", { get: function () {return true;}});", - "Object.defineProperty(foo, \"bar\", { get: function () { ~function (){ return true; }();return true;}});", - "Object.defineProperties(foo, { bar: { get: function () {return true;}} });", - "Object.defineProperties(foo, { bar: { get: function () { ~function (){ return true; }(); return true;}} });", + /* + * test object.create(s) + * option: {allowImplicit: false} + */ + "Object.create(foo, { bar: { get() {return true;} } });", + "Object.create(foo, { bar: { get: function () {return true;} } });", + "Object.create(foo, { bar: { get: () => {return true;} } });", - /* - * test reflect.defineProperty(s) - * option: {allowImplicit: false} - */ - "Reflect.defineProperty(foo, \"bar\", { get: function () {return true;}});", - "Reflect.defineProperty(foo, \"bar\", { get: function () { ~function (){ return true; }();return true;}});", + // option: {allowImplicit: true} + { + code: 'Object.defineProperty(foo, "bar", { get: function () {return true;}});', + options, + }, + { + code: 'Object.defineProperty(foo, "bar", { get: function (){return;}});', + options, + }, + { + code: "Object.defineProperties(foo, { bar: { get: function () {return true;}} });", + options, + }, + { + code: "Object.defineProperties(foo, { bar: { get: function () {return;}} });", + options, + }, + { + code: 'Reflect.defineProperty(foo, "bar", { get: function () {return true;}});', + options, + }, - /* - * test object.create(s) - * option: {allowImplicit: false} - */ - "Object.create(foo, { bar: { get() {return true;} } });", - "Object.create(foo, { bar: { get: function () {return true;} } });", - "Object.create(foo, { bar: { get: () => {return true;} } });", + // not getter. + "var get = function(){};", + "var get = function(){ return true; };", + "var foo = { bar(){} };", + "var foo = { bar(){ return true; } };", + "var foo = { bar: function(){} };", + "var foo = { bar: function(){return;} };", + "var foo = { bar: function(){return true;} };", + "var foo = { get: function () {} }", + "var foo = { get: () => {}};", + "class C { get; foo() {} }", + "foo.defineProperty(null, { get() {} });", + "foo.defineProperties(null, { bar: { get() {} } });", + "foo.create(null, { bar: { get() {} } });", + ], - // option: {allowImplicit: true} - { code: "Object.defineProperty(foo, \"bar\", { get: function () {return true;}});", options }, - { code: "Object.defineProperty(foo, \"bar\", { get: function (){return;}});", options }, - { code: "Object.defineProperties(foo, { bar: { get: function () {return true;}} });", options }, - { code: "Object.defineProperties(foo, { bar: { get: function () {return;}} });", options }, - { code: "Reflect.defineProperty(foo, \"bar\", { get: function () {return true;}});", options }, + invalid: [ + /* + * test obj: get + * option: {allowImplicit: false} + */ + { + code: "var foo = { get bar() {} };", + errors: [ + { + ...expectedError, + line: 1, + column: 13, + endLine: 1, + endColumn: 20, + }, + ], + }, + { + code: "var foo = { get\n bar () {} };", + errors: [ + { + ...expectedError, + line: 1, + column: 13, + endLine: 2, + endColumn: 6, + }, + ], + }, + { + code: "var foo = { get bar(){if(baz) {return true;}} };", + errors: [ + { + ...expectedAlwaysError, + line: 1, + column: 13, + endLine: 1, + endColumn: 20, + }, + ], + }, + { + code: "var foo = { get bar() { ~function () {return true;}} };", + errors: [ + { + ...expectedError, + line: 1, + column: 13, + endLine: 1, + endColumn: 20, + }, + ], + }, + { + code: "var foo = { get bar() { return; } };", + errors: [ + { + ...expectedError, + line: 1, + column: 25, + endLine: 1, + endColumn: 32, + }, + ], + }, - // not getter. - "var get = function(){};", - "var get = function(){ return true; };", - "var foo = { bar(){} };", - "var foo = { bar(){ return true; } };", - "var foo = { bar: function(){} };", - "var foo = { bar: function(){return;} };", - "var foo = { bar: function(){return true;} };", - "var foo = { get: function () {} }", - "var foo = { get: () => {}};", - "class C { get; foo() {} }", - "foo.defineProperty(null, { get() {} });", - "foo.defineProperties(null, { bar: { get() {} } });", - "foo.create(null, { bar: { get() {} } });" - ], + // option: {allowImplicit: true} + { + code: "var foo = { get bar() {} };", + options, + errors: [expectedError], + }, + { + code: "var foo = { get bar() {if (baz) {return;}} };", + options, + errors: [expectedAlwaysError], + }, - invalid: [ + /* + * test class: get + * option: {allowImplicit: false} + */ + { + code: "class foo { get bar(){} }", + errors: [ + { + ...expectedError, + line: 1, + column: 13, + endLine: 1, + endColumn: 20, + }, + ], + }, + { + code: "var foo = class {\n static get\nbar(){} }", + errors: [ + { + messageId: "expected", + data: { name: "static getter 'bar'" }, + line: 2, + column: 3, + endLine: 3, + endColumn: 4, + }, + ], + }, + { + code: "class foo { get bar(){ if (baz) { return true; }}}", + errors: [expectedAlwaysError], + }, + { + code: "class foo { get bar(){ ~function () { return true; }()}}", + errors: [expectedError], + }, - /* - * test obj: get - * option: {allowImplicit: false} - */ - { - code: "var foo = { get bar() {} };", - errors: [{ - ...expectedError, - line: 1, - column: 13, - endLine: 1, - endColumn: 20 - }] - }, - { - code: "var foo = { get\n bar () {} };", - errors: [{ - ...expectedError, - line: 1, - column: 13, - endLine: 2, - endColumn: 6 - }] - }, - { - code: "var foo = { get bar(){if(baz) {return true;}} };", - errors: [{ - ...expectedAlwaysError, - line: 1, - column: 13, - endLine: 1, - endColumn: 20 - }] - }, - { - code: "var foo = { get bar() { ~function () {return true;}} };", - errors: [{ - ...expectedError, - line: 1, - column: 13, - endLine: 1, - endColumn: 20 - }] - }, - { - code: "var foo = { get bar() { return; } };", - errors: [{ - ...expectedError, - line: 1, - column: 25, - endLine: 1, - endColumn: 32 - }] - }, + // option: {allowImplicit: true} + { code: "class foo { get bar(){} }", options, errors: [expectedError] }, + { + code: "class foo { get bar(){if (baz) {return true;} } }", + options, + errors: [expectedAlwaysError], + }, - // option: {allowImplicit: true} - { code: "var foo = { get bar() {} };", options, errors: [expectedError] }, - { code: "var foo = { get bar() {if (baz) {return;}} };", options, errors: [expectedAlwaysError] }, + /* + * test object.defineProperty(s) + * option: {allowImplicit: false} + */ + { + code: "Object.defineProperty(foo, 'bar', { get: function (){}});", + errors: [ + { + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 37, + endLine: 1, + endColumn: 51, + }, + ], + }, + { + code: "Object.defineProperty(foo, 'bar', { get: function getfoo (){}});", + errors: [ + { + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 37, + endLine: 1, + endColumn: 58, + }, + ], + }, + { + code: "Object.defineProperty(foo, 'bar', { get(){} });", + errors: [ + { + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 37, + endLine: 1, + endColumn: 40, + }, + ], + }, + { + code: "Object.defineProperty(foo, 'bar', { get: () => {}});", + errors: [ + { + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 37, + endLine: 1, + endColumn: 42, + }, + ], + }, + { + code: 'Object.defineProperty(foo, "bar", { get: function (){if(bar) {return true;}}});', + errors: [{ messageId: "expectedAlways" }], + }, + { + code: 'Object.defineProperty(foo, "bar", { get: function (){ ~function () { return true; }()}});', + errors: [{ messageId: "expected" }], + }, - /* - * test class: get - * option: {allowImplicit: false} - */ - { - code: "class foo { get bar(){} }", - errors: [{ - ...expectedError, - line: 1, - column: 13, - endLine: 1, - endColumn: 20 - }] - }, - { - code: "var foo = class {\n static get\nbar(){} }", - errors: [{ - messageId: "expected", - data: { name: "static getter 'bar'" }, - line: 2, - column: 3, - endLine: 3, - endColumn: 4 - }] - }, - { code: "class foo { get bar(){ if (baz) { return true; }}}", errors: [expectedAlwaysError] }, - { code: "class foo { get bar(){ ~function () { return true; }()}}", errors: [expectedError] }, + /* + * test reflect.defineProperty(s) + * option: {allowImplicit: false} + */ + { + code: "Reflect.defineProperty(foo, 'bar', { get: function (){}});", + errors: [ + { + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 38, + endLine: 1, + endColumn: 52, + }, + ], + }, - // option: {allowImplicit: true} - { code: "class foo { get bar(){} }", options, errors: [expectedError] }, - { code: "class foo { get bar(){if (baz) {return true;} } }", options, errors: [expectedAlwaysError] }, + /* + * test object.create(s) + * option: {allowImplicit: false} + */ + { + code: "Object.create(foo, { bar: { get: function() {} } })", + errors: [ + { + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 29, + endLine: 1, + endColumn: 42, + }, + ], + }, + { + code: "Object.create(foo, { bar: { get() {} } })", + errors: [ + { + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 29, + endLine: 1, + endColumn: 32, + }, + ], + }, + { + code: "Object.create(foo, { bar: { get: () => {} } })", + errors: [ + { + messageId: "expected", + data: { name: "method 'get'" }, + line: 1, + column: 29, + endLine: 1, + endColumn: 34, + }, + ], + }, - /* - * test object.defineProperty(s) - * option: {allowImplicit: false} - */ - { - code: "Object.defineProperty(foo, 'bar', { get: function (){}});", - errors: [{ - messageId: "expected", - data: { name: "method 'get'" }, - line: 1, - column: 37, - endLine: 1, - endColumn: 51 - }] - }, - { - code: "Object.defineProperty(foo, 'bar', { get: function getfoo (){}});", - errors: [{ - messageId: "expected", - data: { name: "method 'get'" }, - line: 1, - column: 37, - endLine: 1, - endColumn: 58 - }] - }, - { - code: "Object.defineProperty(foo, 'bar', { get(){} });", - errors: [{ - messageId: "expected", - data: { name: "method 'get'" }, - line: 1, - column: 37, - endLine: 1, - endColumn: 40 - }] - }, - { - code: "Object.defineProperty(foo, 'bar', { get: () => {}});", - errors: [{ - messageId: "expected", - data: { name: "method 'get'" }, - line: 1, - column: 37, - endLine: 1, - endColumn: 42 - }] - }, - { code: "Object.defineProperty(foo, \"bar\", { get: function (){if(bar) {return true;}}});", errors: [{ messageId: "expectedAlways" }] }, - { code: "Object.defineProperty(foo, \"bar\", { get: function (){ ~function () { return true; }()}});", errors: [{ messageId: "expected" }] }, + // option: {allowImplicit: true} + { + code: "Object.defineProperties(foo, { bar: { get: function () {}} });", + options, + errors: [{ messageId: "expected" }], + }, + { + code: "Object.defineProperties(foo, { bar: { get: function (){if(bar) {return true;}}}});", + options, + errors: [{ messageId: "expectedAlways" }], + }, + { + code: "Object.defineProperties(foo, { bar: { get: function () {~function () { return true; }()}} });", + options, + errors: [{ messageId: "expected" }], + }, + { + code: 'Object.defineProperty(foo, "bar", { get: function (){}});', + options, + errors: [{ messageId: "expected" }], + }, + { + code: "Object.create(foo, { bar: { get: function (){} } });", + options, + errors: [{ messageId: "expected" }], + }, + { + code: 'Reflect.defineProperty(foo, "bar", { get: function (){}});', + options, + errors: [{ messageId: "expected" }], + }, - /* - * test reflect.defineProperty(s) - * option: {allowImplicit: false} - */ - { - code: "Reflect.defineProperty(foo, 'bar', { get: function (){}});", - errors: [{ - messageId: "expected", - data: { name: "method 'get'" }, - line: 1, - column: 38, - endLine: 1, - endColumn: 52 - }] - }, - - /* - * test object.create(s) - * option: {allowImplicit: false} - */ - { - code: "Object.create(foo, { bar: { get: function() {} } })", - errors: [{ - messageId: "expected", - data: { name: "method 'get'" }, - line: 1, - column: 29, - endLine: 1, - endColumn: 42 - }] - }, - { - code: "Object.create(foo, { bar: { get() {} } })", - errors: [{ - messageId: "expected", - data: { name: "method 'get'" }, - line: 1, - column: 29, - endLine: 1, - endColumn: 32 - }] - }, - { - code: "Object.create(foo, { bar: { get: () => {} } })", - errors: [{ - messageId: "expected", - data: { name: "method 'get'" }, - line: 1, - column: 29, - endLine: 1, - endColumn: 34 - }] - }, - - // option: {allowImplicit: true} - { code: "Object.defineProperties(foo, { bar: { get: function () {}} });", options, errors: [{ messageId: "expected" }] }, - { code: "Object.defineProperties(foo, { bar: { get: function (){if(bar) {return true;}}}});", options, errors: [{ messageId: "expectedAlways" }] }, - { code: "Object.defineProperties(foo, { bar: { get: function () {~function () { return true; }()}} });", options, errors: [{ messageId: "expected" }] }, - { code: "Object.defineProperty(foo, \"bar\", { get: function (){}});", options, errors: [{ messageId: "expected" }] }, - { code: "Object.create(foo, { bar: { get: function (){} } });", options, errors: [{ messageId: "expected" }] }, - { code: "Reflect.defineProperty(foo, \"bar\", { get: function (){}});", options, errors: [{ messageId: "expected" }] }, - - // Optional chaining - { - code: "Object?.defineProperty(foo, 'bar', { get: function (){} });", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "expected", data: { name: "method 'get'" } }] - }, - { - code: "(Object?.defineProperty)(foo, 'bar', { get: function (){} });", - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "expected", data: { name: "method 'get'" } }] - }, - { - code: "Object?.defineProperty(foo, 'bar', { get: function (){} });", - options, - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "expected", data: { name: "method 'get'" } }] - }, - { - code: "(Object?.defineProperty)(foo, 'bar', { get: function (){} });", - options, - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "expected", data: { name: "method 'get'" } }] - }, - { - code: "(Object?.create)(foo, { bar: { get: function (){} } });", - options, - languageOptions: { ecmaVersion: 2020 }, - errors: [{ messageId: "expected", data: { name: "method 'get'" } }] - } - ] + // Optional chaining + { + code: "Object?.defineProperty(foo, 'bar', { get: function (){} });", + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "expected", data: { name: "method 'get'" } }], + }, + { + code: "(Object?.defineProperty)(foo, 'bar', { get: function (){} });", + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "expected", data: { name: "method 'get'" } }], + }, + { + code: "Object?.defineProperty(foo, 'bar', { get: function (){} });", + options, + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "expected", data: { name: "method 'get'" } }], + }, + { + code: "(Object?.defineProperty)(foo, 'bar', { get: function (){} });", + options, + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "expected", data: { name: "method 'get'" } }], + }, + { + code: "(Object?.create)(foo, { bar: { get: function (){} } });", + options, + languageOptions: { ecmaVersion: 2020 }, + errors: [{ messageId: "expected", data: { name: "method 'get'" } }], + }, + ], }); diff --git a/tests/lib/rules/global-require.js b/tests/lib/rules/global-require.js index 7a97cbbbe1ed..1b6db9e71571 100644 --- a/tests/lib/rules/global-require.js +++ b/tests/lib/rules/global-require.js @@ -10,85 +10,88 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/global-require"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, }); const valid = [ - { code: "var x = require('y');" }, - { code: "if (x) { x.require('y'); }" }, - { code: "var x;\nx = require('y');" }, - { code: "var x = 1, y = require('y');" }, - { code: "var x = require('y'), y = require('y'), z = require('z');" }, - { code: "var x = require('y').foo;" }, - { code: "require('y').foo();" }, - { code: "require('y');" }, - { code: "function x(){}\n\n\nx();\n\n\nif (x > y) {\n\tdoSomething()\n\n}\n\nvar x = require('y').foo;" }, - { code: "var logger = require(DEBUG ? 'dev-logger' : 'logger');" }, - { code: "var logger = DEBUG ? require('dev-logger') : require('logger');" }, - { code: "function localScopedRequire(require) { require('y'); }" }, - { code: "var someFunc = require('./someFunc'); someFunc(function(require) { return('bananas'); });" }, + { code: "var x = require('y');" }, + { code: "if (x) { x.require('y'); }" }, + { code: "var x;\nx = require('y');" }, + { code: "var x = 1, y = require('y');" }, + { code: "var x = require('y'), y = require('y'), z = require('z');" }, + { code: "var x = require('y').foo;" }, + { code: "require('y').foo();" }, + { code: "require('y');" }, + { + code: "function x(){}\n\n\nx();\n\n\nif (x > y) {\n\tdoSomething()\n\n}\n\nvar x = require('y').foo;", + }, + { code: "var logger = require(DEBUG ? 'dev-logger' : 'logger');" }, + { code: "var logger = DEBUG ? require('dev-logger') : require('logger');" }, + { code: "function localScopedRequire(require) { require('y'); }" }, + { + code: "var someFunc = require('./someFunc'); someFunc(function(require) { return('bananas'); });", + }, - // Optional chaining - { - code: "var x = require('y')?.foo;", - languageOptions: { ecmaVersion: 2020 } - } + // Optional chaining + { + code: "var x = require('y')?.foo;", + languageOptions: { ecmaVersion: 2020 }, + }, ]; const error = { messageId: "unexpected", type: "CallExpression" }; const invalid = [ + // block statements + { + code: "if (process.env.NODE_ENV === 'DEVELOPMENT') {\n\trequire('debug');\n}", + errors: [error], + }, + { + code: "var x; if (y) { x = require('debug'); }", + errors: [error], + }, + { + code: "var x; if (y) { x = require('debug').baz; }", + errors: [error], + }, + { + code: "function x() { require('y') }", + errors: [error], + }, + { + code: "try { require('x'); } catch (e) { console.log(e); }", + errors: [error], + }, - // block statements - { - code: "if (process.env.NODE_ENV === 'DEVELOPMENT') {\n\trequire('debug');\n}", - errors: [error] - }, - { - code: "var x; if (y) { x = require('debug'); }", - errors: [error] - }, - { - code: "var x; if (y) { x = require('debug').baz; }", - errors: [error] - }, - { - code: "function x() { require('y') }", - errors: [error] - }, - { - code: "try { require('x'); } catch (e) { console.log(e); }", - errors: [error] - }, - - // non-block statements - { - code: "var getModule = x => require(x);", - languageOptions: { ecmaVersion: 6 }, - errors: [error] - }, - { - code: "var x = (x => require(x))('weird')", - languageOptions: { ecmaVersion: 6 }, - errors: [error] - }, - { - code: "switch(x) { case '1': require('1'); break; }", - errors: [error] - } + // non-block statements + { + code: "var getModule = x => require(x);", + languageOptions: { ecmaVersion: 6 }, + errors: [error], + }, + { + code: "var x = (x => require(x))('weird')", + languageOptions: { ecmaVersion: 6 }, + errors: [error], + }, + { + code: "switch(x) { case '1': require('1'); break; }", + errors: [error], + }, ]; ruleTester.run("global-require", rule, { - valid, - invalid + valid, + invalid, }); diff --git a/tests/lib/rules/grouped-accessor-pairs.js b/tests/lib/rules/grouped-accessor-pairs.js index be7ce3923512..7d2bc849b71b 100644 --- a/tests/lib/rules/grouped-accessor-pairs.js +++ b/tests/lib/rules/grouped-accessor-pairs.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/grouped-accessor-pairs"); -const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); +const RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -19,457 +19,1018 @@ const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 2022 } }); ruleTester.run("grouped-accessor-pairs", rule, { - valid: [ + valid: [ + // no accessors + "({})", + "({ a })", + "({ a(){}, b(){}, a(){} })", + "({ a: 1, b: 2 })", + "({ a, ...b, c: 1 })", + "({ a, b, ...a })", + "({ a: 1, [b]: 2, a: 3, [b]: 4 })", + "({ a: function get(){}, b, a: function set(foo){} })", + "({ get(){}, a, set(){} })", + "class A {}", + "(class { a(){} })", + "class A { a(){} [b](){} a(){} [b](){} }", + "(class { a(){} b(){} static a(){} static b(){} })", + "class A { get(){} a(){} set(){} }", - // no accessors - "({})", - "({ a })", - "({ a(){}, b(){}, a(){} })", - "({ a: 1, b: 2 })", - "({ a, ...b, c: 1 })", - "({ a, b, ...a })", - "({ a: 1, [b]: 2, a: 3, [b]: 4 })", - "({ a: function get(){}, b, a: function set(foo){} })", - "({ get(){}, a, set(){} })", - "class A {}", - "(class { a(){} })", - "class A { a(){} [b](){} a(){} [b](){} }", - "(class { a(){} b(){} static a(){} static b(){} })", - "class A { get(){} a(){} set(){} }", + // no accessor pairs + "({ get a(){} })", + "({ set a(foo){} })", + "({ a: 1, get b(){}, c, ...d })", + "({ get a(){}, get b(){}, set c(foo){}, set d(foo){} })", + "({ get a(){}, b: 1, set c(foo){} })", + "({ set a(foo){}, b: 1, a: 2 })", + "({ get a(){}, b: 1, a })", + "({ set a(foo){}, b: 1, a(){} })", + "({ get a(){}, b: 1, set [a](foo){} })", + "({ set a(foo){}, b: 1, get 'a '(){} })", + "({ get a(){}, b: 1, ...a })", + "({ set a(foo){}, b: 1 }, { get a(){} })", + "({ get a(){}, b: 1, ...{ set a(foo){} } })", + { + code: "({ set a(foo){}, get b(){} })", + options: ["getBeforeSet"], + }, + { + code: "({ get a(){}, set b(foo){} })", + options: ["setBeforeGet"], + }, + "class A { get a(){} }", + "(class { set a(foo){} })", + "class A { static set a(foo){} }", + "(class { static get a(){} })", + "class A { a(){} set b(foo){} c(){} }", + "(class { a(){} get b(){} c(){} })", + "class A { get a(){} static get b(){} set c(foo){} static set d(bar){} }", + "(class { get a(){} b(){} a(foo){} })", + "class A { static set a(foo){} b(){} static a(){} }", + "(class { get a(){} static b(){} set [a](foo){} })", + "class A { static set a(foo){} b(){} static get ' a'(){} }", + "(class { set a(foo){} b(){} static get a(){} })", + "class A { static set a(foo){} b(){} get a(){} }", + "(class { get a(){} }, class { b(){} set a(foo){} })", - // no accessor pairs - "({ get a(){} })", - "({ set a(foo){} })", - "({ a: 1, get b(){}, c, ...d })", - "({ get a(){}, get b(){}, set c(foo){}, set d(foo){} })", - "({ get a(){}, b: 1, set c(foo){} })", - "({ set a(foo){}, b: 1, a: 2 })", - "({ get a(){}, b: 1, a })", - "({ set a(foo){}, b: 1, a(){} })", - "({ get a(){}, b: 1, set [a](foo){} })", - "({ set a(foo){}, b: 1, get 'a '(){} })", - "({ get a(){}, b: 1, ...a })", - "({ set a(foo){}, b: 1 }, { get a(){} })", - "({ get a(){}, b: 1, ...{ set a(foo){} } })", - { - code: "({ set a(foo){}, get b(){} })", - options: ["getBeforeSet"] - }, - { - code: "({ get a(){}, set b(foo){} })", - options: ["setBeforeGet"] - }, - "class A { get a(){} }", - "(class { set a(foo){} })", - "class A { static set a(foo){} }", - "(class { static get a(){} })", - "class A { a(){} set b(foo){} c(){} }", - "(class { a(){} get b(){} c(){} })", - "class A { get a(){} static get b(){} set c(foo){} static set d(bar){} }", - "(class { get a(){} b(){} a(foo){} })", - "class A { static set a(foo){} b(){} static a(){} }", - "(class { get a(){} static b(){} set [a](foo){} })", - "class A { static set a(foo){} b(){} static get ' a'(){} }", - "(class { set a(foo){} b(){} static get a(){} })", - "class A { static set a(foo){} b(){} get a(){} }", - "(class { get a(){} }, class { b(){} set a(foo){} })", + // correct grouping + "({ get a(){}, set a(foo){} })", + "({ a: 1, set b(foo){}, get b(){}, c: 2 })", + "({ get a(){}, set a(foo){}, set b(bar){}, get b(){} })", + "({ get [a](){}, set [a](foo){} })", + "({ set a(foo){}, get 'a'(){} })", + "({ a: 1, b: 2, get a(){}, set a(foo){}, c: 3, a: 4 })", + "({ get a(){}, set a(foo){}, set b(bar){} })", + "({ get a(){}, get b(){}, set b(bar){} })", + "class A { get a(){} set a(foo){} }", + "(class { set a(foo){} get a(){} })", + "class A { static set a(foo){} static get a(){} }", + "(class { static get a(){} static set a(foo){} })", + "class A { a(){} set b(foo){} get b(){} c(){} get d(){} set d(bar){} }", + "(class { set a(foo){} get a(){} get b(){} set b(bar){} })", + "class A { static set [a](foo){} static get [a](){} }", + "(class { get a(){} set [`a`](foo){} })", + "class A { static get a(){} static set a(foo){} set a(bar){} static get a(){} }", + "(class { static get a(){} get a(){} set a(foo){} })", - // correct grouping - "({ get a(){}, set a(foo){} })", - "({ a: 1, set b(foo){}, get b(){}, c: 2 })", - "({ get a(){}, set a(foo){}, set b(bar){}, get b(){} })", - "({ get [a](){}, set [a](foo){} })", - "({ set a(foo){}, get 'a'(){} })", - "({ a: 1, b: 2, get a(){}, set a(foo){}, c: 3, a: 4 })", - "({ get a(){}, set a(foo){}, set b(bar){} })", - "({ get a(){}, get b(){}, set b(bar){} })", - "class A { get a(){} set a(foo){} }", - "(class { set a(foo){} get a(){} })", - "class A { static set a(foo){} static get a(){} }", - "(class { static get a(){} static set a(foo){} })", - "class A { a(){} set b(foo){} get b(){} c(){} get d(){} set d(bar){} }", - "(class { set a(foo){} get a(){} get b(){} set b(bar){} })", - "class A { static set [a](foo){} static get [a](){} }", - "(class { get a(){} set [`a`](foo){} })", - "class A { static get a(){} static set a(foo){} set a(bar){} static get a(){} }", - "(class { static get a(){} get a(){} set a(foo){} })", + // correct order + { + code: "({ get a(){}, set a(foo){} })", + options: ["anyOrder"], + }, + { + code: "({ set a(foo){}, get a(){} })", + options: ["anyOrder"], + }, + { + code: "({ get a(){}, set a(foo){} })", + options: ["getBeforeSet"], + }, + { + code: "({ set a(foo){}, get a(){} })", + options: ["setBeforeGet"], + }, + { + code: "class A { get a(){} set a(foo){} }", + options: ["anyOrder"], + }, + { + code: "(class { set a(foo){} get a(){} })", + options: ["anyOrder"], + }, + { + code: "class A { get a(){} set a(foo){} }", + options: ["getBeforeSet"], + }, + { + code: "(class { static set a(foo){} static get a(){} })", + options: ["setBeforeGet"], + }, - // correct order - { - code: "({ get a(){}, set a(foo){} })", - options: ["anyOrder"] - }, - { - code: "({ set a(foo){}, get a(){} })", - options: ["anyOrder"] - }, - { - code: "({ get a(){}, set a(foo){} })", - options: ["getBeforeSet"] - }, - { - code: "({ set a(foo){}, get a(){} })", - options: ["setBeforeGet"] - }, - { - code: "class A { get a(){} set a(foo){} }", - options: ["anyOrder"] - }, - { - code: "(class { set a(foo){} get a(){} })", - options: ["anyOrder"] - }, - { - code: "class A { get a(){} set a(foo){} }", - options: ["getBeforeSet"] - }, - { - code: "(class { static set a(foo){} static get a(){} })", - options: ["setBeforeGet"] - }, + // ignores properties with duplicate getters/setters + "({ get a(){}, b: 1, get a(){} })", + "({ set a(foo){}, b: 1, set a(foo){} })", + "({ get a(){}, b: 1, set a(foo){}, c: 2, get a(){} })", + "({ set a(foo){}, b: 1, set 'a'(bar){}, c: 2, get a(){} })", + "class A { get [a](){} b(){} get [a](){} c(){} set [a](foo){} }", + "(class { static set a(foo){} b(){} static get a(){} static c(){} static set a(bar){} })", - // ignores properties with duplicate getters/setters - "({ get a(){}, b: 1, get a(){} })", - "({ set a(foo){}, b: 1, set a(foo){} })", - "({ get a(){}, b: 1, set a(foo){}, c: 2, get a(){} })", - "({ set a(foo){}, b: 1, set 'a'(bar){}, c: 2, get a(){} })", - "class A { get [a](){} b(){} get [a](){} c(){} set [a](foo){} }", - "(class { static set a(foo){} b(){} static get a(){} static c(){} static set a(bar){} })", + // public and private + "class A { get '#abc'(){} b(){} set #abc(foo){} }", + "class A { get #abc(){} b(){} set '#abc'(foo){} }", + { + code: "class A { set '#abc'(foo){} get #abc(){} }", + options: ["getBeforeSet"], + }, + { + code: "class A { set #abc(foo){} get '#abc'(){} }", + options: ["getBeforeSet"], + }, + ], - // public and private - "class A { get '#abc'(){} b(){} set #abc(foo){} }", - "class A { get #abc(){} b(){} set '#abc'(foo){} }", - { - code: "class A { set '#abc'(foo){} get #abc(){} }", - options: ["getBeforeSet"] - }, - { - code: "class A { set #abc(foo){} get '#abc'(){} }", - options: ["getBeforeSet"] - } - ], + invalid: [ + // basic grouping tests with full messages + { + code: "({ get a(){}, b:1, set a(foo){} })", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "Property", + column: 20, + }, + ], + }, + { + code: "({ set 'abc'(foo){}, b:1, get 'abc'(){} })", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "setter 'abc'", + latterName: "getter 'abc'", + }, + type: "Property", + column: 27, + }, + ], + }, + { + code: "({ get [a](){}, b:1, set [a](foo){} })", + errors: [ + { + messageId: "notGrouped", + data: { formerName: "getter", latterName: "setter" }, + type: "Property", + column: 22, + }, + ], + }, + { + code: "class A { get abc(){} b(){} set abc(foo){} }", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'abc'", + latterName: "setter 'abc'", + }, + type: "MethodDefinition", + column: 29, + }, + ], + }, + { + code: "(class { set abc(foo){} b(){} get abc(){} })", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "setter 'abc'", + latterName: "getter 'abc'", + }, + type: "MethodDefinition", + column: 31, + }, + ], + }, + { + code: "class A { static set a(foo){} b(){} static get a(){} }", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "static setter 'a'", + latterName: "static getter 'a'", + }, + type: "MethodDefinition", + column: 37, + }, + ], + }, + { + code: "(class { static get 123(){} b(){} static set 123(foo){} })", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "static getter '123'", + latterName: "static setter '123'", + }, + type: "MethodDefinition", + column: 35, + }, + ], + }, + { + code: "class A { static get [a](){} b(){} static set [a](foo){} }", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "static getter", + latterName: "static setter", + }, + type: "MethodDefinition", + column: 36, + }, + ], + }, + { + code: "class A { get '#abc'(){} b(){} set '#abc'(foo){} }", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter '#abc'", + latterName: "setter '#abc'", + }, + type: "MethodDefinition", + column: 32, + }, + ], + }, + { + code: "class A { get #abc(){} b(){} set #abc(foo){} }", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "private getter #abc", + latterName: "private setter #abc", + }, + type: "MethodDefinition", + column: 30, + }, + ], + }, - invalid: [ + // basic ordering tests with full messages + { + code: "({ set a(foo){}, get a(){} })", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { + latterName: "getter 'a'", + formerName: "setter 'a'", + }, + type: "Property", + column: 18, + }, + ], + }, + { + code: "({ get 123(){}, set 123(foo){} })", + options: ["setBeforeGet"], + errors: [ + { + messageId: "invalidOrder", + data: { + latterName: "setter '123'", + formerName: "getter '123'", + }, + type: "Property", + column: 17, + }, + ], + }, + { + code: "({ get [a](){}, set [a](foo){} })", + options: ["setBeforeGet"], + errors: [ + { + messageId: "invalidOrder", + data: { latterName: "setter", formerName: "getter" }, + type: "Property", + column: 17, + }, + ], + }, + { + code: "class A { set abc(foo){} get abc(){} }", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { + latterName: "getter 'abc'", + formerName: "setter 'abc'", + }, + type: "MethodDefinition", + column: 26, + }, + ], + }, + { + code: "(class { get [`abc`](){} set [`abc`](foo){} })", + options: ["setBeforeGet"], + errors: [ + { + messageId: "invalidOrder", + data: { + latterName: "setter 'abc'", + formerName: "getter 'abc'", + }, + type: "MethodDefinition", + column: 26, + }, + ], + }, + { + code: "class A { static get a(){} static set a(foo){} }", + options: ["setBeforeGet"], + errors: [ + { + messageId: "invalidOrder", + data: { + latterName: "static setter 'a'", + formerName: "static getter 'a'", + }, + type: "MethodDefinition", + column: 28, + }, + ], + }, + { + code: "(class { static set 'abc'(foo){} static get 'abc'(){} })", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { + latterName: "static getter 'abc'", + formerName: "static setter 'abc'", + }, + type: "MethodDefinition", + column: 34, + }, + ], + }, + { + code: "class A { static set [abc](foo){} static get [abc](){} }", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { + formerName: "static setter", + latterName: "static getter", + }, + type: "MethodDefinition", + column: 35, + }, + ], + }, + { + code: "class A { set '#abc'(foo){} get '#abc'(){} }", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { + latterName: "getter '#abc'", + formerName: "setter '#abc'", + }, + type: "MethodDefinition", + column: 29, + }, + ], + }, + { + code: "class A { set #abc(foo){} get #abc(){} }", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { + latterName: "private getter #abc", + formerName: "private setter #abc", + }, + type: "MethodDefinition", + column: 27, + }, + ], + }, - // basic grouping tests with full messages - { - code: "({ get a(){}, b:1, set a(foo){} })", - errors: [{ messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "Property", column: 20 }] - }, - { - code: "({ set 'abc'(foo){}, b:1, get 'abc'(){} })", - errors: [{ messageId: "notGrouped", data: { formerName: "setter 'abc'", latterName: "getter 'abc'" }, type: "Property", column: 27 }] - }, - { - code: "({ get [a](){}, b:1, set [a](foo){} })", - errors: [{ messageId: "notGrouped", data: { formerName: "getter", latterName: "setter" }, type: "Property", column: 22 }] - }, - { - code: "class A { get abc(){} b(){} set abc(foo){} }", - errors: [{ messageId: "notGrouped", data: { formerName: "getter 'abc'", latterName: "setter 'abc'" }, type: "MethodDefinition", column: 29 }] - }, - { - code: "(class { set abc(foo){} b(){} get abc(){} })", - errors: [{ messageId: "notGrouped", data: { formerName: "setter 'abc'", latterName: "getter 'abc'" }, type: "MethodDefinition", column: 31 }] - }, - { - code: "class A { static set a(foo){} b(){} static get a(){} }", - errors: [{ messageId: "notGrouped", data: { formerName: "static setter 'a'", latterName: "static getter 'a'" }, type: "MethodDefinition", column: 37 }] - }, - { - code: "(class { static get 123(){} b(){} static set 123(foo){} })", - errors: [{ messageId: "notGrouped", data: { formerName: "static getter '123'", latterName: "static setter '123'" }, type: "MethodDefinition", column: 35 }] - }, - { - code: "class A { static get [a](){} b(){} static set [a](foo){} }", - errors: [{ messageId: "notGrouped", data: { formerName: "static getter", latterName: "static setter" }, type: "MethodDefinition", column: 36 }] - }, - { - code: "class A { get '#abc'(){} b(){} set '#abc'(foo){} }", - errors: [{ messageId: "notGrouped", data: { formerName: "getter '#abc'", latterName: "setter '#abc'" }, type: "MethodDefinition", column: 32 }] - }, - { - code: "class A { get #abc(){} b(){} set #abc(foo){} }", - errors: [{ messageId: "notGrouped", data: { formerName: "private getter #abc", latterName: "private setter #abc" }, type: "MethodDefinition", column: 30 }] - }, + // ordering option does not affect the grouping check + { + code: "({ get a(){}, b: 1, set a(foo){} })", + options: ["anyOrder"], + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "Property", + }, + ], + }, + { + code: "({ get a(){}, b: 1, set a(foo){} })", + options: ["setBeforeGet"], + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "Property", + }, + ], + }, + { + code: "({ get a(){}, b: 1, set a(foo){} })", + options: ["getBeforeSet"], + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "Property", + }, + ], + }, + { + code: "class A { set a(foo){} b(){} get a(){} }", + options: ["getBeforeSet"], + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "setter 'a'", + latterName: "getter 'a'", + }, + type: "MethodDefinition", + }, + ], + }, + { + code: "(class { static set a(foo){} b(){} static get a(){} })", + options: ["setBeforeGet"], + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "static setter 'a'", + latterName: "static getter 'a'", + }, + type: "MethodDefinition", + }, + ], + }, - // basic ordering tests with full messages - { - code: "({ set a(foo){}, get a(){} })", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { latterName: "getter 'a'", formerName: "setter 'a'" }, type: "Property", column: 18 }] - }, - { - code: "({ get 123(){}, set 123(foo){} })", - options: ["setBeforeGet"], - errors: [{ messageId: "invalidOrder", data: { latterName: "setter '123'", formerName: "getter '123'" }, type: "Property", column: 17 }] - }, - { - code: "({ get [a](){}, set [a](foo){} })", - options: ["setBeforeGet"], - errors: [{ messageId: "invalidOrder", data: { latterName: "setter", formerName: "getter" }, type: "Property", column: 17 }] - }, - { - code: "class A { set abc(foo){} get abc(){} }", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { latterName: "getter 'abc'", formerName: "setter 'abc'" }, type: "MethodDefinition", column: 26 }] - }, - { - code: "(class { get [`abc`](){} set [`abc`](foo){} })", - options: ["setBeforeGet"], - errors: [{ messageId: "invalidOrder", data: { latterName: "setter 'abc'", formerName: "getter 'abc'" }, type: "MethodDefinition", column: 26 }] - }, - { - code: "class A { static get a(){} static set a(foo){} }", - options: ["setBeforeGet"], - errors: [{ messageId: "invalidOrder", data: { latterName: "static setter 'a'", formerName: "static getter 'a'" }, type: "MethodDefinition", column: 28 }] - }, - { - code: "(class { static set 'abc'(foo){} static get 'abc'(){} })", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { latterName: "static getter 'abc'", formerName: "static setter 'abc'" }, type: "MethodDefinition", column: 34 }] - }, - { - code: "class A { static set [abc](foo){} static get [abc](){} }", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { formerName: "static setter", latterName: "static getter" }, type: "MethodDefinition", column: 35 }] - }, - { - code: "class A { set '#abc'(foo){} get '#abc'(){} }", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { latterName: "getter '#abc'", formerName: "setter '#abc'" }, type: "MethodDefinition", column: 29 }] - }, - { - code: "class A { set #abc(foo){} get #abc(){} }", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { latterName: "private getter #abc", formerName: "private setter #abc" }, type: "MethodDefinition", column: 27 }] - }, + // various kinds of keys + { + code: "({ get 'abc'(){}, d(){}, set 'abc'(foo){} })", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'abc'", + latterName: "setter 'abc'", + }, + type: "Property", + }, + ], + }, + { + code: "({ set ''(foo){}, get [''](){} })", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { formerName: "setter ''", latterName: "getter ''" }, + type: "Property", + }, + ], + }, + { + code: "class A { set abc(foo){} get 'abc'(){} }", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { + formerName: "setter 'abc'", + latterName: "getter 'abc'", + }, + type: "MethodDefinition", + }, + ], + }, + { + code: "(class { set [`abc`](foo){} get abc(){} })", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { + formerName: "setter 'abc'", + latterName: "getter 'abc'", + }, + type: "MethodDefinition", + }, + ], + }, + { + code: "({ set ['abc'](foo){}, get [`abc`](){} })", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { + formerName: "setter 'abc'", + latterName: "getter 'abc'", + }, + type: "Property", + }, + ], + }, + { + code: "({ set 123(foo){}, get [123](){} })", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { + formerName: "setter '123'", + latterName: "getter '123'", + }, + type: "Property", + }, + ], + }, + { + code: "class A { static set '123'(foo){} static get 123(){} }", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { + formerName: "static setter '123'", + latterName: "static getter '123'", + }, + type: "MethodDefinition", + }, + ], + }, + { + code: "(class { set [a+b](foo){} get [a+b](){} })", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { formerName: "setter", latterName: "getter" }, + type: "MethodDefinition", + }, + ], + }, + { + code: "({ set [f(a)](foo){}, get [f(a)](){} })", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { formerName: "setter", latterName: "getter" }, + type: "Property", + }, + ], + }, - // ordering option does not affect the grouping check - { - code: "({ get a(){}, b: 1, set a(foo){} })", - options: ["anyOrder"], - errors: [{ messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "Property" }] - }, - { - code: "({ get a(){}, b: 1, set a(foo){} })", - options: ["setBeforeGet"], - errors: [{ messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "Property" }] - }, - { - code: "({ get a(){}, b: 1, set a(foo){} })", - options: ["getBeforeSet"], - errors: [{ messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "Property" }] - }, - { - code: "class A { set a(foo){} b(){} get a(){} }", - options: ["getBeforeSet"], - errors: [{ messageId: "notGrouped", data: { formerName: "setter 'a'", latterName: "getter 'a'" }, type: "MethodDefinition" }] - }, - { - code: "(class { static set a(foo){} b(){} static get a(){} })", - options: ["setBeforeGet"], - errors: [{ messageId: "notGrouped", data: { formerName: "static setter 'a'", latterName: "static getter 'a'" }, type: "MethodDefinition" }] - }, + // multiple invalid + { + code: "({ get a(){}, b: 1, set a(foo){}, set c(foo){}, d(){}, get c(){} })", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "Property", + column: 21, + }, + { + messageId: "notGrouped", + data: { + formerName: "setter 'c'", + latterName: "getter 'c'", + }, + type: "Property", + column: 56, + }, + ], + }, + { + code: "({ get a(){}, set b(foo){}, set a(bar){}, get b(){} })", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "Property", + column: 29, + }, + { + messageId: "notGrouped", + data: { + formerName: "setter 'b'", + latterName: "getter 'b'", + }, + type: "Property", + column: 43, + }, + ], + }, + { + code: "({ get a(){}, set [a](foo){}, set a(bar){}, get [a](){} })", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "Property", + column: 31, + }, + { + messageId: "notGrouped", + data: { formerName: "setter", latterName: "getter" }, + type: "Property", + column: 45, + }, + ], + }, + { + code: "({ a(){}, set b(foo){}, ...c, get b(){}, set c(bar){}, get c(){} })", + options: ["getBeforeSet"], + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "setter 'b'", + latterName: "getter 'b'", + }, + type: "Property", + column: 31, + }, + { + messageId: "invalidOrder", + data: { + formerName: "setter 'c'", + latterName: "getter 'c'", + }, + type: "Property", + column: 56, + }, + ], + }, + { + code: "({ set [a](foo){}, get [a](){}, set [-a](bar){}, get [-a](){} })", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { formerName: "setter", latterName: "getter" }, + type: "Property", + column: 20, + }, + { + messageId: "invalidOrder", + data: { formerName: "setter", latterName: "getter" }, + type: "Property", + column: 50, + }, + ], + }, + { + code: "class A { get a(){} constructor (){} set a(foo){} get b(){} static c(){} set b(bar){} }", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "MethodDefinition", + column: 38, + }, + { + messageId: "notGrouped", + data: { + formerName: "getter 'b'", + latterName: "setter 'b'", + }, + type: "MethodDefinition", + column: 74, + }, + ], + }, + { + code: "(class { set a(foo){} static get a(){} get a(){} static set a(bar){} })", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "setter 'a'", + latterName: "getter 'a'", + }, + type: "MethodDefinition", + column: 40, + }, + { + messageId: "notGrouped", + data: { + formerName: "static getter 'a'", + latterName: "static setter 'a'", + }, + type: "MethodDefinition", + column: 50, + }, + ], + }, + { + code: "class A { get a(){} set a(foo){} static get b(){} static set b(bar){} }", + options: ["setBeforeGet"], + errors: [ + { + messageId: "invalidOrder", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "MethodDefinition", + column: 21, + }, + { + messageId: "invalidOrder", + data: { + formerName: "static getter 'b'", + latterName: "static setter 'b'", + }, + type: "MethodDefinition", + column: 51, + }, + ], + }, + { + code: "(class { set [a+b](foo){} get [a-b](){} get [a+b](){} set [a-b](bar){} })", + errors: [ + { + messageId: "notGrouped", + data: { formerName: "setter", latterName: "getter" }, + type: "MethodDefinition", + column: 41, + }, + { + messageId: "notGrouped", + data: { formerName: "getter", latterName: "setter" }, + type: "MethodDefinition", + column: 55, + }, + ], + }, - // various kinds of keys - { - code: "({ get 'abc'(){}, d(){}, set 'abc'(foo){} })", - errors: [{ messageId: "notGrouped", data: { formerName: "getter 'abc'", latterName: "setter 'abc'" }, type: "Property" }] - }, - { - code: "({ set ''(foo){}, get [''](){} })", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { formerName: "setter ''", latterName: "getter ''" }, type: "Property" }] - }, - { - code: "class A { set abc(foo){} get 'abc'(){} }", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { formerName: "setter 'abc'", latterName: "getter 'abc'" }, type: "MethodDefinition" }] - }, - { - code: "(class { set [`abc`](foo){} get abc(){} })", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { formerName: "setter 'abc'", latterName: "getter 'abc'" }, type: "MethodDefinition" }] - }, - { - code: "({ set ['abc'](foo){}, get [`abc`](){} })", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { formerName: "setter 'abc'", latterName: "getter 'abc'" }, type: "Property" }] - }, - { - code: "({ set 123(foo){}, get [123](){} })", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { formerName: "setter '123'", latterName: "getter '123'" }, type: "Property" }] - }, - { - code: "class A { static set '123'(foo){} static get 123(){} }", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { formerName: "static setter '123'", latterName: "static getter '123'" }, type: "MethodDefinition" }] - }, - { - code: "(class { set [a+b](foo){} get [a+b](){} })", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { formerName: "setter", latterName: "getter" }, type: "MethodDefinition" }] - }, - { - code: "({ set [f(a)](foo){}, get [f(a)](){} })", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { formerName: "setter", latterName: "getter" }, type: "Property" }] - }, + // combinations of valid and invalid + { + code: "({ get a(){}, set a(foo){}, get b(){}, c: function(){}, set b(bar){} })", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'b'", + latterName: "setter 'b'", + }, + type: "Property", + column: 57, + }, + ], + }, + { + code: "({ get a(){}, get b(){}, set a(foo){} })", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "Property", + column: 26, + }, + ], + }, + { + code: "({ set a(foo){}, get [a](){}, get a(){} })", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "setter 'a'", + latterName: "getter 'a'", + }, + type: "Property", + column: 31, + }, + ], + }, + { + code: "({ set [a](foo){}, set a(bar){}, get [a](){} })", + errors: [ + { + messageId: "notGrouped", + data: { formerName: "setter", latterName: "getter" }, + type: "Property", + column: 34, + }, + ], + }, + { + code: "({ get a(){}, set a(foo){}, set b(bar){}, get b(){} })", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { + formerName: "setter 'b'", + latterName: "getter 'b'", + }, + type: "Property", + column: 43, + }, + ], + }, + { + code: "class A { get a(){} static set b(foo){} static get b(){} set a(foo){} }", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "MethodDefinition", + column: 58, + }, + ], + }, + { + code: "(class { static get a(){} set a(foo){} static set a(bar){} })", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "static getter 'a'", + latterName: "static setter 'a'", + }, + type: "MethodDefinition", + column: 40, + }, + ], + }, + { + code: "class A { set a(foo){} get a(){} static get a(){} static set a(bar){} }", + options: ["setBeforeGet"], + errors: [ + { + messageId: "invalidOrder", + data: { + formerName: "static getter 'a'", + latterName: "static setter 'a'", + }, + type: "MethodDefinition", + column: 51, + }, + ], + }, - // multiple invalid - { - code: "({ get a(){}, b: 1, set a(foo){}, set c(foo){}, d(){}, get c(){} })", - errors: [ - { messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "Property", column: 21 }, - { messageId: "notGrouped", data: { formerName: "setter 'c'", latterName: "getter 'c'" }, type: "Property", column: 56 } - ] - }, - { - code: "({ get a(){}, set b(foo){}, set a(bar){}, get b(){} })", - errors: [ - { messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "Property", column: 29 }, - { messageId: "notGrouped", data: { formerName: "setter 'b'", latterName: "getter 'b'" }, type: "Property", column: 43 } - ] - }, - { - code: "({ get a(){}, set [a](foo){}, set a(bar){}, get [a](){} })", - errors: [ - { messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "Property", column: 31 }, - { messageId: "notGrouped", data: { formerName: "setter", latterName: "getter" }, type: "Property", column: 45 } - ] - }, - { - code: "({ a(){}, set b(foo){}, ...c, get b(){}, set c(bar){}, get c(){} })", - options: ["getBeforeSet"], - errors: [ - { messageId: "notGrouped", data: { formerName: "setter 'b'", latterName: "getter 'b'" }, type: "Property", column: 31 }, - { messageId: "invalidOrder", data: { formerName: "setter 'c'", latterName: "getter 'c'" }, type: "Property", column: 56 } - ] - }, - { - code: "({ set [a](foo){}, get [a](){}, set [-a](bar){}, get [-a](){} })", - options: ["getBeforeSet"], - errors: [ - { messageId: "invalidOrder", data: { formerName: "setter", latterName: "getter" }, type: "Property", column: 20 }, - { messageId: "invalidOrder", data: { formerName: "setter", latterName: "getter" }, type: "Property", column: 50 } - ] - }, - { - code: "class A { get a(){} constructor (){} set a(foo){} get b(){} static c(){} set b(bar){} }", - errors: [ - { messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "MethodDefinition", column: 38 }, - { messageId: "notGrouped", data: { formerName: "getter 'b'", latterName: "setter 'b'" }, type: "MethodDefinition", column: 74 } - ] - }, - { - code: "(class { set a(foo){} static get a(){} get a(){} static set a(bar){} })", - errors: [ - { messageId: "notGrouped", data: { formerName: "setter 'a'", latterName: "getter 'a'" }, type: "MethodDefinition", column: 40 }, - { messageId: "notGrouped", data: { formerName: "static getter 'a'", latterName: "static setter 'a'" }, type: "MethodDefinition", column: 50 } - ] - }, - { - code: "class A { get a(){} set a(foo){} static get b(){} static set b(bar){} }", - options: ["setBeforeGet"], - errors: [ - { messageId: "invalidOrder", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "MethodDefinition", column: 21 }, - { messageId: "invalidOrder", data: { formerName: "static getter 'b'", latterName: "static setter 'b'" }, type: "MethodDefinition", column: 51 } - ] - }, - { - code: "(class { set [a+b](foo){} get [a-b](){} get [a+b](){} set [a-b](bar){} })", - errors: [ - { messageId: "notGrouped", data: { formerName: "setter", latterName: "getter" }, type: "MethodDefinition", column: 41 }, - { messageId: "notGrouped", data: { formerName: "getter", latterName: "setter" }, type: "MethodDefinition", column: 55 } - ] - }, + // non-accessor duplicates do not affect this rule + { + code: "({ get a(){}, a: 1, set a(foo){} })", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "Property", + column: 21, + }, + ], + }, + { + code: "({ a(){}, set a(foo){}, get a(){} })", + options: ["getBeforeSet"], + errors: [ + { + messageId: "invalidOrder", + data: { + formerName: "setter 'a'", + latterName: "getter 'a'", + }, + type: "Property", + column: 25, + }, + ], + }, + { + code: "class A { get a(){} a(){} set a(foo){} }", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "MethodDefinition", + column: 27, + }, + ], + }, + { + code: "class A { get a(){} a; set a(foo){} }", + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "MethodDefinition", + column: 24, + }, + ], + }, - // combinations of valid and invalid - { - code: "({ get a(){}, set a(foo){}, get b(){}, c: function(){}, set b(bar){} })", - errors: [{ messageId: "notGrouped", data: { formerName: "getter 'b'", latterName: "setter 'b'" }, type: "Property", column: 57 }] - }, - { - code: "({ get a(){}, get b(){}, set a(foo){} })", - errors: [{ messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "Property", column: 26 }] - }, - { - code: "({ set a(foo){}, get [a](){}, get a(){} })", - errors: [{ messageId: "notGrouped", data: { formerName: "setter 'a'", latterName: "getter 'a'" }, type: "Property", column: 31 }] - }, - { - code: "({ set [a](foo){}, set a(bar){}, get [a](){} })", - errors: [{ messageId: "notGrouped", data: { formerName: "setter", latterName: "getter" }, type: "Property", column: 34 }] - }, - { - code: "({ get a(){}, set a(foo){}, set b(bar){}, get b(){} })", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { formerName: "setter 'b'", latterName: "getter 'b'" }, type: "Property", column: 43 }] - }, - { - code: "class A { get a(){} static set b(foo){} static get b(){} set a(foo){} }", - errors: [{ messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "MethodDefinition", column: 58 }] - }, - { - code: "(class { static get a(){} set a(foo){} static set a(bar){} })", - errors: [{ messageId: "notGrouped", data: { formerName: "static getter 'a'", latterName: "static setter 'a'" }, type: "MethodDefinition", column: 40 }] - }, - { - code: "class A { set a(foo){} get a(){} static get a(){} static set a(bar){} }", - options: ["setBeforeGet"], - errors: [{ messageId: "invalidOrder", data: { formerName: "static getter 'a'", latterName: "static setter 'a'" }, type: "MethodDefinition", column: 51 }] - }, - - // non-accessor duplicates do not affect this rule - { - code: "({ get a(){}, a: 1, set a(foo){} })", - errors: [{ messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "Property", column: 21 }] - }, - { - code: "({ a(){}, set a(foo){}, get a(){} })", - options: ["getBeforeSet"], - errors: [{ messageId: "invalidOrder", data: { formerName: "setter 'a'", latterName: "getter 'a'" }, type: "Property", column: 25 }] - }, - { - code: "class A { get a(){} a(){} set a(foo){} }", - errors: [{ messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "MethodDefinition", column: 27 }] - }, - { - code: "class A { get a(){} a; set a(foo){} }", - languageOptions: { ecmaVersion: 2022 }, - errors: [{ messageId: "notGrouped", data: { formerName: "getter 'a'", latterName: "setter 'a'" }, type: "MethodDefinition", column: 24 }] - }, - - // full location tests - { - code: "({ get a(){},\n b: 1,\n set a(foo){}\n})", - errors: [ - { - messageId: "notGrouped", - data: { formerName: "getter 'a'", latterName: "setter 'a'" }, - type: "Property", - line: 3, - column: 5, - endLine: 3, - endColumn: 10 - } - ] - }, - { - code: "class A { static set a(foo){} b(){} static get \n a(){}\n}", - errors: [ - { - messageId: "notGrouped", - data: { formerName: "static setter 'a'", latterName: "static getter 'a'" }, - type: "MethodDefinition", - line: 1, - column: 37, - endLine: 2, - endColumn: 3 - } - ] - } - ] + // full location tests + { + code: "({ get a(){},\n b: 1,\n set a(foo){}\n})", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "getter 'a'", + latterName: "setter 'a'", + }, + type: "Property", + line: 3, + column: 5, + endLine: 3, + endColumn: 10, + }, + ], + }, + { + code: "class A { static set a(foo){} b(){} static get \n a(){}\n}", + errors: [ + { + messageId: "notGrouped", + data: { + formerName: "static setter 'a'", + latterName: "static getter 'a'", + }, + type: "MethodDefinition", + line: 1, + column: 37, + endLine: 2, + endColumn: 3, + }, + ], + }, + ], }); diff --git a/tests/lib/rules/guard-for-in.js b/tests/lib/rules/guard-for-in.js index 7cde0aa0f1eb..bf1ab3f52078 100644 --- a/tests/lib/rules/guard-for-in.js +++ b/tests/lib/rules/guard-for-in.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/guard-for-in"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -20,20 +20,26 @@ const ruleTester = new RuleTester(); const error = { messageId: "wrap", type: "ForInStatement" }; ruleTester.run("guard-for-in", rule, { - valid: [ - "for (var x in o);", - "for (var x in o) {}", - "for (var x in o) if (x) f();", - "for (var x in o) { if (x) { f(); } }", - "for (var x in o) { if (x) continue; f(); }", - "for (var x in o) { if (x) { continue; } f(); }" - ], - invalid: [ - { code: "for (var x in o) { if (x) { f(); continue; } g(); }", errors: [error] }, - { code: "for (var x in o) { if (x) { continue; f(); } g(); }", errors: [error] }, - { code: "for (var x in o) { if (x) { f(); } g(); }", errors: [error] }, - { code: "for (var x in o) { if (x) f(); g(); }", errors: [error] }, - { code: "for (var x in o) { foo() }", errors: [error] }, - { code: "for (var x in o) foo();", errors: [error] } - ] + valid: [ + "for (var x in o);", + "for (var x in o) {}", + "for (var x in o) if (x) f();", + "for (var x in o) { if (x) { f(); } }", + "for (var x in o) { if (x) continue; f(); }", + "for (var x in o) { if (x) { continue; } f(); }", + ], + invalid: [ + { + code: "for (var x in o) { if (x) { f(); continue; } g(); }", + errors: [error], + }, + { + code: "for (var x in o) { if (x) { continue; f(); } g(); }", + errors: [error], + }, + { code: "for (var x in o) { if (x) { f(); } g(); }", errors: [error] }, + { code: "for (var x in o) { if (x) f(); g(); }", errors: [error] }, + { code: "for (var x in o) { foo() }", errors: [error] }, + { code: "for (var x in o) foo();", errors: [error] }, + ], }); diff --git a/tests/lib/rules/handle-callback-err.js b/tests/lib/rules/handle-callback-err.js index cf2620db210f..4d49378fedaf 100644 --- a/tests/lib/rules/handle-callback-err.js +++ b/tests/lib/rules/handle-callback-err.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/handle-callback-err"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,57 +18,159 @@ const rule = require("../../../lib/rules/handle-callback-err"), const ruleTester = new RuleTester(); -const expectedFunctionDeclarationError = { messageId: "expected", type: "FunctionDeclaration" }; -const expectedFunctionExpressionError = { messageId: "expected", type: "FunctionExpression" }; +const expectedFunctionDeclarationError = { + messageId: "expected", + type: "FunctionDeclaration", +}; +const expectedFunctionExpressionError = { + messageId: "expected", + type: "FunctionExpression", +}; ruleTester.run("handle-callback-err", rule, { - valid: [ - "function test(error) {}", - "function test(err) {console.log(err);}", - "function test(err, data) {if(err){ data = 'ERROR';}}", - "var test = function(err) {console.log(err);};", - "var test = function(err) {if(err){/* do nothing */}};", - "var test = function(err) {if(!err){doSomethingHere();}else{};}", - "var test = function(err, data) {if(!err) { good(); } else { bad(); }}", - "try { } catch(err) {}", - "getData(function(err, data) {if (err) {}getMoreDataWith(data, function(err, moreData) {if (err) {}getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});});});", - "var test = function(err) {if(! err){doSomethingHere();}};", - "function test(err, data) {if (data) {doSomething(function(err) {console.error(err);});} else if (err) {console.log(err);}}", - "function handler(err, data) {if (data) {doSomethingWith(data);} else if (err) {console.log(err);}}", - "function handler(err) {logThisAction(function(err) {if (err) {}}); console.log(err);}", - "function userHandler(err) {process.nextTick(function() {if (err) {}})}", - "function help() { function userHandler(err) {function tester() { err; process.nextTick(function() { err; }); } } }", - "function help(done) { var err = new Error('error'); done(); }", - { code: "var test = err => err;", languageOptions: { ecmaVersion: 6 } }, - { code: "var test = err => !err;", languageOptions: { ecmaVersion: 6 } }, - { code: "var test = err => err.message;", languageOptions: { ecmaVersion: 6 } }, - { code: "var test = function(error) {if(error){/* do nothing */}};", options: ["error"] }, - { code: "var test = (error) => {if(error){/* do nothing */}};", options: ["error"], languageOptions: { ecmaVersion: 6 } }, - { code: "var test = function(error) {if(! error){doSomethingHere();}};", options: ["error"] }, - { code: "var test = function(err) { console.log(err); };", options: ["^(err|error)$"] }, - { code: "var test = function(error) { console.log(error); };", options: ["^(err|error)$"] }, - { code: "var test = function(anyError) { console.log(anyError); };", options: ["^.+Error$"] }, - { code: "var test = function(any_error) { console.log(anyError); };", options: ["^.+Error$"] }, - { code: "var test = function(any_error) { console.log(any_error); };", options: ["^.+(e|E)rror$"] } - ], - invalid: [ - { code: "function test(err) {}", errors: [expectedFunctionDeclarationError] }, - { code: "function test(err, data) {}", errors: [expectedFunctionDeclarationError] }, - { code: "function test(err) {errorLookingWord();}", errors: [expectedFunctionDeclarationError] }, - { code: "function test(err) {try{} catch(err) {}}", errors: [expectedFunctionDeclarationError] }, - { code: "function test(err, callback) { foo(function(err, callback) {}); }", errors: [expectedFunctionDeclarationError, expectedFunctionExpressionError] }, - { code: "var test = (err) => {};", languageOptions: { ecmaVersion: 6 }, errors: [{ messageId: "expected" }] }, - { code: "var test = function(err) {};", errors: [expectedFunctionExpressionError] }, - { code: "var test = function test(err, data) {};", errors: [expectedFunctionExpressionError] }, - { code: "var test = function test(err) {/* if(err){} */};", errors: [expectedFunctionExpressionError] }, - { code: "function test(err) {doSomethingHere(function(err){console.log(err);})}", errors: [expectedFunctionDeclarationError] }, - { code: "function test(error) {}", options: ["error"], errors: [expectedFunctionDeclarationError] }, - { code: "getData(function(err, data) {getMoreDataWith(data, function(err, moreData) {if (err) {}getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});}); });", errors: [expectedFunctionExpressionError] }, - { code: "getData(function(err, data) {getMoreDataWith(data, function(err, moreData) {getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});}); });", errors: [expectedFunctionExpressionError, expectedFunctionExpressionError] }, - { code: "function userHandler(err) {logThisAction(function(err) {if (err) { console.log(err); } })}", errors: [expectedFunctionDeclarationError] }, - { code: "function help() { function userHandler(err) {function tester(err) { err; process.nextTick(function() { err; }); } } }", errors: [expectedFunctionDeclarationError] }, - { code: "var test = function(anyError) { console.log(otherError); };", options: ["^.+Error$"], errors: [expectedFunctionExpressionError] }, - { code: "var test = function(anyError) { };", options: ["^.+Error$"], errors: [expectedFunctionExpressionError] }, - { code: "var test = function(err) { console.log(error); };", options: ["^(err|error)$"], errors: [expectedFunctionExpressionError] } - ] + valid: [ + "function test(error) {}", + "function test(err) {console.log(err);}", + "function test(err, data) {if(err){ data = 'ERROR';}}", + "var test = function(err) {console.log(err);};", + "var test = function(err) {if(err){/* do nothing */}};", + "var test = function(err) {if(!err){doSomethingHere();}else{};}", + "var test = function(err, data) {if(!err) { good(); } else { bad(); }}", + "try { } catch(err) {}", + "getData(function(err, data) {if (err) {}getMoreDataWith(data, function(err, moreData) {if (err) {}getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});});});", + "var test = function(err) {if(! err){doSomethingHere();}};", + "function test(err, data) {if (data) {doSomething(function(err) {console.error(err);});} else if (err) {console.log(err);}}", + "function handler(err, data) {if (data) {doSomethingWith(data);} else if (err) {console.log(err);}}", + "function handler(err) {logThisAction(function(err) {if (err) {}}); console.log(err);}", + "function userHandler(err) {process.nextTick(function() {if (err) {}})}", + "function help() { function userHandler(err) {function tester() { err; process.nextTick(function() { err; }); } } }", + "function help(done) { var err = new Error('error'); done(); }", + { code: "var test = err => err;", languageOptions: { ecmaVersion: 6 } }, + { + code: "var test = err => !err;", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var test = err => err.message;", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var test = function(error) {if(error){/* do nothing */}};", + options: ["error"], + }, + { + code: "var test = (error) => {if(error){/* do nothing */}};", + options: ["error"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var test = function(error) {if(! error){doSomethingHere();}};", + options: ["error"], + }, + { + code: "var test = function(err) { console.log(err); };", + options: ["^(err|error)$"], + }, + { + code: "var test = function(error) { console.log(error); };", + options: ["^(err|error)$"], + }, + { + code: "var test = function(anyError) { console.log(anyError); };", + options: ["^.+Error$"], + }, + { + code: "var test = function(any_error) { console.log(anyError); };", + options: ["^.+Error$"], + }, + { + code: "var test = function(any_error) { console.log(any_error); };", + options: ["^.+(e|E)rror$"], + }, + ], + invalid: [ + { + code: "function test(err) {}", + errors: [expectedFunctionDeclarationError], + }, + { + code: "function test(err, data) {}", + errors: [expectedFunctionDeclarationError], + }, + { + code: "function test(err) {errorLookingWord();}", + errors: [expectedFunctionDeclarationError], + }, + { + code: "function test(err) {try{} catch(err) {}}", + errors: [expectedFunctionDeclarationError], + }, + { + code: "function test(err, callback) { foo(function(err, callback) {}); }", + errors: [ + expectedFunctionDeclarationError, + expectedFunctionExpressionError, + ], + }, + { + code: "var test = (err) => {};", + languageOptions: { ecmaVersion: 6 }, + errors: [{ messageId: "expected" }], + }, + { + code: "var test = function(err) {};", + errors: [expectedFunctionExpressionError], + }, + { + code: "var test = function test(err, data) {};", + errors: [expectedFunctionExpressionError], + }, + { + code: "var test = function test(err) {/* if(err){} */};", + errors: [expectedFunctionExpressionError], + }, + { + code: "function test(err) {doSomethingHere(function(err){console.log(err);})}", + errors: [expectedFunctionDeclarationError], + }, + { + code: "function test(error) {}", + options: ["error"], + errors: [expectedFunctionDeclarationError], + }, + { + code: "getData(function(err, data) {getMoreDataWith(data, function(err, moreData) {if (err) {}getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});}); });", + errors: [expectedFunctionExpressionError], + }, + { + code: "getData(function(err, data) {getMoreDataWith(data, function(err, moreData) {getEvenMoreDataWith(moreData, function(err, allOfTheThings) {if (err) {}});}); });", + errors: [ + expectedFunctionExpressionError, + expectedFunctionExpressionError, + ], + }, + { + code: "function userHandler(err) {logThisAction(function(err) {if (err) { console.log(err); } })}", + errors: [expectedFunctionDeclarationError], + }, + { + code: "function help() { function userHandler(err) {function tester(err) { err; process.nextTick(function() { err; }); } } }", + errors: [expectedFunctionDeclarationError], + }, + { + code: "var test = function(anyError) { console.log(otherError); };", + options: ["^.+Error$"], + errors: [expectedFunctionExpressionError], + }, + { + code: "var test = function(anyError) { };", + options: ["^.+Error$"], + errors: [expectedFunctionExpressionError], + }, + { + code: "var test = function(err) { console.log(error); };", + options: ["^(err|error)$"], + errors: [expectedFunctionExpressionError], + }, + ], }); diff --git a/tests/lib/rules/id-blacklist.js b/tests/lib/rules/id-blacklist.js index f29afcc6765e..0bcea02bd544 100644 --- a/tests/lib/rules/id-blacklist.js +++ b/tests/lib/rules/id-blacklist.js @@ -10,1373 +10,1351 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/id-blacklist"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, }); const error = { messageId: "restricted", type: "Identifier" }; ruleTester.run("id-blacklist", rule, { - valid: [ - { - code: "foo = \"bar\"", - options: ["bar"] - }, - { - code: "bar = \"bar\"", - options: ["foo"] - }, - { - code: "foo = \"bar\"", - options: ["f", "fo", "fooo", "bar"] - }, - { - code: "function foo(){}", - options: ["bar"] - }, - { - code: "foo()", - options: ["f", "fo", "fooo", "bar"] - }, - { - code: "import { foo as bar } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "export { foo as bar } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "foo.bar()", - options: ["f", "fo", "fooo", "b", "ba", "baz"] - }, - { - code: "var foo = bar.baz;", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz"] - }, - { - code: "var foo = bar.baz.bing;", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "foo.bar.baz = bing.bong.bash;", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "if (foo.bar) {}", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "var obj = { key: foo.bar };", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "const {foo: bar} = baz", - options: ["foo"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "const {foo: {bar: baz}} = qux", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({ bar: baz }) {}", - options: ["bar"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({ bar: {baz: qux} }) {}", - options: ["bar", "baz"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({baz} = obj.qux) {}", - options: ["qux"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({ foo: {baz} = obj.qux }) {}", - options: ["qux"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({a: bar = obj.baz});", - options: ["baz"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({foo: {a: bar = obj.baz}} = qux);", - options: ["baz"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var arr = [foo.bar];", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "[foo.bar]", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "[foo.bar.nesting]", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "if (foo.bar === bar.baz) { [foo.bar] }", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "var myArray = new Array(); var myDate = new Date();", - options: ["array", "date", "mydate", "myarray", "new", "var"] - }, - { - code: "foo()", - options: ["foo"] - }, - { - code: "foo.bar()", - options: ["bar"] - }, - { - code: "foo.bar", - options: ["bar"] - }, - { - code: "({foo: obj.bar.bar.bar.baz} = {});", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({[obj.bar]: a = baz} = qux);", - options: ["bar"], - languageOptions: { ecmaVersion: 6 } - }, + valid: [ + { + code: 'foo = "bar"', + options: ["bar"], + }, + { + code: 'bar = "bar"', + options: ["foo"], + }, + { + code: 'foo = "bar"', + options: ["f", "fo", "fooo", "bar"], + }, + { + code: "function foo(){}", + options: ["bar"], + }, + { + code: "foo()", + options: ["f", "fo", "fooo", "bar"], + }, + { + code: "import { foo as bar } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "export { foo as bar } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "foo.bar()", + options: ["f", "fo", "fooo", "b", "ba", "baz"], + }, + { + code: "var foo = bar.baz;", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz"], + }, + { + code: "var foo = bar.baz.bing;", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "foo.bar.baz = bing.bong.bash;", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "if (foo.bar) {}", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "var obj = { key: foo.bar };", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "const {foo: bar} = baz", + options: ["foo"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "const {foo: {bar: baz}} = qux", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({ bar: baz }) {}", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({ bar: {baz: qux} }) {}", + options: ["bar", "baz"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({baz} = obj.qux) {}", + options: ["qux"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({ foo: {baz} = obj.qux }) {}", + options: ["qux"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({a: bar = obj.baz});", + options: ["baz"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({foo: {a: bar = obj.baz}} = qux);", + options: ["baz"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var arr = [foo.bar];", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "[foo.bar]", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "[foo.bar.nesting]", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "if (foo.bar === bar.baz) { [foo.bar] }", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "var myArray = new Array(); var myDate = new Date();", + options: ["array", "date", "mydate", "myarray", "new", "var"], + }, + { + code: "foo()", + options: ["foo"], + }, + { + code: "foo.bar()", + options: ["bar"], + }, + { + code: "foo.bar", + options: ["bar"], + }, + { + code: "({foo: obj.bar.bar.bar.baz} = {});", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({[obj.bar]: a = baz} = qux);", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + }, - // references to global variables - { - code: "Number.parseInt()", - options: ["Number"] - }, - { - code: "x = Number.NaN;", - options: ["Number"] - }, - { - code: "var foo = undefined;", - options: ["undefined"] - }, - { - code: "if (foo === undefined);", - options: ["undefined"] - }, - { - code: "obj[undefined] = 5;", // creates obj["undefined"]. It should be disallowed, but the rule doesn't know values of globals and can't control computed access. - options: ["undefined"] - }, - { - code: "foo = { [myGlobal]: 1 };", - options: ["myGlobal"], - languageOptions: { - ecmaVersion: 6, - globals: { myGlobal: "readonly" } - } - }, - { - code: "({ myGlobal } = foo);", // writability doesn't affect the logic, it's always assumed that user doesn't have control over the names of globals. - options: ["myGlobal"], - languageOptions: { - ecmaVersion: 6, - globals: { myGlobal: "writable" } - } - }, - { - code: "/* global myGlobal: readonly */ myGlobal = 5;", - options: ["myGlobal"] - }, - { - code: "var foo = [Map];", - options: ["Map"], - languageOptions: { ecmaVersion: 6, sourceType: "script" } - }, - { - code: "var foo = { bar: window.baz };", - options: ["window"], - languageOptions: { - globals: { - window: "readonly" - } - } - } - ], - invalid: [ - { - code: "foo = \"bar\"", - options: ["foo"], - errors: [ - error - ] - }, - { - code: "bar = \"bar\"", - options: ["bar"], - errors: [ - error - ] - }, - { - code: "foo = \"bar\"", - options: ["f", "fo", "foo", "bar"], - errors: [ - error - ] - }, - { - code: "function foo(){}", - options: ["f", "fo", "foo", "bar"], - errors: [ - error - ] - }, - { - code: "import foo from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - error - ] - }, - { - code: "import * as foo from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - error - ] - }, - { - code: "export * as foo from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 2020, sourceType: "module" }, - errors: [ - error - ] - }, - { - code: "import { foo } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - error - ] - }, - { - code: "import { foo as bar } from 'mod'", - options: ["bar"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 17 - }] - }, - { - code: "import { foo as bar } from 'mod'", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 17 - }] - }, - { - code: "import { foo as foo } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 17 - }] - }, - { - code: "import { foo, foo as bar } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 10 - }] - }, - { - code: "import { foo as bar, foo } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 22 - }] - }, - { - code: "import foo, { foo as bar } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 8 - }] - }, - { - code: "var foo; export { foo as bar };", - options: ["bar"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 26 - }] - }, - { - code: "var foo; export { foo };", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 5 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 19 - } - ] - }, - { - code: "var foo; export { foo as bar };", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 5 - }, + // references to global variables + { + code: "Number.parseInt()", + options: ["Number"], + }, + { + code: "x = Number.NaN;", + options: ["Number"], + }, + { + code: "var foo = undefined;", + options: ["undefined"], + }, + { + code: "if (foo === undefined);", + options: ["undefined"], + }, + { + code: "obj[undefined] = 5;", // creates obj["undefined"]. It should be disallowed, but the rule doesn't know values of globals and can't control computed access. + options: ["undefined"], + }, + { + code: "foo = { [myGlobal]: 1 };", + options: ["myGlobal"], + languageOptions: { + ecmaVersion: 6, + globals: { myGlobal: "readonly" }, + }, + }, + { + code: "({ myGlobal } = foo);", // writability doesn't affect the logic, it's always assumed that user doesn't have control over the names of globals. + options: ["myGlobal"], + languageOptions: { + ecmaVersion: 6, + globals: { myGlobal: "writable" }, + }, + }, + { + code: "/* global myGlobal: readonly */ myGlobal = 5;", + options: ["myGlobal"], + }, + { + code: "var foo = [Map];", + options: ["Map"], + languageOptions: { ecmaVersion: 6, sourceType: "script" }, + }, + { + code: "var foo = { bar: window.baz };", + options: ["window"], + languageOptions: { + globals: { + window: "readonly", + }, + }, + }, + ], + invalid: [ + { + code: 'foo = "bar"', + options: ["foo"], + errors: [error], + }, + { + code: 'bar = "bar"', + options: ["bar"], + errors: [error], + }, + { + code: 'foo = "bar"', + options: ["f", "fo", "foo", "bar"], + errors: [error], + }, + { + code: "function foo(){}", + options: ["f", "fo", "foo", "bar"], + errors: [error], + }, + { + code: "import foo from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [error], + }, + { + code: "import * as foo from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [error], + }, + { + code: "export * as foo from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 2020, sourceType: "module" }, + errors: [error], + }, + { + code: "import { foo } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [error], + }, + { + code: "import { foo as bar } from 'mod'", + options: ["bar"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "import { foo as bar } from 'mod'", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "import { foo as foo } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "import { foo, foo as bar } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 10, + }, + ], + }, + { + code: "import { foo as bar, foo } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 22, + }, + ], + }, + { + code: "import foo, { foo as bar } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 8, + }, + ], + }, + { + code: "var foo; export { foo as bar };", + options: ["bar"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 26, + }, + ], + }, + { + code: "var foo; export { foo };", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 5, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 19, + }, + ], + }, + { + code: "var foo; export { foo as bar };", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 5, + }, - // reports each occurrence of local identifier, although it's renamed in this export specifier - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 19 - } - ] - }, - { - code: "var foo; export { foo as foo };", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 5 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 19 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 26 - } - ] - }, - { - code: "var foo; export { foo as bar };", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 5 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 19 - }, - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 26 - } - ] - }, - { - code: "export { foo } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - error - ] - }, - { - code: "export { foo as bar } from 'mod'", - options: ["bar"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 17 - }] - }, - { - code: "export { foo as bar } from 'mod'", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 17 - }] - }, - { - code: "export { foo as foo } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 17 - }] - }, - { - code: "export { foo, foo as bar } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 10 - }] - }, - { - code: "export { foo as bar, foo } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 22 - }] - }, - { - code: "foo.bar()", - options: ["f", "fo", "foo", "b", "ba", "baz"], - errors: [ - error - ] - }, - { - code: "foo[bar] = baz;", - options: ["bar"], - errors: [{ - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier" - }] - }, - { - code: "baz = foo[bar];", - options: ["bar"], - errors: [{ - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier" - }] - }, - { - code: "var foo = bar.baz;", - options: ["f", "fo", "foo", "b", "ba", "barr", "bazz"], - errors: [ - error - ] - }, - { - code: "var foo = bar.baz;", - options: ["f", "fo", "fooo", "b", "ba", "bar", "bazz"], - errors: [ - error - ] - }, - { - code: "if (foo.bar) {}", - options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"], - errors: [ - error - ] - }, - { - code: "var obj = { key: foo.bar };", - options: ["obj"], - errors: [ - error - ] - }, - { - code: "var obj = { key: foo.bar };", - options: ["key"], - errors: [ - error - ] - }, - { - code: "var obj = { key: foo.bar };", - options: ["foo"], - errors: [ - error - ] - }, - { - code: "var arr = [foo.bar];", - options: ["arr"], - errors: [ - error - ] - }, - { - code: "var arr = [foo.bar];", - options: ["foo"], - errors: [ - error - ] - }, - { - code: "[foo.bar]", - options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"], - errors: [ - error - ] - }, - { - code: "if (foo.bar === bar.baz) { [bing.baz] }", - options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"], - errors: [ - error - ] - }, - { - code: "if (foo.bar === bar.baz) { [foo.bar] }", - options: ["f", "fo", "fooo", "b", "ba", "bar", "bazz", "bingg"], - errors: [ - error - ] - }, - { - code: "var myArray = new Array(); var myDate = new Date();", - options: ["array", "date", "myDate", "myarray", "new", "var"], - errors: [ - error - ] - }, - { - code: "var myArray = new Array(); var myDate = new Date();", - options: ["array", "date", "mydate", "myArray", "new", "var"], - errors: [ - error - ] - }, - { - code: "foo.bar = 1", - options: ["bar"], - errors: [ - error - ] - }, - { - code: "foo.bar.baz = 1", - options: ["bar", "baz"], - errors: [ - error - ] - }, - { - code: "const {foo} = baz", - options: ["foo"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 8 - } - ] - }, - { - code: "const {foo: bar} = baz", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 13 - } - ] - }, - { - code: "const {[foo]: bar} = baz", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 9 - }, - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 15 - } - ] - }, - { - code: "const {foo: {bar: baz}} = qux", - options: ["foo", "bar", "baz"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "baz" }, - type: "Identifier", - column: 19 - } - ] - }, - { - code: "const {foo: {[bar]: baz}} = qux", - options: ["foo", "bar", "baz"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 15 - }, - { - messageId: "restricted", - data: { name: "baz" }, - type: "Identifier", - column: 21 - } - ] - }, - { - code: "const {[foo]: {[bar]: baz}} = qux", - options: ["foo", "bar", "baz"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 9 - }, - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 17 - }, - { - messageId: "restricted", - data: { name: "baz" }, - type: "Identifier", - column: 23 - } - ] - }, - { - code: "function foo({ bar: baz }) {}", - options: ["bar", "baz"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "baz" }, - type: "Identifier", - column: 21 - } - ] - }, - { - code: "function foo({ bar: {baz: qux} }) {}", - options: ["bar", "baz", "qux"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "qux" }, - type: "Identifier", - column: 27 - } - ] - }, - { - code: "({foo: obj.bar} = baz);", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 12 - } - ] - }, - { - code: "({foo: obj.bar.bar.bar.baz} = {});", - options: ["foo", "bar", "baz"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "baz" }, - type: "Identifier", - column: 24 - } - ] - }, - { - code: "({[foo]: obj.bar} = baz);", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 4 - }, - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 14 - } - ] - }, - { - code: "({foo: { a: obj.bar }} = baz);", - options: ["bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 17 - } - ] - }, - { - code: "({a: obj.bar = baz} = qux);", - options: ["bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 10 - } - ] - }, - { - code: "({a: obj.bar.bar.baz = obj.qux} = obj.qux);", - options: ["a", "bar", "baz", "qux"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "baz" }, - type: "Identifier", - column: 18 - } - ] - }, - { - code: "({a: obj[bar] = obj.qux} = obj.qux);", - options: ["a", "bar", "baz", "qux"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 10 - } - ] - }, - { - code: "({a: [obj.bar] = baz} = qux);", - options: ["bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 11 - } - ] - }, - { - code: "({foo: { a: obj.bar = baz}} = qux);", - options: ["bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 17 - } - ] - }, - { - code: "({foo: { [a]: obj.bar }} = baz);", - options: ["bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 19 - } - ] - }, - { - code: "({...obj.bar} = baz);", - options: ["bar"], - languageOptions: { ecmaVersion: 9 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 10 - } - ] - }, - { - code: "([obj.bar] = baz);", - options: ["bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 7 - } - ] - }, - { - code: "const [bar] = baz;", - options: ["bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 8 - } - ] - }, + // reports each occurrence of local identifier, although it's renamed in this export specifier + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 19, + }, + ], + }, + { + code: "var foo; export { foo as foo };", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 5, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 19, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 26, + }, + ], + }, + { + code: "var foo; export { foo as bar };", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 5, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 19, + }, + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 26, + }, + ], + }, + { + code: "export { foo } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [error], + }, + { + code: "export { foo as bar } from 'mod'", + options: ["bar"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "export { foo as bar } from 'mod'", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "export { foo as foo } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "export { foo, foo as bar } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 10, + }, + ], + }, + { + code: "export { foo as bar, foo } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 22, + }, + ], + }, + { + code: "foo.bar()", + options: ["f", "fo", "foo", "b", "ba", "baz"], + errors: [error], + }, + { + code: "foo[bar] = baz;", + options: ["bar"], + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + }, + ], + }, + { + code: "baz = foo[bar];", + options: ["bar"], + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + }, + ], + }, + { + code: "var foo = bar.baz;", + options: ["f", "fo", "foo", "b", "ba", "barr", "bazz"], + errors: [error], + }, + { + code: "var foo = bar.baz;", + options: ["f", "fo", "fooo", "b", "ba", "bar", "bazz"], + errors: [error], + }, + { + code: "if (foo.bar) {}", + options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"], + errors: [error], + }, + { + code: "var obj = { key: foo.bar };", + options: ["obj"], + errors: [error], + }, + { + code: "var obj = { key: foo.bar };", + options: ["key"], + errors: [error], + }, + { + code: "var obj = { key: foo.bar };", + options: ["foo"], + errors: [error], + }, + { + code: "var arr = [foo.bar];", + options: ["arr"], + errors: [error], + }, + { + code: "var arr = [foo.bar];", + options: ["foo"], + errors: [error], + }, + { + code: "[foo.bar]", + options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"], + errors: [error], + }, + { + code: "if (foo.bar === bar.baz) { [bing.baz] }", + options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"], + errors: [error], + }, + { + code: "if (foo.bar === bar.baz) { [foo.bar] }", + options: ["f", "fo", "fooo", "b", "ba", "bar", "bazz", "bingg"], + errors: [error], + }, + { + code: "var myArray = new Array(); var myDate = new Date();", + options: ["array", "date", "myDate", "myarray", "new", "var"], + errors: [error], + }, + { + code: "var myArray = new Array(); var myDate = new Date();", + options: ["array", "date", "mydate", "myArray", "new", "var"], + errors: [error], + }, + { + code: "foo.bar = 1", + options: ["bar"], + errors: [error], + }, + { + code: "foo.bar.baz = 1", + options: ["bar", "baz"], + errors: [error], + }, + { + code: "const {foo} = baz", + options: ["foo"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 8, + }, + ], + }, + { + code: "const {foo: bar} = baz", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 13, + }, + ], + }, + { + code: "const {[foo]: bar} = baz", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 9, + }, + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 15, + }, + ], + }, + { + code: "const {foo: {bar: baz}} = qux", + options: ["foo", "bar", "baz"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "baz" }, + type: "Identifier", + column: 19, + }, + ], + }, + { + code: "const {foo: {[bar]: baz}} = qux", + options: ["foo", "bar", "baz"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 15, + }, + { + messageId: "restricted", + data: { name: "baz" }, + type: "Identifier", + column: 21, + }, + ], + }, + { + code: "const {[foo]: {[bar]: baz}} = qux", + options: ["foo", "bar", "baz"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 9, + }, + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 17, + }, + { + messageId: "restricted", + data: { name: "baz" }, + type: "Identifier", + column: 23, + }, + ], + }, + { + code: "function foo({ bar: baz }) {}", + options: ["bar", "baz"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "baz" }, + type: "Identifier", + column: 21, + }, + ], + }, + { + code: "function foo({ bar: {baz: qux} }) {}", + options: ["bar", "baz", "qux"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "qux" }, + type: "Identifier", + column: 27, + }, + ], + }, + { + code: "({foo: obj.bar} = baz);", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 12, + }, + ], + }, + { + code: "({foo: obj.bar.bar.bar.baz} = {});", + options: ["foo", "bar", "baz"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "baz" }, + type: "Identifier", + column: 24, + }, + ], + }, + { + code: "({[foo]: obj.bar} = baz);", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 4, + }, + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 14, + }, + ], + }, + { + code: "({foo: { a: obj.bar }} = baz);", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "({a: obj.bar = baz} = qux);", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 10, + }, + ], + }, + { + code: "({a: obj.bar.bar.baz = obj.qux} = obj.qux);", + options: ["a", "bar", "baz", "qux"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "baz" }, + type: "Identifier", + column: 18, + }, + ], + }, + { + code: "({a: obj[bar] = obj.qux} = obj.qux);", + options: ["a", "bar", "baz", "qux"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 10, + }, + ], + }, + { + code: "({a: [obj.bar] = baz} = qux);", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 11, + }, + ], + }, + { + code: "({foo: { a: obj.bar = baz}} = qux);", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "({foo: { [a]: obj.bar }} = baz);", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 19, + }, + ], + }, + { + code: "({...obj.bar} = baz);", + options: ["bar"], + languageOptions: { ecmaVersion: 9 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 10, + }, + ], + }, + { + code: "([obj.bar] = baz);", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 7, + }, + ], + }, + { + code: "const [bar] = baz;", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 8, + }, + ], + }, - // not a reference to a global variable, because it isn't a reference to a variable - { - code: "foo.undefined = 1;", - options: ["undefined"], - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier" - } - ] - }, - { - code: "var foo = { undefined: 1 };", - options: ["undefined"], - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier" - } - ] - }, - { - code: "var foo = { undefined: undefined };", - options: ["undefined"], - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 13 - } - ] - }, - { - code: "var foo = { Number() {} };", - options: ["Number"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier" - } - ] - }, - { - code: "class Foo { Number() {} }", - options: ["Number"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier" - } - ] - }, - { - code: "myGlobal: while(foo) { break myGlobal; } ", - options: ["myGlobal"], - languageOptions: { - globals: { myGlobal: "readonly" } - }, - errors: [ - { - messageId: "restricted", - data: { name: "myGlobal" }, - type: "Identifier", - column: 1 - }, - { - messageId: "restricted", - data: { name: "myGlobal" }, - type: "Identifier", - column: 30 - } - ] - }, + // not a reference to a global variable, because it isn't a reference to a variable + { + code: "foo.undefined = 1;", + options: ["undefined"], + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + }, + ], + }, + { + code: "var foo = { undefined: 1 };", + options: ["undefined"], + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + }, + ], + }, + { + code: "var foo = { undefined: undefined };", + options: ["undefined"], + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 13, + }, + ], + }, + { + code: "var foo = { Number() {} };", + options: ["Number"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + }, + ], + }, + { + code: "class Foo { Number() {} }", + options: ["Number"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + }, + ], + }, + { + code: "myGlobal: while(foo) { break myGlobal; } ", + options: ["myGlobal"], + languageOptions: { + globals: { myGlobal: "readonly" }, + }, + errors: [ + { + messageId: "restricted", + data: { name: "myGlobal" }, + type: "Identifier", + column: 1, + }, + { + messageId: "restricted", + data: { name: "myGlobal" }, + type: "Identifier", + column: 30, + }, + ], + }, - // globals declared in the given source code are not excluded from consideration - { - code: "const foo = 1; bar = foo;", - options: ["foo"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 7 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 22 - } - ] - }, - { - code: "let foo; foo = bar;", - options: ["foo"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 5 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 10 - } - ] - }, - { - code: "bar = foo; var foo;", - options: ["foo"], - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 7 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 16 - } - ] - }, - { - code: "function foo() {} var bar = foo;", - options: ["foo"], - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 10 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 29 - } - ] - }, - { - code: "class Foo {} var bar = Foo;", - options: ["Foo"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "Foo" }, - type: "Identifier", - column: 7 - }, - { - messageId: "restricted", - data: { name: "Foo" }, - type: "Identifier", - column: 24 - } - ] - }, + // globals declared in the given source code are not excluded from consideration + { + code: "const foo = 1; bar = foo;", + options: ["foo"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 7, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 22, + }, + ], + }, + { + code: "let foo; foo = bar;", + options: ["foo"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 5, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 10, + }, + ], + }, + { + code: "bar = foo; var foo;", + options: ["foo"], + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 7, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 16, + }, + ], + }, + { + code: "function foo() {} var bar = foo;", + options: ["foo"], + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 10, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 29, + }, + ], + }, + { + code: "class Foo {} var bar = Foo;", + options: ["Foo"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "Foo" }, + type: "Identifier", + column: 7, + }, + { + messageId: "restricted", + data: { name: "Foo" }, + type: "Identifier", + column: 24, + }, + ], + }, - // redeclared globals are not excluded from consideration - { - code: "let undefined; undefined = 1;", - options: ["undefined"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 5 - }, - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 16 - } - ] - }, - { - code: "foo = undefined; var undefined;", - options: ["undefined"], - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 7 - }, - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 22 - } - ] - }, - { - code: "function undefined(){} x = undefined;", - options: ["undefined"], - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 10 - }, - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 28 - } - ] - }, - { - code: "class Number {} x = Number.NaN;", - options: ["Number"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 7 - }, - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 21 - } - ] - }, + // redeclared globals are not excluded from consideration + { + code: "let undefined; undefined = 1;", + options: ["undefined"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 5, + }, + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 16, + }, + ], + }, + { + code: "foo = undefined; var undefined;", + options: ["undefined"], + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 7, + }, + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 22, + }, + ], + }, + { + code: "function undefined(){} x = undefined;", + options: ["undefined"], + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 10, + }, + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 28, + }, + ], + }, + { + code: "class Number {} x = Number.NaN;", + options: ["Number"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 7, + }, + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 21, + }, + ], + }, - /* - * Assignment to a property with a restricted name isn't allowed, in general. - * In this case, that restriction prevents creating a global variable with a restricted name. - */ - { - code: "/* globals myGlobal */ window.myGlobal = 5; foo = myGlobal;", - options: ["myGlobal"], - languageOptions: { - globals: { - window: "readonly" - } - }, - errors: [ - { - messageId: "restricted", - data: { name: "myGlobal" }, - type: "Identifier", - column: 31 - } - ] - }, + /* + * Assignment to a property with a restricted name isn't allowed, in general. + * In this case, that restriction prevents creating a global variable with a restricted name. + */ + { + code: "/* globals myGlobal */ window.myGlobal = 5; foo = myGlobal;", + options: ["myGlobal"], + languageOptions: { + globals: { + window: "readonly", + }, + }, + errors: [ + { + messageId: "restricted", + data: { name: "myGlobal" }, + type: "Identifier", + column: 31, + }, + ], + }, - // disabled global variables - { - code: "var foo = undefined;", - options: ["undefined"], - languageOptions: { - globals: { undefined: "off" } - }, - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier" - } - ] - }, - { - code: "/* globals Number: off */ Number.parseInt()", - options: ["Number"], - errors: [ - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier" - } - ] - }, - { - code: "var foo = [Map];", // this actually isn't a disabled global: it was never enabled because es6 environment isn't enabled - options: ["Map"], - errors: [ - { - messageId: "restricted", - data: { name: "Map" }, - type: "Identifier" - } - ] - }, + // disabled global variables + { + code: "var foo = undefined;", + options: ["undefined"], + languageOptions: { + globals: { undefined: "off" }, + }, + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + }, + ], + }, + { + code: "/* globals Number: off */ Number.parseInt()", + options: ["Number"], + errors: [ + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + }, + ], + }, + { + code: "var foo = [Map];", // this actually isn't a disabled global: it was never enabled because es6 environment isn't enabled + options: ["Map"], + errors: [ + { + messageId: "restricted", + data: { name: "Map" }, + type: "Identifier", + }, + ], + }, - // shadowed global variables - { - code: "if (foo) { let undefined; bar = undefined; }", - options: ["undefined"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 16 - }, - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 33 - } - ] - }, - { - code: "function foo(Number) { var x = Number.NaN; }", - options: ["Number"], - errors: [ - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 14 - }, - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 32 - } - ] - }, - { - code: "function foo() { var myGlobal; x = myGlobal; }", - options: ["myGlobal"], - languageOptions: { - globals: { myGlobal: "readonly" } - }, - errors: [ - { - messageId: "restricted", - data: { name: "myGlobal" }, - type: "Identifier", - column: 22 - }, - { - messageId: "restricted", - data: { name: "myGlobal" }, - type: "Identifier", - column: 36 - } - ] - }, - { - code: "function foo(bar) { return Number.parseInt(bar); } const Number = 1;", - options: ["Number"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 28 - }, - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 58 - } - ] - }, - { - code: "import Number from 'myNumber'; const foo = Number.parseInt(bar);", - options: ["Number"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 8 - }, - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 44 - } - ] - }, - { - code: "var foo = function undefined() {};", - options: ["undefined"], - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier" - } - ] - }, + // shadowed global variables + { + code: "if (foo) { let undefined; bar = undefined; }", + options: ["undefined"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 16, + }, + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 33, + }, + ], + }, + { + code: "function foo(Number) { var x = Number.NaN; }", + options: ["Number"], + errors: [ + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 14, + }, + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 32, + }, + ], + }, + { + code: "function foo() { var myGlobal; x = myGlobal; }", + options: ["myGlobal"], + languageOptions: { + globals: { myGlobal: "readonly" }, + }, + errors: [ + { + messageId: "restricted", + data: { name: "myGlobal" }, + type: "Identifier", + column: 22, + }, + { + messageId: "restricted", + data: { name: "myGlobal" }, + type: "Identifier", + column: 36, + }, + ], + }, + { + code: "function foo(bar) { return Number.parseInt(bar); } const Number = 1;", + options: ["Number"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 28, + }, + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 58, + }, + ], + }, + { + code: "import Number from 'myNumber'; const foo = Number.parseInt(bar);", + options: ["Number"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 8, + }, + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 44, + }, + ], + }, + { + code: "var foo = function undefined() {};", + options: ["undefined"], + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + }, + ], + }, - // this is a reference to a global variable, but at the same time creates a property with a restricted name - { - code: "var foo = { undefined }", - options: ["undefined"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier" - } - ] - } - ] + // this is a reference to a global variable, but at the same time creates a property with a restricted name + { + code: "var foo = { undefined }", + options: ["undefined"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + }, + ], + }, + ], }); diff --git a/tests/lib/rules/id-denylist.js b/tests/lib/rules/id-denylist.js index 4ffab3ea07a2..e9fc9dc48831 100644 --- a/tests/lib/rules/id-denylist.js +++ b/tests/lib/rules/id-denylist.js @@ -10,1425 +10,1459 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/id-denylist"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, }); const error = { messageId: "restricted", type: "Identifier" }; ruleTester.run("id-denylist", rule, { - valid: [ - { - code: "foo = \"bar\"", - options: ["bar"] - }, - { - code: "bar = \"bar\"", - options: ["foo"] - }, - { - code: "foo = \"bar\"", - options: ["f", "fo", "fooo", "bar"] - }, - { - code: "function foo(){}", - options: ["bar"] - }, - { - code: "foo()", - options: ["f", "fo", "fooo", "bar"] - }, - { - code: "import { foo as bar } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "export { foo as bar } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "foo.bar()", - options: ["f", "fo", "fooo", "b", "ba", "baz"] - }, - { - code: "var foo = bar.baz;", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz"] - }, - { - code: "var foo = bar.baz.bing;", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "foo.bar.baz = bing.bong.bash;", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "if (foo.bar) {}", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "var obj = { key: foo.bar };", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "const {foo: bar} = baz", - options: ["foo"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "const {foo: {bar: baz}} = qux", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({ bar: baz }) {}", - options: ["bar"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({ bar: {baz: qux} }) {}", - options: ["bar", "baz"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({baz} = obj.qux) {}", - options: ["qux"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "function foo({ foo: {baz} = obj.qux }) {}", - options: ["qux"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({a: bar = obj.baz});", - options: ["baz"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({foo: {a: bar = obj.baz}} = qux);", - options: ["baz"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var arr = [foo.bar];", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "[foo.bar]", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "[foo.bar.nesting]", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "if (foo.bar === bar.baz) { [foo.bar] }", - options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"] - }, - { - code: "var myArray = new Array(); var myDate = new Date();", - options: ["array", "date", "mydate", "myarray", "new", "var"] - }, - { - code: "foo()", - options: ["foo"] - }, - { - code: "foo.bar()", - options: ["bar"] - }, - { - code: "foo.bar", - options: ["bar"] - }, - { - code: "({foo: obj.bar.bar.bar.baz} = {});", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "({[obj.bar]: a = baz} = qux);", - options: ["bar"], - languageOptions: { ecmaVersion: 6 } - }, + valid: [ + { + code: 'foo = "bar"', + options: ["bar"], + }, + { + code: 'bar = "bar"', + options: ["foo"], + }, + { + code: 'foo = "bar"', + options: ["f", "fo", "fooo", "bar"], + }, + { + code: "function foo(){}", + options: ["bar"], + }, + { + code: "foo()", + options: ["f", "fo", "fooo", "bar"], + }, + { + code: "import { foo as bar } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "export { foo as bar } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "foo.bar()", + options: ["f", "fo", "fooo", "b", "ba", "baz"], + }, + { + code: "var foo = bar.baz;", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz"], + }, + { + code: "var foo = bar.baz.bing;", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "foo.bar.baz = bing.bong.bash;", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "if (foo.bar) {}", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "var obj = { key: foo.bar };", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "const {foo: bar} = baz", + options: ["foo"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "const {foo: {bar: baz}} = qux", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({ bar: baz }) {}", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({ bar: {baz: qux} }) {}", + options: ["bar", "baz"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({baz} = obj.qux) {}", + options: ["qux"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo({ foo: {baz} = obj.qux }) {}", + options: ["qux"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({a: bar = obj.baz});", + options: ["baz"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({foo: {a: bar = obj.baz}} = qux);", + options: ["baz"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var arr = [foo.bar];", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "[foo.bar]", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "[foo.bar.nesting]", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "if (foo.bar === bar.baz) { [foo.bar] }", + options: ["f", "fo", "fooo", "b", "ba", "barr", "bazz", "bingg"], + }, + { + code: "var myArray = new Array(); var myDate = new Date();", + options: ["array", "date", "mydate", "myarray", "new", "var"], + }, + { + code: "foo()", + options: ["foo"], + }, + { + code: "foo.bar()", + options: ["bar"], + }, + { + code: "foo.bar", + options: ["bar"], + }, + { + code: "({foo: obj.bar.bar.bar.baz} = {});", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({[obj.bar]: a = baz} = qux);", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + }, - // references to global variables - { - code: "Number.parseInt()", - options: ["Number"] - }, - { - code: "x = Number.NaN;", - options: ["Number"] - }, - { - code: "var foo = undefined;", - options: ["undefined"] - }, - { - code: "if (foo === undefined);", - options: ["undefined"] - }, - { - code: "obj[undefined] = 5;", // creates obj["undefined"]. It should be disallowed, but the rule doesn't know values of globals and can't control computed access. - options: ["undefined"] - }, - { - code: "foo = { [myGlobal]: 1 };", - options: ["myGlobal"], - languageOptions: { - ecmaVersion: 6, - globals: { myGlobal: "readonly" } - } - }, - { - code: "({ myGlobal } = foo);", // writability doesn't affect the logic, it's always assumed that user doesn't have control over the names of globals. - options: ["myGlobal"], - languageOptions: { - ecmaVersion: 6, - globals: { myGlobal: "writable" } - } - }, - { - code: "/* global myGlobal: readonly */ myGlobal = 5;", - options: ["myGlobal"] - }, - { - code: "var foo = [Map];", - options: ["Map"], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "var foo = { bar: window.baz };", - options: ["window"], - languageOptions: { - globals: { - window: "readonly" - } - } - }, + // references to global variables + { + code: "Number.parseInt()", + options: ["Number"], + }, + { + code: "x = Number.NaN;", + options: ["Number"], + }, + { + code: "var foo = undefined;", + options: ["undefined"], + }, + { + code: "if (foo === undefined);", + options: ["undefined"], + }, + { + code: "obj[undefined] = 5;", // creates obj["undefined"]. It should be disallowed, but the rule doesn't know values of globals and can't control computed access. + options: ["undefined"], + }, + { + code: "foo = { [myGlobal]: 1 };", + options: ["myGlobal"], + languageOptions: { + ecmaVersion: 6, + globals: { myGlobal: "readonly" }, + }, + }, + { + code: "({ myGlobal } = foo);", // writability doesn't affect the logic, it's always assumed that user doesn't have control over the names of globals. + options: ["myGlobal"], + languageOptions: { + ecmaVersion: 6, + globals: { myGlobal: "writable" }, + }, + }, + { + code: "/* global myGlobal: readonly */ myGlobal = 5;", + options: ["myGlobal"], + }, + { + code: "var foo = [Map];", + options: ["Map"], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "var foo = { bar: window.baz };", + options: ["window"], + languageOptions: { + globals: { + window: "readonly", + }, + }, + }, - // Class fields - { - code: "class C { camelCase; #camelCase; #camelCase2() {} }", - options: ["foo"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class C { snake_case; #snake_case; #snake_case2() {} }", - options: ["foo"], - languageOptions: { ecmaVersion: 2022 } - } - ], - invalid: [ - { - code: "foo = \"bar\"", - options: ["foo"], - errors: [ - error - ] - }, - { - code: "bar = \"bar\"", - options: ["bar"], - errors: [ - error - ] - }, - { - code: "foo = \"bar\"", - options: ["f", "fo", "foo", "bar"], - errors: [ - error - ] - }, - { - code: "function foo(){}", - options: ["f", "fo", "foo", "bar"], - errors: [ - error - ] - }, - { - code: "import foo from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - error - ] - }, - { - code: "import * as foo from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - error - ] - }, - { - code: "export * as foo from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 2020, sourceType: "module" }, - errors: [ - error - ] - }, - { - code: "import { foo } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - error - ] - }, - { - code: "import { foo as bar } from 'mod'", - options: ["bar"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 17 - }] - }, - { - code: "import { foo as bar } from 'mod'", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 17 - }] - }, - { - code: "import { foo as foo } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 17 - }] - }, - { - code: "import { foo, foo as bar } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 10 - }] - }, - { - code: "import { foo as bar, foo } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 22 - }] - }, - { - code: "import foo, { foo as bar } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 8 - }] - }, - { - code: "var foo; export { foo as bar };", - options: ["bar"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 26 - }] - }, - { - code: "var foo; export { foo };", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 5 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 19 - } - ] - }, - { - code: "var foo; export { foo as bar };", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 5 - }, + // Class fields + { + code: "class C { camelCase; #camelCase; #camelCase2() {} }", + options: ["foo"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class C { snake_case; #snake_case; #snake_case2() {} }", + options: ["foo"], + languageOptions: { ecmaVersion: 2022 }, + }, - // reports each occurrence of local identifier, although it's renamed in this export specifier - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 19 - } - ] - }, - { - code: "var foo; export { foo as foo };", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 5 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 19 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 26 - } - ] - }, - { - code: "var foo; export { foo as bar };", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 5 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 19 - }, - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 26 - } - ] - }, - { - code: "export { foo } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - error - ] - }, - { - code: "export { foo as bar } from 'mod'", - options: ["bar"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 17 - }] - }, - { - code: "export { foo as bar } from 'mod'", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 17 - }] - }, - { - code: "export { foo as foo } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 17 - }] - }, - { - code: "export { foo, foo as bar } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 10 - }] - }, - { - code: "export { foo as bar, foo } from 'mod'", - options: ["foo"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [{ - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 22 - }] - }, - { - code: "foo.bar()", - options: ["f", "fo", "foo", "b", "ba", "baz"], - errors: [ - error - ] - }, - { - code: "foo[bar] = baz;", - options: ["bar"], - errors: [{ - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier" - }] - }, - { - code: "baz = foo[bar];", - options: ["bar"], - errors: [{ - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier" - }] - }, - { - code: "var foo = bar.baz;", - options: ["f", "fo", "foo", "b", "ba", "barr", "bazz"], - errors: [ - error - ] - }, - { - code: "var foo = bar.baz;", - options: ["f", "fo", "fooo", "b", "ba", "bar", "bazz"], - errors: [ - error - ] - }, - { - code: "if (foo.bar) {}", - options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"], - errors: [ - error - ] - }, - { - code: "var obj = { key: foo.bar };", - options: ["obj"], - errors: [ - error - ] - }, - { - code: "var obj = { key: foo.bar };", - options: ["key"], - errors: [ - error - ] - }, - { - code: "var obj = { key: foo.bar };", - options: ["foo"], - errors: [ - error - ] - }, - { - code: "var arr = [foo.bar];", - options: ["arr"], - errors: [ - error - ] - }, - { - code: "var arr = [foo.bar];", - options: ["foo"], - errors: [ - error - ] - }, - { - code: "[foo.bar]", - options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"], - errors: [ - error - ] - }, - { - code: "if (foo.bar === bar.baz) { [bing.baz] }", - options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"], - errors: [ - error - ] - }, - { - code: "if (foo.bar === bar.baz) { [foo.bar] }", - options: ["f", "fo", "fooo", "b", "ba", "bar", "bazz", "bingg"], - errors: [ - error - ] - }, - { - code: "var myArray = new Array(); var myDate = new Date();", - options: ["array", "date", "myDate", "myarray", "new", "var"], - errors: [ - error - ] - }, - { - code: "var myArray = new Array(); var myDate = new Date();", - options: ["array", "date", "mydate", "myArray", "new", "var"], - errors: [ - error - ] - }, - { - code: "foo.bar = 1", - options: ["bar"], - errors: [ - error - ] - }, - { - code: "foo.bar.baz = 1", - options: ["bar", "baz"], - errors: [ - error - ] - }, - { - code: "const {foo} = baz", - options: ["foo"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 8 - } - ] - }, - { - code: "const {foo: bar} = baz", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 13 - } - ] - }, - { - code: "const {[foo]: bar} = baz", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 9 - }, - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 15 - } - ] - }, - { - code: "const {foo: {bar: baz}} = qux", - options: ["foo", "bar", "baz"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "baz" }, - type: "Identifier", - column: 19 - } - ] - }, - { - code: "const {foo: {[bar]: baz}} = qux", - options: ["foo", "bar", "baz"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 15 - }, - { - messageId: "restricted", - data: { name: "baz" }, - type: "Identifier", - column: 21 - } - ] - }, - { - code: "const {[foo]: {[bar]: baz}} = qux", - options: ["foo", "bar", "baz"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 9 - }, - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 17 - }, - { - messageId: "restricted", - data: { name: "baz" }, - type: "Identifier", - column: 23 - } - ] - }, - { - code: "function foo({ bar: baz }) {}", - options: ["bar", "baz"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "baz" }, - type: "Identifier", - column: 21 - } - ] - }, - { - code: "function foo({ bar: {baz: qux} }) {}", - options: ["bar", "baz", "qux"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "qux" }, - type: "Identifier", - column: 27 - } - ] - }, - { - code: "({foo: obj.bar} = baz);", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 12 - } - ] - }, - { - code: "({foo: obj.bar.bar.bar.baz} = {});", - options: ["foo", "bar", "baz"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "baz" }, - type: "Identifier", - column: 24 - } - ] - }, - { - code: "({[foo]: obj.bar} = baz);", - options: ["foo", "bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 4 - }, - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 14 - } - ] - }, - { - code: "({foo: { a: obj.bar }} = baz);", - options: ["bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 17 - } - ] - }, - { - code: "({a: obj.bar = baz} = qux);", - options: ["bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 10 - } - ] - }, - { - code: "({a: obj.bar.bar.baz = obj.qux} = obj.qux);", - options: ["a", "bar", "baz", "qux"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "baz" }, - type: "Identifier", - column: 18 - } - ] - }, - { - code: "({a: obj[bar] = obj.qux} = obj.qux);", - options: ["a", "bar", "baz", "qux"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 10 - } - ] - }, - { - code: "({a: [obj.bar] = baz} = qux);", - options: ["bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 11 - } - ] - }, - { - code: "({foo: { a: obj.bar = baz}} = qux);", - options: ["bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 17 - } - ] - }, - { - code: "({foo: { [a]: obj.bar }} = baz);", - options: ["bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 19 - } - ] - }, - { - code: "({...obj.bar} = baz);", - options: ["bar"], - languageOptions: { ecmaVersion: 9 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 10 - } - ] - }, - { - code: "([obj.bar] = baz);", - options: ["bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 7 - } - ] - }, - { - code: "const [bar] = baz;", - options: ["bar"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "bar" }, - type: "Identifier", - column: 8 - } - ] - }, + // Import attribute keys + { + code: "import foo from 'foo.json' with { type: 'json' }", + options: ["type"], + languageOptions: { ecmaVersion: 2025, sourceType: "module" }, + }, + { + code: "export * from 'foo.json' with { type: 'json' }", + options: ["type"], + languageOptions: { ecmaVersion: 2025, sourceType: "module" }, + }, + { + code: "export { default } from 'foo.json' with { type: 'json' }", + options: ["type"], + languageOptions: { ecmaVersion: 2025, sourceType: "module" }, + }, + { + code: "import('foo.json', { with: { type: 'json' } })", + options: ["with", "type"], + languageOptions: { ecmaVersion: 2025 }, + }, + { + code: "import('foo.json', { 'with': { type: 'json' } })", + options: ["type"], + languageOptions: { ecmaVersion: 2025 }, + }, + { + code: "import('foo.json', { with: { type } })", + options: ["type"], + languageOptions: { ecmaVersion: 2025 }, + }, + ], + invalid: [ + { + code: 'foo = "bar"', + options: ["foo"], + errors: [error], + }, + { + code: 'bar = "bar"', + options: ["bar"], + errors: [error], + }, + { + code: 'foo = "bar"', + options: ["f", "fo", "foo", "bar"], + errors: [error], + }, + { + code: "function foo(){}", + options: ["f", "fo", "foo", "bar"], + errors: [error], + }, + { + code: "import foo from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [error], + }, + { + code: "import * as foo from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [error], + }, + { + code: "export * as foo from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 2020, sourceType: "module" }, + errors: [error], + }, + { + code: "import { foo } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [error], + }, + { + code: "import { foo as bar } from 'mod'", + options: ["bar"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "import { foo as bar } from 'mod'", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "import { foo as foo } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "import { foo, foo as bar } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 10, + }, + ], + }, + { + code: "import { foo as bar, foo } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 22, + }, + ], + }, + { + code: "import foo, { foo as bar } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 8, + }, + ], + }, + { + code: "var foo; export { foo as bar };", + options: ["bar"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 26, + }, + ], + }, + { + code: "var foo; export { foo };", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 5, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 19, + }, + ], + }, + { + code: "var foo; export { foo as bar };", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 5, + }, - // not a reference to a global variable, because it isn't a reference to a variable - { - code: "foo.undefined = 1;", - options: ["undefined"], - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier" - } - ] - }, - { - code: "var foo = { undefined: 1 };", - options: ["undefined"], - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier" - } - ] - }, - { - code: "var foo = { undefined: undefined };", - options: ["undefined"], - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 13 - } - ] - }, - { - code: "var foo = { Number() {} };", - options: ["Number"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier" - } - ] - }, - { - code: "class Foo { Number() {} }", - options: ["Number"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier" - } - ] - }, - { - code: "myGlobal: while(foo) { break myGlobal; } ", - options: ["myGlobal"], - languageOptions: { - globals: { myGlobal: "readonly" } - }, - errors: [ - { - messageId: "restricted", - data: { name: "myGlobal" }, - type: "Identifier", - column: 1 - }, - { - messageId: "restricted", - data: { name: "myGlobal" }, - type: "Identifier", - column: 30 - } - ] - }, + // reports each occurrence of local identifier, although it's renamed in this export specifier + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 19, + }, + ], + }, + { + code: "var foo; export { foo as foo };", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 5, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 19, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 26, + }, + ], + }, + { + code: "var foo; export { foo as bar };", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 5, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 19, + }, + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 26, + }, + ], + }, + { + code: "export { foo } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [error], + }, + { + code: "export { foo as bar } from 'mod'", + options: ["bar"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "export { foo as bar } from 'mod'", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "export { foo as foo } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "export { foo, foo as bar } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 10, + }, + ], + }, + { + code: "export { foo as bar, foo } from 'mod'", + options: ["foo"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 22, + }, + ], + }, + { + code: "foo.bar()", + options: ["f", "fo", "foo", "b", "ba", "baz"], + errors: [error], + }, + { + code: "foo[bar] = baz;", + options: ["bar"], + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + }, + ], + }, + { + code: "baz = foo[bar];", + options: ["bar"], + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + }, + ], + }, + { + code: "var foo = bar.baz;", + options: ["f", "fo", "foo", "b", "ba", "barr", "bazz"], + errors: [error], + }, + { + code: "var foo = bar.baz;", + options: ["f", "fo", "fooo", "b", "ba", "bar", "bazz"], + errors: [error], + }, + { + code: "if (foo.bar) {}", + options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"], + errors: [error], + }, + { + code: "var obj = { key: foo.bar };", + options: ["obj"], + errors: [error], + }, + { + code: "var obj = { key: foo.bar };", + options: ["key"], + errors: [error], + }, + { + code: "var obj = { key: foo.bar };", + options: ["foo"], + errors: [error], + }, + { + code: "var arr = [foo.bar];", + options: ["arr"], + errors: [error], + }, + { + code: "var arr = [foo.bar];", + options: ["foo"], + errors: [error], + }, + { + code: "[foo.bar]", + options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"], + errors: [error], + }, + { + code: "if (foo.bar === bar.baz) { [bing.baz] }", + options: ["f", "fo", "foo", "b", "ba", "barr", "bazz", "bingg"], + errors: [error], + }, + { + code: "if (foo.bar === bar.baz) { [foo.bar] }", + options: ["f", "fo", "fooo", "b", "ba", "bar", "bazz", "bingg"], + errors: [error], + }, + { + code: "var myArray = new Array(); var myDate = new Date();", + options: ["array", "date", "myDate", "myarray", "new", "var"], + errors: [error], + }, + { + code: "var myArray = new Array(); var myDate = new Date();", + options: ["array", "date", "mydate", "myArray", "new", "var"], + errors: [error], + }, + { + code: "foo.bar = 1", + options: ["bar"], + errors: [error], + }, + { + code: "foo.bar.baz = 1", + options: ["bar", "baz"], + errors: [error], + }, + { + code: "const {foo} = baz", + options: ["foo"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 8, + }, + ], + }, + { + code: "const {foo: bar} = baz", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 13, + }, + ], + }, + { + code: "const {[foo]: bar} = baz", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 9, + }, + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 15, + }, + ], + }, + { + code: "const {foo: {bar: baz}} = qux", + options: ["foo", "bar", "baz"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "baz" }, + type: "Identifier", + column: 19, + }, + ], + }, + { + code: "const {foo: {[bar]: baz}} = qux", + options: ["foo", "bar", "baz"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 15, + }, + { + messageId: "restricted", + data: { name: "baz" }, + type: "Identifier", + column: 21, + }, + ], + }, + { + code: "const {[foo]: {[bar]: baz}} = qux", + options: ["foo", "bar", "baz"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 9, + }, + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 17, + }, + { + messageId: "restricted", + data: { name: "baz" }, + type: "Identifier", + column: 23, + }, + ], + }, + { + code: "function foo({ bar: baz }) {}", + options: ["bar", "baz"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "baz" }, + type: "Identifier", + column: 21, + }, + ], + }, + { + code: "function foo({ bar: {baz: qux} }) {}", + options: ["bar", "baz", "qux"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "qux" }, + type: "Identifier", + column: 27, + }, + ], + }, + { + code: "({foo: obj.bar} = baz);", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 12, + }, + ], + }, + { + code: "({foo: obj.bar.bar.bar.baz} = {});", + options: ["foo", "bar", "baz"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "baz" }, + type: "Identifier", + column: 24, + }, + ], + }, + { + code: "({[foo]: obj.bar} = baz);", + options: ["foo", "bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 4, + }, + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 14, + }, + ], + }, + { + code: "({foo: { a: obj.bar }} = baz);", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "({a: obj.bar = baz} = qux);", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 10, + }, + ], + }, + { + code: "({a: obj.bar.bar.baz = obj.qux} = obj.qux);", + options: ["a", "bar", "baz", "qux"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "baz" }, + type: "Identifier", + column: 18, + }, + ], + }, + { + code: "({a: obj[bar] = obj.qux} = obj.qux);", + options: ["a", "bar", "baz", "qux"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 10, + }, + ], + }, + { + code: "({a: [obj.bar] = baz} = qux);", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 11, + }, + ], + }, + { + code: "({foo: { a: obj.bar = baz}} = qux);", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 17, + }, + ], + }, + { + code: "({foo: { [a]: obj.bar }} = baz);", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 19, + }, + ], + }, + { + code: "({...obj.bar} = baz);", + options: ["bar"], + languageOptions: { ecmaVersion: 9 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 10, + }, + ], + }, + { + code: "([obj.bar] = baz);", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 7, + }, + ], + }, + { + code: "const [bar] = baz;", + options: ["bar"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "bar" }, + type: "Identifier", + column: 8, + }, + ], + }, - // globals declared in the given source code are not excluded from consideration - { - code: "const foo = 1; bar = foo;", - options: ["foo"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 7 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 22 - } - ] - }, - { - code: "let foo; foo = bar;", - options: ["foo"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 5 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 10 - } - ] - }, - { - code: "bar = foo; var foo;", - options: ["foo"], - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 7 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 16 - } - ] - }, - { - code: "function foo() {} var bar = foo;", - options: ["foo"], - errors: [ - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 10 - }, - { - messageId: "restricted", - data: { name: "foo" }, - type: "Identifier", - column: 29 - } - ] - }, - { - code: "class Foo {} var bar = Foo;", - options: ["Foo"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "Foo" }, - type: "Identifier", - column: 7 - }, - { - messageId: "restricted", - data: { name: "Foo" }, - type: "Identifier", - column: 24 - } - ] - }, + // not a reference to a global variable, because it isn't a reference to a variable + { + code: "foo.undefined = 1;", + options: ["undefined"], + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + }, + ], + }, + { + code: "var foo = { undefined: 1 };", + options: ["undefined"], + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + }, + ], + }, + { + code: "var foo = { undefined: undefined };", + options: ["undefined"], + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 13, + }, + ], + }, + { + code: "var foo = { Number() {} };", + options: ["Number"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + }, + ], + }, + { + code: "class Foo { Number() {} }", + options: ["Number"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + }, + ], + }, + { + code: "myGlobal: while(foo) { break myGlobal; } ", + options: ["myGlobal"], + languageOptions: { + globals: { myGlobal: "readonly" }, + }, + errors: [ + { + messageId: "restricted", + data: { name: "myGlobal" }, + type: "Identifier", + column: 1, + }, + { + messageId: "restricted", + data: { name: "myGlobal" }, + type: "Identifier", + column: 30, + }, + ], + }, - // redeclared globals are not excluded from consideration - { - code: "let undefined; undefined = 1;", - options: ["undefined"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 5 - }, - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 16 - } - ] - }, - { - code: "foo = undefined; var undefined;", - options: ["undefined"], - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 7 - }, - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 22 - } - ] - }, - { - code: "function undefined(){} x = undefined;", - options: ["undefined"], - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 10 - }, - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 28 - } - ] - }, - { - code: "class Number {} x = Number.NaN;", - options: ["Number"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 7 - }, - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 21 - } - ] - }, + // globals declared in the given source code are not excluded from consideration + { + code: "const foo = 1; bar = foo;", + options: ["foo"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 7, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 22, + }, + ], + }, + { + code: "let foo; foo = bar;", + options: ["foo"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 5, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 10, + }, + ], + }, + { + code: "bar = foo; var foo;", + options: ["foo"], + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 7, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 16, + }, + ], + }, + { + code: "function foo() {} var bar = foo;", + options: ["foo"], + errors: [ + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 10, + }, + { + messageId: "restricted", + data: { name: "foo" }, + type: "Identifier", + column: 29, + }, + ], + }, + { + code: "class Foo {} var bar = Foo;", + options: ["Foo"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "Foo" }, + type: "Identifier", + column: 7, + }, + { + messageId: "restricted", + data: { name: "Foo" }, + type: "Identifier", + column: 24, + }, + ], + }, - /* - * Assignment to a property with a restricted name isn't allowed, in general. - * In this case, that restriction prevents creating a global variable with a restricted name. - */ - { - code: "/* globals myGlobal */ window.myGlobal = 5; foo = myGlobal;", - options: ["myGlobal"], - languageOptions: { - globals: { - window: "readonly" - } - }, - errors: [ - { - messageId: "restricted", - data: { name: "myGlobal" }, - type: "Identifier", - column: 31 - } - ] - }, + // redeclared globals are not excluded from consideration + { + code: "let undefined; undefined = 1;", + options: ["undefined"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 5, + }, + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 16, + }, + ], + }, + { + code: "foo = undefined; var undefined;", + options: ["undefined"], + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 7, + }, + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 22, + }, + ], + }, + { + code: "function undefined(){} x = undefined;", + options: ["undefined"], + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 10, + }, + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 28, + }, + ], + }, + { + code: "class Number {} x = Number.NaN;", + options: ["Number"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 7, + }, + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 21, + }, + ], + }, - // disabled global variables - { - code: "var foo = undefined;", - options: ["undefined"], - languageOptions: { - globals: { undefined: "off" } - }, - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier" - } - ] - }, - { - code: "/* globals Number: off */ Number.parseInt()", - options: ["Number"], - errors: [ - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier" - } - ] - }, - { - code: "var foo = [Map];", // this actually isn't a disabled global: it was never enabled because es6 environment isn't enabled - options: ["Map"], - errors: [ - { - messageId: "restricted", - data: { name: "Map" }, - type: "Identifier" - } - ] - }, + /* + * Assignment to a property with a restricted name isn't allowed, in general. + * In this case, that restriction prevents creating a global variable with a restricted name. + */ + { + code: "/* globals myGlobal */ window.myGlobal = 5; foo = myGlobal;", + options: ["myGlobal"], + languageOptions: { + globals: { + window: "readonly", + }, + }, + errors: [ + { + messageId: "restricted", + data: { name: "myGlobal" }, + type: "Identifier", + column: 31, + }, + ], + }, - // shadowed global variables - { - code: "if (foo) { let undefined; bar = undefined; }", - options: ["undefined"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 16 - }, - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier", - column: 33 - } - ] - }, - { - code: "function foo(Number) { var x = Number.NaN; }", - options: ["Number"], - errors: [ - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 14 - }, - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 32 - } - ] - }, - { - code: "function foo() { var myGlobal; x = myGlobal; }", - options: ["myGlobal"], - languageOptions: { - globals: { myGlobal: "readonly" } - }, - errors: [ - { - messageId: "restricted", - data: { name: "myGlobal" }, - type: "Identifier", - column: 22 - }, - { - messageId: "restricted", - data: { name: "myGlobal" }, - type: "Identifier", - column: 36 - } - ] - }, - { - code: "function foo(bar) { return Number.parseInt(bar); } const Number = 1;", - options: ["Number"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 28 - }, - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 58 - } - ] - }, - { - code: "import Number from 'myNumber'; const foo = Number.parseInt(bar);", - options: ["Number"], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 8 - }, - { - messageId: "restricted", - data: { name: "Number" }, - type: "Identifier", - column: 44 - } - ] - }, - { - code: "var foo = function undefined() {};", - options: ["undefined"], - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier" - } - ] - }, + // disabled global variables + { + code: "var foo = undefined;", + options: ["undefined"], + languageOptions: { + globals: { undefined: "off" }, + }, + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + }, + ], + }, + { + code: "/* globals Number: off */ Number.parseInt()", + options: ["Number"], + errors: [ + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + }, + ], + }, + { + code: "var foo = [Map];", // this actually isn't a disabled global: it was never enabled because es6 environment isn't enabled + options: ["Map"], + errors: [ + { + messageId: "restricted", + data: { name: "Map" }, + type: "Identifier", + }, + ], + }, - // this is a reference to a global variable, but at the same time creates a property with a restricted name - { - code: "var foo = { undefined }", - options: ["undefined"], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "restricted", - data: { name: "undefined" }, - type: "Identifier" - } - ] - }, + // shadowed global variables + { + code: "if (foo) { let undefined; bar = undefined; }", + options: ["undefined"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 16, + }, + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + column: 33, + }, + ], + }, + { + code: "function foo(Number) { var x = Number.NaN; }", + options: ["Number"], + errors: [ + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 14, + }, + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 32, + }, + ], + }, + { + code: "function foo() { var myGlobal; x = myGlobal; }", + options: ["myGlobal"], + languageOptions: { + globals: { myGlobal: "readonly" }, + }, + errors: [ + { + messageId: "restricted", + data: { name: "myGlobal" }, + type: "Identifier", + column: 22, + }, + { + messageId: "restricted", + data: { name: "myGlobal" }, + type: "Identifier", + column: 36, + }, + ], + }, + { + code: "function foo(bar) { return Number.parseInt(bar); } const Number = 1;", + options: ["Number"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 28, + }, + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 58, + }, + ], + }, + { + code: "import Number from 'myNumber'; const foo = Number.parseInt(bar);", + options: ["Number"], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 8, + }, + { + messageId: "restricted", + data: { name: "Number" }, + type: "Identifier", + column: 44, + }, + ], + }, + { + code: "var foo = function undefined() {};", + options: ["undefined"], + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + }, + ], + }, - // Class fields - { - code: "class C { camelCase; #camelCase; #camelCase2() {} }", - options: ["camelCase"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - messageId: "restricted", - data: { name: "camelCase" }, - type: "Identifier" - }, - { - messageId: "restrictedPrivate", - data: { name: "camelCase" }, - type: "PrivateIdentifier" - } - ] + // this is a reference to a global variable, but at the same time creates a property with a restricted name + { + code: "var foo = { undefined }", + options: ["undefined"], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "restricted", + data: { name: "undefined" }, + type: "Identifier", + }, + ], + }, - }, - { - code: "class C { snake_case; #snake_case() {}; #snake_case2() {} }", - options: ["snake_case"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - messageId: "restricted", - data: { name: "snake_case" }, - type: "Identifier" - }, - { - messageId: "restrictedPrivate", - data: { name: "snake_case" }, - type: "PrivateIdentifier" - } - ] + // Class fields + { + code: "class C { camelCase; #camelCase; #camelCase2() {} }", + options: ["camelCase"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "restricted", + data: { name: "camelCase" }, + type: "Identifier", + }, + { + messageId: "restrictedPrivate", + data: { name: "camelCase" }, + type: "PrivateIdentifier", + }, + ], + }, + { + code: "class C { snake_case; #snake_case() {}; #snake_case2() {} }", + options: ["snake_case"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + messageId: "restricted", + data: { name: "snake_case" }, + type: "Identifier", + }, + { + messageId: "restrictedPrivate", + data: { name: "snake_case" }, + type: "PrivateIdentifier", + }, + ], + }, - } - ] + // Not an import attribute key + { + code: "import('foo.json', { with: { [type]: 'json' } })", + options: ["type"], + languageOptions: { ecmaVersion: 2025 }, + errors: [ + { + messageId: "restricted", + data: { name: "type" }, + type: "Identifier", + }, + ], + }, + { + code: "import('foo.json', { with: { type: json } })", + options: ["json"], + languageOptions: { ecmaVersion: 2025 }, + errors: [ + { + messageId: "restricted", + data: { name: "json" }, + type: "Identifier", + }, + ], + }, + ], }); diff --git a/tests/lib/rules/id-length.js b/tests/lib/rules/id-length.js index 5dfcb226d54d..711e483fc3a7 100644 --- a/tests/lib/rules/id-length.js +++ b/tests/lib/rules/id-length.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/id-length"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -18,820 +18,995 @@ const rule = require("../../../lib/rules/id-length"), const ruleTester = new RuleTester(); const tooShortError = { messageId: "tooShort", type: "Identifier" }; -const tooShortErrorPrivate = { messageId: "tooShortPrivate", type: "PrivateIdentifier" }; +const tooShortErrorPrivate = { + messageId: "tooShortPrivate", + type: "PrivateIdentifier", +}; const tooLongError = { messageId: "tooLong", type: "Identifier" }; -const tooLongErrorPrivate = { messageId: "tooLongPrivate", type: "PrivateIdentifier" }; +const tooLongErrorPrivate = { + messageId: "tooLongPrivate", + type: "PrivateIdentifier", +}; ruleTester.run("id-length", rule, { - valid: [ - "var xyz;", - "var xy = 1;", - "function xyz() {};", - "function xyz(abc, de) {};", - "var obj = { abc: 1, de: 2 };", - "var obj = { 'a': 1, bc: 2 };", - "var obj = {}; obj['a'] = 2;", - "abc = d;", - "try { blah(); } catch (err) { /* pass */ }", - "var handler = function ($e) {};", - "var _a = 2", - "var _ad$$ = new $;", - "var xyz = new ÎŖÎŖ();", - "unrelatedExpressionThatNeedsToBeIgnored();", - "var obj = { 'a': 1, bc: 2 }; obj.tk = obj.a;", - "var query = location.query.q || '';", - "var query = location.query.q ? location.query.q : ''", - { code: "let {a: foo} = bar;", languageOptions: { ecmaVersion: 6 } }, - { code: "let foo = { [a]: 1 };", languageOptions: { ecmaVersion: 6 } }, - { code: "let foo = { [a + b]: 1 };", languageOptions: { ecmaVersion: 6 } }, - { code: "var x = Foo(42)", options: [{ min: 1 }] }, - { code: "var x = Foo(42)", options: [{ min: 0 }] }, - { code: "foo.$x = Foo(42)", options: [{ min: 1 }] }, - { code: "var lalala = Foo(42)", options: [{ max: 6 }] }, - { code: "for (var q, h=0; h < 10; h++) { console.log(h); q++; }", options: [{ exceptions: ["h", "q"] }] }, - { code: "(num) => { num * num };", languageOptions: { ecmaVersion: 6 } }, - { code: "function foo(num = 0) { }", languageOptions: { ecmaVersion: 6 } }, - { code: "class MyClass { }", languageOptions: { ecmaVersion: 6 } }, - { code: "class Foo { method() {} }", languageOptions: { ecmaVersion: 6 } }, - { code: "function foo(...args) { }", languageOptions: { ecmaVersion: 6 } }, - { code: "var { prop } = {};", languageOptions: { ecmaVersion: 6 } }, - { code: "var { [a]: prop } = {};", languageOptions: { ecmaVersion: 6 } }, - { code: "var { a: foo } = {};", options: [{ min: 3 }], languageOptions: { ecmaVersion: 6 } }, - { code: "var { prop: foo } = {};", options: [{ max: 3 }], languageOptions: { ecmaVersion: 6 } }, - { code: "var { longName: foo } = {};", options: [{ min: 3, max: 5 }], languageOptions: { ecmaVersion: 6 } }, - { code: "var { foo: a } = {};", options: [{ exceptions: ["a"] }], languageOptions: { ecmaVersion: 6 } }, - { code: "var { a: { b: { c: longName } } } = {};", languageOptions: { ecmaVersion: 6 } }, - { code: "({ a: obj.x.y.z } = {});", options: [{ properties: "never" }], languageOptions: { ecmaVersion: 6 } }, - { code: "import something from 'y';", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, - { code: "export var num = 0;", languageOptions: { ecmaVersion: 6, sourceType: "module" } }, - { code: "({ prop: obj.x.y.something } = {});", languageOptions: { ecmaVersion: 6 } }, - { code: "({ prop: obj.longName } = {});", languageOptions: { ecmaVersion: 6 } }, - { code: "var obj = { a: 1, bc: 2 };", options: [{ properties: "never" }] }, - { code: "var obj = { [a]: 2 };", options: [{ properties: "never" }], languageOptions: { ecmaVersion: 6 } }, - { code: "var obj = {}; obj.a = 1; obj.bc = 2;", options: [{ properties: "never" }] }, - { code: "({ prop: obj.x } = {});", options: [{ properties: "never" }], languageOptions: { ecmaVersion: 6 } }, - { code: "var obj = { aaaaa: 1 };", options: [{ max: 4, properties: "never" }] }, - { code: "var obj = {}; obj.aaaaa = 1;", options: [{ max: 4, properties: "never" }] }, - { code: "({ a: obj.x.y.z } = {});", options: [{ max: 4, properties: "never" }], languageOptions: { ecmaVersion: 6 } }, - { code: "({ prop: obj.xxxxx } = {});", options: [{ max: 4, properties: "never" }], languageOptions: { ecmaVersion: 6 } }, - { code: "var arr = [i,j,f,b]", languageOptions: { ecmaVersion: 6 } }, - { code: "function foo([arr]) {}", languageOptions: { ecmaVersion: 6 } }, - { code: "var {x} = foo;", options: [{ properties: "never" }], languageOptions: { ecmaVersion: 6 } }, - { code: "var {x, y: {z}} = foo;", options: [{ properties: "never" }], languageOptions: { ecmaVersion: 6 } }, - { code: "let foo = { [a]: 1 };", options: [{ properties: "always" }], languageOptions: { ecmaVersion: 6 } }, - { code: "let foo = { [a + b]: 1 };", options: [{ properties: "always" }], languageOptions: { ecmaVersion: 6 } }, - { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_"] }] }, - { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_", "send$"] }] }, - { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_", "^A", "^Z"] }] }, - { code: "function BEFORE_send() {};", options: [{ min: 3, max: 5, exceptionPatterns: ["^A", "^BEFORE_", "^Z"] }] }, - { code: "var x = 1 ;", options: [{ min: 3, max: 5, exceptionPatterns: ["[x-z]"] }] }, + valid: [ + "var xyz;", + "var xy = 1;", + "function xyz() {};", + "function xyz(abc, de) {};", + "var obj = { abc: 1, de: 2 };", + "var obj = { 'a': 1, bc: 2 };", + "var obj = {}; obj['a'] = 2;", + "abc = d;", + "try { blah(); } catch (err) { /* pass */ }", + "var handler = function ($e) {};", + "var _a = 2", + "var _ad$$ = new $;", + "var xyz = new ÎŖÎŖ();", + "unrelatedExpressionThatNeedsToBeIgnored();", + "var obj = { 'a': 1, bc: 2 }; obj.tk = obj.a;", + "var query = location.query.q || '';", + "var query = location.query.q ? location.query.q : ''", + { code: "let {a: foo} = bar;", languageOptions: { ecmaVersion: 6 } }, + { code: "let foo = { [a]: 1 };", languageOptions: { ecmaVersion: 6 } }, + { + code: "let foo = { [a + b]: 1 };", + languageOptions: { ecmaVersion: 6 }, + }, + { code: "var x = Foo(42)", options: [{ min: 1 }] }, + { code: "var x = Foo(42)", options: [{ min: 0 }] }, + { code: "foo.$x = Foo(42)", options: [{ min: 1 }] }, + { code: "var lalala = Foo(42)", options: [{ max: 6 }] }, + { + code: "for (var q, h=0; h < 10; h++) { console.log(h); q++; }", + options: [{ exceptions: ["h", "q"] }], + }, + { + code: "(num) => { num * num };", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo(num = 0) { }", + languageOptions: { ecmaVersion: 6 }, + }, + { code: "class MyClass { }", languageOptions: { ecmaVersion: 6 } }, + { + code: "class Foo { method() {} }", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo(...args) { }", + languageOptions: { ecmaVersion: 6 }, + }, + { code: "var { prop } = {};", languageOptions: { ecmaVersion: 6 } }, + { + code: "var { [a]: prop } = {};", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { a: foo } = {};", + options: [{ min: 3 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { prop: foo } = {};", + options: [{ max: 3 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { longName: foo } = {};", + options: [{ min: 3, max: 5 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { foo: a } = {};", + options: [{ exceptions: ["a"] }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { a: { b: { c: longName } } } = {};", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ a: obj.x.y.z } = {});", + options: [{ properties: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "import something from 'y';", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "export var num = 0;", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import * as something from 'y';", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import { x } from 'y';", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import { x as x } from 'y';", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import { 'x' as x } from 'y';", + languageOptions: { ecmaVersion: 2022, sourceType: "module" }, + }, + { + code: "import { x as foo } from 'y';", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import { longName } from 'y';", + options: [{ max: 5 }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import { x as bar } from 'y';", + options: [{ max: 5 }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "({ prop: obj.x.y.something } = {});", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ prop: obj.longName } = {});", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var obj = { a: 1, bc: 2 };", + options: [{ properties: "never" }], + }, + { + code: "var obj = { [a]: 2 };", + options: [{ properties: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var obj = {}; obj.a = 1; obj.bc = 2;", + options: [{ properties: "never" }], + }, + { + code: "({ prop: obj.x } = {});", + options: [{ properties: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var obj = { aaaaa: 1 };", + options: [{ max: 4, properties: "never" }], + }, + { + code: "var obj = {}; obj.aaaaa = 1;", + options: [{ max: 4, properties: "never" }], + }, + { + code: "({ a: obj.x.y.z } = {});", + options: [{ max: 4, properties: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "({ prop: obj.xxxxx } = {});", + options: [{ max: 4, properties: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { code: "var arr = [i,j,f,b]", languageOptions: { ecmaVersion: 6 } }, + { code: "function foo([arr]) {}", languageOptions: { ecmaVersion: 6 } }, + { + code: "var {x} = foo;", + options: [{ properties: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var {x, y: {z}} = foo;", + options: [{ properties: "never" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "let foo = { [a]: 1 };", + options: [{ properties: "always" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "let foo = { [a + b]: 1 };", + options: [{ properties: "always" }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function BEFORE_send() {};", + options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_"] }], + }, + { + code: "function BEFORE_send() {};", + options: [ + { min: 3, max: 5, exceptionPatterns: ["^BEFORE_", "send$"] }, + ], + }, + { + code: "function BEFORE_send() {};", + options: [ + { min: 3, max: 5, exceptionPatterns: ["^BEFORE_", "^A", "^Z"] }, + ], + }, + { + code: "function BEFORE_send() {};", + options: [ + { min: 3, max: 5, exceptionPatterns: ["^A", "^BEFORE_", "^Z"] }, + ], + }, + { + code: "var x = 1 ;", + options: [{ min: 3, max: 5, exceptionPatterns: ["[x-z]"] }], + }, - // Class Fields - { - code: "class Foo { #xyz() {} }", - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class Foo { xyz = 1 }", - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class Foo { #xyz = 1 }", - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class Foo { #abc() {} }", - options: [{ max: 3 }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class Foo { abc = 1 }", - options: [{ max: 3 }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class Foo { #abc = 1 }", - options: [{ max: 3 }], - languageOptions: { ecmaVersion: 2022 } - }, + // Class Fields + { + code: "class Foo { #xyz() {} }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class Foo { xyz = 1 }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class Foo { #xyz = 1 }", + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class Foo { #abc() {} }", + options: [{ max: 3 }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class Foo { abc = 1 }", + options: [{ max: 3 }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class Foo { #abc = 1 }", + options: [{ max: 3 }], + languageOptions: { ecmaVersion: 2022 }, + }, - // Identifier consisting of two code units - { - code: "var 𠮟 = 2", - options: [{ min: 1, max: 1 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var č‘›ķ „€ = 2", // 2 code points but only 1 grapheme - options: [{ min: 1, max: 1 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var a = { 𐌘: 1 };", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "(𐌘) => { 𐌘 * 𐌘 };", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "class 𠮟 { }", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "class F { 𐌘() {} }", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "class F { #𐌘() {} }", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 2022 - } - }, - { - code: "class F { 𐌘 = 1 }", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 2022 - } - }, - { - code: "class F { #𐌘 = 1 }", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 2022 - } - }, - { - code: "function f(...𐌘) { }", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "function f([𐌘]) { }", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "var [ 𐌘 ] = a;", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "var { p: [𐌘]} = {};", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "function f({𐌘}) { }", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "var { 𐌘 } = {};", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "var { p: 𐌘} = {};", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 6 - } - }, - { - code: "({ prop: o.𐌘 } = {});", - options: [{ min: 1, max: 1 }], - languageOptions: { - ecmaVersion: 6 - } - } - ], - invalid: [ - { code: "var x = 1;", errors: [tooShortError] }, - { code: "var x;", errors: [tooShortError] }, - { code: "obj.e = document.body;", errors: [tooShortError] }, - { code: "function x() {};", errors: [tooShortError] }, - { code: "function xyz(a) {};", errors: [tooShortError] }, - { code: "var obj = { a: 1, bc: 2 };", errors: [tooShortError] }, - { code: "try { blah(); } catch (e) { /* pass */ }", errors: [tooShortError] }, - { code: "var handler = function (e) {};", errors: [tooShortError] }, - { code: "for (var i=0; i < 10; i++) { console.log(i); }", errors: [tooShortError] }, - { code: "var j=0; while (j > -10) { console.log(--j); }", errors: [tooShortError] }, - { code: "var [i] = arr;", languageOptions: { ecmaVersion: 6 }, errors: [tooShortError] }, - { code: "var [,i,a] = arr;", languageOptions: { ecmaVersion: 6 }, errors: [tooShortError, tooShortError] }, - { code: "function foo([a]) {}", languageOptions: { ecmaVersion: 6 }, errors: [tooShortError] }, - { - code: "var _$xt_$ = Foo(42)", - options: [{ min: 2, max: 4 }], - errors: [ - tooLongError - ] - }, - { - code: "var _$x$_t$ = Foo(42)", - options: [{ min: 2, max: 4 }], - errors: [ - tooLongError - ] - }, - { - code: "var toString;", - options: [{ max: 5 }], - errors: [ - tooLongError - ] - }, - { - code: "(a) => { a * a };", - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "function foo(x = 0) { }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "class x { }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "class Foo { x() {} }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "function foo(...x) { }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "function foo({x}) { }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "function foo({x: a}) { }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "tooShort", - data: { name: "a", min: 2 }, - type: "Identifier" - } - ] - }, - { - code: "function foo({x: a, longName}) { }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "function foo({ longName: a }) {}", - options: [{ min: 3, max: 5 }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "function foo({ prop: longName }) {};", - options: [{ min: 3, max: 5 }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooLongError - ] - }, - { - code: "function foo({ a: b }) {};", - options: [{ exceptions: ["a"] }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "tooShort", - data: { name: "b", min: 2 }, - line: 1, - column: 19, - type: "Identifier" - } - ] - }, - { - code: "var hasOwnProperty;", - options: [{ max: 10, exceptions: [] }], - errors: [ - { - messageId: "tooLong", - data: { name: "hasOwnProperty", max: 10 }, - line: 1, - column: 5, - type: "Identifier" - } - ] - }, - { - code: "function foo({ a: { b: { c: d, e } } }) { }", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "tooShort", - data: { name: "d", min: 2 }, - line: 1, - column: 29, - type: "Identifier" - }, - { - messageId: "tooShort", - data: { name: "e", min: 2 }, - line: 1, - column: 32, - type: "Identifier" - } - ] - }, - { - code: "var { x} = {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "var { x: a} = {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "tooShort", - data: { name: "a", min: 2 }, - type: "Identifier" - } - ] - }, - { - code: "var { a: a} = {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "var { prop: a } = {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "var { longName: a } = {};", - options: [{ min: 3, max: 5 }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "var { prop: [x] } = {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "var { prop: [[x]] } = {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "var { prop: longName } = {};", - options: [{ min: 3, max: 5 }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "tooLong", - data: { name: "longName", max: 5 }, - line: 1, - column: 13, - type: "Identifier" - } - ] - }, - { - code: "var { x: a} = {};", - options: [{ exceptions: ["x"] }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "tooShort", - data: { name: "a", min: 2 }, - line: 1, - column: 10, - type: "Identifier" - } - ] - }, - { - code: "var { a: { b: { c: d } } } = {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "tooShort", - data: { name: "d", min: 2 }, - line: 1, - column: 20, - type: "Identifier" - } - ] - }, - { - code: "var { a: { b: { c: d, e } } } = {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "tooShort", - data: { name: "d", min: 2 }, - line: 1, - column: 20, - type: "Identifier" - }, - { - messageId: "tooShort", - data: { name: "e", min: 2 }, - line: 1, - column: 23, - type: "Identifier" - } - ] - }, - { - code: "var { a: { b: { c, e: longName } } } = {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "tooShort", - data: { name: "c", min: 2 }, - line: 1, - column: 17, - type: "Identifier" - } - ] - }, - { - code: "var { a: { b: { c: d, e: longName } } } = {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "tooShort", - data: { name: "d", min: 2 }, - line: 1, - column: 20, - type: "Identifier" - } - ] - }, - { - code: "var { a, b: { c: d, e: longName } } = {};", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "tooShort", - data: { name: "a", min: 2 }, - line: 1, - column: 7, - type: "Identifier" - }, - { - messageId: "tooShort", - data: { name: "d", min: 2 }, - line: 1, - column: 18, - type: "Identifier" - } - ] - }, - { - code: "import x from 'y';", - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - tooShortError - ] - }, - { - code: "export var x = 0;", - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - tooShortError - ] - }, - { - code: "({ a: obj.x.y.z } = {});", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "tooShort", - data: { name: "z", min: 2 }, - line: 1, - column: 15, - type: "Identifier" - } - ] - }, - { - code: "({ prop: obj.x } = {});", - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "tooShort", - data: { name: "x", min: 2 }, - line: 1, - column: 14, - type: "Identifier" - } - ] - }, - { code: "var x = 1;", options: [{ properties: "never" }], errors: [tooShortError] }, - { - code: "var {prop: x} = foo;", - options: [{ properties: "never" }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - messageId: "tooShort", - data: { name: "x", min: 2 }, - line: 1, - column: 12, - type: "Identifier" - } - ] - }, - { - code: "var foo = {x: prop};", - options: [{ properties: "always" }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "function BEFORE_send() {};", - options: [{ min: 3, max: 5 }], - errors: [ - tooLongError - ] - }, - { - code: "function NOTMATCHED_send() {};", - options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_"] }], - errors: [ - tooLongError - ] - }, - { - code: "function N() {};", - options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_"] }], - errors: [ - tooShortError - ] - }, + // Identifier consisting of two code units + { + code: "var 𠮟 = 2", + options: [{ min: 1, max: 1 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var č‘›ķ „€ = 2", // 2 code points but only 1 grapheme + options: [{ min: 1, max: 1 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var a = { 𐌘: 1 };", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "(𐌘) => { 𐌘 * 𐌘 };", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "class 𠮟 { }", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "class F { 𐌘() {} }", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "class F { #𐌘() {} }", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 2022, + }, + }, + { + code: "class F { 𐌘 = 1 }", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 2022, + }, + }, + { + code: "class F { #𐌘 = 1 }", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 2022, + }, + }, + { + code: "function f(...𐌘) { }", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "function f([𐌘]) { }", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "var [ 𐌘 ] = a;", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "var { p: [𐌘]} = {};", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "function f({𐌘}) { }", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "var { 𐌘 } = {};", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "var { p: 𐌘} = {};", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 6, + }, + }, + { + code: "({ prop: o.𐌘 } = {});", + options: [{ min: 1, max: 1 }], + languageOptions: { + ecmaVersion: 6, + }, + }, - // Class Fields - { - code: "class Foo { #x() {} }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - tooShortErrorPrivate - ] - }, - { - code: "class Foo { x = 1 }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - tooShortError - ] - }, - { - code: "class Foo { #x = 1 }", - languageOptions: { ecmaVersion: 2022 }, - errors: [ - tooShortErrorPrivate - ] - }, - { - code: "class Foo { #abcdefg() {} }", - options: [{ max: 3 }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - tooLongErrorPrivate - ] - }, - { - code: "class Foo { abcdefg = 1 }", - options: [{ max: 3 }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - tooLongError - ] - }, - { - code: "class Foo { #abcdefg = 1 }", - options: [{ max: 3 }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - tooLongErrorPrivate - ] - }, + // Import attribute keys + { + code: "import foo from 'foo.json' with { type: 'json' }", + options: [{ min: 1, max: 3, properties: "always" }], + languageOptions: { + ecmaVersion: 2025, + }, + }, + { + code: "export * from 'foo.json' with { type: 'json' }", + options: [{ min: 1, max: 3, properties: "always" }], + languageOptions: { + ecmaVersion: 2025, + }, + }, + { + code: "export { default } from 'foo.json' with { type: 'json' }", + options: [{ min: 1, max: 3, properties: "always" }], + languageOptions: { + ecmaVersion: 2025, + }, + }, + { + code: "import('foo.json', { with: { type: 'json' } })", + options: [{ min: 1, max: 3, properties: "always" }], + languageOptions: { + ecmaVersion: 2025, + }, + }, + { + code: "import('foo.json', { 'with': { type: 'json' } })", + options: [{ min: 1, max: 3, properties: "always" }], + languageOptions: { + ecmaVersion: 2025, + }, + }, + { + code: "import('foo.json', { with: { type } })", + options: [{ min: 1, max: 3, properties: "always" }], + languageOptions: { + ecmaVersion: 2025, + }, + }, + ], + invalid: [ + { code: "var x = 1;", errors: [tooShortError] }, + { code: "var x;", errors: [tooShortError] }, + { code: "obj.e = document.body;", errors: [tooShortError] }, + { code: "function x() {};", errors: [tooShortError] }, + { code: "function xyz(a) {};", errors: [tooShortError] }, + { code: "var obj = { a: 1, bc: 2 };", errors: [tooShortError] }, + { + code: "try { blah(); } catch (e) { /* pass */ }", + errors: [tooShortError], + }, + { code: "var handler = function (e) {};", errors: [tooShortError] }, + { + code: "for (var i=0; i < 10; i++) { console.log(i); }", + errors: [tooShortError], + }, + { + code: "var j=0; while (j > -10) { console.log(--j); }", + errors: [tooShortError], + }, + { + code: "var [i] = arr;", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "var [,i,a] = arr;", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError, tooShortError], + }, + { + code: "function foo([a]) {}", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "import x from 'module';", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "import { x as z } from 'module';", + languageOptions: { ecmaVersion: 6 }, + errors: [{ ...tooShortError, column: 15 }], + }, + { + code: "import { foo as z } from 'module';", + languageOptions: { ecmaVersion: 6 }, + errors: [{ ...tooShortError, column: 17 }], + }, + { + code: "import { 'foo' as z } from 'module';", + languageOptions: { ecmaVersion: 2022 }, + errors: [{ ...tooShortError, column: 19 }], + }, + { + code: "import * as x from 'module';", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "import longName from 'module';", + options: [{ max: 5 }], + languageOptions: { ecmaVersion: 6 }, + errors: [tooLongError], + }, + { + code: "import * as longName from 'module';", + options: [{ max: 5 }], + languageOptions: { ecmaVersion: 6 }, + errors: [tooLongError], + }, + { + code: "import { foo as longName } from 'module';", + options: [{ max: 5 }], + languageOptions: { ecmaVersion: 6 }, + errors: [{ ...tooLongError, column: 17 }], + }, + { + code: "var _$xt_$ = Foo(42)", + options: [{ min: 2, max: 4 }], + errors: [tooLongError], + }, + { + code: "var _$x$_t$ = Foo(42)", + options: [{ min: 2, max: 4 }], + errors: [tooLongError], + }, + { + code: "var toString;", + options: [{ max: 5 }], + errors: [tooLongError], + }, + { + code: "(a) => { a * a };", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "function foo(x = 0) { }", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "class x { }", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "class Foo { x() {} }", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "function foo(...x) { }", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "function foo({x}) { }", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "function foo({x: a}) { }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "tooShort", + data: { name: "a", min: 2 }, + type: "Identifier", + }, + ], + }, + { + code: "function foo({x: a, longName}) { }", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "function foo({ longName: a }) {}", + options: [{ min: 3, max: 5 }], + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "function foo({ prop: longName }) {};", + options: [{ min: 3, max: 5 }], + languageOptions: { ecmaVersion: 6 }, + errors: [tooLongError], + }, + { + code: "function foo({ a: b }) {};", + options: [{ exceptions: ["a"] }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "tooShort", + data: { name: "b", min: 2 }, + line: 1, + column: 19, + type: "Identifier", + }, + ], + }, + { + code: "var hasOwnProperty;", + options: [{ max: 10, exceptions: [] }], + errors: [ + { + messageId: "tooLong", + data: { name: "hasOwnProperty", max: 10 }, + line: 1, + column: 5, + type: "Identifier", + }, + ], + }, + { + code: "function foo({ a: { b: { c: d, e } } }) { }", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "tooShort", + data: { name: "d", min: 2 }, + line: 1, + column: 29, + type: "Identifier", + }, + { + messageId: "tooShort", + data: { name: "e", min: 2 }, + line: 1, + column: 32, + type: "Identifier", + }, + ], + }, + { + code: "var { x} = {};", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "var { x: a} = {};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "tooShort", + data: { name: "a", min: 2 }, + type: "Identifier", + }, + ], + }, + { + code: "var { a: a} = {};", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "var { prop: a } = {};", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "var { longName: a } = {};", + options: [{ min: 3, max: 5 }], + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "var { prop: [x] } = {};", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "var { prop: [[x]] } = {};", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "var { prop: longName } = {};", + options: [{ min: 3, max: 5 }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "tooLong", + data: { name: "longName", max: 5 }, + line: 1, + column: 13, + type: "Identifier", + }, + ], + }, + { + code: "var { x: a} = {};", + options: [{ exceptions: ["x"] }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "tooShort", + data: { name: "a", min: 2 }, + line: 1, + column: 10, + type: "Identifier", + }, + ], + }, + { + code: "var { a: { b: { c: d } } } = {};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "tooShort", + data: { name: "d", min: 2 }, + line: 1, + column: 20, + type: "Identifier", + }, + ], + }, + { + code: "var { a: { b: { c: d, e } } } = {};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "tooShort", + data: { name: "d", min: 2 }, + line: 1, + column: 20, + type: "Identifier", + }, + { + messageId: "tooShort", + data: { name: "e", min: 2 }, + line: 1, + column: 23, + type: "Identifier", + }, + ], + }, + { + code: "var { a: { b: { c, e: longName } } } = {};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "tooShort", + data: { name: "c", min: 2 }, + line: 1, + column: 17, + type: "Identifier", + }, + ], + }, + { + code: "var { a: { b: { c: d, e: longName } } } = {};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "tooShort", + data: { name: "d", min: 2 }, + line: 1, + column: 20, + type: "Identifier", + }, + ], + }, + { + code: "var { a, b: { c: d, e: longName } } = {};", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "tooShort", + data: { name: "a", min: 2 }, + line: 1, + column: 7, + type: "Identifier", + }, + { + messageId: "tooShort", + data: { name: "d", min: 2 }, + line: 1, + column: 18, + type: "Identifier", + }, + ], + }, + { + code: "import x from 'y';", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [tooShortError], + }, + { + code: "export var x = 0;", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [tooShortError], + }, + { + code: "({ a: obj.x.y.z } = {});", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "tooShort", + data: { name: "z", min: 2 }, + line: 1, + column: 15, + type: "Identifier", + }, + ], + }, + { + code: "({ prop: obj.x } = {});", + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "tooShort", + data: { name: "x", min: 2 }, + line: 1, + column: 14, + type: "Identifier", + }, + ], + }, + { + code: "var x = 1;", + options: [{ properties: "never" }], + errors: [tooShortError], + }, + { + code: "var {prop: x} = foo;", + options: [{ properties: "never" }], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + messageId: "tooShort", + data: { name: "x", min: 2 }, + line: 1, + column: 12, + type: "Identifier", + }, + ], + }, + { + code: "var foo = {x: prop};", + options: [{ properties: "always" }], + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "function BEFORE_send() {};", + options: [{ min: 3, max: 5 }], + errors: [tooLongError], + }, + { + code: "function NOTMATCHED_send() {};", + options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_"] }], + errors: [tooLongError], + }, + { + code: "function N() {};", + options: [{ min: 3, max: 5, exceptionPatterns: ["^BEFORE_"] }], + errors: [tooShortError], + }, - // Identifier consisting of two code units - { - code: "var 𠮟 = 2", - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "var č‘›ķ „€ = 2", // 2 code points but only 1 grapheme - languageOptions: { ecmaVersion: 6 }, - errors: [ - tooShortError - ] - }, - { - code: "var myObj = { 𐌘: 1 };", - languageOptions: { - ecmaVersion: 6 - }, - errors: [ - tooShortError - ] - }, - { - code: "(𐌘) => { 𐌘 * 𐌘 };", - languageOptions: { - ecmaVersion: 6 - }, - errors: [ - tooShortError - ] - }, - { - code: "class 𠮟 { }", - languageOptions: { - ecmaVersion: 6 - }, - errors: [ - tooShortError - ] - }, - { - code: "class Foo { 𐌘() {} }", - languageOptions: { - ecmaVersion: 6 - }, - errors: [ - tooShortError - ] - }, - { - code: "class Foo1 { #𐌘() {} }", - languageOptions: { - ecmaVersion: 2022 - }, - errors: [ - tooShortErrorPrivate - ] - }, - { - code: "class Foo2 { 𐌘 = 1 }", - languageOptions: { - ecmaVersion: 2022 - }, - errors: [ - tooShortError - ] - }, - { - code: "class Foo3 { #𐌘 = 1 }", - languageOptions: { - ecmaVersion: 2022 - }, - errors: [ - tooShortErrorPrivate - ] - }, - { - code: "function foo1(...𐌘) { }", - languageOptions: { - ecmaVersion: 6 - }, - errors: [ - tooShortError - ] - }, - { - code: "function foo([𐌘]) { }", - languageOptions: { - ecmaVersion: 6 - }, - errors: [ - tooShortError - ] - }, - { - code: "var [ 𐌘 ] = arr;", - languageOptions: { - ecmaVersion: 6 - }, - errors: [ - tooShortError - ] - }, - { - code: "var { prop: [𐌘]} = {};", - languageOptions: { - ecmaVersion: 6 - }, - errors: [ - tooShortError - ] - }, - { - code: "function foo({𐌘}) { }", - languageOptions: { - ecmaVersion: 6 - }, - errors: [ - tooShortError - ] - }, - { - code: "var { 𐌘 } = {};", - languageOptions: { - ecmaVersion: 6 - }, - errors: [ - tooShortError - ] - }, - { - code: "var { prop: 𐌘} = {};", - languageOptions: { - ecmaVersion: 6 - }, - errors: [ - tooShortError - ] - }, - { - code: "({ prop: obj.𐌘 } = {});", - languageOptions: { - ecmaVersion: 6 - }, - errors: [ - tooShortError - ] - } - ] + // Class Fields + { + code: "class Foo { #x() {} }", + languageOptions: { ecmaVersion: 2022 }, + errors: [tooShortErrorPrivate], + }, + { + code: "class Foo { x = 1 }", + languageOptions: { ecmaVersion: 2022 }, + errors: [tooShortError], + }, + { + code: "class Foo { #x = 1 }", + languageOptions: { ecmaVersion: 2022 }, + errors: [tooShortErrorPrivate], + }, + { + code: "class Foo { #abcdefg() {} }", + options: [{ max: 3 }], + languageOptions: { ecmaVersion: 2022 }, + errors: [tooLongErrorPrivate], + }, + { + code: "class Foo { abcdefg = 1 }", + options: [{ max: 3 }], + languageOptions: { ecmaVersion: 2022 }, + errors: [tooLongError], + }, + { + code: "class Foo { #abcdefg = 1 }", + options: [{ max: 3 }], + languageOptions: { ecmaVersion: 2022 }, + errors: [tooLongErrorPrivate], + }, + + // Identifier consisting of two code units + { + code: "var 𠮟 = 2", + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "var č‘›ķ „€ = 2", // 2 code points but only 1 grapheme + languageOptions: { ecmaVersion: 6 }, + errors: [tooShortError], + }, + { + code: "var myObj = { 𐌘: 1 };", + languageOptions: { + ecmaVersion: 6, + }, + errors: [tooShortError], + }, + { + code: "(𐌘) => { 𐌘 * 𐌘 };", + languageOptions: { + ecmaVersion: 6, + }, + errors: [tooShortError], + }, + { + code: "class 𠮟 { }", + languageOptions: { + ecmaVersion: 6, + }, + errors: [tooShortError], + }, + { + code: "class Foo { 𐌘() {} }", + languageOptions: { + ecmaVersion: 6, + }, + errors: [tooShortError], + }, + { + code: "class Foo1 { #𐌘() {} }", + languageOptions: { + ecmaVersion: 2022, + }, + errors: [tooShortErrorPrivate], + }, + { + code: "class Foo2 { 𐌘 = 1 }", + languageOptions: { + ecmaVersion: 2022, + }, + errors: [tooShortError], + }, + { + code: "class Foo3 { #𐌘 = 1 }", + languageOptions: { + ecmaVersion: 2022, + }, + errors: [tooShortErrorPrivate], + }, + { + code: "function foo1(...𐌘) { }", + languageOptions: { + ecmaVersion: 6, + }, + errors: [tooShortError], + }, + { + code: "function foo([𐌘]) { }", + languageOptions: { + ecmaVersion: 6, + }, + errors: [tooShortError], + }, + { + code: "var [ 𐌘 ] = arr;", + languageOptions: { + ecmaVersion: 6, + }, + errors: [tooShortError], + }, + { + code: "var { prop: [𐌘]} = {};", + languageOptions: { + ecmaVersion: 6, + }, + errors: [tooShortError], + }, + { + code: "function foo({𐌘}) { }", + languageOptions: { + ecmaVersion: 6, + }, + errors: [tooShortError], + }, + { + code: "var { 𐌘 } = {};", + languageOptions: { + ecmaVersion: 6, + }, + errors: [tooShortError], + }, + { + code: "var { prop: 𐌘} = {};", + languageOptions: { + ecmaVersion: 6, + }, + errors: [tooShortError], + }, + { + code: "({ prop: obj.𐌘 } = {});", + languageOptions: { + ecmaVersion: 6, + }, + errors: [tooShortError], + }, + ], }); diff --git a/tests/lib/rules/id-match.js b/tests/lib/rules/id-match.js index 7d4cec1b8511..d870c067946b 100644 --- a/tests/lib/rules/id-match.js +++ b/tests/lib/rules/id-match.js @@ -10,7 +10,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/id-match"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); //------------------------------------------------------------------------------ // Tests @@ -20,695 +20,929 @@ const ruleTester = new RuleTester(); const error = { messageId: "notMatch", type: "Identifier" }; ruleTester.run("id-match", rule, { - valid: [ - { - code: "__foo = \"Matthieu\"", - options: [ - "^[a-z]+$", - { - onlyDeclarations: true - } - ] - }, - { - code: "firstname = \"Matthieu\"", - options: ["^[a-z]+$"] - }, - { - code: "first_name = \"Matthieu\"", - options: ["[a-z]+"] - }, - { - code: "firstname = \"Matthieu\"", - options: ["^f"] - }, - { - code: "last_Name = \"Larcher\"", - options: ["^[a-z]+(_[A-Z][a-z]+)*$"] - }, - { - code: "param = \"none\"", - options: ["^[a-z]+(_[A-Z][a-z])*$"] - }, - { - code: "function noUnder(){}", - options: ["^[^_]+$"] - }, - { - code: "no_under()", - options: ["^[^_]+$"] - }, - { - code: "foo.no_under2()", - options: ["^[^_]+$"] - }, - { - code: "var foo = bar.no_under3;", - options: ["^[^_]+$"] - }, - { - code: "var foo = bar.no_under4.something;", - options: ["^[^_]+$"] - }, - { - code: "foo.no_under5.qux = bar.no_under6.something;", - options: ["^[^_]+$"] - }, - { - code: "if (bar.no_under7) {}", - options: ["^[^_]+$"] - }, - { - code: "var obj = { key: foo.no_under8 };", - options: ["^[^_]+$"] - }, - { - code: "var arr = [foo.no_under9];", - options: ["^[^_]+$"] - }, - { - code: "[foo.no_under10]", - options: ["^[^_]+$"] - }, - { - code: "var arr = [foo.no_under11.qux];", - options: ["^[^_]+$"] - }, - { - code: "[foo.no_under12.nesting]", - options: ["^[^_]+$"] - }, - { - code: "if (foo.no_under13 === boom.no_under14) { [foo.no_under15] }", - options: ["^[^_]+$"] - }, - { - code: "var myArray = new Array(); var myDate = new Date();", - options: ["^[a-z$]+([A-Z][a-z]+)*$"] - }, - { - code: "var x = obj._foo;", - options: ["^[^_]+$"] - }, - { - code: "var obj = {key: no_under}", - options: ["^[^_]+$", { - properties: true, - onlyDeclarations: true - }] - }, - { - code: "var {key_no_under: key} = {}", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var { category_id } = query;", - options: ["^[^_]+$", { - properties: true, - ignoreDestructuring: true - }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var { category_id: category_id } = query;", - options: ["^[^_]+$", { - properties: true, - ignoreDestructuring: true - }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var { category_id = 1 } = query;", - options: ["^[^_]+$", { - properties: true, - ignoreDestructuring: true - }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: "var o = {key: 1}", - options: ["^[^_]+$", { - properties: true - }] - }, - { - code: "var o = {no_under16: 1}", - options: ["^[^_]+$", { - properties: false - }] - }, - { - code: "obj.no_under17 = 2;", - options: ["^[^_]+$", { - properties: false - }] - }, - { - code: "var obj = {\n no_under18: 1 \n};\n obj.no_under19 = 2;", - options: ["^[^_]+$", { - properties: false - }] - }, - { - code: "obj.no_under20 = function(){};", - options: ["^[^_]+$", { - properties: false - }] - }, - { - code: "var x = obj._foo2;", - options: ["^[^_]+$", { - properties: false - }] - }, + valid: [ + { + code: '__foo = "Matthieu"', + options: [ + "^[a-z]+$", + { + onlyDeclarations: true, + }, + ], + }, + { + code: 'firstname = "Matthieu"', + options: ["^[a-z]+$"], + }, + { + code: 'first_name = "Matthieu"', + options: ["[a-z]+"], + }, + { + code: 'firstname = "Matthieu"', + options: ["^f"], + }, + { + code: 'last_Name = "Larcher"', + options: ["^[a-z]+(_[A-Z][a-z]+)*$"], + }, + { + code: 'param = "none"', + options: ["^[a-z]+(_[A-Z][a-z])*$"], + }, + { + code: "function noUnder(){}", + options: ["^[^_]+$"], + }, + { + code: "no_under()", + options: ["^[^_]+$"], + }, + { + code: "foo.no_under2()", + options: ["^[^_]+$"], + }, + { + code: "var foo = bar.no_under3;", + options: ["^[^_]+$"], + }, + { + code: "var foo = bar.no_under4.something;", + options: ["^[^_]+$"], + }, + { + code: "foo.no_under5.qux = bar.no_under6.something;", + options: ["^[^_]+$"], + }, + { + code: "if (bar.no_under7) {}", + options: ["^[^_]+$"], + }, + { + code: "var obj = { key: foo.no_under8 };", + options: ["^[^_]+$"], + }, + { + code: "var arr = [foo.no_under9];", + options: ["^[^_]+$"], + }, + { + code: "[foo.no_under10]", + options: ["^[^_]+$"], + }, + { + code: "var arr = [foo.no_under11.qux];", + options: ["^[^_]+$"], + }, + { + code: "[foo.no_under12.nesting]", + options: ["^[^_]+$"], + }, + { + code: "if (foo.no_under13 === boom.no_under14) { [foo.no_under15] }", + options: ["^[^_]+$"], + }, + { + code: "var myArray = new Array(); var myDate = new Date();", + options: ["^[a-z$]+([A-Z][a-z]+)*$"], + }, + { + code: "var x = obj._foo;", + options: ["^[^_]+$"], + }, + { + code: "var obj = {key: no_under}", + options: [ + "^[^_]+$", + { + properties: true, + onlyDeclarations: true, + }, + ], + }, + { + code: "var {key_no_under: key} = {}", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { category_id } = query;", + options: [ + "^[^_]+$", + { + properties: true, + ignoreDestructuring: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { category_id: category_id } = query;", + options: [ + "^[^_]+$", + { + properties: true, + ignoreDestructuring: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var { category_id = 1 } = query;", + options: [ + "^[^_]+$", + { + properties: true, + ignoreDestructuring: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var o = {key: 1}", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + }, + { + code: "var o = {no_under16: 1}", + options: [ + "^[^_]+$", + { + properties: false, + }, + ], + }, + { + code: "obj.no_under17 = 2;", + options: [ + "^[^_]+$", + { + properties: false, + }, + ], + }, + { + code: "var obj = {\n no_under18: 1 \n};\n obj.no_under19 = 2;", + options: [ + "^[^_]+$", + { + properties: false, + }, + ], + }, + { + code: "obj.no_under20 = function(){};", + options: [ + "^[^_]+$", + { + properties: false, + }, + ], + }, + { + code: "var x = obj._foo2;", + options: [ + "^[^_]+$", + { + properties: false, + }, + ], + }, - // Should not report for global references - https://github.com/eslint/eslint/issues/15395 - { - code: ` + // Should not report for global references - https://github.com/eslint/eslint/issues/15395 + { + code: ` const foo = Object.keys(bar); const a = Array.from(b); const bar = () => Array; `, - options: ["^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$", { - properties: true - }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: ` + options: [ + "^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: ` const foo = { foo_one: 1, bar_one: 2, fooBar: 3 }; `, - options: ["^[^_]+$", { - properties: false - }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: ` + options: [ + "^[^_]+$", + { + properties: false, + }, + ], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: ` const foo = { foo_one: 1, bar_one: 2, fooBar: 3 }; `, - options: ["^[^_]+$", { - onlyDeclarations: true - }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: ` + options: [ + "^[^_]+$", + { + onlyDeclarations: true, + }, + ], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: ` const foo = { foo_one: 1, bar_one: 2, fooBar: 3 }; `, - options: ["^[^_]+$", { - properties: false, - onlyDeclarations: false - }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: ` + options: [ + "^[^_]+$", + { + properties: false, + onlyDeclarations: false, + }, + ], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: ` const foo = { [a]: 1, }; `, - options: ["^[^a]", { - properties: true, - onlyDeclarations: true - }], - languageOptions: { ecmaVersion: 2022 } - }, + options: [ + "^[^a]", + { + properties: true, + onlyDeclarations: true, + }, + ], + languageOptions: { ecmaVersion: 2022 }, + }, - // Class Methods - { - code: "class x { foo() {} }", - options: ["^[^_]+$"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class x { #foo() {} }", - options: ["^[^_]+$"], - languageOptions: { ecmaVersion: 2022 } - }, + // Class Methods + { + code: "class x { foo() {} }", + options: ["^[^_]+$"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class x { #foo() {} }", + options: ["^[^_]+$"], + languageOptions: { ecmaVersion: 2022 }, + }, - // Class Fields - { - code: "class x { _foo = 1; }", - options: ["^[^_]+$", { - classFields: false - }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: "class x { #_foo = 1; }", - options: ["^[^_]+$", { - classFields: false - }], - languageOptions: { ecmaVersion: 2022 } - } + // Class Fields + { + code: "class x { _foo = 1; }", + options: ["^[^_]+$"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class x { _foo = 1; }", + options: [ + "^[^_]+$", + { + classFields: false, + }, + ], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class x { #_foo = 1; }", + options: [ + "^[^_]+$", + { + classFields: false, + }, + ], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: "class x { #_foo = 1; }", + options: ["^[^_]+$"], + languageOptions: { ecmaVersion: 2022 }, + }, - ], - invalid: [ - { - code: "var __foo = \"Matthieu\"", - options: [ - "^[a-z]+$", - { - onlyDeclarations: true - } - ], - errors: [error] - }, - { - code: "first_name = \"Matthieu\"", - options: ["^[a-z]+$"], - errors: [error] - }, - { - code: "first_name = \"Matthieu\"", - options: ["^z"], - errors: [ - error - ] - }, - { - code: "Last_Name = \"Larcher\"", - options: ["^[a-z]+(_[A-Z][a-z])*$"], - errors: [error - ] - }, - { - code: "var obj = {key: no_under}", - options: ["^[^_]+$", { - properties: true - }], - errors: [ - { - message: "Identifier 'no_under' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "function no_under21(){}", - options: ["^[^_]+$"], - errors: [error - ] - }, - { - code: "obj.no_under22 = function(){};", - options: ["^[^_]+$", { - properties: true - }], - errors: [error - ] - }, - { - code: "no_under23.foo = function(){};", - options: ["^[^_]+$", { - properties: true - }], - errors: [error - ] - }, - { - code: "[no_under24.baz]", - options: ["^[^_]+$", { - properties: true - }], - errors: [error - ] - }, - { - code: "if (foo.bar_baz === boom.bam_pow) { [no_under25.baz] }", - options: ["^[^_]+$", { - properties: true - }], - errors: [error - ] - }, - { - code: "foo.no_under26 = boom.bam_pow", - options: ["^[^_]+$", { - properties: true - }], - errors: [error - ] - }, - { - code: "var foo = { no_under27: boom.bam_pow }", - options: ["^[^_]+$", { - properties: true - }], - errors: [error - ] - }, - { - code: "foo.qux.no_under28 = { bar: boom.bam_pow }", - options: ["^[^_]+$", { - properties: true - }], - errors: [error - ] - }, - { - code: "var o = {no_under29: 1}", - options: ["^[^_]+$", { - properties: true - }], - errors: [error - ] - }, - { - code: "obj.no_under30 = 2;", - options: ["^[^_]+$", { - properties: true - }], - errors: [ - { - messageId: "notMatch", - data: { name: "no_under30", pattern: "^[^_]+$" } - } - ] - }, - { - code: "var { category_id: category_alias } = query;", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - message: "Identifier 'category_alias' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "var { category_id: category_alias } = query;", - options: ["^[^_]+$", { - properties: true, - ignoreDestructuring: true - }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - message: "Identifier 'category_alias' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "var { category_id: categoryId, ...other_props } = query;", - options: ["^[^_]+$", { - properties: true, - ignoreDestructuring: true - }], - languageOptions: { ecmaVersion: 2018 }, - errors: [ - { - message: "Identifier 'other_props' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "var { category_id } = query;", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - message: "Identifier 'category_id' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "var { category_id = 1 } = query;", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - message: "Identifier 'category_id' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "import no_camelcased from \"external-module\";", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - message: "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "import * as no_camelcased from \"external-module\";", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - message: "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "export * as no_camelcased from \"external-module\";", - options: ["^[^_]+$"], - languageOptions: { ecmaVersion: 2020, sourceType: "module" }, - errors: [ - { - message: "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "import { no_camelcased } from \"external-module\";", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - message: "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "import { no_camelcased as no_camel_cased } from \"external module\";", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - message: "Identifier 'no_camel_cased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "import { camelCased as no_camel_cased } from \"external module\";", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - message: "Identifier 'no_camel_cased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "import { camelCased, no_camelcased } from \"external-module\";", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - message: "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "import { no_camelcased as camelCased, another_no_camelcased } from \"external-module\";", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - message: "Identifier 'another_no_camelcased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "import camelCased, { no_camelcased } from \"external-module\";", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - message: "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "import no_camelcased, { another_no_camelcased as camelCased } from \"external-module\";", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: [ - { - message: "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "function foo({ no_camelcased }) {};", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - message: "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "function foo({ no_camelcased = 'default value' }) {};", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - message: "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "const no_camelcased = 0; function foo({ camelcased_value = no_camelcased }) {}", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - message: "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - }, - { - message: "Identifier 'camelcased_value' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "const { bar: no_camelcased } = foo;", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - message: "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "function foo({ value_1: my_default }) {}", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - message: "Identifier 'my_default' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "function foo({ isCamelcased: no_camelcased }) {};", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - message: "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "var { foo: bar_baz = 1 } = quz;", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - message: "Identifier 'bar_baz' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "const { no_camelcased = false } = bar;", - options: ["^[^_]+$", { - properties: true - }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - message: "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, + // Import attribute keys + { + code: "import foo from 'foo.json' with { type: 'json' }", + options: [ + "^foo", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 2025 }, + }, + { + code: "export * from 'foo.json' with { type: 'json' }", + options: [ + "^foo", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 2025 }, + }, + { + code: "export { default } from 'foo.json' with { type: 'json' }", + options: [ + "^def", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 2025 }, + }, + { + code: "import('foo.json', { with: { type: 'json' } })", + options: [ + "^foo", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 2025 }, + }, + { + code: "import('foo.json', { 'with': { type: 'json' } })", + options: [ + "^foo", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 2025 }, + }, + { + code: "import('foo.json', { with: { type } })", + options: [ + "^foo", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 2025 }, + }, + ], + invalid: [ + { + code: 'var __foo = "Matthieu"', + options: [ + "^[a-z]+$", + { + onlyDeclarations: true, + }, + ], + errors: [error], + }, + { + code: 'first_name = "Matthieu"', + options: ["^[a-z]+$"], + errors: [error], + }, + { + code: 'first_name = "Matthieu"', + options: ["^z"], + errors: [error], + }, + { + code: 'Last_Name = "Larcher"', + options: ["^[a-z]+(_[A-Z][a-z])*$"], + errors: [error], + }, + { + code: "var obj = {key: no_under}", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + errors: [ + { + message: + "Identifier 'no_under' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "function no_under21(){}", + options: ["^[^_]+$"], + errors: [error], + }, + { + code: "obj.no_under22 = function(){};", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + errors: [error], + }, + { + code: "no_under23.foo = function(){};", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + errors: [error], + }, + { + code: "[no_under24.baz]", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + errors: [error], + }, + { + code: "if (foo.bar_baz === boom.bam_pow) { [no_under25.baz] }", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + errors: [error], + }, + { + code: "foo.no_under26 = boom.bam_pow", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + errors: [error], + }, + { + code: "var foo = { no_under27: boom.bam_pow }", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + errors: [error], + }, + { + code: "foo.qux.no_under28 = { bar: boom.bam_pow }", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + errors: [error], + }, + { + code: "var o = {no_under29: 1}", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + errors: [error], + }, + { + code: "obj.no_under30 = 2;", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + errors: [ + { + messageId: "notMatch", + data: { name: "no_under30", pattern: "^[^_]+$" }, + }, + ], + }, + { + code: "var { category_id: category_alias } = query;", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Identifier 'category_alias' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "var { category_id: category_alias } = query;", + options: [ + "^[^_]+$", + { + properties: true, + ignoreDestructuring: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Identifier 'category_alias' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "var { category_id: categoryId, ...other_props } = query;", + options: [ + "^[^_]+$", + { + properties: true, + ignoreDestructuring: true, + }, + ], + languageOptions: { ecmaVersion: 2018 }, + errors: [ + { + message: + "Identifier 'other_props' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "var { category_id } = query;", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Identifier 'category_id' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "var { category_id = 1 } = query;", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Identifier 'category_id' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: 'import no_camelcased from "external-module";', + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + message: + "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: 'import * as no_camelcased from "external-module";', + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + message: + "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: 'export * as no_camelcased from "external-module";', + options: ["^[^_]+$"], + languageOptions: { ecmaVersion: 2020, sourceType: "module" }, + errors: [ + { + message: + "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: 'import { no_camelcased } from "external-module";', + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + message: + "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: 'import { no_camelcased as no_camel_cased } from "external module";', + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + message: + "Identifier 'no_camel_cased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: 'import { camelCased as no_camel_cased } from "external module";', + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + message: + "Identifier 'no_camel_cased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: 'import { camelCased, no_camelcased } from "external-module";', + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + message: + "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: 'import { no_camelcased as camelCased, another_no_camelcased } from "external-module";', + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + message: + "Identifier 'another_no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: 'import camelCased, { no_camelcased } from "external-module";', + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + message: + "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: 'import no_camelcased, { another_no_camelcased as camelCased } from "external-module";', + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: [ + { + message: + "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "function foo({ no_camelcased }) {};", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "function foo({ no_camelcased = 'default value' }) {};", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "const no_camelcased = 0; function foo({ camelcased_value = no_camelcased }) {}", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + { + message: + "Identifier 'camelcased_value' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "const { bar: no_camelcased } = foo;", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "function foo({ value_1: my_default }) {}", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Identifier 'my_default' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "function foo({ isCamelcased: no_camelcased }) {};", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "var { foo: bar_baz = 1 } = quz;", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Identifier 'bar_baz' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "const { no_camelcased = false } = bar;", + options: [ + "^[^_]+$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Identifier 'no_camelcased' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, - // https://github.com/eslint/eslint/issues/15395 - { - code: ` + // https://github.com/eslint/eslint/issues/15395 + { + code: ` const foo_variable = 1; class MyClass { } @@ -720,200 +954,272 @@ ruleTester.run("id-match", rule, { let f = (Array) => Array.from(obj, prop); // not global Array foo.Array = 5; // not global Array `, - options: ["^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$", { - properties: true - }], - languageOptions: { ecmaVersion: 6 }, - errors: [ - { - message: "Identifier 'foo_variable' does not match the pattern '^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$'.", - type: "Identifier", - line: 2, - column: 19 - }, - { - message: "Identifier 'MyClass' does not match the pattern '^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$'.", - type: "Identifier", - line: 3, - column: 19 - }, + options: [ + "^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 6 }, + errors: [ + { + message: + "Identifier 'foo_variable' does not match the pattern '^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$'.", + type: "Identifier", + line: 2, + column: 19, + }, + { + message: + "Identifier 'MyClass' does not match the pattern '^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$'.", + type: "Identifier", + line: 3, + column: 19, + }, - // let e = (Object) => Object.keys(obj, prop) - { - message: "Identifier 'Object' does not match the pattern '^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$'.", - type: "Identifier", - line: 9, - column: 22 - }, - { - message: "Identifier 'Object' does not match the pattern '^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$'.", - type: "Identifier", - line: 9, - column: 33 - }, + // let e = (Object) => Object.keys(obj, prop) + { + message: + "Identifier 'Object' does not match the pattern '^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$'.", + type: "Identifier", + line: 9, + column: 22, + }, + { + message: + "Identifier 'Object' does not match the pattern '^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$'.", + type: "Identifier", + line: 9, + column: 33, + }, - // let f =(Array) => Array.from(obj, prop); - { - message: "Identifier 'Array' does not match the pattern '^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$'.", - type: "Identifier", - line: 10, - column: 22 - }, - { - message: "Identifier 'Array' does not match the pattern '^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$'.", - type: "Identifier", - line: 10, - column: 32 - }, + // let f =(Array) => Array.from(obj, prop); + { + message: + "Identifier 'Array' does not match the pattern '^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$'.", + type: "Identifier", + line: 10, + column: 22, + }, + { + message: + "Identifier 'Array' does not match the pattern '^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$'.", + type: "Identifier", + line: 10, + column: 32, + }, - // foo.Array = 5; - { - message: "Identifier 'Array' does not match the pattern '^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$'.", - type: "Identifier", - line: 11, - column: 17 - } - ] - }, + // foo.Array = 5; + { + message: + "Identifier 'Array' does not match the pattern '^\\$?[a-z]+([A-Z0-9][a-z0-9]+)*$'.", + type: "Identifier", + line: 11, + column: 17, + }, + ], + }, - // Class Methods - { - code: "class x { _foo() {} }", - options: ["^[^_]+$"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - message: "Identifier '_foo' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "class x { #_foo() {} }", - options: ["^[^_]+$"], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - message: "Identifier '#_foo' does not match the pattern '^[^_]+$'.", - type: "PrivateIdentifier" - } - ] - }, + // Class Methods + { + code: "class x { _foo() {} }", + options: ["^[^_]+$"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: + "Identifier '_foo' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "class x { #_foo() {} }", + options: ["^[^_]+$"], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: + "Identifier '#_foo' does not match the pattern '^[^_]+$'.", + type: "PrivateIdentifier", + }, + ], + }, - // Class Fields - { - code: "class x { _foo = 1; }", - options: ["^[^_]+$", { - classFields: true - }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - message: "Identifier '_foo' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: "class x { #_foo = 1; }", - options: ["^[^_]+$", { - classFields: true - }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - message: "Identifier '#_foo' does not match the pattern '^[^_]+$'.", - type: "PrivateIdentifier" - } - ] - }, + // Class Fields + { + code: "class x { _foo = 1; }", + options: [ + "^[^_]+$", + { + classFields: true, + }, + ], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: + "Identifier '_foo' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: "class x { #_foo = 1; }", + options: [ + "^[^_]+$", + { + classFields: true, + }, + ], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: + "Identifier '#_foo' does not match the pattern '^[^_]+$'.", + type: "PrivateIdentifier", + }, + ], + }, - // https://github.com/eslint/eslint/issues/15123 - { - code: ` + // https://github.com/eslint/eslint/issues/15123 + { + code: ` const foo = { foo_one: 1, bar_one: 2, fooBar: 3 }; `, - options: ["^[^_]+$", { - properties: true, - onlyDeclarations: true - }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - message: "Identifier 'foo_one' does not match the pattern '^[^_]+$'.", - type: "Identifier" - }, - { - message: "Identifier 'bar_one' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: ` + options: [ + "^[^_]+$", + { + properties: true, + onlyDeclarations: true, + }, + ], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: + "Identifier 'foo_one' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + { + message: + "Identifier 'bar_one' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: ` const foo = { foo_one: 1, bar_one: 2, fooBar: 3 }; `, - options: ["^[^_]+$", { - properties: true, - onlyDeclarations: false - }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - message: "Identifier 'foo_one' does not match the pattern '^[^_]+$'.", - type: "Identifier" - }, - { - message: "Identifier 'bar_one' does not match the pattern '^[^_]+$'.", - type: "Identifier" - } - ] - }, - { - code: ` + options: [ + "^[^_]+$", + { + properties: true, + onlyDeclarations: false, + }, + ], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: + "Identifier 'foo_one' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + { + message: + "Identifier 'bar_one' does not match the pattern '^[^_]+$'.", + type: "Identifier", + }, + ], + }, + { + code: ` const foo = { [a]: 1, }; `, - options: ["^[^a]", { - properties: true, - onlyDeclarations: false - }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - message: "Identifier 'a' does not match the pattern '^[^a]'.", - type: "Identifier" - } - ] - }, + options: [ + "^[^a]", + { + properties: true, + onlyDeclarations: false, + }, + ], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: + "Identifier 'a' does not match the pattern '^[^a]'.", + type: "Identifier", + }, + ], + }, - // https://github.com/eslint/eslint/issues/15443 - { - code: ` + // https://github.com/eslint/eslint/issues/15443 + { + code: ` const foo = { [a]: 1, }; `, - options: ["^[^a]", { - properties: false, - onlyDeclarations: false - }], - languageOptions: { ecmaVersion: 2022 }, - errors: [ - { - message: "Identifier 'a' does not match the pattern '^[^a]'.", - type: "Identifier" - } - ] - } - ] + options: [ + "^[^a]", + { + properties: false, + onlyDeclarations: false, + }, + ], + languageOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: + "Identifier 'a' does not match the pattern '^[^a]'.", + type: "Identifier", + }, + ], + }, + + // Not an import attribute key + { + code: "import('foo.json', { with: { [type]: 'json' } })", + options: [ + "^foo", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 2025 }, + errors: [ + { + message: + "Identifier 'type' does not match the pattern '^foo'.", + }, + ], + }, + { + code: "import('foo.json', { with: { type: json } })", + options: [ + "^foo", + { + properties: true, + }, + ], + languageOptions: { ecmaVersion: 2025 }, + errors: [ + { + message: + "Identifier 'json' does not match the pattern '^foo'.", + }, + ], + }, + ], }); diff --git a/tests/lib/rules/implicit-arrow-linebreak.js b/tests/lib/rules/implicit-arrow-linebreak.js index 7f081197853b..a4aaf293cec8 100644 --- a/tests/lib/rules/implicit-arrow-linebreak.js +++ b/tests/lib/rules/implicit-arrow-linebreak.js @@ -9,7 +9,7 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/implicit-arrow-linebreak"); -const RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); +const RuleTester = require("../../../lib/rule-tester/rule-tester"); const { unIndent } = require("../../_utils"); const EXPECTED_LINEBREAK = { messageId: "expected" }; @@ -22,28 +22,26 @@ const UNEXPECTED_LINEBREAK = { messageId: "unexpected" }; const ruleTester = new RuleTester({ languageOptions: { ecmaVersion: 6 } }); ruleTester.run("implicit-arrow-linebreak", rule, { - - valid: [ - - // always valid - `(foo) => { + valid: [ + // always valid + `(foo) => { bar }`, - // 'beside' option - "() => bar;", - "() => (bar);", - "() => bar => baz;", - "() => ((((bar))));", - `(foo) => ( + // 'beside' option + "() => bar;", + "() => (bar);", + "() => bar => baz;", + "() => ((((bar))));", + `(foo) => ( bar )`, - "(foo) => bar();", - ` + "(foo) => bar();", + ` //comment foo => bar; `, - ` + ` foo => ( // comment bar => ( @@ -52,187 +50,198 @@ ruleTester.run("implicit-arrow-linebreak", rule, { ) ) `, - ` + ` foo => ( // comment bar => baz ) `, - ` + ` /* text */ () => bar; `, - ` + ` /* foo */ const bar = () => baz; `, - ` + ` (foo) => ( //comment bar ) `, - ` + ` [ // comment foo => 'bar' ] `, - ` + ` /* One two three four Five six seven nine. */ (foo) => bar `, - ` + ` const foo = { id: 'bar', // comment prop: (foo1) => 'returning this string', } `, - ` + ` // comment "foo".split('').map((char) => char ) `, - { - code: ` + { + code: ` async foo => () => bar; `, - languageOptions: { ecmaVersion: 8 } - }, - { - code: ` + languageOptions: { ecmaVersion: 8 }, + }, + { + code: ` // comment async foo => 'string' `, - languageOptions: { ecmaVersion: 8 } - }, + languageOptions: { ecmaVersion: 8 }, + }, - // 'below' option - { - code: ` + // 'below' option + { + code: ` (foo) => ( bar ) `, - options: ["below"] - }, { - code: ` + options: ["below"], + }, + { + code: ` () => ((((bar)))); `, - options: ["below"] - }, { - code: ` + options: ["below"], + }, + { + code: ` () => bar(); `, - options: ["below"] - }, { - code: ` + options: ["below"], + }, + { + code: ` () => (bar); `, - options: ["below"] - }, { - code: ` + options: ["below"], + }, + { + code: ` () => bar => baz; `, - options: ["below"] - } - ], + options: ["below"], + }, + ], - invalid: [ - - // 'beside' option - { - code: ` + invalid: [ + // 'beside' option + { + code: ` (foo) => bar(); `, - output: ` + output: ` (foo) => bar(); `, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: ` + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: ` () => (bar); `, - output: ` + output: ` () => (bar); `, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: ` + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: ` () => bar => baz; `, - output: ` + output: ` () => bar => baz; `, - errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK] - }, { - code: ` + errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK], + }, + { + code: ` () => ((((bar)))); `, - output: ` + output: ` () => ((((bar)))); `, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: ` + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: ` (foo) => ( bar ) `, - output: ` + output: ` (foo) => ( bar ) `, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` (foo) => // test comment bar `, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` const foo = () => // comment [] `, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: ` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: ` (foo) => ( //comment bar ) `, - output: ` + output: ` (foo) => ( //comment bar ) `, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: ` + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: ` (foo) => ( bar @@ -240,61 +249,66 @@ ruleTester.run("implicit-arrow-linebreak", rule, { ) `, - output: ` + output: ` (foo) => ( bar //comment ) `, - errors: [UNEXPECTED_LINEBREAK] - - }, { - code: unIndent` + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` (foo) => // comment // another comment bar`, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` (foo) => // comment ( // another comment bar )`, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, - { - code: "() => // comment \n bar", - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: "(foo) => //comment \n bar", - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: "() => // comment \n bar", + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: "(foo) => //comment \n bar", + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` (foo) => /* test comment */ bar `, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` (foo) => // hi bar => // there baz;`, - output: null, - errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` (foo) => // hi bar => ( @@ -302,10 +316,11 @@ ruleTester.run("implicit-arrow-linebreak", rule, { baz ) `, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` const foo = { id: 'bar', prop: (foo1) => @@ -313,37 +328,41 @@ ruleTester.run("implicit-arrow-linebreak", rule, { 'returning this string', } `, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` [ foo => // comment 'bar' ] `, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` "foo".split('').map((char) => // comment char ) `, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` new Promise((resolve, reject) => // comment resolve() ) `, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` () => /* succinct @@ -352,10 +371,11 @@ ruleTester.run("implicit-arrow-linebreak", rule, { */ bar `, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` stepOne => /* here is @@ -365,10 +385,11 @@ ruleTester.run("implicit-arrow-linebreak", rule, { stepTwo => // then this happens stepThree`, - output: null, - errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` () => /* multi @@ -381,66 +402,73 @@ ruleTester.run("implicit-arrow-linebreak", rule, { */ baz `, - output: null, - errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` foo('', boo => // comment bar ) `, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` async foo => // comment 'string' `, - output: null, - languageOptions: { ecmaVersion: 8 }, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + languageOptions: { ecmaVersion: 8 }, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` async foo => // comment // another bar; `, - output: null, - languageOptions: { ecmaVersion: 8 }, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + languageOptions: { ecmaVersion: 8 }, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` async (foo) => // comment 'string' `, - output: null, - languageOptions: { ecmaVersion: 8 }, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + languageOptions: { ecmaVersion: 8 }, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` const foo = 1, bar = 2, baz = () => // comment qux `, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` const foo = () => //comment qux, bar = 2, baz = 3 `, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` const foo = () => //two 1, @@ -449,10 +477,11 @@ ruleTester.run("implicit-arrow-linebreak", rule, { 2, bop = "what" `, - output: null, - errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK, UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` start() .then(() => /* If I put a comment here, eslint --fix breaks badly */ @@ -462,17 +491,19 @@ ruleTester.run("implicit-arrow-linebreak", rule, { /* catch seems to be needed here */ console.log('Error: ', err) })`, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` hello(response => // comment response, param => param)`, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, { - code: unIndent` + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, + { + code: unIndent` start( arr => // cometh @@ -481,44 +512,48 @@ ruleTester.run("implicit-arrow-linebreak", rule, { yyyy } )`, - output: null, - errors: [UNEXPECTED_LINEBREAK] - }, + output: null, + errors: [UNEXPECTED_LINEBREAK], + }, - // 'below' option - { - code: "(foo) => bar();", - output: "(foo) => \nbar();", - options: ["below"], - errors: [EXPECTED_LINEBREAK] - }, { - code: "(foo) => bar => baz;", - output: "(foo) => \nbar => \nbaz;", - options: ["below"], - errors: [EXPECTED_LINEBREAK, EXPECTED_LINEBREAK] - }, { - code: "(foo) => (bar);", - output: "(foo) => \n(bar);", - options: ["below"], - errors: [EXPECTED_LINEBREAK] - }, { - code: "(foo) => (((bar)));", - output: "(foo) => \n(((bar)));", - options: ["below"], - errors: [EXPECTED_LINEBREAK] - }, { - code: ` + // 'below' option + { + code: "(foo) => bar();", + output: "(foo) => \nbar();", + options: ["below"], + errors: [EXPECTED_LINEBREAK], + }, + { + code: "(foo) => bar => baz;", + output: "(foo) => \nbar => \nbaz;", + options: ["below"], + errors: [EXPECTED_LINEBREAK, EXPECTED_LINEBREAK], + }, + { + code: "(foo) => (bar);", + output: "(foo) => \n(bar);", + options: ["below"], + errors: [EXPECTED_LINEBREAK], + }, + { + code: "(foo) => (((bar)));", + output: "(foo) => \n(((bar)));", + options: ["below"], + errors: [EXPECTED_LINEBREAK], + }, + { + code: ` (foo) => ( bar ) `, - output: ` + output: ` (foo) => \n( bar ) `, - options: ["below"], - errors: [EXPECTED_LINEBREAK] - } - ] + options: ["below"], + errors: [EXPECTED_LINEBREAK], + }, + ], }); diff --git a/tests/lib/rules/indent-legacy.js b/tests/lib/rules/indent-legacy.js index 923d3e08399e..bc4f17a035e1 100644 --- a/tests/lib/rules/indent-legacy.js +++ b/tests/lib/rules/indent-legacy.js @@ -10,16 +10,28 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/indent-legacy"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); -const fs = require("fs"); -const path = require("path"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); +const fs = require("node:fs"); +const path = require("node:path"); //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ -const fixture = fs.readFileSync(path.join(__dirname, "../../fixtures/rules/indent-legacy/indent-invalid-fixture-1.js"), "utf8"); -const fixedFixture = fs.readFileSync(path.join(__dirname, "../../fixtures/rules/indent-legacy/indent-valid-fixture-1.js"), "utf8"); +const fixture = fs.readFileSync( + path.join( + __dirname, + "../../fixtures/rules/indent-legacy/indent-invalid-fixture-1.js", + ), + "utf8", +); +const fixedFixture = fs.readFileSync( + path.join( + __dirname, + "../../fixtures/rules/indent-legacy/indent-valid-fixture-1.js", + ), + "utf8", +); /** * Create error message object for failure cases with a single 'found' indentation type @@ -29,3853 +41,3559 @@ const fixedFixture = fs.readFileSync(path.join(__dirname, "../../fixtures/rules/ * @private */ function expectedErrors(providedIndentType, providedErrors) { - let indentType; - let errors; + let indentType; + let errors; - if (Array.isArray(providedIndentType)) { - errors = Array.isArray(providedIndentType[0]) ? providedIndentType : [providedIndentType]; - indentType = "space"; - } else { - errors = Array.isArray(providedErrors[0]) ? providedErrors : [providedErrors]; - indentType = providedIndentType; - } + if (Array.isArray(providedIndentType)) { + errors = Array.isArray(providedIndentType[0]) + ? providedIndentType + : [providedIndentType]; + indentType = "space"; + } else { + errors = Array.isArray(providedErrors[0]) + ? providedErrors + : [providedErrors]; + indentType = providedIndentType; + } - return errors.map(err => ({ - messageId: "expected", - data: { - expected: typeof err[1] === "string" && typeof err[2] === "string" - ? err[1] - : `${err[1]} ${indentType}${err[1] === 1 ? "" : "s"}`, - actual: err[2] - }, - type: err[3], - line: err[0] - })); + return errors.map(err => ({ + messageId: "expected", + data: { + expected: + typeof err[1] === "string" && typeof err[2] === "string" + ? err[1] + : `${err[1]} ${indentType}${err[1] === 1 ? "" : "s"}`, + actual: err[2], + }, + type: err[3], + line: err[0], + })); } const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 5, - sourceType: "script" - } + languageOptions: { + ecmaVersion: 5, + sourceType: "script", + }, }); ruleTester.run("indent-legacy", rule, { - valid: [ - { - code: - "bridge.callHandler(\n" + - " 'getAppVersion', 'test23', function(responseData) {\n" + - " window.ah.mobileAppVersion = responseData;\n" + - " }\n" + - ");\n", - options: [2] - }, - { - code: - "var a = [\n" + - " , /*{\n" + - " }, */{\n" + - " name: 'foo',\n" + - " }\n" + - "];\n", - options: [2] - }, - { - code: - "bridge.callHandler(\n" + - " 'getAppVersion', 'test23', function(responseData) {\n" + - " window.ah.mobileAppVersion = responseData;\n" + - " });\n", - options: [2] - }, - { - code: - "bridge.callHandler(\n" + - " 'getAppVersion',\n" + - " null,\n" + - " function responseCallback(responseData) {\n" + - " window.ah.mobileAppVersion = responseData;\n" + - " }\n" + - ");\n", - options: [2] - }, - { - code: - "bridge.callHandler(\n" + - " 'getAppVersion',\n" + - " null,\n" + - " function responseCallback(responseData) {\n" + - " window.ah.mobileAppVersion = responseData;\n" + - " });\n", - options: [2] - }, - { - code: - "function doStuff(keys) {\n" + - " _.forEach(\n" + - " keys,\n" + - " key => {\n" + - " doSomething(key);\n" + - " }\n" + - " );\n" + - "}\n", - options: [4], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "example(\n" + - " function () {\n" + - " console.log('example');\n" + - " }\n" + - ");\n", - options: [4] - }, - { - code: - "let foo = somethingList\n" + - " .filter(x => {\n" + - " return x;\n" + - " })\n" + - " .map(x => {\n" + - " return 100 * x;\n" + - " });\n", - options: [4], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "var x = 0 &&\n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " };", - options: [4] - }, - { - code: - "var x = 0 &&\n" + - "\t{\n" + - "\t\ta: 1,\n" + - "\t\tb: 2\n" + - "\t};", - options: ["tab"] - }, - { - code: - "var x = 0 &&\n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " }||\n" + - " {\n" + - " c: 3,\n" + - " d: 4\n" + - " };", - options: [4] - }, - { - code: - "var x = [\n" + - " 'a',\n" + - " 'b',\n" + - " 'c'\n" + - "];", - options: [4] - }, - { - code: - "var x = ['a',\n" + - " 'b',\n" + - " 'c',\n" + - "];", - options: [4] - }, - { - code: - "var x = 0 && 1;", - options: [4] - }, - { - code: - "var x = 0 && { a: 1, b: 2 };", - options: [4] - }, - { - code: - "var x = 0 &&\n" + - " (\n" + - " 1\n" + - " );", - options: [4] - }, - { - code: - "require('http').request({hostname: 'localhost',\n" + - " port: 80}, function(res) {\n" + - " res.end();\n" + - "});\n", - options: [2] - }, - { - code: - "function test() {\n" + - " return client.signUp(email, PASSWORD, { preVerified: true })\n" + - " .then(function (result) {\n" + - " // hi\n" + - " })\n" + - " .then(function () {\n" + - " return FunctionalHelpers.clearBrowserState(self, {\n" + - " contentServer: true,\n" + - " contentServer1: true\n" + - " });\n" + - " });\n" + - "}", - options: [2] - }, - { - code: - "it('should... some lengthy test description that is forced to be' +\n" + - " 'wrapped into two lines since the line length limit is set', () => {\n" + - " expect(true).toBe(true);\n" + - "});\n", - options: [2], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "function test() {\n" + - " return client.signUp(email, PASSWORD, { preVerified: true })\n" + - " .then(function (result) {\n" + - " var x = 1;\n" + - " var y = 1;\n" + - " }, function(err){\n" + - " var o = 1 - 2;\n" + - " var y = 1 - 2;\n" + - " return true;\n" + - " })\n" + - "}", - options: [4] - }, - { - code: - "function test() {\n" + - " return client.signUp(email, PASSWORD, { preVerified: true })\n" + - " .then(function (result) {\n" + - " var x = 1;\n" + - " var y = 1;\n" + - " }, function(err){\n" + - " var o = 1 - 2;\n" + - " var y = 1 - 2;\n" + - " return true;\n" + - " });\n" + - "}", - options: [4, { MemberExpression: 0 }] - }, + valid: [ + { + code: + "bridge.callHandler(\n" + + " 'getAppVersion', 'test23', function(responseData) {\n" + + " window.ah.mobileAppVersion = responseData;\n" + + " }\n" + + ");\n", + options: [2], + }, + { + code: + "var a = [\n" + + " , /*{\n" + + " }, */{\n" + + " name: 'foo',\n" + + " }\n" + + "];\n", + options: [2], + }, + { + code: + "bridge.callHandler(\n" + + " 'getAppVersion', 'test23', function(responseData) {\n" + + " window.ah.mobileAppVersion = responseData;\n" + + " });\n", + options: [2], + }, + { + code: + "bridge.callHandler(\n" + + " 'getAppVersion',\n" + + " null,\n" + + " function responseCallback(responseData) {\n" + + " window.ah.mobileAppVersion = responseData;\n" + + " }\n" + + ");\n", + options: [2], + }, + { + code: + "bridge.callHandler(\n" + + " 'getAppVersion',\n" + + " null,\n" + + " function responseCallback(responseData) {\n" + + " window.ah.mobileAppVersion = responseData;\n" + + " });\n", + options: [2], + }, + { + code: + "function doStuff(keys) {\n" + + " _.forEach(\n" + + " keys,\n" + + " key => {\n" + + " doSomething(key);\n" + + " }\n" + + " );\n" + + "}\n", + options: [4], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "example(\n" + + " function () {\n" + + " console.log('example');\n" + + " }\n" + + ");\n", + options: [4], + }, + { + code: + "let foo = somethingList\n" + + " .filter(x => {\n" + + " return x;\n" + + " })\n" + + " .map(x => {\n" + + " return 100 * x;\n" + + " });\n", + options: [4], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "var x = 0 &&\n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " };", + options: [4], + }, + { + code: + "var x = 0 &&\n" + + "\t{\n" + + "\t\ta: 1,\n" + + "\t\tb: 2\n" + + "\t};", + options: ["tab"], + }, + { + code: + "var x = 0 &&\n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " }||\n" + + " {\n" + + " c: 3,\n" + + " d: 4\n" + + " };", + options: [4], + }, + { + code: + "var x = [\n" + + " 'a',\n" + + " 'b',\n" + + " 'c'\n" + + "];", + options: [4], + }, + { + code: "var x = ['a',\n" + " 'b',\n" + " 'c',\n" + "];", + options: [4], + }, + { + code: "var x = 0 && 1;", + options: [4], + }, + { + code: "var x = 0 && { a: 1, b: 2 };", + options: [4], + }, + { + code: "var x = 0 &&\n" + " (\n" + " 1\n" + " );", + options: [4], + }, + { + code: + "require('http').request({hostname: 'localhost',\n" + + " port: 80}, function(res) {\n" + + " res.end();\n" + + "});\n", + options: [2], + }, + { + code: + "function test() {\n" + + " return client.signUp(email, PASSWORD, { preVerified: true })\n" + + " .then(function (result) {\n" + + " // hi\n" + + " })\n" + + " .then(function () {\n" + + " return FunctionalHelpers.clearBrowserState(self, {\n" + + " contentServer: true,\n" + + " contentServer1: true\n" + + " });\n" + + " });\n" + + "}", + options: [2], + }, + { + code: + "it('should... some lengthy test description that is forced to be' +\n" + + " 'wrapped into two lines since the line length limit is set', () => {\n" + + " expect(true).toBe(true);\n" + + "});\n", + options: [2], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "function test() {\n" + + " return client.signUp(email, PASSWORD, { preVerified: true })\n" + + " .then(function (result) {\n" + + " var x = 1;\n" + + " var y = 1;\n" + + " }, function(err){\n" + + " var o = 1 - 2;\n" + + " var y = 1 - 2;\n" + + " return true;\n" + + " })\n" + + "}", + options: [4], + }, + { + code: + "function test() {\n" + + " return client.signUp(email, PASSWORD, { preVerified: true })\n" + + " .then(function (result) {\n" + + " var x = 1;\n" + + " var y = 1;\n" + + " }, function(err){\n" + + " var o = 1 - 2;\n" + + " var y = 1 - 2;\n" + + " return true;\n" + + " });\n" + + "}", + options: [4, { MemberExpression: 0 }], + }, - { - code: - "// hi", - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "var Command = function() {\n" + - " var fileList = [],\n" + - " files = []\n" + - "\n" + - " files.concat(fileList)\n" + - "};\n", - options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }] - }, - { - code: - " ", - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "if(data) {\n" + - " console.log('hi');\n" + - " b = true;};", - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "foo = () => {\n" + - " console.log('hi');\n" + - " return true;};", - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "function test(data) {\n" + - " console.log('hi');\n" + - " return true;};", - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "var test = function(data) {\n" + - " console.log('hi');\n" + - "};", - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "arr.forEach(function(data) {\n" + - " otherdata.forEach(function(zero) {\n" + - " console.log('hi');\n" + - " }) });", - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "a = [\n" + - " ,3\n" + - "]", - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "[\n" + - " ['gzip', 'gunzip'],\n" + - " ['gzip', 'unzip'],\n" + - " ['deflate', 'inflate'],\n" + - " ['deflateRaw', 'inflateRaw'],\n" + - "].forEach(function(method) {\n" + - " console.log(method);\n" + - "});\n", - options: [2, { SwitchCase: 1, VariableDeclarator: 2 }] - }, - { - code: - "test(123, {\n" + - " bye: {\n" + - " hi: [1,\n" + - " {\n" + - " b: 2\n" + - " }\n" + - " ]\n" + - " }\n" + - "});", - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "var xyz = 2,\n" + - " lmn = [\n" + - " {\n" + - " a: 1\n" + - " }\n" + - " ];", - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "lmn = [{\n" + - " a: 1\n" + - "},\n" + - "{\n" + - " b: 2\n" + - "}," + - "{\n" + - " x: 2\n" + - "}];", - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "abc({\n" + - " test: [\n" + - " [\n" + - " c,\n" + - " xyz,\n" + - " 2\n" + - " ].join(',')\n" + - " ]\n" + - "});", - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "abc = {\n" + - " test: [\n" + - " [\n" + - " c,\n" + - " xyz,\n" + - " 2\n" + - " ]\n" + - " ]\n" + - "};", - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "abc(\n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " }\n" + - ");", - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "abc({\n" + - " a: 1,\n" + - " b: 2\n" + - "});", - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "var abc = \n" + - " [\n" + - " c,\n" + - " xyz,\n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " }\n" + - " ];", - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "var abc = [\n" + - " c,\n" + - " xyz,\n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " }\n" + - "];", - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "var abc = 5,\n" + - " c = 2,\n" + - " xyz = \n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " };", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: - "var abc = \n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " };", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: - "var a = new abc({\n" + - " a: 1,\n" + - " b: 2\n" + - " }),\n" + - " b = 2;", - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "var a = 2,\n" + - " c = {\n" + - " a: 1,\n" + - " b: 2\n" + - " },\n" + - " b = 2;", - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: - "var x = 2,\n" + - " y = {\n" + - " a: 1,\n" + - " b: 2\n" + - " },\n" + - " b = 2;", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: - "var e = {\n" + - " a: 1,\n" + - " b: 2\n" + - " },\n" + - " b = 2;", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: - "var a = {\n" + - " a: 1,\n" + - " b: 2\n" + - "};", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: - "function test() {\n" + - " if (true ||\n " + - " false){\n" + - " console.log(val);\n" + - " }\n" + - "}", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: - "for (var val in obj)\n" + - " if (true)\n" + - " console.log(val);", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: - "if(true)\n" + - " if (true)\n" + - " if (true)\n" + - " console.log(val);", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: - "function hi(){ var a = 1;\n" + - " y++; x++;\n" + - "}", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: - "for(;length > index; index++)if(NO_HOLES || index in self){\n" + - " x++;\n" + - "}", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: - "function test(){\n" + - " switch(length){\n" + - " case 1: return function(a){\n" + - " return fn.call(that, a);\n" + - " };\n" + - " }\n" + - "}", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: - "var geometry = 2,\n" + - "rotate = 2;", - options: [2, { VariableDeclarator: 0 }] - }, - { - code: - "var geometry,\n" + - " rotate;", - options: [4, { VariableDeclarator: 1 }] - }, - { - code: - "var geometry,\n" + - "\trotate;", - options: ["tab", { VariableDeclarator: 1 }] - }, - { - code: - "var geometry,\n" + - " rotate;", - options: [2, { VariableDeclarator: 1 }] - }, - { - code: - "var geometry,\n" + - " rotate;", - options: [2, { VariableDeclarator: 2 }] - }, - { - code: - "let geometry,\n" + - " rotate;", - options: [2, { VariableDeclarator: 2 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "const geometry = 2,\n" + - " rotate = 3;", - options: [2, { VariableDeclarator: 2 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth,\n" + - " height, rotate;", - options: [2, { SwitchCase: 1 }] - }, - { - code: - "var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth;", - options: [2, { SwitchCase: 1 }] - }, - { - code: - "if (1 < 2){\n" + - "//hi sd \n" + - "}", - options: [2] - }, - { - code: - "while (1 < 2){\n" + - " //hi sd \n" + - "}", - options: [2] - }, - { - code: - "while (1 < 2) console.log('hi');", - options: [2] - }, + { + code: "// hi", + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "var Command = function() {\n" + + " var fileList = [],\n" + + " files = []\n" + + "\n" + + " files.concat(fileList)\n" + + "};\n", + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + }, + { + code: " ", + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: "if(data) {\n" + " console.log('hi');\n" + " b = true;};", + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "foo = () => {\n" + + " console.log('hi');\n" + + " return true;};", + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "function test(data) {\n" + + " console.log('hi');\n" + + " return true;};", + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "var test = function(data) {\n" + + " console.log('hi');\n" + + "};", + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "arr.forEach(function(data) {\n" + + " otherdata.forEach(function(zero) {\n" + + " console.log('hi');\n" + + " }) });", + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: "a = [\n" + " ,3\n" + "]", + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "[\n" + + " ['gzip', 'gunzip'],\n" + + " ['gzip', 'unzip'],\n" + + " ['deflate', 'inflate'],\n" + + " ['deflateRaw', 'inflateRaw'],\n" + + "].forEach(function(method) {\n" + + " console.log(method);\n" + + "});\n", + options: [2, { SwitchCase: 1, VariableDeclarator: 2 }], + }, + { + code: + "test(123, {\n" + + " bye: {\n" + + " hi: [1,\n" + + " {\n" + + " b: 2\n" + + " }\n" + + " ]\n" + + " }\n" + + "});", + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "var xyz = 2,\n" + + " lmn = [\n" + + " {\n" + + " a: 1\n" + + " }\n" + + " ];", + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "lmn = [{\n" + + " a: 1\n" + + "},\n" + + "{\n" + + " b: 2\n" + + "}," + + "{\n" + + " x: 2\n" + + "}];", + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "abc({\n" + + " test: [\n" + + " [\n" + + " c,\n" + + " xyz,\n" + + " 2\n" + + " ].join(',')\n" + + " ]\n" + + "});", + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "abc = {\n" + + " test: [\n" + + " [\n" + + " c,\n" + + " xyz,\n" + + " 2\n" + + " ]\n" + + " ]\n" + + "};", + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "abc(\n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " }\n" + + ");", + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: "abc({\n" + " a: 1,\n" + " b: 2\n" + "});", + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "var abc = \n" + + " [\n" + + " c,\n" + + " xyz,\n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " }\n" + + " ];", + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "var abc = [\n" + + " c,\n" + + " xyz,\n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " }\n" + + "];", + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "var abc = 5,\n" + + " c = 2,\n" + + " xyz = \n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " };", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: + "var abc = \n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " };", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: + "var a = new abc({\n" + + " a: 1,\n" + + " b: 2\n" + + " }),\n" + + " b = 2;", + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "var a = 2,\n" + + " c = {\n" + + " a: 1,\n" + + " b: 2\n" + + " },\n" + + " b = 2;", + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: + "var x = 2,\n" + + " y = {\n" + + " a: 1,\n" + + " b: 2\n" + + " },\n" + + " b = 2;", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: + "var e = {\n" + + " a: 1,\n" + + " b: 2\n" + + " },\n" + + " b = 2;", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: "var a = {\n" + " a: 1,\n" + " b: 2\n" + "};", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: + "function test() {\n" + + " if (true ||\n " + + " false){\n" + + " console.log(val);\n" + + " }\n" + + "}", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: + "for (var val in obj)\n" + + " if (true)\n" + + " console.log(val);", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: + "if(true)\n" + + " if (true)\n" + + " if (true)\n" + + " console.log(val);", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: + "function hi(){ var a = 1;\n" + + " y++; x++;\n" + + "}", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: + "for(;length > index; index++)if(NO_HOLES || index in self){\n" + + " x++;\n" + + "}", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: + "function test(){\n" + + " switch(length){\n" + + " case 1: return function(a){\n" + + " return fn.call(that, a);\n" + + " };\n" + + " }\n" + + "}", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: "var geometry = 2,\n" + "rotate = 2;", + options: [2, { VariableDeclarator: 0 }], + }, + { + code: "var geometry,\n" + " rotate;", + options: [4, { VariableDeclarator: 1 }], + }, + { + code: "var geometry,\n" + "\trotate;", + options: ["tab", { VariableDeclarator: 1 }], + }, + { + code: "var geometry,\n" + " rotate;", + options: [2, { VariableDeclarator: 1 }], + }, + { + code: "var geometry,\n" + " rotate;", + options: [2, { VariableDeclarator: 2 }], + }, + { + code: "let geometry,\n" + " rotate;", + options: [2, { VariableDeclarator: 2 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "const geometry = 2,\n" + " rotate = 3;", + options: [2, { VariableDeclarator: 2 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth,\n" + + " height, rotate;", + options: [2, { SwitchCase: 1 }], + }, + { + code: "var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth;", + options: [2, { SwitchCase: 1 }], + }, + { + code: "if (1 < 2){\n" + "//hi sd \n" + "}", + options: [2], + }, + { + code: "while (1 < 2){\n" + " //hi sd \n" + "}", + options: [2], + }, + { + code: "while (1 < 2) console.log('hi');", + options: [2], + }, - { - code: - "[a, b,\n" + - " c].forEach((index) => {\n" + - " index;\n" + - " });\n", - options: [4], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "[a, b, c].forEach((index) => {\n" + - " index;\n" + - "});\n", - options: [4], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "[a, b, c].forEach(function(index){\n" + - " return index;\n" + - "});\n", - options: [4], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "switch (x) {\n" + - " case \"foo\":\n" + - " a();\n" + - " break;\n" + - " case \"bar\":\n" + - " switch (y) {\n" + - " case \"1\":\n" + - " break;\n" + - " case \"2\":\n" + - " a = 6;\n" + - " break;\n" + - " }\n" + - " case \"test\":\n" + - " break;\n" + - "}", - options: [4, { SwitchCase: 1 }] - }, - { - code: - "switch (x) {\n" + - " case \"foo\":\n" + - " a();\n" + - " break;\n" + - " case \"bar\":\n" + - " switch (y) {\n" + - " case \"1\":\n" + - " break;\n" + - " case \"2\":\n" + - " a = 6;\n" + - " break;\n" + - " }\n" + - " case \"test\":\n" + - " break;\n" + - "}", - options: [4, { SwitchCase: 2 }] - }, - "switch (a) {\n" + - "case \"foo\":\n" + - " a();\n" + - " break;\n" + - "case \"bar\":\n" + - " switch(x){\n" + - " case '1':\n" + - " break;\n" + - " case '2':\n" + - " a = 6;\n" + - " break;\n" + - " }\n" + - "}", - "switch (a) {\n" + - "case \"foo\":\n" + - " a();\n" + - " break;\n" + - "case \"bar\":\n" + - " if(x){\n" + - " a = 2;\n" + - " }\n" + - " else{\n" + - " a = 6;\n" + - " }\n" + - "}", - "switch (a) {\n" + - "case \"foo\":\n" + - " a();\n" + - " break;\n" + - "case \"bar\":\n" + - " if(x){\n" + - " a = 2;\n" + - " }\n" + - " else\n" + - " a = 6;\n" + - "}", - "switch (a) {\n" + - "case \"foo\":\n" + - " a();\n" + - " break;\n" + - "case \"bar\":\n" + - " a(); break;\n" + - "case \"baz\":\n" + - " a(); break;\n" + - "}", - "switch (0) {\n}", - "function foo() {\n" + - " var a = \"a\";\n" + - " switch(a) {\n" + - " case \"a\":\n" + - " return \"A\";\n" + - " case \"b\":\n" + - " return \"B\";\n" + - " }\n" + - "}\n" + - "foo();", - { - code: - "switch(value){\n" + - " case \"1\":\n" + - " case \"2\":\n" + - " a();\n" + - " break;\n" + - " default:\n" + - " a();\n" + - " break;\n" + - "}\n" + - "switch(value){\n" + - " case \"1\":\n" + - " a();\n" + - " break;\n" + - " case \"2\":\n" + - " break;\n" + - " default:\n" + - " break;\n" + - "}", - options: [4, { SwitchCase: 1 }] - }, - "var obj = {foo: 1, bar: 2};\n" + - "with (obj) {\n" + - " console.log(foo + bar);\n" + - "}\n", - "if (a) {\n" + - " (1 + 2 + 3);\n" + // no error on this line - "}", - "switch(value){ default: a(); break; }\n", - { - code: "import {addons} from 'react/addons'\nimport React from 'react'", - options: [2], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: - "var a = 1,\n" + - " b = 2,\n" + - " c = 3;\n", - options: [4] - }, - { - code: - "var a = 1\n" + - " ,b = 2\n" + - " ,c = 3;\n", - options: [4] - }, - { - code: "while (1 < 2) console.log('hi')\n", - options: [2] - }, - { - code: - "function salutation () {\n" + - " switch (1) {\n" + - " case 0: return console.log('hi')\n" + - " case 1: return console.log('hey')\n" + - " }\n" + - "}\n", - options: [2, { SwitchCase: 1 }] - }, - { - code: - "var items = [\n" + - " {\n" + - " foo: 'bar'\n" + - " }\n" + - "];\n", - options: [2, { VariableDeclarator: 2 }] - }, - { - code: - "const a = 1,\n" + - " b = 2;\n" + - "const items1 = [\n" + - " {\n" + - " foo: 'bar'\n" + - " }\n" + - "];\n" + - "const items2 = Items(\n" + - " {\n" + - " foo: 'bar'\n" + - " }\n" + - ");\n", - options: [2, { VariableDeclarator: 3 }], - languageOptions: { ecmaVersion: 6 } + { + code: + "[a, b,\n" + + " c].forEach((index) => {\n" + + " index;\n" + + " });\n", + options: [4], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "[a, b, c].forEach((index) => {\n" + " index;\n" + "});\n", + options: [4], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "[a, b, c].forEach(function(index){\n" + + " return index;\n" + + "});\n", + options: [4], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "switch (x) {\n" + + ' case "foo":\n' + + " a();\n" + + " break;\n" + + ' case "bar":\n' + + " switch (y) {\n" + + ' case "1":\n' + + " break;\n" + + ' case "2":\n' + + " a = 6;\n" + + " break;\n" + + " }\n" + + ' case "test":\n' + + " break;\n" + + "}", + options: [4, { SwitchCase: 1 }], + }, + { + code: + "switch (x) {\n" + + ' case "foo":\n' + + " a();\n" + + " break;\n" + + ' case "bar":\n' + + " switch (y) {\n" + + ' case "1":\n' + + " break;\n" + + ' case "2":\n' + + " a = 6;\n" + + " break;\n" + + " }\n" + + ' case "test":\n' + + " break;\n" + + "}", + options: [4, { SwitchCase: 2 }], + }, + "switch (a) {\n" + + 'case "foo":\n' + + " a();\n" + + " break;\n" + + 'case "bar":\n' + + " switch(x){\n" + + " case '1':\n" + + " break;\n" + + " case '2':\n" + + " a = 6;\n" + + " break;\n" + + " }\n" + + "}", + "switch (a) {\n" + + 'case "foo":\n' + + " a();\n" + + " break;\n" + + 'case "bar":\n' + + " if(x){\n" + + " a = 2;\n" + + " }\n" + + " else{\n" + + " a = 6;\n" + + " }\n" + + "}", + "switch (a) {\n" + + 'case "foo":\n' + + " a();\n" + + " break;\n" + + 'case "bar":\n' + + " if(x){\n" + + " a = 2;\n" + + " }\n" + + " else\n" + + " a = 6;\n" + + "}", + "switch (a) {\n" + + 'case "foo":\n' + + " a();\n" + + " break;\n" + + 'case "bar":\n' + + " a(); break;\n" + + 'case "baz":\n' + + " a(); break;\n" + + "}", + "switch (0) {\n}", + "function foo() {\n" + + ' var a = "a";\n' + + " switch(a) {\n" + + ' case "a":\n' + + ' return "A";\n' + + ' case "b":\n' + + ' return "B";\n' + + " }\n" + + "}\n" + + "foo();", + { + code: + "switch(value){\n" + + ' case "1":\n' + + ' case "2":\n' + + " a();\n" + + " break;\n" + + " default:\n" + + " a();\n" + + " break;\n" + + "}\n" + + "switch(value){\n" + + ' case "1":\n' + + " a();\n" + + " break;\n" + + ' case "2":\n' + + " break;\n" + + " default:\n" + + " break;\n" + + "}", + options: [4, { SwitchCase: 1 }], + }, + "var obj = {foo: 1, bar: 2};\n" + + "with (obj) {\n" + + " console.log(foo + bar);\n" + + "}\n", + "if (a) {\n" + + " (1 + 2 + 3);\n" + // no error on this line + "}", + "switch(value){ default: a(); break; }\n", + { + code: "import {addons} from 'react/addons'\nimport React from 'react'", + options: [2], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "var a = 1,\n" + " b = 2,\n" + " c = 3;\n", + options: [4], + }, + { + code: "var a = 1\n" + " ,b = 2\n" + " ,c = 3;\n", + options: [4], + }, + { + code: "while (1 < 2) console.log('hi')\n", + options: [2], + }, + { + code: + "function salutation () {\n" + + " switch (1) {\n" + + " case 0: return console.log('hi')\n" + + " case 1: return console.log('hey')\n" + + " }\n" + + "}\n", + options: [2, { SwitchCase: 1 }], + }, + { + code: + "var items = [\n" + + " {\n" + + " foo: 'bar'\n" + + " }\n" + + "];\n", + options: [2, { VariableDeclarator: 2 }], + }, + { + code: + "const a = 1,\n" + + " b = 2;\n" + + "const items1 = [\n" + + " {\n" + + " foo: 'bar'\n" + + " }\n" + + "];\n" + + "const items2 = Items(\n" + + " {\n" + + " foo: 'bar'\n" + + " }\n" + + ");\n", + options: [2, { VariableDeclarator: 3 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "const geometry = 2,\n" + + " rotate = 3;\n" + + "var a = 1,\n" + + " b = 2;\n" + + "let light = true,\n" + + " shadow = false;", + options: [2, { VariableDeclarator: { const: 3, let: 2 } }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "const abc = 5,\n" + + " c = 2,\n" + + " xyz = \n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " };\n" + + "let abc2 = 5,\n" + + " c2 = 2,\n" + + " xyz2 = \n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " };\n" + + "var abc3 = 5,\n" + + " c3 = 2,\n" + + " xyz3 = \n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " };\n", + options: [ + 2, + { VariableDeclarator: { var: 2, const: 3 }, SwitchCase: 1 }, + ], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "module.exports =\n" + + "{\n" + + " 'Unit tests':\n" + + " {\n" + + " rootPath: './',\n" + + " environment: 'node',\n" + + " tests:\n" + + " [\n" + + " 'test/test-*.js'\n" + + " ],\n" + + " sources:\n" + + " [\n" + + " '*.js',\n" + + " 'test/**.js'\n" + + " ]\n" + + " }\n" + + "};", + options: [2], + }, + { + code: + "var path = require('path')\n" + + " , crypto = require('crypto')\n" + + " ;\n", + options: [2], + }, + "var a = 1\n" + " ,b = 2\n" + " ;", + { + code: + "export function create (some,\n" + + " argument) {\n" + + " return Object.create({\n" + + " a: some,\n" + + " b: argument\n" + + " });\n" + + "};", + options: [2], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: + "export function create (id, xfilter, rawType,\n" + + " width=defaultWidth, height=defaultHeight,\n" + + " footerHeight=defaultFooterHeight,\n" + + " padding=defaultPadding) {\n" + + " // ... function body, indented two spaces\n" + + "}\n", + options: [2], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: + "var obj = {\n" + + " foo: function () {\n" + + " return new p()\n" + + " .then(function (ok) {\n" + + " return ok;\n" + + " }, function () {\n" + + " // ignore things\n" + + " });\n" + + " }\n" + + "};\n", + options: [2], + }, + { + code: + "a.b()\n" + + " .c(function(){\n" + + " var a;\n" + + " }).d.e;\n", + options: [2], + }, + { + code: + "const YO = 'bah',\n" + + " TE = 'mah'\n" + + "\n" + + "var res,\n" + + " a = 5,\n" + + " b = 4\n", + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "const YO = 'bah',\n" + + " TE = 'mah'\n" + + "\n" + + "var res,\n" + + " a = 5,\n" + + " b = 4\n" + + "\n" + + "if (YO) console.log(TE)", + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "var foo = 'foo',\n" + + " bar = 'bar',\n" + + " baz = function() {\n" + + " \n" + + " }\n" + + "\n" + + "function hello () {\n" + + " \n" + + "}\n", + options: [2], + }, + { + code: + "var obj = {\n" + + " send: function () {\n" + + " return P.resolve({\n" + + " type: 'POST'\n" + + " })\n" + + " .then(function () {\n" + + " return true;\n" + + " }, function () {\n" + + " return false;\n" + + " });\n" + + " }\n" + + "};\n", + options: [2], + }, + { + code: + "var obj = {\n" + + " send: function () {\n" + + " return P.resolve({\n" + + " type: 'POST'\n" + + " })\n" + + " .then(function () {\n" + + " return true;\n" + + " }, function () {\n" + + " return false;\n" + + " });\n" + + " }\n" + + "};\n", + options: [2, { MemberExpression: 0 }], + }, + { + code: + "const someOtherFunction = argument => {\n" + + " console.log(argument);\n" + + " },\n" + + " someOtherValue = 'someOtherValue';\n", + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "[\n" + + " 'a',\n" + + " 'b'\n" + + "].sort().should.deepEqual([\n" + + " 'x',\n" + + " 'y'\n" + + "]);\n", + options: [2], + }, + { + code: + "var a = 1,\n" + + " B = class {\n" + + " constructor(){}\n" + + " a(){}\n" + + " get b(){}\n" + + " };", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "var a = 1,\n" + + " B = \n" + + " class {\n" + + " constructor(){}\n" + + " a(){}\n" + + " get b(){}\n" + + " },\n" + + " c = 3;", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "class A{\n" + + " constructor(){}\n" + + " a(){}\n" + + " get b(){}\n" + + "}", + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "var A = class {\n" + + " constructor(){}\n" + + " a(){}\n" + + " get b(){}\n" + + "}", + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "var a = {\n" + " some: 1\n" + ", name: 2\n" + "};\n", + options: [2], + }, + { + code: + "a.c = {\n" + + " aa: function() {\n" + + " 'test1';\n" + + " return 'aa';\n" + + " }\n" + + " , bb: function() {\n" + + " return this.bb();\n" + + " }\n" + + "};\n", + options: [4], + }, + { + code: + "var a =\n" + + "{\n" + + " actions:\n" + + " [\n" + + " {\n" + + " name: 'compile'\n" + + " }\n" + + " ]\n" + + "};\n", + options: [4, { VariableDeclarator: 0, SwitchCase: 1 }], + }, + { + code: + "var a =\n" + + "[\n" + + " {\n" + + " name: 'compile'\n" + + " }\n" + + "];\n", + options: [4, { VariableDeclarator: 0, SwitchCase: 1 }], + }, + { + code: + "const func = function (opts) {\n" + + " return Promise.resolve()\n" + + " .then(() => {\n" + + " [\n" + + " 'ONE', 'TWO'\n" + + " ].forEach(command => { doSomething(); });\n" + + " });\n" + + "};", + options: [4, { MemberExpression: 0 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "const func = function (opts) {\n" + + " return Promise.resolve()\n" + + " .then(() => {\n" + + " [\n" + + " 'ONE', 'TWO'\n" + + " ].forEach(command => { doSomething(); });\n" + + " });\n" + + "};", + options: [4], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "var haveFun = function () {\n" + + " SillyFunction(\n" + + " {\n" + + " value: true,\n" + + " },\n" + + " {\n" + + " _id: true,\n" + + " }\n" + + " );\n" + + "};", + options: [4], + }, + { + code: + "var haveFun = function () {\n" + + " new SillyFunction(\n" + + " {\n" + + " value: true,\n" + + " },\n" + + " {\n" + + " _id: true,\n" + + " }\n" + + " );\n" + + "};", + options: [4], + }, + { + code: + "let object1 = {\n" + + " doThing() {\n" + + " return _.chain([])\n" + + " .map(v => (\n" + + " {\n" + + " value: true,\n" + + " }\n" + + " ))\n" + + " .value();\n" + + " }\n" + + "};", + options: [2], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class Foo\n" + " extends Bar {\n" + " baz() {}\n" + "}", + options: [2], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "class Foo extends\n" + " Bar {\n" + " baz() {}\n" + "}", + options: [2], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "fs.readdirSync(path.join(__dirname, '../rules')).forEach(name => {\n" + + " files[name] = foo;\n" + + "});", + options: [2, { outerIIFEBody: 0 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "(function(){\n" + + "function foo(x) {\n" + + " return x + 1;\n" + + "}\n" + + "})();", + options: [2, { outerIIFEBody: 0 }], + }, + { + code: + "(function(){\n" + + " function foo(x) {\n" + + " return x + 1;\n" + + " }\n" + + "})();", + options: [4, { outerIIFEBody: 2 }], + }, + { + code: + "(function(x, y){\n" + + "function foo(x) {\n" + + " return x + 1;\n" + + "}\n" + + "})(1, 2);", + options: [2, { outerIIFEBody: 0 }], + }, + { + code: + "(function(){\n" + + "function foo(x) {\n" + + " return x + 1;\n" + + "}\n" + + "}());", + options: [2, { outerIIFEBody: 0 }], + }, + { + code: + "!function(){\n" + + "function foo(x) {\n" + + " return x + 1;\n" + + "}\n" + + "}();", + options: [2, { outerIIFEBody: 0 }], + }, + { + code: + "!function(){\n" + + "\t\t\tfunction foo(x) {\n" + + "\t\t\t\treturn x + 1;\n" + + "\t\t\t}\n" + + "}();", + options: ["tab", { outerIIFEBody: 3 }], + }, + { + code: + "var out = function(){\n" + + " function fooVar(x) {\n" + + " return x + 1;\n" + + " }\n" + + "};", + options: [2, { outerIIFEBody: 0 }], + }, + { + code: + "var ns = function(){\n" + + "function fooVar(x) {\n" + + " return x + 1;\n" + + "}\n" + + "}();", + options: [2, { outerIIFEBody: 0 }], + }, + { + code: + "ns = function(){\n" + + "function fooVar(x) {\n" + + " return x + 1;\n" + + "}\n" + + "}();", + options: [2, { outerIIFEBody: 0 }], + }, + { + code: + "var ns = (function(){\n" + + "function fooVar(x) {\n" + + " return x + 1;\n" + + "}\n" + + "}(x));", + options: [2, { outerIIFEBody: 0 }], + }, + { + code: + "var ns = (function(){\n" + + " function fooVar(x) {\n" + + " return x + 1;\n" + + " }\n" + + "}(x));", + options: [4, { outerIIFEBody: 2 }], + }, + { + code: + "var obj = {\n" + + " foo: function() {\n" + + " return true;\n" + + " }\n" + + "};", + options: [2, { outerIIFEBody: 0 }], + }, + { + code: + "while (\n" + + " function() {\n" + + " return true;\n" + + " }()) {\n" + + "\n" + + " x = x + 1;\n" + + "};", + options: [2, { outerIIFEBody: 20 }], + }, + { + code: + "(() => {\n" + + "function foo(x) {\n" + + " return x + 1;\n" + + "}\n" + + "})();", + options: [2, { outerIIFEBody: 0 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "function foo() {\n" + "}", + options: ["tab", { outerIIFEBody: 0 }], + }, + { + code: + ";(() => {\n" + + "function foo(x) {\n" + + " return x + 1;\n" + + "}\n" + + "})();", + options: [2, { outerIIFEBody: 0 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: "if(data) {\n" + " console.log('hi');\n" + "}", + options: [2, { outerIIFEBody: 0 }], + }, + { + code: "Buffer.length", + options: [4, { MemberExpression: 1 }], + }, + { + code: "Buffer\n" + " .indexOf('a')\n" + " .toString()", + options: [4, { MemberExpression: 1 }], + }, + { + code: "Buffer.\n" + " length", + options: [4, { MemberExpression: 1 }], + }, + { + code: "Buffer\n" + " .foo\n" + " .bar", + options: [4, { MemberExpression: 1 }], + }, + { + code: "Buffer\n" + "\t.foo\n" + "\t.bar", + options: ["tab", { MemberExpression: 1 }], + }, + { + code: "Buffer\n" + " .foo\n" + " .bar", + options: [2, { MemberExpression: 2 }], + }, + { + code: + "MemberExpression\n" + + ".is" + + " .off" + + " .by" + + " .default();", + options: [4], + }, + { + code: "foo = bar.baz()\n" + " .bip();", + options: [4, { MemberExpression: 1 }], + }, + { + code: + "if (foo) {\n" + + " bar();\n" + + "} else if (baz) {\n" + + " foobar();\n" + + "} else if (qux) {\n" + + " qux();\n" + + "}", + options: [2], + }, + { + code: + "function foo(aaa,\n" + + " bbb, ccc, ddd) {\n" + + " bar();\n" + + "}", + options: [2, { FunctionDeclaration: { parameters: 1, body: 2 } }], + }, + { + code: + "function foo(aaa, bbb,\n" + + " ccc, ddd) {\n" + + " bar();\n" + + "}", + options: [2, { FunctionDeclaration: { parameters: 3, body: 1 } }], + }, + { + code: + "function foo(aaa,\n" + + " bbb,\n" + + " ccc) {\n" + + " bar();\n" + + "}", + options: [4, { FunctionDeclaration: { parameters: 1, body: 3 } }], + }, + { + code: + "function foo(aaa,\n" + + " bbb, ccc,\n" + + " ddd, eee, fff) {\n" + + " bar();\n" + + "}", + options: [ + 2, + { FunctionDeclaration: { parameters: "first", body: 1 } }, + ], + }, + { + code: "function foo(aaa, bbb)\n" + "{\n" + " bar();\n" + "}", + options: [2, { FunctionDeclaration: { body: 3 } }], + }, + { + code: + "function foo(\n" + + " aaa,\n" + + " bbb) {\n" + + " bar();\n" + + "}", + options: [ + 2, + { FunctionDeclaration: { parameters: "first", body: 2 } }, + ], + }, + { + code: + "var foo = function(aaa,\n" + + " bbb,\n" + + " ccc,\n" + + " ddd) {\n" + + "bar();\n" + + "}", + options: [2, { FunctionExpression: { parameters: 2, body: 0 } }], + }, + { + code: + "var foo = function(aaa,\n" + + " bbb,\n" + + " ccc) {\n" + + " bar();\n" + + "}", + options: [2, { FunctionExpression: { parameters: 1, body: 10 } }], + }, + { + code: + "var foo = function(aaa,\n" + + " bbb, ccc, ddd,\n" + + " eee, fff) {\n" + + " bar();\n" + + "}", + options: [ + 4, + { FunctionExpression: { parameters: "first", body: 1 } }, + ], + }, + { + code: + "var foo = function(\n" + + " aaa, bbb, ccc,\n" + + " ddd, eee) {\n" + + " bar();\n" + + "}", + options: [ + 2, + { FunctionExpression: { parameters: "first", body: 3 } }, + ], + }, + { + code: + "function foo() {\n" + + " bar();\n" + + " \tbaz();\n" + + "\t \t\t\t \t\t\t \t \tqux();\n" + + "}", + options: [2], + }, + { + code: + "function foo() {\n" + + " function bar() {\n" + + " baz();\n" + + " }\n" + + "}", + options: [2, { FunctionDeclaration: { body: 1 } }], + }, + { + code: "function foo() {\n" + " bar();\n" + " \t\t}", + options: [2], + }, + { + code: + "function foo() {\n" + + " function bar(baz,\n" + + " qux) {\n" + + " foobar();\n" + + " }\n" + + "}", + options: [2, { FunctionDeclaration: { body: 1, parameters: 2 } }], + }, + { + code: + "function foo() {\n" + + " var bar = function(baz,\n" + + " qux) {\n" + + " foobar();\n" + + " };\n" + + "}", + options: [2, { FunctionExpression: { parameters: 3 } }], + }, + { + code: + "function foo() {\n" + + " return (bar === 1 || bar === 2 &&\n" + + " (/Function/.test(grandparent.type))) &&\n" + + " directives(parent).indexOf(node) >= 0;\n" + + "}", + options: [2], + }, + { + code: + "function foo() {\n" + + " return (bar === 1 || bar === 2) &&\n" + + " (z === 3 || z === 4);\n" + + "}", + options: [2], + }, + { + code: + "function foo() {\n" + + " return ((bar === 1 || bar === 2) &&\n" + + " (z === 3 || z === 4)\n" + + " );\n" + + "}", + options: [2], + }, + { + code: + "function foo() {\n" + + " return ((bar === 1 || bar === 2) &&\n" + + " (z === 3 || z === 4));\n" + + "}", + options: [2], + }, + { + code: "foo(\n" + " bar,\n" + " baz,\n" + " qux\n" + ");", + options: [2, { CallExpression: { arguments: 1 } }], + }, + { + code: "foo(\n" + "\tbar,\n" + "\tbaz,\n" + "\tqux\n" + ");", + options: ["tab", { CallExpression: { arguments: 1 } }], + }, + { + code: "foo(bar,\n" + " baz,\n" + " qux);", + options: [4, { CallExpression: { arguments: 2 } }], + }, + { + code: "foo(\n" + "bar,\n" + "baz,\n" + "qux\n" + ");", + options: [2, { CallExpression: { arguments: 0 } }], + }, + { + code: "foo(bar,\n" + " baz,\n" + " qux\n" + ");", + options: [2, { CallExpression: { arguments: "first" } }], + }, + { + code: + "foo(bar, baz,\n" + + " qux, barbaz,\n" + + " barqux, bazqux);", + options: [2, { CallExpression: { arguments: "first" } }], + }, + { + code: + "foo(\n" + + " bar, baz,\n" + + " qux);", + options: [2, { CallExpression: { arguments: "first" } }], + }, + { + code: + "foo(bar,\n" + + " 1 + 2,\n" + + " !baz,\n" + + " new Car('!')\n" + + ");", + options: [2, { CallExpression: { arguments: 4 } }], + }, - }, - { - code: - "const geometry = 2,\n" + - " rotate = 3;\n" + - "var a = 1,\n" + - " b = 2;\n" + - "let light = true,\n" + - " shadow = false;", - options: [2, { VariableDeclarator: { const: 3, let: 2 } }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "const abc = 5,\n" + - " c = 2,\n" + - " xyz = \n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " };\n" + - "let abc2 = 5,\n" + - " c2 = 2,\n" + - " xyz2 = \n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " };\n" + - "var abc3 = 5,\n" + - " c3 = 2,\n" + - " xyz3 = \n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " };\n", - options: [2, { VariableDeclarator: { var: 2, const: 3 }, SwitchCase: 1 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "module.exports =\n" + - "{\n" + - " 'Unit tests':\n" + - " {\n" + - " rootPath: './',\n" + - " environment: 'node',\n" + - " tests:\n" + - " [\n" + - " 'test/test-*.js'\n" + - " ],\n" + - " sources:\n" + - " [\n" + - " '*.js',\n" + - " 'test/**.js'\n" + - " ]\n" + - " }\n" + - "};", - options: [2] - }, - { - code: - "var path = require('path')\n" + - " , crypto = require('crypto')\n" + - " ;\n", - options: [2] - }, - "var a = 1\n" + - " ,b = 2\n" + - " ;", - { - code: - "export function create (some,\n" + - " argument) {\n" + - " return Object.create({\n" + - " a: some,\n" + - " b: argument\n" + - " });\n" + - "};", - options: [2], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: - "export function create (id, xfilter, rawType,\n" + - " width=defaultWidth, height=defaultHeight,\n" + - " footerHeight=defaultFooterHeight,\n" + - " padding=defaultPadding) {\n" + - " // ... function body, indented two spaces\n" + - "}\n", - options: [2], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: - "var obj = {\n" + - " foo: function () {\n" + - " return new p()\n" + - " .then(function (ok) {\n" + - " return ok;\n" + - " }, function () {\n" + - " // ignore things\n" + - " });\n" + - " }\n" + - "};\n", - options: [2] - }, - { - code: - "a.b()\n" + - " .c(function(){\n" + - " var a;\n" + - " }).d.e;\n", - options: [2] - }, - { - code: - "const YO = 'bah',\n" + - " TE = 'mah'\n" + - "\n" + - "var res,\n" + - " a = 5,\n" + - " b = 4\n", - options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "const YO = 'bah',\n" + - " TE = 'mah'\n" + - "\n" + - "var res,\n" + - " a = 5,\n" + - " b = 4\n" + - "\n" + - "if (YO) console.log(TE)", - options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "var foo = 'foo',\n" + - " bar = 'bar',\n" + - " baz = function() {\n" + - " \n" + - " }\n" + - "\n" + - "function hello () {\n" + - " \n" + - "}\n", - options: [2] - }, - { - code: - "var obj = {\n" + - " send: function () {\n" + - " return P.resolve({\n" + - " type: 'POST'\n" + - " })\n" + - " .then(function () {\n" + - " return true;\n" + - " }, function () {\n" + - " return false;\n" + - " });\n" + - " }\n" + - "};\n", - options: [2] - }, - { - code: - "var obj = {\n" + - " send: function () {\n" + - " return P.resolve({\n" + - " type: 'POST'\n" + - " })\n" + - " .then(function () {\n" + - " return true;\n" + - " }, function () {\n" + - " return false;\n" + - " });\n" + - " }\n" + - "};\n", - options: [2, { MemberExpression: 0 }] - }, - { - code: - "const someOtherFunction = argument => {\n" + - " console.log(argument);\n" + - " },\n" + - " someOtherValue = 'someOtherValue';\n", - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "[\n" + - " 'a',\n" + - " 'b'\n" + - "].sort().should.deepEqual([\n" + - " 'x',\n" + - " 'y'\n" + - "]);\n", - options: [2] - }, - { - code: - "var a = 1,\n" + - " B = class {\n" + - " constructor(){}\n" + - " a(){}\n" + - " get b(){}\n" + - " };", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "var a = 1,\n" + - " B = \n" + - " class {\n" + - " constructor(){}\n" + - " a(){}\n" + - " get b(){}\n" + - " },\n" + - " c = 3;", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "class A{\n" + - " constructor(){}\n" + - " a(){}\n" + - " get b(){}\n" + - "}", - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "var A = class {\n" + - " constructor(){}\n" + - " a(){}\n" + - " get b(){}\n" + - "}", - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "var a = {\n" + - " some: 1\n" + - ", name: 2\n" + - "};\n", - options: [2] - }, - { - code: - "a.c = {\n" + - " aa: function() {\n" + - " 'test1';\n" + - " return 'aa';\n" + - " }\n" + - " , bb: function() {\n" + - " return this.bb();\n" + - " }\n" + - "};\n", - options: [4] - }, - { - code: - "var a =\n" + - "{\n" + - " actions:\n" + - " [\n" + - " {\n" + - " name: 'compile'\n" + - " }\n" + - " ]\n" + - "};\n", - options: [4, { VariableDeclarator: 0, SwitchCase: 1 }] - }, - { - code: - "var a =\n" + - "[\n" + - " {\n" + - " name: 'compile'\n" + - " }\n" + - "];\n", - options: [4, { VariableDeclarator: 0, SwitchCase: 1 }] - }, - { - code: - "const func = function (opts) {\n" + - " return Promise.resolve()\n" + - " .then(() => {\n" + - " [\n" + - " 'ONE', 'TWO'\n" + - " ].forEach(command => { doSomething(); });\n" + - " });\n" + - "};", - options: [4, { MemberExpression: 0 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "const func = function (opts) {\n" + - " return Promise.resolve()\n" + - " .then(() => {\n" + - " [\n" + - " 'ONE', 'TWO'\n" + - " ].forEach(command => { doSomething(); });\n" + - " });\n" + - "};", - options: [4], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "var haveFun = function () {\n" + - " SillyFunction(\n" + - " {\n" + - " value: true,\n" + - " },\n" + - " {\n" + - " _id: true,\n" + - " }\n" + - " );\n" + - "};", - options: [4] - }, - { - code: - "var haveFun = function () {\n" + - " new SillyFunction(\n" + - " {\n" + - " value: true,\n" + - " },\n" + - " {\n" + - " _id: true,\n" + - " }\n" + - " );\n" + - "};", - options: [4] - }, - { - code: - "let object1 = {\n" + - " doThing() {\n" + - " return _.chain([])\n" + - " .map(v => (\n" + - " {\n" + - " value: true,\n" + - " }\n" + - " ))\n" + - " .value();\n" + - " }\n" + - "};", - options: [2], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "class Foo\n" + - " extends Bar {\n" + - " baz() {}\n" + - "}", - options: [2], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "class Foo extends\n" + - " Bar {\n" + - " baz() {}\n" + - "}", - options: [2], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "fs.readdirSync(path.join(__dirname, '../rules')).forEach(name => {\n" + - " files[name] = foo;\n" + - "});", - options: [2, { outerIIFEBody: 0 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "(function(){\n" + - "function foo(x) {\n" + - " return x + 1;\n" + - "}\n" + - "})();", - options: [2, { outerIIFEBody: 0 }] - }, - { - code: - "(function(){\n" + - " function foo(x) {\n" + - " return x + 1;\n" + - " }\n" + - "})();", - options: [4, { outerIIFEBody: 2 }] - }, - { - code: - "(function(x, y){\n" + - "function foo(x) {\n" + - " return x + 1;\n" + - "}\n" + - "})(1, 2);", - options: [2, { outerIIFEBody: 0 }] - }, - { - code: - "(function(){\n" + - "function foo(x) {\n" + - " return x + 1;\n" + - "}\n" + - "}());", - options: [2, { outerIIFEBody: 0 }] - }, - { - code: - "!function(){\n" + - "function foo(x) {\n" + - " return x + 1;\n" + - "}\n" + - "}();", - options: [2, { outerIIFEBody: 0 }] - }, - { - code: - "!function(){\n" + - "\t\t\tfunction foo(x) {\n" + - "\t\t\t\treturn x + 1;\n" + - "\t\t\t}\n" + - "}();", - options: ["tab", { outerIIFEBody: 3 }] - }, - { - code: - "var out = function(){\n" + - " function fooVar(x) {\n" + - " return x + 1;\n" + - " }\n" + - "};", - options: [2, { outerIIFEBody: 0 }] - }, - { - code: - "var ns = function(){\n" + - "function fooVar(x) {\n" + - " return x + 1;\n" + - "}\n" + - "}();", - options: [2, { outerIIFEBody: 0 }] - }, - { - code: - "ns = function(){\n" + - "function fooVar(x) {\n" + - " return x + 1;\n" + - "}\n" + - "}();", - options: [2, { outerIIFEBody: 0 }] - }, - { - code: - "var ns = (function(){\n" + - "function fooVar(x) {\n" + - " return x + 1;\n" + - "}\n" + - "}(x));", - options: [2, { outerIIFEBody: 0 }] - }, - { - code: - "var ns = (function(){\n" + - " function fooVar(x) {\n" + - " return x + 1;\n" + - " }\n" + - "}(x));", - options: [4, { outerIIFEBody: 2 }] - }, - { - code: - "var obj = {\n" + - " foo: function() {\n" + - " return true;\n" + - " }\n" + - "};", - options: [2, { outerIIFEBody: 0 }] - }, - { - code: - "while (\n" + - " function() {\n" + - " return true;\n" + - " }()) {\n" + - "\n" + - " x = x + 1;\n" + - "};", - options: [2, { outerIIFEBody: 20 }] - }, - { - code: - "(() => {\n" + - "function foo(x) {\n" + - " return x + 1;\n" + - "}\n" + - "})();", - options: [2, { outerIIFEBody: 0 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "function foo() {\n" + - "}", - options: ["tab", { outerIIFEBody: 0 }] - }, - { - code: - ";(() => {\n" + - "function foo(x) {\n" + - " return x + 1;\n" + - "}\n" + - "})();", - options: [2, { outerIIFEBody: 0 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "if(data) {\n" + - " console.log('hi');\n" + - "}", - options: [2, { outerIIFEBody: 0 }] - }, - { - code: - "Buffer.length", - options: [4, { MemberExpression: 1 }] - }, - { - code: - "Buffer\n" + - " .indexOf('a')\n" + - " .toString()", - options: [4, { MemberExpression: 1 }] - }, - { - code: - "Buffer.\n" + - " length", - options: [4, { MemberExpression: 1 }] - }, - { - code: - "Buffer\n" + - " .foo\n" + - " .bar", - options: [4, { MemberExpression: 1 }] - }, - { - code: - "Buffer\n" + - "\t.foo\n" + - "\t.bar", - options: ["tab", { MemberExpression: 1 }] - }, - { - code: - "Buffer\n" + - " .foo\n" + - " .bar", - options: [2, { MemberExpression: 2 }] - }, - { - code: - "MemberExpression\n" + - ".is" + - " .off" + - " .by" + - " .default();", - options: [4] - }, - { - code: - "foo = bar.baz()\n" + - " .bip();", - options: [4, { MemberExpression: 1 }] - }, - { - code: - "if (foo) {\n" + - " bar();\n" + - "} else if (baz) {\n" + - " foobar();\n" + - "} else if (qux) {\n" + - " qux();\n" + - "}", - options: [2] - }, - { - code: - "function foo(aaa,\n" + - " bbb, ccc, ddd) {\n" + - " bar();\n" + - "}", - options: [2, { FunctionDeclaration: { parameters: 1, body: 2 } }] - }, - { - code: - "function foo(aaa, bbb,\n" + - " ccc, ddd) {\n" + - " bar();\n" + - "}", - options: [2, { FunctionDeclaration: { parameters: 3, body: 1 } }] - }, - { - code: - "function foo(aaa,\n" + - " bbb,\n" + - " ccc) {\n" + - " bar();\n" + - "}", - options: [4, { FunctionDeclaration: { parameters: 1, body: 3 } }] - }, - { - code: - "function foo(aaa,\n" + - " bbb, ccc,\n" + - " ddd, eee, fff) {\n" + - " bar();\n" + - "}", - options: [2, { FunctionDeclaration: { parameters: "first", body: 1 } }] - }, - { - code: - "function foo(aaa, bbb)\n" + - "{\n" + - " bar();\n" + - "}", - options: [2, { FunctionDeclaration: { body: 3 } }] - }, - { - code: - "function foo(\n" + - " aaa,\n" + - " bbb) {\n" + - " bar();\n" + - "}", - options: [2, { FunctionDeclaration: { parameters: "first", body: 2 } }] - }, - { - code: - "var foo = function(aaa,\n" + - " bbb,\n" + - " ccc,\n" + - " ddd) {\n" + - "bar();\n" + - "}", - options: [2, { FunctionExpression: { parameters: 2, body: 0 } }] - }, - { - code: - "var foo = function(aaa,\n" + - " bbb,\n" + - " ccc) {\n" + - " bar();\n" + - "}", - options: [2, { FunctionExpression: { parameters: 1, body: 10 } }] - }, - { - code: - "var foo = function(aaa,\n" + - " bbb, ccc, ddd,\n" + - " eee, fff) {\n" + - " bar();\n" + - "}", - options: [4, { FunctionExpression: { parameters: "first", body: 1 } }] - }, - { - code: - "var foo = function(\n" + - " aaa, bbb, ccc,\n" + - " ddd, eee) {\n" + - " bar();\n" + - "}", - options: [2, { FunctionExpression: { parameters: "first", body: 3 } }] - }, - { - code: - "function foo() {\n" + - " bar();\n" + - " \tbaz();\n" + - "\t \t\t\t \t\t\t \t \tqux();\n" + - "}", - options: [2] - }, - { - code: - "function foo() {\n" + - " function bar() {\n" + - " baz();\n" + - " }\n" + - "}", - options: [2, { FunctionDeclaration: { body: 1 } }] - }, - { - code: - "function foo() {\n" + - " bar();\n" + - " \t\t}", - options: [2] - }, - { - code: - "function foo() {\n" + - " function bar(baz,\n" + - " qux) {\n" + - " foobar();\n" + - " }\n" + - "}", - options: [2, { FunctionDeclaration: { body: 1, parameters: 2 } }] - }, - { - code: - "function foo() {\n" + - " var bar = function(baz,\n" + - " qux) {\n" + - " foobar();\n" + - " };\n" + - "}", - options: [2, { FunctionExpression: { parameters: 3 } }] - }, - { - code: - "function foo() {\n" + - " return (bar === 1 || bar === 2 &&\n" + - " (/Function/.test(grandparent.type))) &&\n" + - " directives(parent).indexOf(node) >= 0;\n" + - "}", - options: [2] - }, - { - code: - "function foo() {\n" + - " return (bar === 1 || bar === 2) &&\n" + - " (z === 3 || z === 4);\n" + - "}", - options: [2] - }, - { - code: - "function foo() {\n" + - " return ((bar === 1 || bar === 2) &&\n" + - " (z === 3 || z === 4)\n" + - " );\n" + - "}", - options: [2] - }, - { - code: - "function foo() {\n" + - " return ((bar === 1 || bar === 2) &&\n" + - " (z === 3 || z === 4));\n" + - "}", - options: [2] - }, { - code: - "foo(\n" + - " bar,\n" + - " baz,\n" + - " qux\n" + - ");", - options: [2, { CallExpression: { arguments: 1 } }] - }, { - code: - "foo(\n" + - "\tbar,\n" + - "\tbaz,\n" + - "\tqux\n" + - ");", - options: ["tab", { CallExpression: { arguments: 1 } }] - }, { - code: - "foo(bar,\n" + - " baz,\n" + - " qux);", - options: [4, { CallExpression: { arguments: 2 } }] - }, { - code: - "foo(\n" + - "bar,\n" + - "baz,\n" + - "qux\n" + - ");", - options: [2, { CallExpression: { arguments: 0 } }] - }, { - code: - "foo(bar,\n" + - " baz,\n" + - " qux\n" + - ");", - options: [2, { CallExpression: { arguments: "first" } }] - }, { - code: - "foo(bar, baz,\n" + - " qux, barbaz,\n" + - " barqux, bazqux);", - options: [2, { CallExpression: { arguments: "first" } }] - }, { - code: - "foo(\n" + - " bar, baz,\n" + - " qux);", - options: [2, { CallExpression: { arguments: "first" } }] - }, { - code: - "foo(bar,\n" + - " 1 + 2,\n" + - " !baz,\n" + - " new Car('!')\n" + - ");", - options: [2, { CallExpression: { arguments: 4 } }] - }, + // https://github.com/eslint/eslint/issues/7484 + { + code: + "var foo = function() {\n" + + " return bar(\n" + + " [{\n" + + " }].concat(baz)\n" + + " );\n" + + "};", + options: [2], + }, - // https://github.com/eslint/eslint/issues/7484 - { - code: - "var foo = function() {\n" + - " return bar(\n" + - " [{\n" + - " }].concat(baz)\n" + - " );\n" + - "};", - options: [2] - }, + // https://github.com/eslint/eslint/issues/7573 + { + code: "return (\n" + " foo\n" + ");", + languageOptions: { + sourceType: "script", + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + }, + { + code: "return (\n" + " foo\n" + ")", + languageOptions: { + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + }, + "var foo = [\n" + " bar,\n" + " baz\n" + "]", + "var foo = [bar,\n" + " baz,\n" + " qux\n" + "]", + { + code: "var foo = [bar,\n" + "baz,\n" + "qux\n" + "]", + options: [2, { ArrayExpression: 0 }], + }, + { + code: + "var foo = [bar,\n" + + " baz,\n" + + " qux\n" + + "]", + options: [2, { ArrayExpression: 8 }], + }, + { + code: + "var foo = [bar,\n" + + " baz,\n" + + " qux\n" + + "]", + options: [2, { ArrayExpression: "first" }], + }, + { + code: "var foo = [bar,\n" + " baz, qux\n" + "]", + options: [2, { ArrayExpression: "first" }], + }, + { + code: + "var foo = [\n" + + " { bar: 1,\n" + + " baz: 2 },\n" + + " { bar: 3,\n" + + " qux: 4 }\n" + + "]", + options: [4, { ArrayExpression: 2, ObjectExpression: "first" }], + }, + { + code: "var foo = {\n" + "bar: 1,\n" + "baz: 2\n" + "};", + options: [2, { ObjectExpression: 0 }], + }, + { + code: "var foo = { foo: 1, bar: 2,\n" + " baz: 3 }", + options: [2, { ObjectExpression: "first" }], + }, + { + code: + "var foo = [\n" + + " {\n" + + " foo: 1\n" + + " }\n" + + "]", + options: [4, { ArrayExpression: 2 }], + }, + { + code: + "function foo() {\n" + + " [\n" + + " foo\n" + + " ]\n" + + "}", + options: [2, { ArrayExpression: 4 }], + }, + { + code: "[\n]", + options: [2, { ArrayExpression: "first" }], + }, + { + code: "[\n]", + options: [2, { ArrayExpression: 1 }], + }, + { + code: "{\n}", + options: [2, { ObjectExpression: "first" }], + }, + { + code: "{\n}", + options: [2, { ObjectExpression: 1 }], + }, + { + code: "var foo = [\n" + " [\n" + " 1\n" + " ]\n" + "]", + options: [2, { ArrayExpression: "first" }], + }, + { + code: + "var foo = [ 1,\n" + + " [\n" + + " 2\n" + + " ]\n" + + "];", + options: [2, { ArrayExpression: "first" }], + }, + { + code: + "var foo = bar(1,\n" + + " [ 2,\n" + + " 3\n" + + " ]\n" + + ");", + options: [ + 4, + { + ArrayExpression: "first", + CallExpression: { arguments: "first" }, + }, + ], + }, + { + code: "var foo =\n" + " [\n" + " ]()", + options: [ + 4, + { + CallExpression: { arguments: "first" }, + ArrayExpression: "first", + }, + ], + }, - // https://github.com/eslint/eslint/issues/7573 - { - code: - "return (\n" + - " foo\n" + - ");", - languageOptions: { sourceType: "script", parserOptions: { ecmaFeatures: { globalReturn: true } } } - }, - { - code: - "return (\n" + - " foo\n" + - ")", - languageOptions: { parserOptions: { ecmaFeatures: { globalReturn: true } } } - }, - "var foo = [\n" + - " bar,\n" + - " baz\n" + - "]", - "var foo = [bar,\n" + - " baz,\n" + - " qux\n" + - "]", - { - code: - "var foo = [bar,\n" + - "baz,\n" + - "qux\n" + - "]", - options: [2, { ArrayExpression: 0 }] - }, - { - code: - "var foo = [bar,\n" + - " baz,\n" + - " qux\n" + - "]", - options: [2, { ArrayExpression: 8 }] - }, - { - code: - "var foo = [bar,\n" + - " baz,\n" + - " qux\n" + - "]", - options: [2, { ArrayExpression: "first" }] - }, - { - code: - "var foo = [bar,\n" + - " baz, qux\n" + - "]", - options: [2, { ArrayExpression: "first" }] - }, - { - code: - "var foo = [\n" + - " { bar: 1,\n" + - " baz: 2 },\n" + - " { bar: 3,\n" + - " qux: 4 }\n" + - "]", - options: [4, { ArrayExpression: 2, ObjectExpression: "first" }] - }, - { - code: - "var foo = {\n" + - "bar: 1,\n" + - "baz: 2\n" + - "};", - options: [2, { ObjectExpression: 0 }] - }, - { - code: - "var foo = { foo: 1, bar: 2,\n" + - " baz: 3 }", - options: [2, { ObjectExpression: "first" }] - }, - { - code: - "var foo = [\n" + - " {\n" + - " foo: 1\n" + - " }\n" + - "]", - options: [4, { ArrayExpression: 2 }] - }, - { - code: - "function foo() {\n" + - " [\n" + - " foo\n" + - " ]\n" + - "}", - options: [2, { ArrayExpression: 4 }] - }, - { - code: "[\n]", - options: [2, { ArrayExpression: "first" }] - }, - { - code: "[\n]", - options: [2, { ArrayExpression: 1 }] - }, - { - code: "{\n}", - options: [2, { ObjectExpression: "first" }] - }, - { - code: "{\n}", - options: [2, { ObjectExpression: 1 }] - }, - { - code: - "var foo = [\n" + - " [\n" + - " 1\n" + - " ]\n" + - "]", - options: [2, { ArrayExpression: "first" }] - }, - { - code: - "var foo = [ 1,\n" + - " [\n" + - " 2\n" + - " ]\n" + - "];", - options: [2, { ArrayExpression: "first" }] - }, - { - code: - "var foo = bar(1,\n" + - " [ 2,\n" + - " 3\n" + - " ]\n" + - ");", - options: [4, { ArrayExpression: "first", CallExpression: { arguments: "first" } }] - }, - { - code: - "var foo =\n" + - " [\n" + - " ]()", - options: [4, { CallExpression: { arguments: "first" }, ArrayExpression: "first" }] - }, + // https://github.com/eslint/eslint/issues/7732 + { + code: + "const lambda = foo => {\n" + + " Object.assign({},\n" + + " filterName,\n" + + " {\n" + + " display\n" + + " }\n" + + " );" + + "}", + options: [2, { ObjectExpression: 1 }], + languageOptions: { ecmaVersion: 6 }, + }, + { + code: + "const lambda = foo => {\n" + + " Object.assign({},\n" + + " filterName,\n" + + " {\n" + + " display\n" + + " }\n" + + " );" + + "}", + options: [2, { ObjectExpression: "first" }], + languageOptions: { ecmaVersion: 6 }, + }, - // https://github.com/eslint/eslint/issues/7732 - { - code: - "const lambda = foo => {\n" + - " Object.assign({},\n" + - " filterName,\n" + - " {\n" + - " display\n" + - " }\n" + - " );" + - "}", - options: [2, { ObjectExpression: 1 }], - languageOptions: { ecmaVersion: 6 } - }, - { - code: - "const lambda = foo => {\n" + - " Object.assign({},\n" + - " filterName,\n" + - " {\n" + - " display\n" + - " }\n" + - " );" + - "}", - options: [2, { ObjectExpression: "first" }], - languageOptions: { ecmaVersion: 6 } - }, + // https://github.com/eslint/eslint/issues/7733 + { + code: + "var foo = function() {\n" + + "\twindow.foo('foo',\n" + + "\t\t{\n" + + "\t\t\tfoo: 'bar'," + + "\t\t\tbar: {\n" + + "\t\t\t\tfoo: 'bar'\n" + + "\t\t\t}\n" + + "\t\t}\n" + + "\t);\n" + + "}", + options: ["tab"], + }, + { + code: + "echo = spawn('cmd.exe',\n" + + " ['foo', 'bar',\n" + + " 'baz']);", + options: [ + 2, + { + ArrayExpression: "first", + CallExpression: { arguments: "first" }, + }, + ], + }, + ], + invalid: [ + { + code: "var a = b;\n" + "if (a) {\n" + "b();\n" + "}\n", + output: "var a = b;\n" + "if (a) {\n" + " b();\n" + "}\n", + options: [2], + errors: expectedErrors([[3, 2, 0, "ExpressionStatement"]]), + }, + { + code: + "require('http').request({hostname: 'localhost',\n" + + " port: 80}, function(res) {\n" + + " res.end();\n" + + "});\n", + output: + "require('http').request({hostname: 'localhost',\n" + + " port: 80}, function(res) {\n" + + " res.end();\n" + + "});\n", + options: [2], + errors: expectedErrors([[2, 2, 18, "Property"]]), + }, + { + code: + "if (array.some(function(){\n" + + " return true;\n" + + "})) {\n" + + "a++; // ->\n" + + " b++;\n" + + " c++; // <-\n" + + "}\n", + output: + "if (array.some(function(){\n" + + " return true;\n" + + "})) {\n" + + " a++; // ->\n" + + " b++;\n" + + " c++; // <-\n" + + "}\n", + options: [2], + errors: expectedErrors([ + [4, 2, 0, "ExpressionStatement"], + [6, 2, 4, "ExpressionStatement"], + ]), + }, + { + code: "if (a){\n\tb=c;\n\t\tc=d;\ne=f;\n}", + output: "if (a){\n\tb=c;\n\tc=d;\n\te=f;\n}", + options: ["tab"], + errors: expectedErrors("tab", [ + [3, 1, 2, "ExpressionStatement"], + [4, 1, 0, "ExpressionStatement"], + ]), + }, + { + code: "if (a){\n b=c;\n c=d;\n e=f;\n}", + output: "if (a){\n b=c;\n c=d;\n e=f;\n}", + options: [4], + errors: expectedErrors([ + [3, 4, 6, "ExpressionStatement"], + [4, 4, 1, "ExpressionStatement"], + ]), + }, + { + code: fixture, + output: fixedFixture, + options: [2, { SwitchCase: 1, MemberExpression: 1 }], + errors: expectedErrors([ + [5, 2, 4, "VariableDeclaration"], + [10, 4, 6, "BlockStatement"], + [11, 2, 4, "BlockStatement"], + [15, 4, 2, "ExpressionStatement"], + [16, 2, 4, "BlockStatement"], + [23, 2, 4, "BlockStatement"], + [29, 2, 4, "ForStatement"], + [31, 4, 2, "BlockStatement"], + [36, 4, 6, "ExpressionStatement"], + [38, 2, 4, "BlockStatement"], + [39, 4, 2, "ExpressionStatement"], + [40, 2, 0, "BlockStatement"], + [46, 0, 1, "VariableDeclaration"], + [54, 2, 4, "BlockStatement"], + [114, 4, 2, "VariableDeclaration"], + [120, 4, 6, "VariableDeclaration"], + [124, 4, 2, "BreakStatement"], + [134, 4, 6, "BreakStatement"], + [138, 2, 3, "Punctuator"], + [139, 2, 3, "Punctuator"], + [143, 4, 0, "ExpressionStatement"], + [151, 4, 6, "ExpressionStatement"], + [159, 4, 2, "ExpressionStatement"], + [161, 4, 6, "ExpressionStatement"], + [175, 2, 0, "ExpressionStatement"], + [177, 2, 4, "ExpressionStatement"], + [189, 2, 0, "VariableDeclaration"], + [193, 6, 4, "ExpressionStatement"], + [195, 6, 8, "ExpressionStatement"], + [304, 4, 6, "ExpressionStatement"], + [306, 4, 8, "ExpressionStatement"], + [307, 2, 4, "BlockStatement"], + [308, 2, 4, "VariableDeclarator"], + [311, 4, 6, "Identifier"], + [312, 4, 6, "Identifier"], + [313, 4, 6, "Identifier"], + [314, 2, 4, "ArrayExpression"], + [315, 2, 4, "VariableDeclarator"], + [318, 4, 6, "Property"], + [319, 4, 6, "Property"], + [320, 4, 6, "Property"], + [321, 2, 4, "ObjectExpression"], + [322, 2, 4, "VariableDeclarator"], + [326, 2, 1, "Literal"], + [327, 2, 1, "Literal"], + [328, 2, 1, "Literal"], + [329, 2, 1, "Literal"], + [330, 2, 1, "Literal"], + [331, 2, 1, "Literal"], + [332, 2, 1, "Literal"], + [333, 2, 1, "Literal"], + [334, 2, 1, "Literal"], + [335, 2, 1, "Literal"], + [340, 2, 4, "ExpressionStatement"], + [341, 2, 0, "ExpressionStatement"], + [344, 2, 4, "ExpressionStatement"], + [345, 2, 0, "ExpressionStatement"], + [348, 2, 4, "ExpressionStatement"], + [349, 2, 0, "ExpressionStatement"], + [355, 2, 0, "ExpressionStatement"], + [357, 2, 4, "ExpressionStatement"], + [361, 4, 6, "ExpressionStatement"], + [362, 2, 4, "BlockStatement"], + [363, 2, 4, "VariableDeclarator"], + [368, 2, 0, "SwitchCase"], + [370, 2, 4, "SwitchCase"], + [374, 4, 6, "VariableDeclaration"], + [376, 4, 2, "VariableDeclaration"], + [383, 2, 0, "ExpressionStatement"], + [385, 2, 4, "ExpressionStatement"], + [390, 2, 0, "ExpressionStatement"], + [392, 2, 4, "ExpressionStatement"], + [409, 2, 0, "ExpressionStatement"], + [410, 2, 4, "ExpressionStatement"], + [416, 2, 0, "ExpressionStatement"], + [417, 2, 4, "ExpressionStatement"], + [422, 2, 4, "ExpressionStatement"], + [423, 2, 0, "ExpressionStatement"], + [427, 2, 6, "ExpressionStatement"], + [428, 2, 8, "ExpressionStatement"], + [429, 2, 4, "ExpressionStatement"], + [430, 0, 4, "BlockStatement"], + [433, 2, 4, "ExpressionStatement"], + [434, 0, 4, "BlockStatement"], + [437, 2, 0, "ExpressionStatement"], + [438, 0, 4, "BlockStatement"], + [451, 2, 0, "ExpressionStatement"], + [453, 2, 4, "ExpressionStatement"], + [499, 6, 8, "BlockStatement"], + [500, 10, 8, "ExpressionStatement"], + [501, 8, 6, "BlockStatement"], + [506, 6, 8, "BlockStatement"], + ]), + }, + { + code: + "switch(value){\n" + + ' case "1":\n' + + " a();\n" + + " break;\n" + + ' case "2":\n' + + " a();\n" + + " break;\n" + + " default:\n" + + " a();\n" + + " break;\n" + + "}", + output: + "switch(value){\n" + + ' case "1":\n' + + " a();\n" + + " break;\n" + + ' case "2":\n' + + " a();\n" + + " break;\n" + + " default:\n" + + " a();\n" + + " break;\n" + + "}", + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [4, 8, 4, "BreakStatement"], + [7, 8, 4, "BreakStatement"], + ]), + }, + { + code: + "var x = 0 &&\n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " };", + output: + "var x = 0 &&\n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " };", + options: [4], + errors: expectedErrors([ + [3, 8, 7, "Property"], + [4, 8, 10, "Property"], + ]), + }, + { + code: + "switch(value){\n" + + ' case "1":\n' + + " a();\n" + + " break;\n" + + ' case "2":\n' + + " a();\n" + + " break;\n" + + " default:\n" + + " break;\n" + + "}", + output: + "switch(value){\n" + + ' case "1":\n' + + " a();\n" + + " break;\n" + + ' case "2":\n' + + " a();\n" + + " break;\n" + + " default:\n" + + " break;\n" + + "}", + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([9, 8, 4, "BreakStatement"]), + }, + { + code: + "switch(value){\n" + + ' case "1":\n' + + ' case "2":\n' + + " a();\n" + + " break;\n" + + " default:\n" + + " break;\n" + + "}\n" + + "switch(value){\n" + + ' case "1":\n' + + " break;\n" + + ' case "2":\n' + + " a();\n" + + " break;\n" + + " default:\n" + + " a();\n" + + " break;\n" + + "}", + output: + "switch(value){\n" + + ' case "1":\n' + + ' case "2":\n' + + " a();\n" + + " break;\n" + + " default:\n" + + " break;\n" + + "}\n" + + "switch(value){\n" + + ' case "1":\n' + + " break;\n" + + ' case "2":\n' + + " a();\n" + + " break;\n" + + " default:\n" + + " a();\n" + + " break;\n" + + "}", + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [11, 8, 4, "BreakStatement"], + [14, 8, 4, "BreakStatement"], + [17, 8, 4, "BreakStatement"], + ]), + }, + { + code: + "switch(value){\n" + + 'case "1":\n' + + " a();\n" + + " break;\n" + + ' case "2":\n' + + " break;\n" + + " default:\n" + + " break;\n" + + "}", + output: + "switch(value){\n" + + 'case "1":\n' + + " a();\n" + + " break;\n" + + 'case "2":\n' + + " break;\n" + + "default:\n" + + " break;\n" + + "}", + options: [4], + errors: expectedErrors([ + [3, 4, 8, "ExpressionStatement"], + [4, 4, 8, "BreakStatement"], + [5, 0, 4, "SwitchCase"], + [6, 4, 8, "BreakStatement"], + [7, 0, 4, "SwitchCase"], + [8, 4, 8, "BreakStatement"], + ]), + }, + { + code: + "var obj = {foo: 1, bar: 2};\n" + + "with (obj) {\n" + + "console.log(foo + bar);\n" + + "}\n", + output: + "var obj = {foo: 1, bar: 2};\n" + + "with (obj) {\n" + + " console.log(foo + bar);\n" + + "}\n", + errors: expectedErrors([3, 4, 0, "ExpressionStatement"]), + }, + { + code: + "switch (a) {\n" + + "case '1':\n" + + "b();\n" + + "break;\n" + + "default:\n" + + "c();\n" + + "break;\n" + + "}\n", + output: + "switch (a) {\n" + + " case '1':\n" + + " b();\n" + + " break;\n" + + " default:\n" + + " c();\n" + + " break;\n" + + "}\n", + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [2, 4, 0, "SwitchCase"], + [3, 8, 0, "ExpressionStatement"], + [4, 8, 0, "BreakStatement"], + [5, 4, 0, "SwitchCase"], + [6, 8, 0, "ExpressionStatement"], + [7, 8, 0, "BreakStatement"], + ]), + }, + { + code: + "var foo = function(){\n" + + " foo\n" + + " .bar\n" + + "}", + output: + "var foo = function(){\n" + + " foo\n" + + " .bar\n" + + "}", + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([3, 8, 10, "Punctuator"]), + }, + { + code: + "var foo = function(){\n" + + " foo\n" + + " .bar\n" + + "}", + output: + "var foo = function(){\n" + + " foo\n" + + " .bar\n" + + "}", + options: [4, { MemberExpression: 2 }], + errors: expectedErrors([3, 12, 13, "Punctuator"]), + }, + { + code: + "var foo = () => {\n" + + " foo\n" + + " .bar\n" + + "}", + output: + "var foo = () => {\n" + + " foo\n" + + " .bar\n" + + "}", + options: [4, { MemberExpression: 2 }], + languageOptions: { ecmaVersion: 6 }, + errors: expectedErrors([3, 12, 13, "Punctuator"]), + }, + { + code: + "TestClass.prototype.method = function () {\n" + + " return Promise.resolve(3)\n" + + " .then(function (x) {\n" + + " return x;\n" + + " });\n" + + "};", + output: + "TestClass.prototype.method = function () {\n" + + " return Promise.resolve(3)\n" + + " .then(function (x) {\n" + + " return x;\n" + + " });\n" + + "};", + options: [2, { MemberExpression: 1 }], + languageOptions: { ecmaVersion: 6 }, + errors: expectedErrors([[3, 4, 6, "Punctuator"]]), + }, + { + code: "while (a) \n" + "b();", + output: "while (a) \n" + " b();", + options: [4], + errors: expectedErrors([[2, 4, 0, "ExpressionStatement"]]), + }, + { + code: "for (;;) \n" + "b();", + output: "for (;;) \n" + " b();", + options: [4], + errors: expectedErrors([[2, 4, 0, "ExpressionStatement"]]), + }, + { + code: "for (a in x) \n" + "b();", + output: "for (a in x) \n" + " b();", + options: [4], + errors: expectedErrors([[2, 4, 0, "ExpressionStatement"]]), + }, + { + code: "do \n" + "b();\n" + "while(true)", + output: "do \n" + " b();\n" + "while(true)", + options: [4], + errors: expectedErrors([[2, 4, 0, "ExpressionStatement"]]), + }, + { + code: "if(true) \n" + "b();", + output: "if(true) \n" + " b();", + options: [4], + errors: expectedErrors([[2, 4, 0, "ExpressionStatement"]]), + }, + { + code: + "var test = {\n" + " a: 1,\n" + " b: 2\n" + " };\n", + output: "var test = {\n" + " a: 1,\n" + " b: 2\n" + "};\n", + options: [2], + errors: expectedErrors([ + [2, 2, 6, "Property"], + [3, 2, 4, "Property"], + [4, 0, 4, "ObjectExpression"], + ]), + }, + { + code: + "var a = function() {\n" + + " a++;\n" + + " b++;\n" + + " c++;\n" + + " },\n" + + " b;\n", + output: + "var a = function() {\n" + + " a++;\n" + + " b++;\n" + + " c++;\n" + + " },\n" + + " b;\n", + options: [4], + errors: expectedErrors([ + [2, 8, 6, "ExpressionStatement"], + [3, 8, 4, "ExpressionStatement"], + [4, 8, 10, "ExpressionStatement"], + ]), + }, + { + code: "var a = 1,\n" + "b = 2,\n" + "c = 3;\n", + output: "var a = 1,\n" + " b = 2,\n" + " c = 3;\n", + options: [4], + errors: expectedErrors([ + [2, 4, 0, "VariableDeclarator"], + [3, 4, 0, "VariableDeclarator"], + ]), + }, + { + code: "[a, b, \nc].forEach((index) => {\n" + " index;\n" + "});\n", + output: + "[a, b, \n" + + " c].forEach((index) => {\n" + + " index;\n" + + "});\n", + options: [4], + languageOptions: { ecmaVersion: 6 }, + errors: expectedErrors([ + [2, 4, 0, "Identifier"], + [3, 4, 2, "ExpressionStatement"], + ]), + }, + { + code: + "[a, b, \nc].forEach(function(index){\n" + + " return index;\n" + + "});\n", + output: + "[a, b, \n" + + " c].forEach(function(index){\n" + + " return index;\n" + + "});\n", + options: [4], + languageOptions: { ecmaVersion: 6 }, + errors: expectedErrors([ + [2, 4, 0, "Identifier"], + [3, 4, 2, "ReturnStatement"], + ]), + }, + { + code: + "[a, b, \nc].forEach(function(index){\n" + + " return index;\n" + + "});\n", + output: + "[a, b, \n" + + " c].forEach(function(index){\n" + + " return index;\n" + + "});\n", + options: [4], + languageOptions: { ecmaVersion: 6 }, + errors: expectedErrors([[2, 4, 0, "Identifier"]]), + }, + { + code: "[a, b, c].forEach((index) => {\n" + " index;\n" + "});\n", + output: + "[a, b, c].forEach((index) => {\n" + " index;\n" + "});\n", + options: [4], + languageOptions: { ecmaVersion: 6 }, + errors: expectedErrors([[2, 4, 2, "ExpressionStatement"]]), + }, + { + code: + "[a, b, c].forEach(function(index){\n" + + " return index;\n" + + "});\n", + output: + "[a, b, c].forEach(function(index){\n" + + " return index;\n" + + "});\n", + options: [4], + languageOptions: { ecmaVersion: 6 }, + errors: expectedErrors([[2, 4, 2, "ReturnStatement"]]), + }, + { + code: + "var x = ['a',\n" + " 'b',\n" + " 'c'\n" + "];", + output: "var x = ['a',\n" + " 'b',\n" + " 'c'\n" + "];", + options: [4], + errors: expectedErrors([ + [2, 4, 9, "Literal"], + [3, 4, 9, "Literal"], + ]), + }, + { + code: + "var x = [\n" + + " 'a',\n" + + " 'b',\n" + + " 'c'\n" + + "];", + output: + "var x = [\n" + + " 'a',\n" + + " 'b',\n" + + " 'c'\n" + + "];", + options: [4], + errors: expectedErrors([ + [2, 4, 9, "Literal"], + [3, 4, 9, "Literal"], + [4, 4, 9, "Literal"], + ]), + }, + { + code: + "var x = [\n" + + " 'a',\n" + + " 'b',\n" + + " 'c',\n" + + "'d'];", + output: + "var x = [\n" + + " 'a',\n" + + " 'b',\n" + + " 'c',\n" + + " 'd'];", + options: [4], + errors: expectedErrors([ + [2, 4, 9, "Literal"], + [3, 4, 9, "Literal"], + [4, 4, 9, "Literal"], + [5, 4, 0, "Literal"], + ]), + }, + { + code: + "var x = [\n" + + " 'a',\n" + + " 'b',\n" + + " 'c'\n" + + " ];", + output: + "var x = [\n" + + " 'a',\n" + + " 'b',\n" + + " 'c'\n" + + "];", + options: [4], + languageOptions: { ecmaVersion: 6 }, + errors: expectedErrors([ + [2, 4, 9, "Literal"], + [3, 4, 9, "Literal"], + [4, 4, 9, "Literal"], + [5, 0, 2, "ArrayExpression"], + ]), + }, + { + code: "while (1 < 2)\nconsole.log('foo')\n console.log('bar')", + output: "while (1 < 2)\n console.log('foo')\nconsole.log('bar')", + options: [2], + errors: expectedErrors([ + [2, 2, 0, "ExpressionStatement"], + [3, 0, 2, "ExpressionStatement"], + ]), + }, + { + code: + "function salutation () {\n" + + " switch (1) {\n" + + " case 0: return console.log('hi')\n" + + " case 1: return console.log('hey')\n" + + " }\n" + + "}\n", + output: + "function salutation () {\n" + + " switch (1) {\n" + + " case 0: return console.log('hi')\n" + + " case 1: return console.log('hey')\n" + + " }\n" + + "}\n", + options: [2, { SwitchCase: 1 }], + errors: expectedErrors([[3, 4, 2, "SwitchCase"]]), + }, + { + code: + "var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth,\n" + + "height, rotate;", + output: + "var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth,\n" + + " height, rotate;", + options: [2, { SwitchCase: 1 }], + errors: expectedErrors([[2, 2, 0, "VariableDeclarator"]]), + }, + { + code: + "switch (a) {\n" + + "case '1':\n" + + "b();\n" + + "break;\n" + + "default:\n" + + "c();\n" + + "break;\n" + + "}\n", + output: + "switch (a) {\n" + + " case '1':\n" + + " b();\n" + + " break;\n" + + " default:\n" + + " c();\n" + + " break;\n" + + "}\n", + options: [4, { SwitchCase: 2 }], + errors: expectedErrors([ + [2, 8, 0, "SwitchCase"], + [3, 12, 0, "ExpressionStatement"], + [4, 12, 0, "BreakStatement"], + [5, 8, 0, "SwitchCase"], + [6, 12, 0, "ExpressionStatement"], + [7, 12, 0, "BreakStatement"], + ]), + }, + { + code: "var geometry,\n" + "rotate;", + output: "var geometry,\n" + " rotate;", + options: [2, { VariableDeclarator: 1 }], + errors: expectedErrors([[2, 2, 0, "VariableDeclarator"]]), + }, + { + code: "var geometry,\n" + " rotate;", + output: "var geometry,\n" + " rotate;", + options: [2, { VariableDeclarator: 2 }], + errors: expectedErrors([[2, 4, 2, "VariableDeclarator"]]), + }, + { + code: "var geometry,\n" + "\trotate;", + output: "var geometry,\n" + "\t\trotate;", + options: ["tab", { VariableDeclarator: 2 }], + errors: expectedErrors("tab", [[2, 2, 1, "VariableDeclarator"]]), + }, + { + code: "let geometry,\n" + " rotate;", + output: "let geometry,\n" + " rotate;", + options: [2, { VariableDeclarator: 2 }], + languageOptions: { ecmaVersion: 6 }, + errors: expectedErrors([[2, 4, 2, "VariableDeclarator"]]), + }, + { + code: + "if(true)\n" + + " if (true)\n" + + " if (true)\n" + + " console.log(val);", + output: + "if(true)\n" + + " if (true)\n" + + " if (true)\n" + + " console.log(val);", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([[4, 6, 4, "ExpressionStatement"]]), + }, + { + code: "var a = {\n" + " a: 1,\n" + " b: 2\n" + "}", + output: "var a = {\n" + " a: 1,\n" + " b: 2\n" + "}", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 2, 4, "Property"], + [3, 2, 4, "Property"], + ]), + }, + { + code: "var a = [\n" + " a,\n" + " b\n" + "]", + output: "var a = [\n" + " a,\n" + " b\n" + "]", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 2, 4, "Identifier"], + [3, 2, 4, "Identifier"], + ]), + }, + { + code: "let a = [\n" + " a,\n" + " b\n" + "]", + output: "let a = [\n" + " a,\n" + " b\n" + "]", + options: [2, { VariableDeclarator: { let: 2 }, SwitchCase: 1 }], + languageOptions: { ecmaVersion: 6 }, + errors: expectedErrors([ + [2, 2, 4, "Identifier"], + [3, 2, 4, "Identifier"], + ]), + }, + { + code: + "var a = new Test({\n" + + " a: 1\n" + + " }),\n" + + " b = 4;\n", + output: + "var a = new Test({\n" + + " a: 1\n" + + " }),\n" + + " b = 4;\n", + options: [4], + errors: expectedErrors([ + [2, 8, 6, "Property"], + [3, 4, 2, "ObjectExpression"], + ]), + }, + { + code: + "var a = new Test({\n" + + " a: 1\n" + + " }),\n" + + " b = 4;\n" + + "const c = new Test({\n" + + " a: 1\n" + + " }),\n" + + " d = 4;\n", + output: + "var a = new Test({\n" + + " a: 1\n" + + " }),\n" + + " b = 4;\n" + + "const c = new Test({\n" + + " a: 1\n" + + " }),\n" + + " d = 4;\n", + options: [2, { VariableDeclarator: { var: 2 } }], + languageOptions: { ecmaVersion: 6 }, + errors: expectedErrors([ + [6, 4, 6, "Property"], + [7, 2, 4, "ObjectExpression"], + [8, 2, 4, "VariableDeclarator"], + ]), + }, + { + code: + "var abc = 5,\n" + + " c = 2,\n" + + " xyz = \n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " };", + output: + "var abc = 5,\n" + + " c = 2,\n" + + " xyz = \n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " };", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([ + [4, 4, 5, "ObjectExpression"], + [5, 6, 7, "Property"], + [6, 6, 8, "Property"], + [7, 4, 5, "ObjectExpression"], + ]), + }, + { + code: + "var abc = \n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " };", + output: + "var abc = \n" + + " {\n" + + " a: 1,\n" + + " b: 2\n" + + " };", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 4, 5, "ObjectExpression"], + [3, 6, 7, "Property"], + [4, 6, 8, "Property"], + [5, 4, 5, "ObjectExpression"], + ]), + }, + { + code: + "var path = require('path')\n" + + " , crypto = require('crypto')\n" + + ";\n", + output: + "var path = require('path')\n" + + " , crypto = require('crypto')\n" + + " ;\n", + options: [2], + errors: expectedErrors([[3, 1, 0, "VariableDeclaration"]]), + }, + { + code: "var a = 1\n" + " ,b = 2\n" + ";", + output: "var a = 1\n" + " ,b = 2\n" + " ;", + errors: expectedErrors([[3, 3, 0, "VariableDeclaration"]]), + }, + { + code: + "class A{\n" + + " constructor(){}\n" + + " a(){}\n" + + " get b(){}\n" + + "}", + output: + "class A{\n" + + " constructor(){}\n" + + " a(){}\n" + + " get b(){}\n" + + "}", + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + languageOptions: { ecmaVersion: 6 }, + errors: expectedErrors([[2, 4, 2, "MethodDefinition"]]), + }, + { + code: + "var A = class {\n" + + " constructor(){}\n" + + " a(){}\n" + + " get b(){}\n" + + "};", + output: + "var A = class {\n" + + " constructor(){}\n" + + " a(){}\n" + + " get b(){}\n" + + "};", + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + languageOptions: { ecmaVersion: 6 }, + errors: expectedErrors([ + [2, 4, 2, "MethodDefinition"], + [4, 4, 2, "MethodDefinition"], + ]), + }, + { + code: + "var a = 1,\n" + + " B = class {\n" + + " constructor(){}\n" + + " a(){}\n" + + " get b(){}\n" + + " };", + output: + "var a = 1,\n" + + " B = class {\n" + + " constructor(){}\n" + + " a(){}\n" + + " get b(){}\n" + + " };", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + languageOptions: { ecmaVersion: 6 }, + errors: expectedErrors([[3, 6, 4, "MethodDefinition"]]), + }, + { + code: + "{\n" + + " if(a){\n" + + " foo();\n" + + " }\n" + + " else{\n" + + " bar();\n" + + " }\n" + + "}\n", + output: + "{\n" + + " if(a){\n" + + " foo();\n" + + " }\n" + + " else{\n" + + " bar();\n" + + " }\n" + + "}\n", + options: [4], + errors: expectedErrors([[5, 4, 2, "Keyword"]]), + }, + { + code: + "{\n" + + " if(a){\n" + + " foo();\n" + + " }\n" + + " else\n" + + " bar();\n" + + " \n" + + "}\n", + output: + "{\n" + + " if(a){\n" + + " foo();\n" + + " }\n" + + " else\n" + + " bar();\n" + + " \n" + + "}\n", + options: [4], + errors: expectedErrors([[5, 4, 2, "Keyword"]]), + }, + { + code: + "{\n" + + " if(a)\n" + + " foo();\n" + + " else\n" + + " bar();\n" + + "}\n", + output: + "{\n" + + " if(a)\n" + + " foo();\n" + + " else\n" + + " bar();\n" + + "}\n", + options: [4], + errors: expectedErrors([[4, 4, 2, "Keyword"]]), + }, + { + code: + "(function(){\n" + + " function foo(x) {\n" + + " return x + 1;\n" + + " }\n" + + "})();", + output: + "(function(){\n" + + "function foo(x) {\n" + + " return x + 1;\n" + + " }\n" + + "})();", + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([[2, 0, 2, "FunctionDeclaration"]]), + }, + { + code: + "(function(){\n" + + " function foo(x) {\n" + + " return x + 1;\n" + + " }\n" + + "})();", + output: + "(function(){\n" + + " function foo(x) {\n" + + " return x + 1;\n" + + " }\n" + + "})();", + options: [4, { outerIIFEBody: 2 }], + errors: expectedErrors([[2, 8, 4, "FunctionDeclaration"]]), + }, + { + code: "if(data) {\n" + "console.log('hi');\n" + "}", + output: "if(data) {\n" + " console.log('hi');\n" + "}", + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([[2, 2, 0, "ExpressionStatement"]]), + }, + { + code: + "var ns = function(){\n" + + " function fooVar(x) {\n" + + " return x + 1;\n" + + " }\n" + + "}(x);", + output: + "var ns = function(){\n" + + " function fooVar(x) {\n" + + " return x + 1;\n" + + " }\n" + + "}(x);", + options: [4, { outerIIFEBody: 2 }], + errors: expectedErrors([[2, 8, 4, "FunctionDeclaration"]]), + }, + { + code: + "var obj = {\n" + + " foo: function() {\n" + + " return true;\n" + + " }()\n" + + "};\n", + output: + "var obj = {\n" + + " foo: function() {\n" + + " return true;\n" + + " }()\n" + + "};\n", + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([[3, 4, 2, "ReturnStatement"]]), + }, + { + code: + "typeof function() {\n" + + " function fooVar(x) {\n" + + " return x + 1;\n" + + " }\n" + + "}();", + output: + "typeof function() {\n" + + " function fooVar(x) {\n" + + " return x + 1;\n" + + " }\n" + + "}();", + options: [2, { outerIIFEBody: 2 }], + errors: expectedErrors([[2, 2, 4, "FunctionDeclaration"]]), + }, + { + code: + "{\n" + + "\t!function(x) {\n" + + "\t\t\t\treturn x + 1;\n" + + "\t}()\n" + + "};", + output: + "{\n" + + "\t!function(x) {\n" + + "\t\treturn x + 1;\n" + + "\t}()\n" + + "};", + options: ["tab", { outerIIFEBody: 3 }], + errors: expectedErrors("tab", [[3, 2, 4, "ReturnStatement"]]), + }, + { + code: "Buffer\n" + ".toString()", + output: "Buffer\n" + " .toString()", + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[2, 4, 0, "Punctuator"]]), + }, + { + code: "Buffer\n" + " .indexOf('a')\n" + ".toString()", + output: "Buffer\n" + " .indexOf('a')\n" + " .toString()", + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[3, 4, 0, "Punctuator"]]), + }, + { + code: "Buffer.\n" + "length", + output: "Buffer.\n" + " length", + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[2, 4, 0, "Identifier"]]), + }, + { + code: "Buffer.\n" + "\t\tlength", + output: "Buffer.\n" + "\tlength", + options: ["tab", { MemberExpression: 1 }], + errors: expectedErrors("tab", [[2, 1, 2, "Identifier"]]), + }, + { + code: "Buffer\n" + " .foo\n" + " .bar", + output: "Buffer\n" + " .foo\n" + " .bar", + options: [2, { MemberExpression: 2 }], + errors: expectedErrors([ + [2, 4, 2, "Punctuator"], + [3, 4, 2, "Punctuator"], + ]), + }, + { + // Indentation with multiple else statements: https://github.com/eslint/eslint/issues/6956 - // https://github.com/eslint/eslint/issues/7733 - { - code: - "var foo = function() {\n" + - "\twindow.foo('foo',\n" + - "\t\t{\n" + - "\t\t\tfoo: 'bar'," + - "\t\t\tbar: {\n" + - "\t\t\t\tfoo: 'bar'\n" + - "\t\t\t}\n" + - "\t\t}\n" + - "\t);\n" + - "}", - options: ["tab"] - }, - { - code: - "echo = spawn('cmd.exe',\n" + - " ['foo', 'bar',\n" + - " 'baz']);", - options: [2, { ArrayExpression: "first", CallExpression: { arguments: "first" } }] - } - ], - invalid: [ - { - code: - "var a = b;\n" + - "if (a) {\n" + - "b();\n" + - "}\n", - output: - "var a = b;\n" + - "if (a) {\n" + - " b();\n" + - "}\n", - options: [2], - errors: expectedErrors([[3, 2, 0, "ExpressionStatement"]]) - }, - { - code: - "require('http').request({hostname: 'localhost',\n" + - " port: 80}, function(res) {\n" + - " res.end();\n" + - "});\n", - output: - "require('http').request({hostname: 'localhost',\n" + - " port: 80}, function(res) {\n" + - " res.end();\n" + - "});\n", - options: [2], - errors: expectedErrors([[2, 2, 18, "Property"]]) - }, - { - code: - "if (array.some(function(){\n" + - " return true;\n" + - "})) {\n" + - "a++; // ->\n" + - " b++;\n" + - " c++; // <-\n" + - "}\n", - output: - "if (array.some(function(){\n" + - " return true;\n" + - "})) {\n" + - " a++; // ->\n" + - " b++;\n" + - " c++; // <-\n" + - "}\n", - options: [2], - errors: expectedErrors([[4, 2, 0, "ExpressionStatement"], [6, 2, 4, "ExpressionStatement"]]) - }, - { - code: "if (a){\n\tb=c;\n\t\tc=d;\ne=f;\n}", - output: "if (a){\n\tb=c;\n\tc=d;\n\te=f;\n}", - options: ["tab"], - errors: expectedErrors("tab", [[3, 1, 2, "ExpressionStatement"], [4, 1, 0, "ExpressionStatement"]]) - }, - { - code: "if (a){\n b=c;\n c=d;\n e=f;\n}", - output: "if (a){\n b=c;\n c=d;\n e=f;\n}", - options: [4], - errors: expectedErrors([[3, 4, 6, "ExpressionStatement"], [4, 4, 1, "ExpressionStatement"]]) - }, - { - code: fixture, - output: fixedFixture, - options: [2, { SwitchCase: 1, MemberExpression: 1 }], - errors: expectedErrors([ - [5, 2, 4, "VariableDeclaration"], - [10, 4, 6, "BlockStatement"], - [11, 2, 4, "BlockStatement"], - [15, 4, 2, "ExpressionStatement"], - [16, 2, 4, "BlockStatement"], - [23, 2, 4, "BlockStatement"], - [29, 2, 4, "ForStatement"], - [31, 4, 2, "BlockStatement"], - [36, 4, 6, "ExpressionStatement"], - [38, 2, 4, "BlockStatement"], - [39, 4, 2, "ExpressionStatement"], - [40, 2, 0, "BlockStatement"], - [46, 0, 1, "VariableDeclaration"], - [54, 2, 4, "BlockStatement"], - [114, 4, 2, "VariableDeclaration"], - [120, 4, 6, "VariableDeclaration"], - [124, 4, 2, "BreakStatement"], - [134, 4, 6, "BreakStatement"], - [138, 2, 3, "Punctuator"], - [139, 2, 3, "Punctuator"], - [143, 4, 0, "ExpressionStatement"], - [151, 4, 6, "ExpressionStatement"], - [159, 4, 2, "ExpressionStatement"], - [161, 4, 6, "ExpressionStatement"], - [175, 2, 0, "ExpressionStatement"], - [177, 2, 4, "ExpressionStatement"], - [189, 2, 0, "VariableDeclaration"], - [193, 6, 4, "ExpressionStatement"], - [195, 6, 8, "ExpressionStatement"], - [304, 4, 6, "ExpressionStatement"], - [306, 4, 8, "ExpressionStatement"], - [307, 2, 4, "BlockStatement"], - [308, 2, 4, "VariableDeclarator"], - [311, 4, 6, "Identifier"], - [312, 4, 6, "Identifier"], - [313, 4, 6, "Identifier"], - [314, 2, 4, "ArrayExpression"], - [315, 2, 4, "VariableDeclarator"], - [318, 4, 6, "Property"], - [319, 4, 6, "Property"], - [320, 4, 6, "Property"], - [321, 2, 4, "ObjectExpression"], - [322, 2, 4, "VariableDeclarator"], - [326, 2, 1, "Literal"], - [327, 2, 1, "Literal"], - [328, 2, 1, "Literal"], - [329, 2, 1, "Literal"], - [330, 2, 1, "Literal"], - [331, 2, 1, "Literal"], - [332, 2, 1, "Literal"], - [333, 2, 1, "Literal"], - [334, 2, 1, "Literal"], - [335, 2, 1, "Literal"], - [340, 2, 4, "ExpressionStatement"], - [341, 2, 0, "ExpressionStatement"], - [344, 2, 4, "ExpressionStatement"], - [345, 2, 0, "ExpressionStatement"], - [348, 2, 4, "ExpressionStatement"], - [349, 2, 0, "ExpressionStatement"], - [355, 2, 0, "ExpressionStatement"], - [357, 2, 4, "ExpressionStatement"], - [361, 4, 6, "ExpressionStatement"], - [362, 2, 4, "BlockStatement"], - [363, 2, 4, "VariableDeclarator"], - [368, 2, 0, "SwitchCase"], - [370, 2, 4, "SwitchCase"], - [374, 4, 6, "VariableDeclaration"], - [376, 4, 2, "VariableDeclaration"], - [383, 2, 0, "ExpressionStatement"], - [385, 2, 4, "ExpressionStatement"], - [390, 2, 0, "ExpressionStatement"], - [392, 2, 4, "ExpressionStatement"], - [409, 2, 0, "ExpressionStatement"], - [410, 2, 4, "ExpressionStatement"], - [416, 2, 0, "ExpressionStatement"], - [417, 2, 4, "ExpressionStatement"], - [422, 2, 4, "ExpressionStatement"], - [423, 2, 0, "ExpressionStatement"], - [427, 2, 6, "ExpressionStatement"], - [428, 2, 8, "ExpressionStatement"], - [429, 2, 4, "ExpressionStatement"], - [430, 0, 4, "BlockStatement"], - [433, 2, 4, "ExpressionStatement"], - [434, 0, 4, "BlockStatement"], - [437, 2, 0, "ExpressionStatement"], - [438, 0, 4, "BlockStatement"], - [451, 2, 0, "ExpressionStatement"], - [453, 2, 4, "ExpressionStatement"], - [499, 6, 8, "BlockStatement"], - [500, 10, 8, "ExpressionStatement"], - [501, 8, 6, "BlockStatement"], - [506, 6, 8, "BlockStatement"] - ]) - }, - { - code: - "switch(value){\n" + - " case \"1\":\n" + - " a();\n" + - " break;\n" + - " case \"2\":\n" + - " a();\n" + - " break;\n" + - " default:\n" + - " a();\n" + - " break;\n" + - "}", - output: - "switch(value){\n" + - " case \"1\":\n" + - " a();\n" + - " break;\n" + - " case \"2\":\n" + - " a();\n" + - " break;\n" + - " default:\n" + - " a();\n" + - " break;\n" + - "}", - options: [4, { SwitchCase: 1 }], - errors: expectedErrors([[4, 8, 4, "BreakStatement"], [7, 8, 4, "BreakStatement"]]) - }, - { - code: - "var x = 0 &&\n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " };", - output: - "var x = 0 &&\n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " };", - options: [4], - errors: expectedErrors([[3, 8, 7, "Property"], [4, 8, 10, "Property"]]) - }, - { - code: - "switch(value){\n" + - " case \"1\":\n" + - " a();\n" + - " break;\n" + - " case \"2\":\n" + - " a();\n" + - " break;\n" + - " default:\n" + - " break;\n" + - "}", - output: - "switch(value){\n" + - " case \"1\":\n" + - " a();\n" + - " break;\n" + - " case \"2\":\n" + - " a();\n" + - " break;\n" + - " default:\n" + - " break;\n" + - "}", - options: [4, { SwitchCase: 1 }], - errors: expectedErrors([9, 8, 4, "BreakStatement"]) - }, - { - code: - "switch(value){\n" + - " case \"1\":\n" + - " case \"2\":\n" + - " a();\n" + - " break;\n" + - " default:\n" + - " break;\n" + - "}\n" + - "switch(value){\n" + - " case \"1\":\n" + - " break;\n" + - " case \"2\":\n" + - " a();\n" + - " break;\n" + - " default:\n" + - " a();\n" + - " break;\n" + - "}", - output: - "switch(value){\n" + - " case \"1\":\n" + - " case \"2\":\n" + - " a();\n" + - " break;\n" + - " default:\n" + - " break;\n" + - "}\n" + - "switch(value){\n" + - " case \"1\":\n" + - " break;\n" + - " case \"2\":\n" + - " a();\n" + - " break;\n" + - " default:\n" + - " a();\n" + - " break;\n" + - "}", - options: [4, { SwitchCase: 1 }], - errors: expectedErrors([[11, 8, 4, "BreakStatement"], [14, 8, 4, "BreakStatement"], [17, 8, 4, "BreakStatement"]]) - }, - { - code: - "switch(value){\n" + - "case \"1\":\n" + - " a();\n" + - " break;\n" + - " case \"2\":\n" + - " break;\n" + - " default:\n" + - " break;\n" + - "}", - output: - "switch(value){\n" + - "case \"1\":\n" + - " a();\n" + - " break;\n" + - "case \"2\":\n" + - " break;\n" + - "default:\n" + - " break;\n" + - "}", - options: [4], - errors: expectedErrors([ - [3, 4, 8, "ExpressionStatement"], - [4, 4, 8, "BreakStatement"], - [5, 0, 4, "SwitchCase"], - [6, 4, 8, "BreakStatement"], - [7, 0, 4, "SwitchCase"], - [8, 4, 8, "BreakStatement"] - ]) - }, - { - code: - "var obj = {foo: 1, bar: 2};\n" + - "with (obj) {\n" + - "console.log(foo + bar);\n" + - "}\n", - output: - "var obj = {foo: 1, bar: 2};\n" + - "with (obj) {\n" + - " console.log(foo + bar);\n" + - "}\n", - errors: expectedErrors([3, 4, 0, "ExpressionStatement"]) - }, - { - code: - "switch (a) {\n" + - "case '1':\n" + - "b();\n" + - "break;\n" + - "default:\n" + - "c();\n" + - "break;\n" + - "}\n", - output: - "switch (a) {\n" + - " case '1':\n" + - " b();\n" + - " break;\n" + - " default:\n" + - " c();\n" + - " break;\n" + - "}\n", - options: [4, { SwitchCase: 1 }], - errors: expectedErrors([ - [2, 4, 0, "SwitchCase"], - [3, 8, 0, "ExpressionStatement"], - [4, 8, 0, "BreakStatement"], - [5, 4, 0, "SwitchCase"], - [6, 8, 0, "ExpressionStatement"], - [7, 8, 0, "BreakStatement"] - ]) - }, - { - code: - "var foo = function(){\n" + - " foo\n" + - " .bar\n" + - "}", - output: - "var foo = function(){\n" + - " foo\n" + - " .bar\n" + - "}", - options: [4, { MemberExpression: 1 }], - errors: expectedErrors( - [3, 8, 10, "Punctuator"] - ) - }, - { - code: - "var foo = function(){\n" + - " foo\n" + - " .bar\n" + - "}", - output: - "var foo = function(){\n" + - " foo\n" + - " .bar\n" + - "}", - options: [4, { MemberExpression: 2 }], - errors: expectedErrors( - [3, 12, 13, "Punctuator"] - ) - }, - { - code: - "var foo = () => {\n" + - " foo\n" + - " .bar\n" + - "}", - output: - "var foo = () => {\n" + - " foo\n" + - " .bar\n" + - "}", - options: [4, { MemberExpression: 2 }], - languageOptions: { ecmaVersion: 6 }, - errors: expectedErrors( - [3, 12, 13, "Punctuator"] - ) - }, - { - code: - "TestClass.prototype.method = function () {\n" + - " return Promise.resolve(3)\n" + - " .then(function (x) {\n" + - " return x;\n" + - " });\n" + - "};", - output: - "TestClass.prototype.method = function () {\n" + - " return Promise.resolve(3)\n" + - " .then(function (x) {\n" + - " return x;\n" + - " });\n" + - "};", - options: [2, { MemberExpression: 1 }], - languageOptions: { ecmaVersion: 6 }, - errors: expectedErrors( - [ - [3, 4, 6, "Punctuator"] - ] - ) - }, - { - code: - "while (a) \n" + - "b();", - output: - "while (a) \n" + - " b();", - options: [4], - errors: expectedErrors([ - [2, 4, 0, "ExpressionStatement"] - ]) - }, - { - code: - "for (;;) \n" + - "b();", - output: - "for (;;) \n" + - " b();", - options: [4], - errors: expectedErrors([ - [2, 4, 0, "ExpressionStatement"] - ]) - }, - { - code: - "for (a in x) \n" + - "b();", - output: - "for (a in x) \n" + - " b();", - options: [4], - errors: expectedErrors([ - [2, 4, 0, "ExpressionStatement"] - ]) - }, - { - code: - "do \n" + - "b();\n" + - "while(true)", - output: - "do \n" + - " b();\n" + - "while(true)", - options: [4], - errors: expectedErrors([ - [2, 4, 0, "ExpressionStatement"] - ]) - }, - { - code: - "if(true) \n" + - "b();", - output: - "if(true) \n" + - " b();", - options: [4], - errors: expectedErrors([ - [2, 4, 0, "ExpressionStatement"] - ]) - }, - { - code: - "var test = {\n" + - " a: 1,\n" + - " b: 2\n" + - " };\n", - output: - "var test = {\n" + - " a: 1,\n" + - " b: 2\n" + - "};\n", - options: [2], - errors: expectedErrors([ - [2, 2, 6, "Property"], - [3, 2, 4, "Property"], - [4, 0, 4, "ObjectExpression"] - ]) - }, - { - code: - "var a = function() {\n" + - " a++;\n" + - " b++;\n" + - " c++;\n" + - " },\n" + - " b;\n", - output: - "var a = function() {\n" + - " a++;\n" + - " b++;\n" + - " c++;\n" + - " },\n" + - " b;\n", - options: [4], - errors: expectedErrors([ - [2, 8, 6, "ExpressionStatement"], - [3, 8, 4, "ExpressionStatement"], - [4, 8, 10, "ExpressionStatement"] - ]) - }, - { - code: - "var a = 1,\n" + - "b = 2,\n" + - "c = 3;\n", - output: - "var a = 1,\n" + - " b = 2,\n" + - " c = 3;\n", - options: [4], - errors: expectedErrors([ - [2, 4, 0, "VariableDeclarator"], - [3, 4, 0, "VariableDeclarator"] - ]) - }, - { - code: - "[a, b, \nc].forEach((index) => {\n" + - " index;\n" + - "});\n", - output: - "[a, b, \n" + - " c].forEach((index) => {\n" + - " index;\n" + - "});\n", - options: [4], - languageOptions: { ecmaVersion: 6 }, - errors: expectedErrors([ - [2, 4, 0, "Identifier"], - [3, 4, 2, "ExpressionStatement"] - ]) - }, - { - code: - "[a, b, \nc].forEach(function(index){\n" + - " return index;\n" + - "});\n", - output: - "[a, b, \n" + - " c].forEach(function(index){\n" + - " return index;\n" + - "});\n", - options: [4], - languageOptions: { ecmaVersion: 6 }, - errors: expectedErrors([ - [2, 4, 0, "Identifier"], - [3, 4, 2, "ReturnStatement"] - ]) - }, - { - code: - "[a, b, \nc].forEach(function(index){\n" + - " return index;\n" + - "});\n", - output: - "[a, b, \n" + - " c].forEach(function(index){\n" + - " return index;\n" + - "});\n", - options: [4], - languageOptions: { ecmaVersion: 6 }, - errors: expectedErrors([[2, 4, 0, "Identifier"]]) - }, - { - code: - "[a, b, c].forEach((index) => {\n" + - " index;\n" + - "});\n", - output: - "[a, b, c].forEach((index) => {\n" + - " index;\n" + - "});\n", - options: [4], - languageOptions: { ecmaVersion: 6 }, - errors: expectedErrors([ - [2, 4, 2, "ExpressionStatement"] - ]) - }, - { - code: - "[a, b, c].forEach(function(index){\n" + - " return index;\n" + - "});\n", - output: - "[a, b, c].forEach(function(index){\n" + - " return index;\n" + - "});\n", - options: [4], - languageOptions: { ecmaVersion: 6 }, - errors: expectedErrors([ - [2, 4, 2, "ReturnStatement"] - ]) - }, - { - code: - "var x = ['a',\n" + - " 'b',\n" + - " 'c'\n" + - "];", - output: - "var x = ['a',\n" + - " 'b',\n" + - " 'c'\n" + - "];", - options: [4], - errors: expectedErrors([ - [2, 4, 9, "Literal"], - [3, 4, 9, "Literal"] - ]) - }, - { - code: - "var x = [\n" + - " 'a',\n" + - " 'b',\n" + - " 'c'\n" + - "];", - output: - "var x = [\n" + - " 'a',\n" + - " 'b',\n" + - " 'c'\n" + - "];", - options: [4], - errors: expectedErrors([ - [2, 4, 9, "Literal"], - [3, 4, 9, "Literal"], - [4, 4, 9, "Literal"] - ]) - }, - { - code: - "var x = [\n" + - " 'a',\n" + - " 'b',\n" + - " 'c',\n" + - "'d'];", - output: - "var x = [\n" + - " 'a',\n" + - " 'b',\n" + - " 'c',\n" + - " 'd'];", - options: [4], - errors: expectedErrors([ - [2, 4, 9, "Literal"], - [3, 4, 9, "Literal"], - [4, 4, 9, "Literal"], - [5, 4, 0, "Literal"] - ]) - }, - { - code: - "var x = [\n" + - " 'a',\n" + - " 'b',\n" + - " 'c'\n" + - " ];", - output: - "var x = [\n" + - " 'a',\n" + - " 'b',\n" + - " 'c'\n" + - "];", - options: [4], - languageOptions: { ecmaVersion: 6 }, - errors: expectedErrors([ - [2, 4, 9, "Literal"], - [3, 4, 9, "Literal"], - [4, 4, 9, "Literal"], - [5, 0, 2, "ArrayExpression"] - ]) - }, - { - code: "while (1 < 2)\nconsole.log('foo')\n console.log('bar')", - output: "while (1 < 2)\n console.log('foo')\nconsole.log('bar')", - options: [2], - errors: expectedErrors([ - [2, 2, 0, "ExpressionStatement"], - [3, 0, 2, "ExpressionStatement"] - ]) - }, - { - code: - "function salutation () {\n" + - " switch (1) {\n" + - " case 0: return console.log('hi')\n" + - " case 1: return console.log('hey')\n" + - " }\n" + - "}\n", - output: - "function salutation () {\n" + - " switch (1) {\n" + - " case 0: return console.log('hi')\n" + - " case 1: return console.log('hey')\n" + - " }\n" + - "}\n", - options: [2, { SwitchCase: 1 }], - errors: expectedErrors([ - [3, 4, 2, "SwitchCase"] - ]) - }, - { - code: - "var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth,\n" + - "height, rotate;", - output: - "var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth,\n" + - " height, rotate;", - options: [2, { SwitchCase: 1 }], - errors: expectedErrors([ - [2, 2, 0, "VariableDeclarator"] - ]) - }, - { - code: - "switch (a) {\n" + - "case '1':\n" + - "b();\n" + - "break;\n" + - "default:\n" + - "c();\n" + - "break;\n" + - "}\n", - output: - "switch (a) {\n" + - " case '1':\n" + - " b();\n" + - " break;\n" + - " default:\n" + - " c();\n" + - " break;\n" + - "}\n", - options: [4, { SwitchCase: 2 }], - errors: expectedErrors([ - [2, 8, 0, "SwitchCase"], - [3, 12, 0, "ExpressionStatement"], - [4, 12, 0, "BreakStatement"], - [5, 8, 0, "SwitchCase"], - [6, 12, 0, "ExpressionStatement"], - [7, 12, 0, "BreakStatement"] - ]) - }, - { - code: - "var geometry,\n" + - "rotate;", - output: - "var geometry,\n" + - " rotate;", - options: [2, { VariableDeclarator: 1 }], - errors: expectedErrors([ - [2, 2, 0, "VariableDeclarator"] - ]) - }, - { - code: - "var geometry,\n" + - " rotate;", - output: - "var geometry,\n" + - " rotate;", - options: [2, { VariableDeclarator: 2 }], - errors: expectedErrors([ - [2, 4, 2, "VariableDeclarator"] - ]) - }, - { - code: - "var geometry,\n" + - "\trotate;", - output: - "var geometry,\n" + - "\t\trotate;", - options: ["tab", { VariableDeclarator: 2 }], - errors: expectedErrors("tab", [ - [2, 2, 1, "VariableDeclarator"] - ]) - }, - { - code: - "let geometry,\n" + - " rotate;", - output: - "let geometry,\n" + - " rotate;", - options: [2, { VariableDeclarator: 2 }], - languageOptions: { ecmaVersion: 6 }, - errors: expectedErrors([ - [2, 4, 2, "VariableDeclarator"] - ]) - }, - { - code: - "if(true)\n" + - " if (true)\n" + - " if (true)\n" + - " console.log(val);", - output: - "if(true)\n" + - " if (true)\n" + - " if (true)\n" + - " console.log(val);", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - errors: expectedErrors([ - [4, 6, 4, "ExpressionStatement"] - ]) - }, - { - code: - "var a = {\n" + - " a: 1,\n" + - " b: 2\n" + - "}", - output: - "var a = {\n" + - " a: 1,\n" + - " b: 2\n" + - "}", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - errors: expectedErrors([ - [2, 2, 4, "Property"], - [3, 2, 4, "Property"] - ]) - }, - { - code: - "var a = [\n" + - " a,\n" + - " b\n" + - "]", - output: - "var a = [\n" + - " a,\n" + - " b\n" + - "]", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - errors: expectedErrors([ - [2, 2, 4, "Identifier"], - [3, 2, 4, "Identifier"] - ]) - }, - { - code: - "let a = [\n" + - " a,\n" + - " b\n" + - "]", - output: - "let a = [\n" + - " a,\n" + - " b\n" + - "]", - options: [2, { VariableDeclarator: { let: 2 }, SwitchCase: 1 }], - languageOptions: { ecmaVersion: 6 }, - errors: expectedErrors([ - [2, 2, 4, "Identifier"], - [3, 2, 4, "Identifier"] - ]) - }, - { - code: - "var a = new Test({\n" + - " a: 1\n" + - " }),\n" + - " b = 4;\n", - output: - "var a = new Test({\n" + - " a: 1\n" + - " }),\n" + - " b = 4;\n", - options: [4], - errors: expectedErrors([ - [2, 8, 6, "Property"], - [3, 4, 2, "ObjectExpression"] - ]) - }, - { - code: - "var a = new Test({\n" + - " a: 1\n" + - " }),\n" + - " b = 4;\n" + - "const c = new Test({\n" + - " a: 1\n" + - " }),\n" + - " d = 4;\n", - output: - "var a = new Test({\n" + - " a: 1\n" + - " }),\n" + - " b = 4;\n" + - "const c = new Test({\n" + - " a: 1\n" + - " }),\n" + - " d = 4;\n", - options: [2, { VariableDeclarator: { var: 2 } }], - languageOptions: { ecmaVersion: 6 }, - errors: expectedErrors([ - [6, 4, 6, "Property"], - [7, 2, 4, "ObjectExpression"], - [8, 2, 4, "VariableDeclarator"] - ]) - }, - { - code: - "var abc = 5,\n" + - " c = 2,\n" + - " xyz = \n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " };", - output: - "var abc = 5,\n" + - " c = 2,\n" + - " xyz = \n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " };", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - errors: expectedErrors([ - [4, 4, 5, "ObjectExpression"], - [5, 6, 7, "Property"], - [6, 6, 8, "Property"], - [7, 4, 5, "ObjectExpression"] - ]) - }, - { - code: - "var abc = \n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " };", - output: - "var abc = \n" + - " {\n" + - " a: 1,\n" + - " b: 2\n" + - " };", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - errors: expectedErrors([ - [2, 4, 5, "ObjectExpression"], - [3, 6, 7, "Property"], - [4, 6, 8, "Property"], - [5, 4, 5, "ObjectExpression"] - ]) - }, - { - code: - "var path = require('path')\n" + - " , crypto = require('crypto')\n" + - ";\n", - output: - "var path = require('path')\n" + - " , crypto = require('crypto')\n" + - " ;\n", - options: [2], - errors: expectedErrors([ - [3, 1, 0, "VariableDeclaration"] - ]) - }, - { - code: - "var a = 1\n" + - " ,b = 2\n" + - ";", - output: - "var a = 1\n" + - " ,b = 2\n" + - " ;", - errors: expectedErrors([ - [3, 3, 0, "VariableDeclaration"] - ]) - }, - { - code: - "class A{\n" + - " constructor(){}\n" + - " a(){}\n" + - " get b(){}\n" + - "}", - output: - "class A{\n" + - " constructor(){}\n" + - " a(){}\n" + - " get b(){}\n" + - "}", - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], - languageOptions: { ecmaVersion: 6 }, - errors: expectedErrors([[2, 4, 2, "MethodDefinition"]]) - }, - { - code: - "var A = class {\n" + - " constructor(){}\n" + - " a(){}\n" + - " get b(){}\n" + - "};", - output: - "var A = class {\n" + - " constructor(){}\n" + - " a(){}\n" + - " get b(){}\n" + - "};", - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], - languageOptions: { ecmaVersion: 6 }, - errors: expectedErrors([[2, 4, 2, "MethodDefinition"], [4, 4, 2, "MethodDefinition"]]) - }, - { - code: - "var a = 1,\n" + - " B = class {\n" + - " constructor(){}\n" + - " a(){}\n" + - " get b(){}\n" + - " };", - output: - "var a = 1,\n" + - " B = class {\n" + - " constructor(){}\n" + - " a(){}\n" + - " get b(){}\n" + - " };", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - languageOptions: { ecmaVersion: 6 }, - errors: expectedErrors([[3, 6, 4, "MethodDefinition"]]) - }, - { - code: - "{\n" + - " if(a){\n" + - " foo();\n" + - " }\n" + - " else{\n" + - " bar();\n" + - " }\n" + - "}\n", - output: - "{\n" + - " if(a){\n" + - " foo();\n" + - " }\n" + - " else{\n" + - " bar();\n" + - " }\n" + - "}\n", - options: [4], - errors: expectedErrors([[5, 4, 2, "Keyword"]]) - }, - { - code: - "{\n" + - " if(a){\n" + - " foo();\n" + - " }\n" + - " else\n" + - " bar();\n" + - " \n" + - "}\n", - output: - "{\n" + - " if(a){\n" + - " foo();\n" + - " }\n" + - " else\n" + - " bar();\n" + - " \n" + - "}\n", - options: [4], - errors: expectedErrors([[5, 4, 2, "Keyword"]]) - }, - { - code: - "{\n" + - " if(a)\n" + - " foo();\n" + - " else\n" + - " bar();\n" + - "}\n", - output: - "{\n" + - " if(a)\n" + - " foo();\n" + - " else\n" + - " bar();\n" + - "}\n", - options: [4], - errors: expectedErrors([[4, 4, 2, "Keyword"]]) - }, - { - code: - "(function(){\n" + - " function foo(x) {\n" + - " return x + 1;\n" + - " }\n" + - "})();", - output: - "(function(){\n" + - "function foo(x) {\n" + - " return x + 1;\n" + - " }\n" + - "})();", - options: [2, { outerIIFEBody: 0 }], - errors: expectedErrors([[2, 0, 2, "FunctionDeclaration"]]) - }, - { - code: - "(function(){\n" + - " function foo(x) {\n" + - " return x + 1;\n" + - " }\n" + - "})();", - output: - "(function(){\n" + - " function foo(x) {\n" + - " return x + 1;\n" + - " }\n" + - "})();", - options: [4, { outerIIFEBody: 2 }], - errors: expectedErrors([[2, 8, 4, "FunctionDeclaration"]]) - }, - { - code: - "if(data) {\n" + - "console.log('hi');\n" + - "}", - output: - "if(data) {\n" + - " console.log('hi');\n" + - "}", - options: [2, { outerIIFEBody: 0 }], - errors: expectedErrors([[2, 2, 0, "ExpressionStatement"]]) - }, - { - code: - "var ns = function(){\n" + - " function fooVar(x) {\n" + - " return x + 1;\n" + - " }\n" + - "}(x);", - output: - "var ns = function(){\n" + - " function fooVar(x) {\n" + - " return x + 1;\n" + - " }\n" + - "}(x);", - options: [4, { outerIIFEBody: 2 }], - errors: expectedErrors([[2, 8, 4, "FunctionDeclaration"]]) - }, - { - code: - "var obj = {\n" + - " foo: function() {\n" + - " return true;\n" + - " }()\n" + - "};\n", - output: - "var obj = {\n" + - " foo: function() {\n" + - " return true;\n" + - " }()\n" + - "};\n", - options: [2, { outerIIFEBody: 0 }], - errors: expectedErrors([[3, 4, 2, "ReturnStatement"]]) - }, - { - code: - "typeof function() {\n" + - " function fooVar(x) {\n" + - " return x + 1;\n" + - " }\n" + - "}();", - output: - "typeof function() {\n" + - " function fooVar(x) {\n" + - " return x + 1;\n" + - " }\n" + - "}();", - options: [2, { outerIIFEBody: 2 }], - errors: expectedErrors([[2, 2, 4, "FunctionDeclaration"]]) - }, - { - code: - "{\n" + - "\t!function(x) {\n" + - "\t\t\t\treturn x + 1;\n" + - "\t}()\n" + - "};", - output: - "{\n" + - "\t!function(x) {\n" + - "\t\treturn x + 1;\n" + - "\t}()\n" + - "};", - options: ["tab", { outerIIFEBody: 3 }], - errors: expectedErrors("tab", [[3, 2, 4, "ReturnStatement"]]) - }, - { - code: - "Buffer\n" + - ".toString()", - output: - "Buffer\n" + - " .toString()", - options: [4, { MemberExpression: 1 }], - errors: expectedErrors([[2, 4, 0, "Punctuator"]]) - }, - { - code: - "Buffer\n" + - " .indexOf('a')\n" + - ".toString()", - output: - "Buffer\n" + - " .indexOf('a')\n" + - " .toString()", - options: [4, { MemberExpression: 1 }], - errors: expectedErrors([[3, 4, 0, "Punctuator"]]) - }, - { - code: - "Buffer.\n" + - "length", - output: - "Buffer.\n" + - " length", - options: [4, { MemberExpression: 1 }], - errors: expectedErrors([[2, 4, 0, "Identifier"]]) - }, - { - code: - "Buffer.\n" + - "\t\tlength", - output: - "Buffer.\n" + - "\tlength", - options: ["tab", { MemberExpression: 1 }], - errors: expectedErrors("tab", [[2, 1, 2, "Identifier"]]) - }, - { - code: - "Buffer\n" + - " .foo\n" + - " .bar", - output: - "Buffer\n" + - " .foo\n" + - " .bar", - options: [2, { MemberExpression: 2 }], - errors: expectedErrors([[2, 4, 2, "Punctuator"], [3, 4, 2, "Punctuator"]]) - }, - { + code: + "if (foo) bar();\n" + + "else if (baz) foobar();\n" + + " else if (qux) qux();", + output: + "if (foo) bar();\n" + + "else if (baz) foobar();\n" + + "else if (qux) qux();", + options: [2], + errors: expectedErrors([3, 0, 2, "Keyword"]), + }, + { + code: + "if (foo) bar();\n" + + "else if (baz) foobar();\n" + + " else qux();", + output: + "if (foo) bar();\n" + + "else if (baz) foobar();\n" + + "else qux();", + options: [2], + errors: expectedErrors([3, 0, 2, "Keyword"]), + }, + { + code: "foo();\n" + " if (baz) foobar();\n" + " else qux();", + output: "foo();\n" + "if (baz) foobar();\n" + "else qux();", + options: [2], + errors: expectedErrors([ + [2, 0, 2, "IfStatement"], + [3, 0, 2, "Keyword"], + ]), + }, + { + code: + "if (foo) bar();\n" + + "else if (baz) foobar();\n" + + " else if (bip) {\n" + + " qux();\n" + + " }", + output: + "if (foo) bar();\n" + + "else if (baz) foobar();\n" + + "else if (bip) {\n" + + " qux();\n" + // (fixed on the next pass) + " }", + options: [2], + errors: expectedErrors([3, 0, 5, "Keyword"]), + }, + { + code: + "if (foo) bar();\n" + + "else if (baz) {\n" + + " foobar();\n" + + " } else if (boop) {\n" + + " qux();\n" + + " }", + output: + "if (foo) bar();\n" + + "else if (baz) {\n" + + " foobar();\n" + + "} else if (boop) {\n" + + " qux();\n" + // (fixed on the next pass) + " }", + options: [2], + errors: expectedErrors([ + [3, 2, 4, "ExpressionStatement"], + [4, 0, 5, "BlockStatement"], + ]), + }, + { + code: + "function foo(aaa,\n" + + " bbb, ccc, ddd) {\n" + + " bar();\n" + + "}", + output: + "function foo(aaa,\n" + + " bbb, ccc, ddd) {\n" + + " bar();\n" + + "}", + options: [2, { FunctionDeclaration: { parameters: 1, body: 2 } }], + errors: expectedErrors([ + [2, 2, 4, "Identifier"], + [3, 4, 6, "ExpressionStatement"], + ]), + }, + { + code: + "function foo(aaa, bbb,\n" + + " ccc, ddd) {\n" + + "bar();\n" + + "}", + output: + "function foo(aaa, bbb,\n" + + " ccc, ddd) {\n" + + " bar();\n" + + "}", + options: [2, { FunctionDeclaration: { parameters: 3, body: 1 } }], + errors: expectedErrors([ + [2, 6, 2, "Identifier"], + [3, 2, 0, "ExpressionStatement"], + ]), + }, + { + code: + "function foo(aaa,\n" + + " bbb,\n" + + " ccc) {\n" + + " bar();\n" + + "}", + output: + "function foo(aaa,\n" + + " bbb,\n" + + " ccc) {\n" + + " bar();\n" + + "}", + options: [4, { FunctionDeclaration: { parameters: 1, body: 3 } }], + errors: expectedErrors([ + [2, 4, 8, "Identifier"], + [3, 4, 2, "Identifier"], + [4, 12, 6, "ExpressionStatement"], + ]), + }, + { + code: + "function foo(aaa,\n" + + " bbb, ccc,\n" + + " ddd, eee, fff) {\n" + + " bar();\n" + + "}", + output: + "function foo(aaa,\n" + + " bbb, ccc,\n" + + " ddd, eee, fff) {\n" + + " bar();\n" + + "}", + options: [ + 2, + { FunctionDeclaration: { parameters: "first", body: 1 } }, + ], + errors: expectedErrors([ + [2, 13, 2, "Identifier"], + [3, 13, 19, "Identifier"], + [4, 2, 3, "ExpressionStatement"], + ]), + }, + { + code: "function foo(aaa, bbb)\n" + "{\n" + "bar();\n" + "}", + output: "function foo(aaa, bbb)\n" + "{\n" + " bar();\n" + "}", + options: [2, { FunctionDeclaration: { body: 3 } }], + errors: expectedErrors([3, 6, 0, "ExpressionStatement"]), + }, + { + code: + "function foo(\n" + + "aaa,\n" + + " bbb) {\n" + + "bar();\n" + + "}", + output: + "function foo(\n" + + "aaa,\n" + + "bbb) {\n" + + " bar();\n" + + "}", + options: [ + 2, + { FunctionDeclaration: { parameters: "first", body: 2 } }, + ], + errors: expectedErrors([ + [3, 0, 4, "Identifier"], + [4, 4, 0, "ExpressionStatement"], + ]), + }, + { + code: + "var foo = function(aaa,\n" + + " bbb,\n" + + " ccc,\n" + + " ddd) {\n" + + " bar();\n" + + "}", + output: + "var foo = function(aaa,\n" + + " bbb,\n" + + " ccc,\n" + + " ddd) {\n" + + "bar();\n" + + "}", + options: [2, { FunctionExpression: { parameters: 2, body: 0 } }], + errors: expectedErrors([ + [2, 4, 2, "Identifier"], + [4, 4, 6, "Identifier"], + [5, 0, 2, "ExpressionStatement"], + ]), + }, + { + code: + "var foo = function(aaa,\n" + + " bbb,\n" + + " ccc) {\n" + + " bar();\n" + + "}", + output: + "var foo = function(aaa,\n" + + " bbb,\n" + + " ccc) {\n" + + " bar();\n" + + "}", + options: [2, { FunctionExpression: { parameters: 1, body: 10 } }], + errors: expectedErrors([ + [2, 2, 3, "Identifier"], + [3, 2, 1, "Identifier"], + [4, 20, 2, "ExpressionStatement"], + ]), + }, + { + code: + "var foo = function(aaa,\n" + + " bbb, ccc, ddd,\n" + + " eee, fff) {\n" + + " bar();\n" + + "}", + output: + "var foo = function(aaa,\n" + + " bbb, ccc, ddd,\n" + + " eee, fff) {\n" + + " bar();\n" + + "}", + options: [ + 4, + { FunctionExpression: { parameters: "first", body: 1 } }, + ], + errors: expectedErrors([ + [2, 19, 2, "Identifier"], + [3, 19, 24, "Identifier"], + [4, 4, 8, "ExpressionStatement"], + ]), + }, + { + code: + "var foo = function(\n" + + "aaa, bbb, ccc,\n" + + " ddd, eee) {\n" + + " bar();\n" + + "}", + output: + "var foo = function(\n" + + "aaa, bbb, ccc,\n" + + "ddd, eee) {\n" + + " bar();\n" + + "}", + options: [ + 2, + { FunctionExpression: { parameters: "first", body: 3 } }, + ], + errors: expectedErrors([ + [3, 0, 4, "Identifier"], + [4, 6, 2, "ExpressionStatement"], + ]), + }, + { + code: "var foo = bar;\n" + "\t\t\tvar baz = qux;", + output: "var foo = bar;\n" + "var baz = qux;", + options: [2], + errors: expectedErrors([ + 2, + "0 spaces", + "3 tabs", + "VariableDeclaration", + ]), + }, + { + code: + "function foo() {\n" + + "\tbar();\n" + + " baz();\n" + + " qux();\n" + + "}", + output: + "function foo() {\n" + + "\tbar();\n" + + "\tbaz();\n" + + "\tqux();\n" + + "}", + options: ["tab"], + errors: expectedErrors("tab", [ + [3, "1 tab", "2 spaces", "ExpressionStatement"], + [4, "1 tab", "14 spaces", "ExpressionStatement"], + ]), + }, + { + code: "function foo() {\n" + " bar();\n" + "\t\t}", + output: "function foo() {\n" + " bar();\n" + "}", + options: [2], + errors: expectedErrors([ + [3, "0 spaces", "2 tabs", "BlockStatement"], + ]), + }, + { + code: + "function foo() {\n" + + " function bar() {\n" + + " baz();\n" + + " }\n" + + "}", + output: + "function foo() {\n" + + " function bar() {\n" + + " baz();\n" + + " }\n" + + "}", + options: [2, { FunctionDeclaration: { body: 1 } }], + errors: expectedErrors([3, 4, 8, "ExpressionStatement"]), + }, + { + code: + "function foo() {\n" + + " function bar(baz,\n" + + " qux) {\n" + + " foobar();\n" + + " }\n" + + "}", + output: + "function foo() {\n" + + " function bar(baz,\n" + + " qux) {\n" + + " foobar();\n" + + " }\n" + + "}", + options: [2, { FunctionDeclaration: { body: 1, parameters: 2 } }], + errors: expectedErrors([3, 6, 4, "Identifier"]), + }, + { + code: + "function foo() {\n" + + " var bar = function(baz,\n" + + " qux) {\n" + + " foobar();\n" + + " };\n" + + "}", + output: + "function foo() {\n" + + " var bar = function(baz,\n" + + " qux) {\n" + + " foobar();\n" + + " };\n" + + "}", + options: [2, { FunctionExpression: { parameters: 3 } }], + errors: expectedErrors([3, 8, 10, "Identifier"]), + }, + { + code: + "{\n" + + " try {\n" + + " }\n" + + "catch (err) {\n" + + " }\n" + + "finally {\n" + + " }\n" + + "}", + output: + "{\n" + + " try {\n" + + " }\n" + + " catch (err) {\n" + + " }\n" + + " finally {\n" + + " }\n" + + "}", + errors: expectedErrors([ + [4, 4, 0, "Keyword"], + [6, 4, 0, "Keyword"], + ]), + }, + { + code: "{\n" + " do {\n" + " }\n" + "while (true)\n" + "}", + output: + "{\n" + " do {\n" + " }\n" + " while (true)\n" + "}", + errors: expectedErrors([4, 4, 0, "Keyword"]), + }, + { + code: + "function foo() {\n" + + " return (\n" + + " 1\n" + + " )\n" + + "}", + output: + "function foo() {\n" + + " return (\n" + + " 1\n" + + " )\n" + + "}", + options: [2], + errors: expectedErrors([[4, "2 spaces", "4", "ReturnStatement"]]), + }, + { + code: + "function foo() {\n" + + " return (\n" + + " 1\n" + + " );\n" + + "}", + output: + "function foo() {\n" + + " return (\n" + + " 1\n" + + " );\n" + + "}", + options: [2], + errors: expectedErrors([[4, "2 spaces", "4", "ReturnStatement"]]), + }, + { + code: + "function test(){\n" + + " switch(length){\n" + + " case 1: return function(a){\n" + + " return fn.call(that, a);\n" + + " };\n" + + " }\n" + + "}", + output: + "function test(){\n" + + " switch(length){\n" + + " case 1: return function(a){\n" + + " return fn.call(that, a);\n" + + " };\n" + + " }\n" + + "}", + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([[4, "6 spaces", "4", "ReturnStatement"]]), + }, + { + code: "function foo() {\n" + " return 1\n" + "}", + output: "function foo() {\n" + " return 1\n" + "}", + options: [2], + errors: expectedErrors([[2, "2 spaces", "3", "ReturnStatement"]]), + }, + { + code: "function foo() {\n" + " return 1;\n" + "}", + output: "function foo() {\n" + " return 1;\n" + "}", + options: [2], + errors: expectedErrors([[2, "2 spaces", "3", "ReturnStatement"]]), + }, + { + code: "foo(\n" + "bar,\n" + " baz,\n" + " qux);", + output: "foo(\n" + " bar,\n" + " baz,\n" + " qux);", + options: [2, { CallExpression: { arguments: 1 } }], + errors: expectedErrors([ + [2, 2, 0, "Identifier"], + [4, 2, 4, "Identifier"], + ]), + }, + { + code: "foo(\n" + "\tbar,\n" + "\tbaz);", + output: "foo(\n" + " bar,\n" + " baz);", + options: [2, { CallExpression: { arguments: 2 } }], + errors: expectedErrors([ + [2, "4 spaces", "1 tab", "Identifier"], + [3, "4 spaces", "1 tab", "Identifier"], + ]), + }, + { + code: "foo(bar,\n" + "\t\tbaz,\n" + "\t\tqux);", + output: "foo(bar,\n" + "\tbaz,\n" + "\tqux);", + options: ["tab", { CallExpression: { arguments: 1 } }], + errors: expectedErrors("tab", [ + [2, 1, 2, "Identifier"], + [3, 1, 2, "Identifier"], + ]), + }, + { + code: "foo(bar, baz,\n" + " qux);", + output: "foo(bar, baz,\n" + " qux);", + options: [2, { CallExpression: { arguments: "first" } }], + errors: expectedErrors([2, 4, 9, "Identifier"]), + }, + { + code: "foo(\n" + " bar,\n" + " baz);", + output: "foo(\n" + " bar,\n" + " baz);", + options: [2, { CallExpression: { arguments: "first" } }], + errors: expectedErrors([3, 10, 4, "Identifier"]), + }, + { + code: + "foo(bar,\n" + + " 1 + 2,\n" + + " !baz,\n" + + " new Car('!')\n" + + ");", + output: + "foo(bar,\n" + + " 1 + 2,\n" + + " !baz,\n" + + " new Car('!')\n" + + ");", + options: [2, { CallExpression: { arguments: 3 } }], + errors: expectedErrors([ + [2, 6, 2, "BinaryExpression"], + [3, 6, 14, "UnaryExpression"], + [4, 6, 8, "NewExpression"], + ]), + }, - // Indentation with multiple else statements: https://github.com/eslint/eslint/issues/6956 + // https://github.com/eslint/eslint/issues/7573 + { + code: "return (\n" + " foo\n" + " );", + output: "return (\n" + " foo\n" + ");", + languageOptions: { + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + errors: expectedErrors([3, 0, 4, "ReturnStatement"]), + }, + { + code: "return (\n" + " foo\n" + " )", + output: "return (\n" + " foo\n" + ")", + languageOptions: { + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + errors: expectedErrors([3, 0, 4, "ReturnStatement"]), + }, - code: - "if (foo) bar();\n" + - "else if (baz) foobar();\n" + - " else if (qux) qux();", - output: - "if (foo) bar();\n" + - "else if (baz) foobar();\n" + - "else if (qux) qux();", - options: [2], - errors: expectedErrors([3, 0, 2, "Keyword"]) - }, - { - code: - "if (foo) bar();\n" + - "else if (baz) foobar();\n" + - " else qux();", - output: - "if (foo) bar();\n" + - "else if (baz) foobar();\n" + - "else qux();", - options: [2], - errors: expectedErrors([3, 0, 2, "Keyword"]) - }, - { - code: - "foo();\n" + - " if (baz) foobar();\n" + - " else qux();", - output: - "foo();\n" + - "if (baz) foobar();\n" + - "else qux();", - options: [2], - errors: expectedErrors([[2, 0, 2, "IfStatement"], [3, 0, 2, "Keyword"]]) - }, - { - code: - "if (foo) bar();\n" + - "else if (baz) foobar();\n" + - " else if (bip) {\n" + - " qux();\n" + - " }", - output: - "if (foo) bar();\n" + - "else if (baz) foobar();\n" + - "else if (bip) {\n" + - " qux();\n" + // (fixed on the next pass) - " }", - options: [2], - errors: expectedErrors([3, 0, 5, "Keyword"]) - }, - { - code: - "if (foo) bar();\n" + - "else if (baz) {\n" + - " foobar();\n" + - " } else if (boop) {\n" + - " qux();\n" + - " }", - output: - "if (foo) bar();\n" + - "else if (baz) {\n" + - " foobar();\n" + - "} else if (boop) {\n" + - " qux();\n" + // (fixed on the next pass) - " }", - options: [2], - errors: expectedErrors([[3, 2, 4, "ExpressionStatement"], [4, 0, 5, "BlockStatement"]]) - }, - { - code: - "function foo(aaa,\n" + - " bbb, ccc, ddd) {\n" + - " bar();\n" + - "}", - output: - "function foo(aaa,\n" + - " bbb, ccc, ddd) {\n" + - " bar();\n" + - "}", - options: [2, { FunctionDeclaration: { parameters: 1, body: 2 } }], - errors: expectedErrors([[2, 2, 4, "Identifier"], [3, 4, 6, "ExpressionStatement"]]) - }, - { - code: - "function foo(aaa, bbb,\n" + - " ccc, ddd) {\n" + - "bar();\n" + - "}", - output: - "function foo(aaa, bbb,\n" + - " ccc, ddd) {\n" + - " bar();\n" + - "}", - options: [2, { FunctionDeclaration: { parameters: 3, body: 1 } }], - errors: expectedErrors([[2, 6, 2, "Identifier"], [3, 2, 0, "ExpressionStatement"]]) - }, - { - code: - "function foo(aaa,\n" + - " bbb,\n" + - " ccc) {\n" + - " bar();\n" + - "}", - output: - "function foo(aaa,\n" + - " bbb,\n" + - " ccc) {\n" + - " bar();\n" + - "}", - options: [4, { FunctionDeclaration: { parameters: 1, body: 3 } }], - errors: expectedErrors([[2, 4, 8, "Identifier"], [3, 4, 2, "Identifier"], [4, 12, 6, "ExpressionStatement"]]) - }, - { - code: - "function foo(aaa,\n" + - " bbb, ccc,\n" + - " ddd, eee, fff) {\n" + - " bar();\n" + - "}", - output: - "function foo(aaa,\n" + - " bbb, ccc,\n" + - " ddd, eee, fff) {\n" + - " bar();\n" + - "}", - options: [2, { FunctionDeclaration: { parameters: "first", body: 1 } }], - errors: expectedErrors([[2, 13, 2, "Identifier"], [3, 13, 19, "Identifier"], [4, 2, 3, "ExpressionStatement"]]) - }, - { - code: - "function foo(aaa, bbb)\n" + - "{\n" + - "bar();\n" + - "}", - output: - "function foo(aaa, bbb)\n" + - "{\n" + - " bar();\n" + - "}", - options: [2, { FunctionDeclaration: { body: 3 } }], - errors: expectedErrors([3, 6, 0, "ExpressionStatement"]) - }, - { - code: - "function foo(\n" + - "aaa,\n" + - " bbb) {\n" + - "bar();\n" + - "}", - output: - "function foo(\n" + - "aaa,\n" + - "bbb) {\n" + - " bar();\n" + - "}", - options: [2, { FunctionDeclaration: { parameters: "first", body: 2 } }], - errors: expectedErrors([[3, 0, 4, "Identifier"], [4, 4, 0, "ExpressionStatement"]]) - }, - { - code: - "var foo = function(aaa,\n" + - " bbb,\n" + - " ccc,\n" + - " ddd) {\n" + - " bar();\n" + - "}", - output: - "var foo = function(aaa,\n" + - " bbb,\n" + - " ccc,\n" + - " ddd) {\n" + - "bar();\n" + - "}", - options: [2, { FunctionExpression: { parameters: 2, body: 0 } }], - errors: expectedErrors([[2, 4, 2, "Identifier"], [4, 4, 6, "Identifier"], [5, 0, 2, "ExpressionStatement"]]) - }, - { - code: - "var foo = function(aaa,\n" + - " bbb,\n" + - " ccc) {\n" + - " bar();\n" + - "}", - output: - "var foo = function(aaa,\n" + - " bbb,\n" + - " ccc) {\n" + - " bar();\n" + - "}", - options: [2, { FunctionExpression: { parameters: 1, body: 10 } }], - errors: expectedErrors([[2, 2, 3, "Identifier"], [3, 2, 1, "Identifier"], [4, 20, 2, "ExpressionStatement"]]) - }, - { - code: - "var foo = function(aaa,\n" + - " bbb, ccc, ddd,\n" + - " eee, fff) {\n" + - " bar();\n" + - "}", - output: - "var foo = function(aaa,\n" + - " bbb, ccc, ddd,\n" + - " eee, fff) {\n" + - " bar();\n" + - "}", - options: [4, { FunctionExpression: { parameters: "first", body: 1 } }], - errors: expectedErrors([[2, 19, 2, "Identifier"], [3, 19, 24, "Identifier"], [4, 4, 8, "ExpressionStatement"]]) - }, - { - code: - "var foo = function(\n" + - "aaa, bbb, ccc,\n" + - " ddd, eee) {\n" + - " bar();\n" + - "}", - output: - "var foo = function(\n" + - "aaa, bbb, ccc,\n" + - "ddd, eee) {\n" + - " bar();\n" + - "}", - options: [2, { FunctionExpression: { parameters: "first", body: 3 } }], - errors: expectedErrors([[3, 0, 4, "Identifier"], [4, 6, 2, "ExpressionStatement"]]) - }, - { - code: - "var foo = bar;\n" + - "\t\t\tvar baz = qux;", - output: - "var foo = bar;\n" + - "var baz = qux;", - options: [2], - errors: expectedErrors([2, "0 spaces", "3 tabs", "VariableDeclaration"]) - }, - { - code: - "function foo() {\n" + - "\tbar();\n" + - " baz();\n" + - " qux();\n" + - "}", - output: - "function foo() {\n" + - "\tbar();\n" + - "\tbaz();\n" + - "\tqux();\n" + - "}", - options: ["tab"], - errors: expectedErrors("tab", [[3, "1 tab", "2 spaces", "ExpressionStatement"], [4, "1 tab", "14 spaces", "ExpressionStatement"]]) - }, - { - code: - "function foo() {\n" + - " bar();\n" + - "\t\t}", - output: - "function foo() {\n" + - " bar();\n" + - "}", - options: [2], - errors: expectedErrors([[3, "0 spaces", "2 tabs", "BlockStatement"]]) - }, - { - code: - "function foo() {\n" + - " function bar() {\n" + - " baz();\n" + - " }\n" + - "}", - output: - "function foo() {\n" + - " function bar() {\n" + - " baz();\n" + - " }\n" + - "}", - options: [2, { FunctionDeclaration: { body: 1 } }], - errors: expectedErrors([3, 4, 8, "ExpressionStatement"]) - }, - { - code: - "function foo() {\n" + - " function bar(baz,\n" + - " qux) {\n" + - " foobar();\n" + - " }\n" + - "}", - output: - "function foo() {\n" + - " function bar(baz,\n" + - " qux) {\n" + - " foobar();\n" + - " }\n" + - "}", - options: [2, { FunctionDeclaration: { body: 1, parameters: 2 } }], - errors: expectedErrors([3, 6, 4, "Identifier"]) - }, - { - code: - "function foo() {\n" + - " var bar = function(baz,\n" + - " qux) {\n" + - " foobar();\n" + - " };\n" + - "}", - output: - "function foo() {\n" + - " var bar = function(baz,\n" + - " qux) {\n" + - " foobar();\n" + - " };\n" + - "}", - options: [2, { FunctionExpression: { parameters: 3 } }], - errors: expectedErrors([3, 8, 10, "Identifier"]) - }, - { - code: - "{\n" + - " try {\n" + - " }\n" + - "catch (err) {\n" + - " }\n" + - "finally {\n" + - " }\n" + - "}", - output: - "{\n" + - " try {\n" + - " }\n" + - " catch (err) {\n" + - " }\n" + - " finally {\n" + - " }\n" + - "}", - errors: expectedErrors([ - [4, 4, 0, "Keyword"], - [6, 4, 0, "Keyword"] - ]) - }, - { - code: - "{\n" + - " do {\n" + - " }\n" + - "while (true)\n" + - "}", - output: - "{\n" + - " do {\n" + - " }\n" + - " while (true)\n" + - "}", - errors: expectedErrors([4, 4, 0, "Keyword"]) - }, - { - code: - "function foo() {\n" + - " return (\n" + - " 1\n" + - " )\n" + - "}", - output: - "function foo() {\n" + - " return (\n" + - " 1\n" + - " )\n" + - "}", - options: [2], - errors: expectedErrors([[4, "2 spaces", "4", "ReturnStatement"]]) - }, - { - code: - "function foo() {\n" + - " return (\n" + - " 1\n" + - " );\n" + - "}", - output: - "function foo() {\n" + - " return (\n" + - " 1\n" + - " );\n" + - "}", - options: [2], - errors: expectedErrors([[4, "2 spaces", "4", "ReturnStatement"]]) - }, - { - code: - "function test(){\n" + - " switch(length){\n" + - " case 1: return function(a){\n" + - " return fn.call(that, a);\n" + - " };\n" + - " }\n" + - "}", - output: - "function test(){\n" + - " switch(length){\n" + - " case 1: return function(a){\n" + - " return fn.call(that, a);\n" + - " };\n" + - " }\n" + - "}", - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - errors: expectedErrors([[4, "6 spaces", "4", "ReturnStatement"]]) - }, - { - code: - "function foo() {\n" + - " return 1\n" + - "}", - output: - "function foo() {\n" + - " return 1\n" + - "}", - options: [2], - errors: expectedErrors([[2, "2 spaces", "3", "ReturnStatement"]]) - }, - { - code: - "function foo() {\n" + - " return 1;\n" + - "}", - output: - "function foo() {\n" + - " return 1;\n" + - "}", - options: [2], - errors: expectedErrors([[2, "2 spaces", "3", "ReturnStatement"]]) - }, - { - code: - "foo(\n" + - "bar,\n" + - " baz,\n" + - " qux);", - output: - "foo(\n" + - " bar,\n" + - " baz,\n" + - " qux);", - options: [2, { CallExpression: { arguments: 1 } }], - errors: expectedErrors([[2, 2, 0, "Identifier"], [4, 2, 4, "Identifier"]]) - }, - { - code: - "foo(\n" + - "\tbar,\n" + - "\tbaz);", - output: - "foo(\n" + - " bar,\n" + - " baz);", - options: [2, { CallExpression: { arguments: 2 } }], - errors: expectedErrors([[2, "4 spaces", "1 tab", "Identifier"], [3, "4 spaces", "1 tab", "Identifier"]]) - }, - { - code: - "foo(bar,\n" + - "\t\tbaz,\n" + - "\t\tqux);", - output: - "foo(bar,\n" + - "\tbaz,\n" + - "\tqux);", - options: ["tab", { CallExpression: { arguments: 1 } }], - errors: expectedErrors("tab", [[2, 1, 2, "Identifier"], [3, 1, 2, "Identifier"]]) - }, - { - code: - "foo(bar, baz,\n" + - " qux);", - output: - "foo(bar, baz,\n" + - " qux);", - options: [2, { CallExpression: { arguments: "first" } }], - errors: expectedErrors([2, 4, 9, "Identifier"]) - }, - { - code: - "foo(\n" + - " bar,\n" + - " baz);", - output: - "foo(\n" + - " bar,\n" + - " baz);", - options: [2, { CallExpression: { arguments: "first" } }], - errors: expectedErrors([3, 10, 4, "Identifier"]) - }, - { - code: - "foo(bar,\n" + - " 1 + 2,\n" + - " !baz,\n" + - " new Car('!')\n" + - ");", - output: - "foo(bar,\n" + - " 1 + 2,\n" + - " !baz,\n" + - " new Car('!')\n" + - ");", - options: [2, { CallExpression: { arguments: 3 } }], - errors: expectedErrors([[2, 6, 2, "BinaryExpression"], [3, 6, 14, "UnaryExpression"], [4, 6, 8, "NewExpression"]]) - }, - - // https://github.com/eslint/eslint/issues/7573 - { - code: - "return (\n" + - " foo\n" + - " );", - output: - "return (\n" + - " foo\n" + - ");", - languageOptions: { parserOptions: { ecmaFeatures: { globalReturn: true } } }, - errors: expectedErrors([3, 0, 4, "ReturnStatement"]) - }, - { - code: - "return (\n" + - " foo\n" + - " )", - output: - "return (\n" + - " foo\n" + - ")", - languageOptions: { parserOptions: { ecmaFeatures: { globalReturn: true } } }, - errors: expectedErrors([3, 0, 4, "ReturnStatement"]) - }, - - // https://github.com/eslint/eslint/issues/7604 - { - code: - "if (foo) {\n" + - " /* comment */bar();\n" + - "}", - output: - "if (foo) {\n" + - " /* comment */bar();\n" + - "}", - errors: expectedErrors([2, 4, 8, "ExpressionStatement"]) - }, - { - code: - "foo('bar',\n" + - " /** comment */{\n" + - " ok: true" + - " });", - output: - "foo('bar',\n" + - " /** comment */{\n" + - " ok: true" + - " });", - errors: expectedErrors([2, 4, 8, "ObjectExpression"]) - }, - { - code: - "var foo = [\n" + - " bar,\n" + - " baz\n" + - " ]", - output: - "var foo = [\n" + - " bar,\n" + - " baz\n" + - "]", - errors: expectedErrors([[2, 4, 11, "Identifier"], [3, 4, 2, "Identifier"], [4, 0, 10, "ArrayExpression"]]) - }, - { - code: - "var foo = [bar,\n" + - "baz,\n" + - " qux\n" + - "]", - output: - "var foo = [bar,\n" + - " baz,\n" + - " qux\n" + - "]", - errors: expectedErrors([2, 4, 0, "Identifier"]) - }, - { - code: - "var foo = [bar,\n" + - " baz,\n" + - " qux\n" + - "]", - output: - "var foo = [bar,\n" + - "baz,\n" + - "qux\n" + - "]", - options: [2, { ArrayExpression: 0 }], - errors: expectedErrors([[2, 0, 2, "Identifier"], [3, 0, 2, "Identifier"]]) - }, - { - code: - "var foo = [bar,\n" + - " baz,\n" + - " qux\n" + - "]", - output: - "var foo = [bar,\n" + - " baz,\n" + - " qux\n" + - "]", - options: [2, { ArrayExpression: 8 }], - errors: expectedErrors([[2, 16, 2, "Identifier"], [3, 16, 2, "Identifier"]]) - }, - { - code: - "var foo = [bar,\n" + - " baz,\n" + - " qux\n" + - "]", - output: - "var foo = [bar,\n" + - " baz,\n" + - " qux\n" + - "]", - options: [2, { ArrayExpression: "first" }], - errors: expectedErrors([[2, 11, 4, "Identifier"], [3, 11, 4, "Identifier"]]) - }, - { - code: - "var foo = [bar,\n" + - " baz, qux\n" + - "]", - output: - "var foo = [bar,\n" + - " baz, qux\n" + - "]", - options: [2, { ArrayExpression: "first" }], - errors: expectedErrors([2, 11, 4, "Identifier"]) - }, - { - code: - "var foo = [\n" + - " { bar: 1,\n" + - " baz: 2 },\n" + - " { bar: 3,\n" + - " qux: 4 }\n" + - "]", - output: - "var foo = [\n" + - " { bar: 1,\n" + - " baz: 2 },\n" + - " { bar: 3,\n" + - " qux: 4 }\n" + - "]", - options: [4, { ArrayExpression: 2, ObjectExpression: "first" }], - errors: expectedErrors([[3, 10, 12, "Property"], [5, 10, 12, "Property"]]) - }, - { - code: - "var foo = {\n" + - " bar: 1,\n" + - " baz: 2\n" + - "};", - output: - "var foo = {\n" + - "bar: 1,\n" + - "baz: 2\n" + - "};", - options: [2, { ObjectExpression: 0 }], - errors: expectedErrors([[2, 0, 2, "Property"], [3, 0, 2, "Property"]]) - }, - { - code: - "var quux = { foo: 1, bar: 2,\n" + - "baz: 3 }", - output: - "var quux = { foo: 1, bar: 2,\n" + - " baz: 3 }", - options: [2, { ObjectExpression: "first" }], - errors: expectedErrors([2, 13, 0, "Property"]) - }, - { - code: - "function foo() {\n" + - " [\n" + - " foo\n" + - " ]\n" + - "}", - output: - "function foo() {\n" + - " [\n" + - " foo\n" + - " ]\n" + - "}", - options: [2, { ArrayExpression: 4 }], - errors: expectedErrors([2, 2, 4, "ExpressionStatement"]) - }, - { - code: - "echo = spawn('cmd.exe',\n" + - " ['foo', 'bar',\n" + - " 'baz']);", - output: - "echo = spawn('cmd.exe',\n" + - " ['foo', 'bar',\n" + - " 'baz']);", - options: [2, { ArrayExpression: "first", CallExpression: { arguments: "first" } }], - errors: expectedErrors([2, 13, 12, "ArrayExpression"]) - } - ] + // https://github.com/eslint/eslint/issues/7604 + { + code: "if (foo) {\n" + " /* comment */bar();\n" + "}", + output: "if (foo) {\n" + " /* comment */bar();\n" + "}", + errors: expectedErrors([2, 4, 8, "ExpressionStatement"]), + }, + { + code: + "foo('bar',\n" + + " /** comment */{\n" + + " ok: true" + + " });", + output: + "foo('bar',\n" + + " /** comment */{\n" + + " ok: true" + + " });", + errors: expectedErrors([2, 4, 8, "ObjectExpression"]), + }, + { + code: + "var foo = [\n" + + " bar,\n" + + " baz\n" + + " ]", + output: "var foo = [\n" + " bar,\n" + " baz\n" + "]", + errors: expectedErrors([ + [2, 4, 11, "Identifier"], + [3, 4, 2, "Identifier"], + [4, 0, 10, "ArrayExpression"], + ]), + }, + { + code: "var foo = [bar,\n" + "baz,\n" + " qux\n" + "]", + output: "var foo = [bar,\n" + " baz,\n" + " qux\n" + "]", + errors: expectedErrors([2, 4, 0, "Identifier"]), + }, + { + code: "var foo = [bar,\n" + " baz,\n" + " qux\n" + "]", + output: "var foo = [bar,\n" + "baz,\n" + "qux\n" + "]", + options: [2, { ArrayExpression: 0 }], + errors: expectedErrors([ + [2, 0, 2, "Identifier"], + [3, 0, 2, "Identifier"], + ]), + }, + { + code: "var foo = [bar,\n" + " baz,\n" + " qux\n" + "]", + output: + "var foo = [bar,\n" + + " baz,\n" + + " qux\n" + + "]", + options: [2, { ArrayExpression: 8 }], + errors: expectedErrors([ + [2, 16, 2, "Identifier"], + [3, 16, 2, "Identifier"], + ]), + }, + { + code: "var foo = [bar,\n" + " baz,\n" + " qux\n" + "]", + output: + "var foo = [bar,\n" + + " baz,\n" + + " qux\n" + + "]", + options: [2, { ArrayExpression: "first" }], + errors: expectedErrors([ + [2, 11, 4, "Identifier"], + [3, 11, 4, "Identifier"], + ]), + }, + { + code: "var foo = [bar,\n" + " baz, qux\n" + "]", + output: "var foo = [bar,\n" + " baz, qux\n" + "]", + options: [2, { ArrayExpression: "first" }], + errors: expectedErrors([2, 11, 4, "Identifier"]), + }, + { + code: + "var foo = [\n" + + " { bar: 1,\n" + + " baz: 2 },\n" + + " { bar: 3,\n" + + " qux: 4 }\n" + + "]", + output: + "var foo = [\n" + + " { bar: 1,\n" + + " baz: 2 },\n" + + " { bar: 3,\n" + + " qux: 4 }\n" + + "]", + options: [4, { ArrayExpression: 2, ObjectExpression: "first" }], + errors: expectedErrors([ + [3, 10, 12, "Property"], + [5, 10, 12, "Property"], + ]), + }, + { + code: "var foo = {\n" + " bar: 1,\n" + " baz: 2\n" + "};", + output: "var foo = {\n" + "bar: 1,\n" + "baz: 2\n" + "};", + options: [2, { ObjectExpression: 0 }], + errors: expectedErrors([ + [2, 0, 2, "Property"], + [3, 0, 2, "Property"], + ]), + }, + { + code: "var quux = { foo: 1, bar: 2,\n" + "baz: 3 }", + output: "var quux = { foo: 1, bar: 2,\n" + " baz: 3 }", + options: [2, { ObjectExpression: "first" }], + errors: expectedErrors([2, 13, 0, "Property"]), + }, + { + code: + "function foo() {\n" + + " [\n" + + " foo\n" + + " ]\n" + + "}", + output: + "function foo() {\n" + + " [\n" + + " foo\n" + + " ]\n" + + "}", + options: [2, { ArrayExpression: 4 }], + errors: expectedErrors([2, 2, 4, "ExpressionStatement"]), + }, + { + code: + "echo = spawn('cmd.exe',\n" + + " ['foo', 'bar',\n" + + " 'baz']);", + output: + "echo = spawn('cmd.exe',\n" + + " ['foo', 'bar',\n" + + " 'baz']);", + options: [ + 2, + { + ArrayExpression: "first", + CallExpression: { arguments: "first" }, + }, + ], + errors: expectedErrors([2, 13, 12, "ArrayExpression"]), + }, + ], }); diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js index 6f5ca1fae383..ca83c8a1fae2 100644 --- a/tests/lib/rules/indent.js +++ b/tests/lib/rules/indent.js @@ -10,20 +10,31 @@ //------------------------------------------------------------------------------ const rule = require("../../../lib/rules/indent"), - RuleTester = require("../../../lib/rule-tester/flat-rule-tester"); -const fs = require("fs"); -const path = require("path"); + RuleTester = require("../../../lib/rule-tester/rule-tester"); +const fs = require("node:fs"); +const path = require("node:path"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ -const fixture = fs.readFileSync(path.join(__dirname, "../../fixtures/rules/indent/indent-invalid-fixture-1.js"), "utf8"); -const fixedFixture = fs.readFileSync(path.join(__dirname, "../../fixtures/rules/indent/indent-valid-fixture-1.js"), "utf8"); +const fixture = fs.readFileSync( + path.join( + __dirname, + "../../fixtures/rules/indent/indent-invalid-fixture-1.js", + ), + "utf8", +); +const fixedFixture = fs.readFileSync( + path.join( + __dirname, + "../../fixtures/rules/indent/indent-valid-fixture-1.js", + ), + "utf8", +); const parser = require("../../fixtures/fixture-parser"); const { unIndent } = require("../../_utils"); - /** * Create error message object for failure cases with a single 'found' indentation type * @param {string} providedIndentType indent type of string or tab @@ -32,28 +43,33 @@ const { unIndent } = require("../../_utils"); * @private */ function expectedErrors(providedIndentType, providedErrors) { - let indentType; - let errors; - - if (Array.isArray(providedIndentType)) { - errors = Array.isArray(providedIndentType[0]) ? providedIndentType : [providedIndentType]; - indentType = "space"; - } else { - errors = Array.isArray(providedErrors[0]) ? providedErrors : [providedErrors]; - indentType = providedIndentType; - } - - return errors.map(err => ({ - messageId: "wrongIndentation", - data: { - expected: typeof err[1] === "string" && typeof err[2] === "string" - ? err[1] - : `${err[1]} ${indentType}${err[1] === 1 ? "" : "s"}`, - actual: err[2] - }, - type: err[3], - line: err[0] - })); + let indentType; + let errors; + + if (Array.isArray(providedIndentType)) { + errors = Array.isArray(providedIndentType[0]) + ? providedIndentType + : [providedIndentType]; + indentType = "space"; + } else { + errors = Array.isArray(providedErrors[0]) + ? providedErrors + : [providedErrors]; + indentType = providedIndentType; + } + + return errors.map(err => ({ + messageId: "wrongIndentation", + data: { + expected: + typeof err[1] === "string" && typeof err[2] === "string" + ? err[1] + : `${err[1]} ${indentType}${err[1] === 1 ? "" : "s"}`, + actual: err[2], + }, + type: err[3], + line: err[0], + })); } //------------------------------------------------------------------------------ @@ -61,38 +77,38 @@ function expectedErrors(providedIndentType, providedErrors) { //------------------------------------------------------------------------------ const ruleTester = new RuleTester({ - languageOptions: { - ecmaVersion: 8, - sourceType: "script", - parserOptions: { - ecmaFeatures: { jsx: true } - } - } + languageOptions: { + ecmaVersion: 8, + sourceType: "script", + parserOptions: { + ecmaFeatures: { jsx: true }, + }, + }, }); ruleTester.run("indent", rule, { - valid: [ - { - code: unIndent` + valid: [ + { + code: unIndent` bridge.callHandler( 'getAppVersion', 'test23', function(responseData) { window.ah.mobileAppVersion = responseData; } ); `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` bridge.callHandler( 'getAppVersion', 'test23', function(responseData) { window.ah.mobileAppVersion = responseData; }); `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` bridge.callHandler( 'getAppVersion', null, @@ -101,10 +117,10 @@ ruleTester.run("indent", rule, { } ); `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` bridge.callHandler( 'getAppVersion', null, @@ -112,10 +128,10 @@ ruleTester.run("indent", rule, { window.ah.mobileAppVersion = responseData; }); `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` function doStuff(keys) { _.forEach( keys, @@ -125,20 +141,20 @@ ruleTester.run("indent", rule, { ); } `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` example( function () { console.log('example'); } ); `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` let foo = somethingList .filter(x => { return x; @@ -147,30 +163,30 @@ ruleTester.run("indent", rule, { return 100 * x; }); `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` var x = 0 && { a: 1, b: 2 }; `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` var x = 0 && \t{ \t\ta: 1, \t\tb: 2 \t}; `, - options: ["tab"] - }, - { - code: unIndent` + options: ["tab"], + }, + { + code: unIndent` var x = 0 && { a: 1, @@ -181,55 +197,55 @@ ruleTester.run("indent", rule, { d: 4 }; `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` var x = [ 'a', 'b', 'c' ]; `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` var x = ['a', 'b', 'c', ]; `, - options: [4] - }, - { - code: "var x = 0 && 1;", - options: [4] - }, - { - code: "var x = 0 && { a: 1, b: 2 };", - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: "var x = 0 && 1;", + options: [4], + }, + { + code: "var x = 0 && { a: 1, b: 2 };", + options: [4], + }, + { + code: unIndent` var x = 0 && ( 1 ); `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` require('http').request({hostname: 'localhost', port: 80}, function(res) { res.end(); }); `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` function test() { return client.signUp(email, PASSWORD, { preVerified: true }) .then(function (result) { @@ -243,19 +259,19 @@ ruleTester.run("indent", rule, { }); } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` it('should... some lengthy test description that is forced to be' + 'wrapped into two lines since the line length limit is set', () => { expect(true).toBe(true); }); `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` function test() { return client.signUp(email, PASSWORD, { preVerified: true }) .then(function (result) { @@ -268,21 +284,20 @@ ruleTester.run("indent", rule, { }) } `, - options: [4] - }, - { - - // https://github.com/eslint/eslint/issues/11802 - code: unIndent` + options: [4], + }, + { + // https://github.com/eslint/eslint/issues/11802 + code: unIndent` import foo from "foo" ;(() => {})() `, - options: [4], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: unIndent` function test() { return client.signUp(email, PASSWORD, { preVerified: true }) .then(function (result) { @@ -295,15 +310,15 @@ ruleTester.run("indent", rule, { }); } `, - options: [4, { MemberExpression: 0 }] - }, + options: [4, { MemberExpression: 0 }], + }, - { - code: "// hi", - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + { + code: "// hi", + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` var Command = function() { var fileList = [], files = [] @@ -311,63 +326,63 @@ ruleTester.run("indent", rule, { files.concat(fileList) }; `, - options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }] - }, - { - code: " ", - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + }, + { + code: " ", + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` if(data) { console.log('hi'); b = true;}; `, - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` foo = () => { console.log('hi'); return true;}; `, - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` function test(data) { console.log('hi'); return true;}; `, - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` var test = function(data) { console.log('hi'); }; `, - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` arr.forEach(function(data) { otherdata.forEach(function(zero) { console.log('hi'); }) }); `, - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` a = [ ,3 ] `, - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` [ ['gzip', 'gunzip'], ['gzip', 'unzip'], @@ -377,10 +392,10 @@ ruleTester.run("indent", rule, { console.log(method); }); `, - options: [2, { SwitchCase: 1, VariableDeclarator: 2 }] - }, - { - code: unIndent` + options: [2, { SwitchCase: 1, VariableDeclarator: 2 }], + }, + { + code: unIndent` test(123, { bye: { hi: [1, @@ -391,10 +406,10 @@ ruleTester.run("indent", rule, { } }); `, - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` var xyz = 2, lmn = [ { @@ -402,10 +417,10 @@ ruleTester.run("indent", rule, { } ]; `, - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` lmnn = [{ a: 1 }, @@ -415,9 +430,9 @@ ruleTester.run("indent", rule, { x: 2 }]; `, - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - unIndent` + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + unIndent` [{ foo: 1 }, { @@ -426,7 +441,7 @@ ruleTester.run("indent", rule, { foo: 3 }] `, - unIndent` + unIndent` foo([ bar ], [ @@ -435,8 +450,8 @@ ruleTester.run("indent", rule, { qux ]); `, - { - code: unIndent` + { + code: unIndent` abc({ test: [ [ @@ -447,10 +462,10 @@ ruleTester.run("indent", rule, { ] }); `, - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` abc = { test: [ [ @@ -461,10 +476,10 @@ ruleTester.run("indent", rule, { ] }; `, - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` abc( { a: 1, @@ -472,19 +487,19 @@ ruleTester.run("indent", rule, { } ); `, - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` abc({ a: 1, b: 2 }); `, - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` var abc = [ c, @@ -495,10 +510,10 @@ ruleTester.run("indent", rule, { } ]; `, - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` var abc = [ c, xyz, @@ -508,10 +523,10 @@ ruleTester.run("indent", rule, { } ]; `, - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` var abc = 5, c = 2, xyz = @@ -520,9 +535,9 @@ ruleTester.run("indent", rule, { b: 2 }; `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + unIndent` var x = { a: 1, @@ -531,7 +546,7 @@ ruleTester.run("indent", rule, { b: 2 } `, - unIndent` + unIndent` const x = { a: 1, @@ -540,7 +555,7 @@ ruleTester.run("indent", rule, { b: 2 } `, - unIndent` + unIndent` let x = { a: 1, @@ -549,12 +564,12 @@ ruleTester.run("indent", rule, { b: 2 } `, - unIndent` + unIndent` var foo = { a: 1 }, bar = { b: 2 }; `, - unIndent` + unIndent` var foo = { a: 1 }, bar = { b: 2 }, @@ -562,39 +577,39 @@ ruleTester.run("indent", rule, { c: 3 } `, - unIndent` + unIndent` const { foo } = 1, bar = 2 `, - { - code: unIndent` + { + code: unIndent` var foo = 1, bar = 2 `, - options: [2, { VariableDeclarator: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` var foo = 1, bar = 2 `, - options: [2, { VariableDeclarator: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` var foo = 1, bar = 2 `, - options: [2, { VariableDeclarator: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` var foo = 1, @@ -602,112 +617,112 @@ ruleTester.run("indent", rule, { = 2 `, - options: [2, { VariableDeclarator: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` var foo = (1), bar = (2) `, - options: [2, { VariableDeclarator: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` let foo = 'foo', bar = bar; const a = 'a', b = 'b'; `, - options: [2, { VariableDeclarator: "first" }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: "first" }], + }, + { + code: unIndent` let foo = 'foo', bar = bar // <-- no semicolon here const a = 'a', b = 'b' // <-- no semicolon here `, - options: [2, { VariableDeclarator: "first" }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: "first" }], + }, + { + code: unIndent` var foo = 1, bar = 2, baz = 3 ; `, - options: [2, { VariableDeclarator: { var: 2 } }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: { var: 2 } }], + }, + { + code: unIndent` var foo = 1, bar = 2, baz = 3 ; `, - options: [2, { VariableDeclarator: { var: 2 } }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: { var: 2 } }], + }, + { + code: unIndent` var foo = 'foo', bar = bar; `, - options: [2, { VariableDeclarator: { var: "first" } }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: { var: "first" } }], + }, + { + code: unIndent` var foo = 'foo', bar = 'bar' // <-- no semicolon here `, - options: [2, { VariableDeclarator: { var: "first" } }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: { var: "first" } }], + }, + { + code: unIndent` let foo = 1, bar = 2, baz `, - options: [2, { VariableDeclarator: "first" }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: "first" }], + }, + { + code: unIndent` let foo `, - options: [4, { VariableDeclarator: "first" }] - }, - { - code: unIndent` + options: [4, { VariableDeclarator: "first" }], + }, + { + code: unIndent` let foo = 1, bar = 2 `, - options: [2, { VariableDeclarator: "first" }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: "first" }], + }, + { + code: unIndent` var abc = { a: 1, b: 2 }; `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` var a = new abc({ a: 1, b: 2 }), b = 2; `, - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` var a = 2, c = { a: 1, @@ -715,10 +730,10 @@ ruleTester.run("indent", rule, { }, b = 2; `, - options: [2, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` var x = 2, y = { a: 1, @@ -726,29 +741,29 @@ ruleTester.run("indent", rule, { }, b = 2; `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` var e = { a: 1, b: 2 }, b = 2; `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` var a = { a: 1, b: 2 }; `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` function test() { if (true || false){ @@ -756,76 +771,76 @@ ruleTester.run("indent", rule, { } } `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + unIndent` var foo = bar || !( baz ); `, - unIndent` + unIndent` for (var foo = 1; foo < 10; foo++) {} `, - unIndent` + unIndent` for ( var foo = 1; foo < 10; foo++ ) {} `, - { - code: unIndent` + { + code: unIndent` for (var val in obj) if (true) console.log(val); `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` with (a) b(); `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` with (a) b(); c(); `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if(true) if (true) if (true) console.log(val); `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` function hi(){ var a = 1; y++; x++; } `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` for(;length > index; index++)if(NO_HOLES || index in self){ x++; } `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` function test(){ switch(length){ case 1: return function(a){ @@ -834,134 +849,134 @@ ruleTester.run("indent", rule, { } } `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` var geometry = 2, rotate = 2; `, - options: [2, { VariableDeclarator: 0 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 0 }], + }, + { + code: unIndent` var geometry, rotate; `, - options: [4, { VariableDeclarator: 1 }] - }, - { - code: unIndent` + options: [4, { VariableDeclarator: 1 }], + }, + { + code: unIndent` var geometry, \trotate; `, - options: ["tab", { VariableDeclarator: 1 }] - }, - { - code: unIndent` + options: ["tab", { VariableDeclarator: 1 }], + }, + { + code: unIndent` var geometry, rotate; `, - options: [2, { VariableDeclarator: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` var geometry, rotate; `, - options: [2, { VariableDeclarator: 2 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` let geometry, rotate; `, - options: [2, { VariableDeclarator: 2 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` const geometry = 2, rotate = 3; `, - options: [2, { VariableDeclarator: 2 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth, height, rotate; `, - options: [2, { SwitchCase: 1 }] - }, - { - code: "var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth;", - options: [2, { SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { SwitchCase: 1 }], + }, + { + code: "var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth;", + options: [2, { SwitchCase: 1 }], + }, + { + code: unIndent` if (1 < 2){ //hi sd } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` while (1 < 2){ //hi sd } `, - options: [2] - }, - { - code: "while (1 < 2) console.log('hi');", - options: [2] - }, + options: [2], + }, + { + code: "while (1 < 2) console.log('hi');", + options: [2], + }, - { - code: unIndent` + { + code: unIndent` [a, boop, c].forEach((index) => { index; }); `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` [a, b, c].forEach(function(index){ return index; }); `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` [a, b, c].forEach((index) => { index; }); `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` [a, b, c].forEach(function(index){ return index; }); `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` (foo) .bar([ baz ]); `, - options: [4, { MemberExpression: 1 }] - }, - { - code: unIndent` + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` switch (x) { case "foo": a(); @@ -978,10 +993,10 @@ ruleTester.run("indent", rule, { break; } `, - options: [4, { SwitchCase: 1 }] - }, - { - code: unIndent` + options: [4, { SwitchCase: 1 }], + }, + { + code: unIndent` switch (x) { case "foo": a(); @@ -998,9 +1013,9 @@ ruleTester.run("indent", rule, { break; } `, - options: [4, { SwitchCase: 2 }] - }, - unIndent` + options: [4, { SwitchCase: 2 }], + }, + unIndent` switch (a) { case "foo": a(); @@ -1015,7 +1030,7 @@ ruleTester.run("indent", rule, { } } `, - unIndent` + unIndent` switch (a) { case "foo": a(); @@ -1029,7 +1044,7 @@ ruleTester.run("indent", rule, { } } `, - unIndent` + unIndent` switch (a) { case "foo": a(); @@ -1042,7 +1057,7 @@ ruleTester.run("indent", rule, { a = 6; } `, - unIndent` + unIndent` switch (a) { case "foo": a(); @@ -1053,11 +1068,11 @@ ruleTester.run("indent", rule, { a(); break; } `, - unIndent` + unIndent` switch (0) { } `, - unIndent` + unIndent` function foo() { var a = "a"; switch(a) { @@ -1069,8 +1084,8 @@ ruleTester.run("indent", rule, { } foo(); `, - { - code: unIndent` + { + code: unIndent` switch(value){ case "1": case "2": @@ -1090,40 +1105,40 @@ ruleTester.run("indent", rule, { break; } `, - options: [4, { SwitchCase: 1 }] - }, - unIndent` + options: [4, { SwitchCase: 1 }], + }, + unIndent` var obj = {foo: 1, bar: 2}; with (obj) { console.log(foo + bar); } `, - unIndent` + unIndent` if (a) { (1 + 2 + 3); // no error on this line } `, - "switch(value){ default: a(); break; }", - { - code: unIndent` + "switch(value){ default: a(); break; }", + { + code: unIndent` import {addons} from 'react/addons' import React from 'react' `, - options: [2], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: unIndent` + options: [2], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: unIndent` import { foo, bar, baz } from 'qux'; `, - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: unIndent` var foo = 0, bar = 0; baz = 0; export { foo, @@ -1131,30 +1146,30 @@ ruleTester.run("indent", rule, { baz } from 'qux'; `, - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: unIndent` var a = 1, b = 2, c = 3; `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` var a = 1 ,b = 2 ,c = 3; `, - options: [4] - }, - { - code: "while (1 < 2) console.log('hi')", - options: [2] - }, - { - code: unIndent` + options: [4], + }, + { + code: "while (1 < 2) console.log('hi')", + options: [2], + }, + { + code: unIndent` function salutation () { switch (1) { case 0: return console.log('hi') @@ -1162,20 +1177,20 @@ ruleTester.run("indent", rule, { } } `, - options: [2, { SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { SwitchCase: 1 }], + }, + { + code: unIndent` var items = [ { foo: 'bar' } ]; `, - options: [2, { VariableDeclarator: 2 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` const a = 1, b = 2; const items1 = [ @@ -1189,11 +1204,10 @@ ruleTester.run("indent", rule, { } ); `, - options: [2, { VariableDeclarator: 3 }] - - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 3 }], + }, + { + code: unIndent` const geometry = 2, rotate = 3; var a = 1, @@ -1201,10 +1215,10 @@ ruleTester.run("indent", rule, { let light = true, shadow = false; `, - options: [2, { VariableDeclarator: { const: 3, let: 2 } }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: { const: 3, let: 2 } }], + }, + { + code: unIndent` const abc = 5, c = 2, xyz = @@ -1227,10 +1241,13 @@ ruleTester.run("indent", rule, { b: 2 }; `, - options: [2, { VariableDeclarator: { var: 2, const: 3 }, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [ + 2, + { VariableDeclarator: { var: 2, const: 3 }, SwitchCase: 1 }, + ], + }, + { + code: unIndent` module.exports = { 'Unit tests': { @@ -1248,38 +1265,38 @@ ruleTester.run("indent", rule, { } }; `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` foo = bar; `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` foo = ( bar ); `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` var path = require('path') , crypto = require('crypto') ; `, - options: [2] - }, - unIndent` + options: [2], + }, + unIndent` var a = 1 ,b = 2 ; `, - { - code: unIndent` + { + code: unIndent` export function create (some, argument) { return Object.create({ @@ -1288,11 +1305,11 @@ ruleTester.run("indent", rule, { }); }; `, - options: [2, { FunctionDeclaration: { parameters: "first" } }], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: unIndent` + options: [2, { FunctionDeclaration: { parameters: "first" } }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: unIndent` export function create (id, xfilter, rawType, width=defaultWidth, height=defaultHeight, footerHeight=defaultFooterHeight, @@ -1300,11 +1317,11 @@ ruleTester.run("indent", rule, { // ... function body, indented two spaces } `, - options: [2, { FunctionDeclaration: { parameters: "first" } }], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: unIndent` + options: [2, { FunctionDeclaration: { parameters: "first" } }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: unIndent` var obj = { foo: function () { return new p() @@ -1316,19 +1333,19 @@ ruleTester.run("indent", rule, { } }; `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` a.b() .c(function(){ var a; }).d.e; `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` const YO = 'bah', TE = 'mah' @@ -1336,10 +1353,10 @@ ruleTester.run("indent", rule, { a = 5, b = 4 `, - options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + }, + { + code: unIndent` const YO = 'bah', TE = 'mah' @@ -1349,10 +1366,10 @@ ruleTester.run("indent", rule, { if (YO) console.log(TE) `, - options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + }, + { + code: unIndent` var foo = 'foo', bar = 'bar', baz = function() { @@ -1363,10 +1380,10 @@ ruleTester.run("indent", rule, { } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` var obj = { send: function () { return P.resolve({ @@ -1380,10 +1397,10 @@ ruleTester.run("indent", rule, { } }; `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` var obj = { send: function () { return P.resolve({ @@ -1397,16 +1414,16 @@ ruleTester.run("indent", rule, { } }; `, - options: [2, { MemberExpression: 0 }] - }, - unIndent` + options: [2, { MemberExpression: 0 }], + }, + unIndent` const someOtherFunction = argument => { console.log(argument); }, someOtherValue = 'someOtherValue'; `, - { - code: unIndent` + { + code: unIndent` [ 'a', 'b' @@ -1415,10 +1432,10 @@ ruleTester.run("indent", rule, { 'y' ]); `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` var a = 1, B = class { constructor(){} @@ -1426,10 +1443,10 @@ ruleTester.run("indent", rule, { get b(){} }; `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` var a = 1, B = class { @@ -1439,39 +1456,39 @@ ruleTester.run("indent", rule, { }, c = 3; `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` class A{ constructor(){} a(){} get b(){} } `, - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` var A = class { constructor(){} a(){} get b(){} } `, - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` var a = { some: 1 , name: 2 }; `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` a.c = { aa: function() { 'test1'; @@ -1482,10 +1499,10 @@ ruleTester.run("indent", rule, { } }; `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` var a = { actions: @@ -1496,10 +1513,10 @@ ruleTester.run("indent", rule, { ] }; `, - options: [4, { VariableDeclarator: 0, SwitchCase: 1 }] - }, - { - code: unIndent` + options: [4, { VariableDeclarator: 0, SwitchCase: 1 }], + }, + { + code: unIndent` var a = [ { @@ -1507,16 +1524,16 @@ ruleTester.run("indent", rule, { } ]; `, - options: [4, { VariableDeclarator: 0, SwitchCase: 1 }] - }, - unIndent` + options: [4, { VariableDeclarator: 0, SwitchCase: 1 }], + }, + unIndent` [[ ], function( foo ) {} ] `, - unIndent` + unIndent` define([ 'foo' ], function( @@ -1526,8 +1543,8 @@ ruleTester.run("indent", rule, { } ) `, - { - code: unIndent` + { + code: unIndent` const func = function (opts) { return Promise.resolve() .then(() => { @@ -1537,10 +1554,10 @@ ruleTester.run("indent", rule, { }); }; `, - options: [4, { MemberExpression: 0 }] - }, - { - code: unIndent` + options: [4, { MemberExpression: 0 }], + }, + { + code: unIndent` const func = function (opts) { return Promise.resolve() .then(() => { @@ -1550,10 +1567,10 @@ ruleTester.run("indent", rule, { }); }; `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` var haveFun = function () { SillyFunction( { @@ -1565,10 +1582,10 @@ ruleTester.run("indent", rule, { ); }; `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` var haveFun = function () { new SillyFunction( { @@ -1580,10 +1597,10 @@ ruleTester.run("indent", rule, { ); }; `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` let object1 = { doThing() { return _.chain([]) @@ -1596,10 +1613,10 @@ ruleTester.run("indent", rule, { } }; `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` var foo = { bar: 1, baz: { @@ -1608,28 +1625,28 @@ ruleTester.run("indent", rule, { }, bar = 1; `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` class Foo extends Bar { baz() {} } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` class Foo extends Bar { baz() {} } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` class Foo extends ( Bar @@ -1637,138 +1654,138 @@ ruleTester.run("indent", rule, { baz() {} } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` fs.readdirSync(path.join(__dirname, '../rules')).forEach(name => { files[name] = foo; }); `, - options: [2, { outerIIFEBody: 0 }] - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` (function(){ function foo(x) { return x + 1; } })(); `, - options: [2, { outerIIFEBody: 0 }] - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` (function(){ function foo(x) { return x + 1; } })(); `, - options: [4, { outerIIFEBody: 2 }] - }, - { - code: unIndent` + options: [4, { outerIIFEBody: 2 }], + }, + { + code: unIndent` (function(x, y){ function foo(x) { return x + 1; } })(1, 2); `, - options: [2, { outerIIFEBody: 0 }] - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` (function(){ function foo(x) { return x + 1; } }()); `, - options: [2, { outerIIFEBody: 0 }] - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` !function(){ function foo(x) { return x + 1; } }(); `, - options: [2, { outerIIFEBody: 0 }] - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` !function(){ \t\t\tfunction foo(x) { \t\t\t\treturn x + 1; \t\t\t} }(); `, - options: ["tab", { outerIIFEBody: 3 }] - }, - { - code: unIndent` + options: ["tab", { outerIIFEBody: 3 }], + }, + { + code: unIndent` var out = function(){ function fooVar(x) { return x + 1; } }; `, - options: [2, { outerIIFEBody: 0 }] - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` var ns = function(){ function fooVar(x) { return x + 1; } }(); `, - options: [2, { outerIIFEBody: 0 }] - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` ns = function(){ function fooVar(x) { return x + 1; } }(); `, - options: [2, { outerIIFEBody: 0 }] - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` var ns = (function(){ function fooVar(x) { return x + 1; } }(x)); `, - options: [2, { outerIIFEBody: 0 }] - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` var ns = (function(){ function fooVar(x) { return x + 1; } }(x)); `, - options: [4, { outerIIFEBody: 2 }] - }, - { - code: unIndent` + options: [4, { outerIIFEBody: 2 }], + }, + { + code: unIndent` var obj = { foo: function() { return true; } }; `, - options: [2, { outerIIFEBody: 0 }] - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` while ( function() { return true; @@ -1777,136 +1794,136 @@ ruleTester.run("indent", rule, { x = x + 1; }; `, - options: [2, { outerIIFEBody: 20 }] - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 20 }], + }, + { + code: unIndent` (() => { function foo(x) { return x + 1; } })(); `, - options: [2, { outerIIFEBody: 0 }] - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` function foo() { } `, - options: ["tab", { outerIIFEBody: 0 }] - }, - { - code: unIndent` + options: ["tab", { outerIIFEBody: 0 }], + }, + { + code: unIndent` ;(() => { function foo(x) { return x + 1; } })(); `, - options: [2, { outerIIFEBody: 0 }] - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` if(data) { console.log('hi'); } `, - options: [2, { outerIIFEBody: 0 }] - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` (function(x) { return x + 1; })(); `, - options: [4, { outerIIFEBody: "off" }] - }, - { - code: unIndent` + options: [4, { outerIIFEBody: "off" }], + }, + { + code: unIndent` (function(x) { return x + 1; })(); `, - options: [4, { outerIIFEBody: "off" }] - }, - { - code: unIndent` + options: [4, { outerIIFEBody: "off" }], + }, + { + code: unIndent` ;(() => { function x(y) { return y + 1; } })(); `, - options: [4, { outerIIFEBody: "off" }] - }, - { - code: unIndent` + options: [4, { outerIIFEBody: "off" }], + }, + { + code: unIndent` ;(() => { function x(y) { return y + 1; } })(); `, - options: [4, { outerIIFEBody: "off" }] - }, - { - code: unIndent` + options: [4, { outerIIFEBody: "off" }], + }, + { + code: unIndent` function foo() { } `, - options: [4, { outerIIFEBody: "off" }] - }, - { - code: "Buffer.length", - options: [4, { MemberExpression: 1 }] - }, - { - code: unIndent` + options: [4, { outerIIFEBody: "off" }], + }, + { + code: "Buffer.length", + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` Buffer .indexOf('a') .toString() `, - options: [4, { MemberExpression: 1 }] - }, - { - code: unIndent` + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` Buffer. length `, - options: [4, { MemberExpression: 1 }] - }, - { - code: unIndent` + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` Buffer .foo .bar `, - options: [4, { MemberExpression: 1 }] - }, - { - code: unIndent` + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` Buffer \t.foo \t.bar `, - options: ["tab", { MemberExpression: 1 }] - }, - { - code: unIndent` + options: ["tab", { MemberExpression: 1 }], + }, + { + code: unIndent` Buffer .foo .bar `, - options: [2, { MemberExpression: 2 }] - }, - unIndent` + options: [2, { MemberExpression: 2 }], + }, + unIndent` ( foo .bar ) `, - unIndent` + unIndent` ( ( foo @@ -1914,13 +1931,13 @@ ruleTester.run("indent", rule, { ) ) `, - unIndent` + unIndent` ( foo ) .bar `, - unIndent` + unIndent` ( ( foo @@ -1928,7 +1945,7 @@ ruleTester.run("indent", rule, { .bar ) `, - unIndent` + unIndent` ( ( foo @@ -1940,49 +1957,49 @@ ruleTester.run("indent", rule, { ] ) `, - unIndent` + unIndent` ( foo[bar] ) .baz `, - unIndent` + unIndent` ( (foo.bar) ) .baz `, - { - code: unIndent` + { + code: unIndent` MemberExpression .can .be .turned .off(); `, - options: [4, { MemberExpression: "off" }] - }, - { - code: unIndent` + options: [4, { MemberExpression: "off" }], + }, + { + code: unIndent` foo = bar.baz() .bip(); `, - options: [4, { MemberExpression: 1 }] - }, - unIndent` + options: [4, { MemberExpression: 1 }], + }, + unIndent` function foo() { new .target } `, - unIndent` + unIndent` function foo() { new. target } `, - { - code: unIndent` + { + code: unIndent` if (foo) { bar(); } else if (baz) { @@ -1991,67 +2008,73 @@ ruleTester.run("indent", rule, { qux(); } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` function foo(aaa, bbb, ccc, ddd) { bar(); } `, - options: [2, { FunctionDeclaration: { parameters: 1, body: 2 } }] - }, - { - code: unIndent` + options: [2, { FunctionDeclaration: { parameters: 1, body: 2 } }], + }, + { + code: unIndent` function foo(aaa, bbb, ccc, ddd) { bar(); } `, - options: [2, { FunctionDeclaration: { parameters: 3, body: 1 } }] - }, - { - code: unIndent` + options: [2, { FunctionDeclaration: { parameters: 3, body: 1 } }], + }, + { + code: unIndent` function foo(aaa, bbb, ccc) { bar(); } `, - options: [4, { FunctionDeclaration: { parameters: 1, body: 3 } }] - }, - { - code: unIndent` + options: [4, { FunctionDeclaration: { parameters: 1, body: 3 } }], + }, + { + code: unIndent` function foo(aaa, bbb, ccc, ddd, eee, fff) { bar(); } `, - options: [2, { FunctionDeclaration: { parameters: "first", body: 1 } }] - }, - { - code: unIndent` + options: [ + 2, + { FunctionDeclaration: { parameters: "first", body: 1 } }, + ], + }, + { + code: unIndent` function foo(aaa, bbb) { bar(); } `, - options: [2, { FunctionDeclaration: { body: 3 } }] - }, - { - code: unIndent` + options: [2, { FunctionDeclaration: { body: 3 } }], + }, + { + code: unIndent` function foo( aaa, bbb) { bar(); } `, - options: [2, { FunctionDeclaration: { parameters: "first", body: 2 } }] - }, - { - code: unIndent` + options: [ + 2, + { FunctionDeclaration: { parameters: "first", body: 2 } }, + ], + }, + { + code: unIndent` var foo = function(aaa, bbb, ccc, @@ -2059,78 +2082,90 @@ ruleTester.run("indent", rule, { bar(); } `, - options: [2, { FunctionExpression: { parameters: 2, body: 0 } }] - }, - { - code: unIndent` + options: [2, { FunctionExpression: { parameters: 2, body: 0 } }], + }, + { + code: unIndent` var foo = function(aaa, bbb, ccc) { bar(); } `, - options: [2, { FunctionExpression: { parameters: 1, body: 10 } }] - }, - { - code: unIndent` + options: [2, { FunctionExpression: { parameters: 1, body: 10 } }], + }, + { + code: unIndent` var foo = function(aaa, bbb, ccc, ddd, eee, fff) { bar(); } `, - options: [4, { FunctionExpression: { parameters: "first", body: 1 } }] - }, - { - code: unIndent` + options: [ + 4, + { FunctionExpression: { parameters: "first", body: 1 } }, + ], + }, + { + code: unIndent` var foo = function( aaa, bbb, ccc, ddd, eee) { bar(); } `, - options: [2, { FunctionExpression: { parameters: "first", body: 3 } }] - }, - { - code: unIndent` + options: [ + 2, + { FunctionExpression: { parameters: "first", body: 3 } }, + ], + }, + { + code: unIndent` foo.bar( baz, qux, function() { qux; } ); `, - options: [2, { FunctionExpression: { body: 3 }, CallExpression: { arguments: 3 } }] - }, - { - code: unIndent` + options: [ + 2, + { + FunctionExpression: { body: 3 }, + CallExpression: { arguments: 3 }, + }, + ], + }, + { + code: unIndent` function foo() { bar(); \tbaz(); \t \t\t\t \t\t\t \t \tqux(); } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` function foo() { function bar() { baz(); } } `, - options: [2, { FunctionDeclaration: { body: 1 } }] - }, - { - code: unIndent` + options: [2, { FunctionDeclaration: { body: 1 } }], + }, + { + code: unIndent` function foo() { bar(); \t\t} `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` function foo() { function bar(baz, qux) { @@ -2138,37 +2173,37 @@ ruleTester.run("indent", rule, { } } `, - options: [2, { FunctionDeclaration: { body: 1, parameters: 2 } }] - }, - { - code: unIndent` + options: [2, { FunctionDeclaration: { body: 1, parameters: 2 } }], + }, + { + code: unIndent` (( foo )) `, - options: [4] - }, + options: [4], + }, - // ternary expressions (https://github.com/eslint/eslint/issues/7420) - { - code: unIndent` + // ternary expressions (https://github.com/eslint/eslint/issues/7420) + { + code: unIndent` foo ? bar : baz `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` foo = (bar ? baz : qux ); `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` condition ? () => { return true @@ -2181,10 +2216,10 @@ ruleTester.run("indent", rule, { return false } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` condition ? () => { return true @@ -2197,10 +2232,10 @@ ruleTester.run("indent", rule, { return false } `, - options: [2, { offsetTernaryExpressions: false }] - }, - { - code: unIndent` + options: [2, { offsetTernaryExpressions: false }], + }, + { + code: unIndent` condition ? () => { return true @@ -2213,10 +2248,10 @@ ruleTester.run("indent", rule, { return false } `, - options: [2, { offsetTernaryExpressions: true }] - }, - { - code: unIndent` + options: [2, { offsetTernaryExpressions: true }], + }, + { + code: unIndent` condition ? () => { return true @@ -2229,30 +2264,30 @@ ruleTester.run("indent", rule, { return false } `, - options: [4, { offsetTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { offsetTernaryExpressions: true }], + }, + { + code: unIndent` condition1 ? condition2 ? Promise.resolve(1) : Promise.resolve(2) : Promise.resolve(3) `, - options: [2, { offsetTernaryExpressions: true }] - }, - { - code: unIndent` + options: [2, { offsetTernaryExpressions: true }], + }, + { + code: unIndent` condition1 ? Promise.resolve(1) : condition2 ? Promise.resolve(2) : Promise.resolve(3) `, - options: [2, { offsetTernaryExpressions: true }] - }, - { - code: unIndent` + options: [2, { offsetTernaryExpressions: true }], + }, + { + code: unIndent` condition \t? () => { \t\t\treturn true @@ -2265,9 +2300,9 @@ ruleTester.run("indent", rule, { \t\t\t\treturn false \t\t\t} `, - options: ["tab", { offsetTernaryExpressions: true }] - }, - unIndent` + options: ["tab", { offsetTernaryExpressions: true }], + }, + unIndent` [ foo ? bar : @@ -2275,13 +2310,12 @@ ruleTester.run("indent", rule, { qux ]; `, - { - - /* - * Checking comments: - * https://github.com/eslint/eslint/issues/3845, https://github.com/eslint/eslint/issues/6571 - */ - code: unIndent` + { + /* + * Checking comments: + * https://github.com/eslint/eslint/issues/3845, https://github.com/eslint/eslint/issues/6571 + */ + code: unIndent` foo(); // Line /* multiline @@ -2289,40 +2323,39 @@ ruleTester.run("indent", rule, { bar(); // trailing comment `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` switch (foo) { case bar: baz(); // call the baz function } `, - options: [2, { SwitchCase: 1 }] - }, - { - code: unIndent` + options: [2, { SwitchCase: 1 }], + }, + { + code: unIndent` switch (foo) { case bar: baz(); // no default } `, - options: [2, { SwitchCase: 1 }] - }, - unIndent` + options: [2, { SwitchCase: 1 }], + }, + unIndent` [ // no elements ] `, - { - - /* - * Destructuring assignments: - * https://github.com/eslint/eslint/issues/6813 - */ - code: unIndent` + { + /* + * Destructuring assignments: + * https://github.com/eslint/eslint/issues/6813 + */ + code: unIndent` var { foo, bar, @@ -2330,10 +2363,10 @@ ruleTester.run("indent", rule, { foobar: baz = foobar } = qux; `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` var [ foo, bar, @@ -2341,10 +2374,10 @@ ruleTester.run("indent", rule, { foobar = baz ] = qux; `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` const { a } @@ -2353,20 +2386,20 @@ ruleTester.run("indent", rule, { a: 1 } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` const { a } = { a: 1 } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` const { a @@ -2374,106 +2407,102 @@ ruleTester.run("indent", rule, { a: 1 }; `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` const foo = { bar: 1 } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` const [ a ] = [ 1 ] `, - options: [2] - }, - { - - // https://github.com/eslint/eslint/issues/7233 - code: unIndent` + options: [2], + }, + { + // https://github.com/eslint/eslint/issues/7233 + code: unIndent` var folder = filePath .foo() .bar; `, - options: [2, { MemberExpression: 2 }] - }, - { - code: unIndent` + options: [2, { MemberExpression: 2 }], + }, + { + code: unIndent` for (const foo of bar) baz(); `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` var x = () => 5; `, - options: [2] - }, - unIndent` + options: [2], + }, + unIndent` ( foo )( bar ) `, - unIndent` + unIndent` (() => foo )( bar ) `, - unIndent` + unIndent` (() => { foo(); })( bar ) `, - { - - // Don't lint the indentation of the first token after a : - code: unIndent` + { + // Don't lint the indentation of the first token after a : + code: unIndent` ({code: "foo.bar();"}) `, - options: [2] - }, - { - - // Don't lint the indentation of the first token after a : - code: unIndent` + options: [2], + }, + { + // Don't lint the indentation of the first token after a : + code: unIndent` ({code: "foo.bar();"}) `, - options: [2] - }, - unIndent` + options: [2], + }, + unIndent` ({ foo: bar }) `, - unIndent` + unIndent` ({ [foo]: bar }) `, - { - - // Comments in switch cases - code: unIndent` + { + // Comments in switch cases + code: unIndent` switch (foo) { // comment case study: @@ -2484,12 +2513,11 @@ ruleTester.run("indent", rule, { */ } `, - options: [2, { SwitchCase: 1 }] - }, - { - - // Comments in switch cases - code: unIndent` + options: [2, { SwitchCase: 1 }], + }, + { + // Comments in switch cases + code: unIndent` switch (foo) { // comment case study: @@ -2497,57 +2525,55 @@ ruleTester.run("indent", rule, { case closed: } `, - options: [2, { SwitchCase: 1 }] - }, - { - - // BinaryExpressions with parens - code: unIndent` + options: [2, { SwitchCase: 1 }], + }, + { + // BinaryExpressions with parens + code: unIndent` foo && ( bar ) `, - options: [4] - }, - { - - // BinaryExpressions with parens - code: unIndent` + options: [4], + }, + { + // BinaryExpressions with parens + code: unIndent` foo && (( bar )) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` foo && ( bar ) `, - options: [4] - }, - unIndent` + options: [4], + }, + unIndent` foo && !bar( ) `, - unIndent` + unIndent` foo && ![].map(() => { bar(); }) `, - { - code: unIndent` + { + code: unIndent` foo = bar; `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` function foo() { var bar = function(baz, qux) { @@ -2555,17 +2581,17 @@ ruleTester.run("indent", rule, { }; } `, - options: [2, { FunctionExpression: { parameters: 3 } }] - }, - unIndent` + options: [2, { FunctionExpression: { parameters: 3 } }], + }, + unIndent` function foo() { return (bar === 1 || bar === 2 && (/Function/.test(grandparent.type))) && directives(parent).indexOf(node) >= 0; } `, - { - code: unIndent` + { + code: unIndent` function foo() { return (foo === bar || ( baz === qux && ( @@ -2576,9 +2602,9 @@ ruleTester.run("indent", rule, { )) } `, - options: [4] - }, - unIndent` + options: [4], + }, + unIndent` if ( foo === 1 || bar === 1 || @@ -2586,34 +2612,33 @@ ruleTester.run("indent", rule, { (baz === 1 && qux === 1) ) {} `, - { - code: unIndent` + { + code: unIndent` foo = (bar + baz); `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` function foo() { return (bar === 1 || bar === 2) && (z === 3 || z === 4); } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` /* comment */ if (foo) { bar(); } `, - options: [2] - }, - { - - // Comments at the end of if blocks that have `else` blocks can either refer to the lines above or below them - code: unIndent` + options: [2], + }, + { + // Comments at the end of if blocks that have `else` blocks can either refer to the lines above or below them + code: unIndent` if (foo) { bar(); // Otherwise, if foo is false, do baz. @@ -2622,99 +2647,99 @@ ruleTester.run("indent", rule, { baz(); } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` function foo() { return ((bar === 1 || bar === 2) && (z === 3 || z === 4)); } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` foo( bar, baz, qux ); `, - options: [2, { CallExpression: { arguments: 1 } }] - }, - { - code: unIndent` + options: [2, { CallExpression: { arguments: 1 } }], + }, + { + code: unIndent` foo( \tbar, \tbaz, \tqux ); `, - options: ["tab", { CallExpression: { arguments: 1 } }] - }, - { - code: unIndent` + options: ["tab", { CallExpression: { arguments: 1 } }], + }, + { + code: unIndent` foo(bar, baz, qux); `, - options: [4, { CallExpression: { arguments: 2 } }] - }, - { - code: unIndent` + options: [4, { CallExpression: { arguments: 2 } }], + }, + { + code: unIndent` foo( bar, baz, qux ); `, - options: [2, { CallExpression: { arguments: 0 } }] - }, - { - code: unIndent` + options: [2, { CallExpression: { arguments: 0 } }], + }, + { + code: unIndent` foo(bar, baz, qux ); `, - options: [2, { CallExpression: { arguments: "first" } }] - }, - { - code: unIndent` + options: [2, { CallExpression: { arguments: "first" } }], + }, + { + code: unIndent` foo(bar, baz, qux, barbaz, barqux, bazqux); `, - options: [2, { CallExpression: { arguments: "first" } }] - }, - { - code: unIndent` + options: [2, { CallExpression: { arguments: "first" } }], + }, + { + code: unIndent` foo(bar, 1 + 2, !baz, new Car('!') ); `, - options: [2, { CallExpression: { arguments: 4 } }] - }, - unIndent` + options: [2, { CallExpression: { arguments: 4 } }], + }, + unIndent` foo( (bar) ); `, - { - code: unIndent` + { + code: unIndent` foo( (bar) ); `, - options: [4, { CallExpression: { arguments: 1 } }] - }, + options: [4, { CallExpression: { arguments: 1 } }], + }, - // https://github.com/eslint/eslint/issues/7484 - { - code: unIndent` + // https://github.com/eslint/eslint/issues/7484 + { + code: unIndent` var foo = function() { return bar( [{ @@ -2722,75 +2747,79 @@ ruleTester.run("indent", rule, { ); }; `, - options: [2] - }, + options: [2], + }, - // https://github.com/eslint/eslint/issues/7573 - { - code: unIndent` + // https://github.com/eslint/eslint/issues/7573 + { + code: unIndent` return ( foo ); `, - languageOptions: { parserOptions: { ecmaFeatures: { globalReturn: true } } } - }, - { - code: unIndent` + languageOptions: { + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + }, + { + code: unIndent` return ( foo ) `, - languageOptions: { parserOptions: { ecmaFeatures: { globalReturn: true } } } - }, - unIndent` + languageOptions: { + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + }, + unIndent` var foo = [ bar, baz ] `, - unIndent` + unIndent` var foo = [bar, baz, qux ] `, - { - code: unIndent` + { + code: unIndent` var foo = [bar, baz, qux ] `, - options: [2, { ArrayExpression: 0 }] - }, - { - code: unIndent` + options: [2, { ArrayExpression: 0 }], + }, + { + code: unIndent` var foo = [bar, baz, qux ] `, - options: [2, { ArrayExpression: 8 }] - }, - { - code: unIndent` + options: [2, { ArrayExpression: 8 }], + }, + { + code: unIndent` var foo = [bar, baz, qux ] `, - options: [2, { ArrayExpression: "first" }] - }, - { - code: unIndent` + options: [2, { ArrayExpression: "first" }], + }, + { + code: unIndent` var foo = [bar, baz, qux ] `, - options: [2, { ArrayExpression: "first" }] - }, - { - code: unIndent` + options: [2, { ArrayExpression: "first" }], + }, + { + code: unIndent` var foo = [ { bar: 1, baz: 2 }, @@ -2798,102 +2827,114 @@ ruleTester.run("indent", rule, { baz: 4 } ] `, - options: [4, { ArrayExpression: 2, ObjectExpression: "first" }] - }, - { - code: unIndent` + options: [4, { ArrayExpression: 2, ObjectExpression: "first" }], + }, + { + code: unIndent` var foo = { bar: 1, baz: 2 }; `, - options: [2, { ObjectExpression: 0 }] - }, - { - code: unIndent` + options: [2, { ObjectExpression: 0 }], + }, + { + code: unIndent` var foo = { foo: 1, bar: 2, baz: 3 } `, - options: [2, { ObjectExpression: "first" }] - }, - { - code: unIndent` + options: [2, { ObjectExpression: "first" }], + }, + { + code: unIndent` var foo = [ { foo: 1 } ] `, - options: [4, { ArrayExpression: 2 }] - }, - { - code: unIndent` + options: [4, { ArrayExpression: 2 }], + }, + { + code: unIndent` function foo() { [ foo ] } `, - options: [2, { ArrayExpression: 4 }] - }, - { - code: "[\n]", - options: [2, { ArrayExpression: "first" }] - }, - { - code: "[\n]", - options: [2, { ArrayExpression: 1 }] - }, - { - code: "{\n}", - options: [2, { ObjectExpression: "first" }] - }, - { - code: "{\n}", - options: [2, { ObjectExpression: 1 }] - }, - { - code: unIndent` + options: [2, { ArrayExpression: 4 }], + }, + { + code: "[\n]", + options: [2, { ArrayExpression: "first" }], + }, + { + code: "[\n]", + options: [2, { ArrayExpression: 1 }], + }, + { + code: "{\n}", + options: [2, { ObjectExpression: "first" }], + }, + { + code: "{\n}", + options: [2, { ObjectExpression: 1 }], + }, + { + code: unIndent` var foo = [ [ 1 ] ] `, - options: [2, { ArrayExpression: "first" }] - }, - { - code: unIndent` + options: [2, { ArrayExpression: "first" }], + }, + { + code: unIndent` var foo = [ 1, [ 2 ] ]; `, - options: [2, { ArrayExpression: "first" }] - }, - { - code: unIndent` + options: [2, { ArrayExpression: "first" }], + }, + { + code: unIndent` var foo = bar(1, [ 2, 3 ] ); `, - options: [4, { ArrayExpression: "first", CallExpression: { arguments: "first" } }] - }, - { - code: unIndent` + options: [ + 4, + { + ArrayExpression: "first", + CallExpression: { arguments: "first" }, + }, + ], + }, + { + code: unIndent` var foo = [ ]() `, - options: [4, { CallExpression: { arguments: "first" }, ArrayExpression: "first" }] - }, - - // https://github.com/eslint/eslint/issues/7732 - { - code: unIndent` + options: [ + 4, + { + CallExpression: { arguments: "first" }, + ArrayExpression: "first", + }, + ], + }, + + // https://github.com/eslint/eslint/issues/7732 + { + code: unIndent` const lambda = foo => { Object.assign({}, filterName, @@ -2903,10 +2944,10 @@ ruleTester.run("indent", rule, { ); } `, - options: [2, { ObjectExpression: 1 }] - }, - { - code: unIndent` + options: [2, { ObjectExpression: 1 }], + }, + { + code: unIndent` const lambda = foo => { Object.assign({}, filterName, @@ -2916,12 +2957,12 @@ ruleTester.run("indent", rule, { ); } `, - options: [2, { ObjectExpression: "first" }] - }, + options: [2, { ObjectExpression: "first" }], + }, - // https://github.com/eslint/eslint/issues/7733 - { - code: unIndent` + // https://github.com/eslint/eslint/issues/7733 + { + code: unIndent` var foo = function() { \twindow.foo('foo', \t\t{ @@ -2933,18 +2974,24 @@ ruleTester.run("indent", rule, { \t); } `, - options: ["tab"] - }, - { - code: unIndent` + options: ["tab"], + }, + { + code: unIndent` echo = spawn('cmd.exe', ['foo', 'bar', 'baz']); `, - options: [2, { ArrayExpression: "first", CallExpression: { arguments: "first" } }] - }, - { - code: unIndent` + options: [ + 2, + { + ArrayExpression: "first", + CallExpression: { arguments: "first" }, + }, + ], + }, + { + code: unIndent` if (foo) bar(); // Otherwise, if foo is false, do baz. @@ -2953,10 +3000,10 @@ ruleTester.run("indent", rule, { baz(); } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` if ( foo && bar || baz && qux // This line is ignored because BinaryExpressions are not checked. @@ -2964,27 +3011,27 @@ ruleTester.run("indent", rule, { qux(); } `, - options: [4] - }, - unIndent` + options: [4], + }, + unIndent` [ ] || [ ] `, - unIndent` + unIndent` ( [ ] || [ ] ) `, - unIndent` + unIndent` 1 + ( 1 ) `, - unIndent` + unIndent` ( foo && ( bar || @@ -2992,35 +3039,35 @@ ruleTester.run("indent", rule, { ) ) `, - unIndent` + unIndent` foo || ( bar ) `, - unIndent` + unIndent` foo || ( bar ) `, - { - code: unIndent` + { + code: unIndent` var foo = 1; `, - options: [4, { VariableDeclarator: 2 }] - }, - { - code: unIndent` + options: [4, { VariableDeclarator: 2 }], + }, + { + code: unIndent` var foo = 1, bar = 2; `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` switch (foo) { case bar: { @@ -3028,60 +3075,60 @@ ruleTester.run("indent", rule, { } } `, - options: [2, { SwitchCase: 1 }] - }, + options: [2, { SwitchCase: 1 }], + }, - // Template curlies - { - code: unIndent` + // Template curlies + { + code: unIndent` \`foo\${ bar}\` `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` \`foo\${ \`bar\${ baz}\`}\` `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` \`foo\${ \`bar\${ baz }\` }\` `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` \`foo\${ ( bar ) }\` `, - options: [2] - }, - unIndent` + options: [2], + }, + unIndent` foo(\` bar \`, { baz: 1 }); `, - unIndent` + unIndent` function foo() { \`foo\${bar}baz\${ qux}foo\${ bar}baz\` } `, - unIndent` + unIndent` JSON .stringify( { @@ -3090,34 +3137,33 @@ ruleTester.run("indent", rule, { ); `, - // Don't check AssignmentExpression assignments - unIndent` + // Don't check AssignmentExpression assignments + unIndent` foo = bar = baz; `, - unIndent` + unIndent` foo = bar = baz; `, - unIndent` + unIndent` function foo() { const template = \`this indentation is not checked because it's part of a template literal.\`; } `, - unIndent` + unIndent` function foo() { const template = \`the indentation of a \${ node.type } node is checked.\`; } `, - { - - // https://github.com/eslint/eslint/issues/7320 - code: unIndent` + { + // https://github.com/eslint/eslint/issues/7320 + code: unIndent` JSON .stringify( { @@ -3125,9 +3171,9 @@ ruleTester.run("indent", rule, { } ); `, - options: [4, { CallExpression: { arguments: 1 } }] - }, - unIndent` + options: [4, { CallExpression: { arguments: 1 } }], + }, + unIndent` [ foo, // comment @@ -3135,26 +3181,26 @@ ruleTester.run("indent", rule, { bar ] `, - unIndent` + unIndent` if (foo) { /* comment */ bar(); } `, - unIndent` + unIndent` function foo() { return ( 1 ); } `, - unIndent` + unIndent` function foo() { return ( 1 ) } `, - unIndent` + unIndent` if ( foo && !( @@ -3162,10 +3208,9 @@ ruleTester.run("indent", rule, { ) ) {} `, - { - - // https://github.com/eslint/eslint/issues/6007 - code: unIndent` + { + // https://github.com/eslint/eslint/issues/6007 + code: unIndent` var abc = [ ( '' @@ -3173,10 +3218,10 @@ ruleTester.run("indent", rule, { def, ] `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` var abc = [ ( '' @@ -3186,9 +3231,9 @@ ruleTester.run("indent", rule, { ) ] `, - options: [2] - }, - unIndent` + options: [2], + }, + unIndent` function f() { return asyncCall() .then( @@ -3201,10 +3246,9 @@ ruleTester.run("indent", rule, { ); } `, - { - - // https://github.com/eslint/eslint/issues/6670 - code: unIndent` + { + // https://github.com/eslint/eslint/issues/6670 + code: unIndent` function f() { return asyncCall() .then( @@ -3217,30 +3261,29 @@ ruleTester.run("indent", rule, { ); } `, - options: [4, { MemberExpression: 1 }] - }, + options: [4, { MemberExpression: 1 }], + }, - // https://github.com/eslint/eslint/issues/7242 - unIndent` + // https://github.com/eslint/eslint/issues/7242 + unIndent` var x = [ [1], [2] ] `, - unIndent` + unIndent` var y = [ {a: 1}, {b: 2} ] `, - unIndent` + unIndent` foo( ) `, - { - - // https://github.com/eslint/eslint/issues/7616 - code: unIndent` + { + // https://github.com/eslint/eslint/issues/7616 + code: unIndent` foo( bar, { @@ -3248,17 +3291,17 @@ ruleTester.run("indent", rule, { } ) `, - options: [4, { CallExpression: { arguments: "first" } }] - }, - "new Foo", - "new (Foo)", - unIndent` + options: [4, { CallExpression: { arguments: "first" } }], + }, + "new Foo", + "new (Foo)", + unIndent` if (Foo) { new Foo } `, - { - code: unIndent` + { + code: unIndent` var foo = 0, bar = 0, baz = 0; export { foo, @@ -3266,42 +3309,42 @@ ruleTester.run("indent", rule, { baz } `, - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: unIndent` foo ? bar : baz `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` foo ? bar : baz `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` foo ? bar : baz `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` foo ? bar : baz `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` foo ? bar : baz @@ -3310,10 +3353,10 @@ ruleTester.run("indent", rule, { ? boop : beep `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` foo ? bar : baz ? @@ -3322,64 +3365,64 @@ ruleTester.run("indent", rule, { boop : beep `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` var a = foo ? bar : baz ? qux : foobar ? boop : /*else*/ beep `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` var a = foo ? bar : baz `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` var a = foo ? bar : baz `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` a = foo ? bar : baz ? qux : foobar ? boop : /*else*/ beep `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` a = foo ? bar : baz `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` a = foo ? bar : baz `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` foo( foo ? bar : baz ? qux : @@ -3387,10 +3430,10 @@ ruleTester.run("indent", rule, { /*else*/ beep ) `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` function wrap() { return ( foo ? bar : @@ -3400,20 +3443,20 @@ ruleTester.run("indent", rule, { ) } `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` function wrap() { return foo ? bar : baz } `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` function wrap() { return ( foo @@ -3422,29 +3465,29 @@ ruleTester.run("indent", rule, { ) } `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` foo( foo ? bar : baz ) `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` foo(foo ? bar : baz ) `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` foo ? bar : baz @@ -3453,10 +3496,10 @@ ruleTester.run("indent", rule, { ? boop : beep `, - options: [4, { flatTernaryExpressions: false }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: false }], + }, + { + code: unIndent` foo ? bar : baz ? @@ -3465,59 +3508,59 @@ ruleTester.run("indent", rule, { boop : beep `, - options: [4, { flatTernaryExpressions: false }] - }, - { - code: "[,]", - options: [2, { ArrayExpression: "first" }] - }, - { - code: "[,]", - options: [2, { ArrayExpression: "off" }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: false }], + }, + { + code: "[,]", + options: [2, { ArrayExpression: "first" }], + }, + { + code: "[,]", + options: [2, { ArrayExpression: "off" }], + }, + { + code: unIndent` [ , foo ] `, - options: [4, { ArrayExpression: "first" }] - }, - { - code: "[sparse, , array];", - options: [2, { ArrayExpression: "first" }] - }, - { - code: unIndent` + options: [4, { ArrayExpression: "first" }], + }, + { + code: "[sparse, , array];", + options: [2, { ArrayExpression: "first" }], + }, + { + code: unIndent` foo.bar('baz', function(err) { qux; }); `, - options: [2, { CallExpression: { arguments: "first" } }] - }, - { - code: unIndent` + options: [2, { CallExpression: { arguments: "first" } }], + }, + { + code: unIndent` foo.bar(function() { cookies; }).baz(function() { cookies; }); `, - options: [2, { MemberExpression: 1 }] - }, - { - code: unIndent` + options: [2, { MemberExpression: 1 }], + }, + { + code: unIndent` foo.bar().baz(function() { cookies; }).qux(function() { cookies; }); `, - options: [2, { MemberExpression: 1 }] - }, - { - code: unIndent` + options: [2, { MemberExpression: 1 }], + }, + { + code: unIndent` ( { foo: 1, @@ -3525,134 +3568,134 @@ ruleTester.run("indent", rule, { } ); `, - options: [2, { ObjectExpression: "first" }] - }, - { - code: unIndent` + options: [2, { ObjectExpression: "first" }], + }, + { + code: unIndent` foo(() => { bar; }, () => { baz; }) `, - options: [4, { CallExpression: { arguments: "first" } }] - }, - { - code: unIndent` + options: [4, { CallExpression: { arguments: "first" } }], + }, + { + code: unIndent` [ foo, bar ].forEach(function() { baz; }) `, - options: [2, { ArrayExpression: "first", MemberExpression: 1 }] - }, - unIndent` + options: [2, { ArrayExpression: "first", MemberExpression: 1 }], + }, + unIndent` foo = bar[ baz ]; `, - { - code: unIndent` + { + code: unIndent` foo[ bar ]; `, - options: [4, { MemberExpression: 1 }] - }, - { - code: unIndent` + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` foo[ ( bar ) ]; `, - options: [4, { MemberExpression: 1 }] - }, - unIndent` + options: [4, { MemberExpression: 1 }], + }, + unIndent` if (foo) bar; else if (baz) qux; `, - unIndent` + unIndent` if (foo) bar() ; [1, 2, 3].map(baz) `, - unIndent` + unIndent` if (foo) ; `, - "x => {}", - { - code: unIndent` + "x => {}", + { + code: unIndent` import {foo} from 'bar'; `, - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: "import 'foo'", - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: "import 'foo'", + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: unIndent` import { foo, bar, baz, } from 'qux'; `, - options: [4, { ImportDeclaration: 1 }], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: unIndent` + options: [4, { ImportDeclaration: 1 }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: unIndent` import { foo, bar, baz, } from 'qux'; `, - options: [4, { ImportDeclaration: 1 }], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: unIndent` + options: [4, { ImportDeclaration: 1 }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: unIndent` import { apple as a, banana as b } from 'fruits'; import { cat } from 'animals'; `, - options: [4, { ImportDeclaration: "first" }], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: unIndent` + options: [4, { ImportDeclaration: "first" }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: unIndent` import { declaration, can, be, turned } from 'off'; `, - options: [4, { ImportDeclaration: "off" }], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, + options: [4, { ImportDeclaration: "off" }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, - // https://github.com/eslint/eslint/issues/8455 - unIndent` + // https://github.com/eslint/eslint/issues/8455 + unIndent` ( a ) => b => { c } `, - unIndent` + unIndent` ( a ) => b => c => d => { e } `, - unIndent` + unIndent` ( a ) => @@ -3662,52 +3705,52 @@ ruleTester.run("indent", rule, { c } `, - unIndent` + unIndent` if ( foo ) bar( baz ); `, - unIndent` + unIndent` if (foo) { bar(); } `, - unIndent` + unIndent` function foo(bar) { baz(); } `, - unIndent` + unIndent` () => ({}) `, - unIndent` + unIndent` () => (({})) `, - unIndent` + unIndent` ( () => ({}) ) `, - unIndent` + unIndent` var x = function foop(bar) { baz(); } `, - unIndent` + unIndent` var x = (bar) => { baz(); } `, - unIndent` + unIndent` class Foo { constructor() @@ -3721,7 +3764,7 @@ ruleTester.run("indent", rule, { } } `, - unIndent` + unIndent` class Foo extends Bar { @@ -3736,7 +3779,7 @@ ruleTester.run("indent", rule, { } } `, - unIndent` + unIndent` ( class Foo { @@ -3752,120 +3795,120 @@ ruleTester.run("indent", rule, { } ) `, - { - code: unIndent` + { + code: unIndent` switch (foo) { case 1: bar(); } `, - options: [4, { SwitchCase: 1 }] - }, - unIndent` + options: [4, { SwitchCase: 1 }], + }, + unIndent` foo .bar(function() { baz }) `, - { - code: unIndent` + { + code: unIndent` foo .bar(function() { baz }) `, - options: [4, { MemberExpression: 2 }] - }, - unIndent` + options: [4, { MemberExpression: 2 }], + }, + unIndent` foo [bar](function() { baz }) `, - unIndent` + unIndent` foo. bar. baz `, - { - code: unIndent` + { + code: unIndent` foo .bar(function() { baz }) `, - options: [4, { MemberExpression: "off" }] - }, - { - code: unIndent` + options: [4, { MemberExpression: "off" }], + }, + { + code: unIndent` foo .bar(function() { baz }) `, - options: [4, { MemberExpression: "off" }] - }, - { - code: unIndent` + options: [4, { MemberExpression: "off" }], + }, + { + code: unIndent` foo [bar](function() { baz }) `, - options: [4, { MemberExpression: "off" }] - }, - { - code: unIndent` + options: [4, { MemberExpression: "off" }], + }, + { + code: unIndent` foo. bar. baz `, - options: [4, { MemberExpression: "off" }] - }, - { - code: unIndent` + options: [4, { MemberExpression: "off" }], + }, + { + code: unIndent` foo = bar( ).baz( ) `, - options: [4, { MemberExpression: "off" }] - }, - { - code: unIndent` + options: [4, { MemberExpression: "off" }], + }, + { + code: unIndent` foo[ bar ? baz : qux ] `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` function foo() { return foo ? bar : baz } `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` throw foo ? bar : baz `, - options: [4, { flatTernaryExpressions: true }] - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` foo( bar ) ? baz : qux `, - options: [4, { flatTernaryExpressions: true }] - }, - unIndent` + options: [4, { flatTernaryExpressions: true }], + }, + unIndent` foo [ bar @@ -3874,7 +3917,7 @@ ruleTester.run("indent", rule, { quz(); }) `, - unIndent` + unIndent` [ foo ][ @@ -3882,28 +3925,28 @@ ruleTester.run("indent", rule, { qux(); }) `, - unIndent` + unIndent` ( a.b(function() { c; }) ) `, - unIndent` + unIndent` ( foo ).bar(function() { baz(); }) `, - unIndent` + unIndent` new Foo( bar .baz .qux ) `, - unIndent` + unIndent` const foo = a.b(), longName = (baz( @@ -3911,7 +3954,7 @@ ruleTester.run("indent", rule, { 'bar' )); `, - unIndent` + unIndent` const foo = a.b(), longName = (baz( @@ -3919,7 +3962,7 @@ ruleTester.run("indent", rule, { 'bar' )); `, - unIndent` + unIndent` const foo = a.b(), longName = baz( @@ -3927,7 +3970,7 @@ ruleTester.run("indent", rule, { 'bar' ); `, - unIndent` + unIndent` const foo = a.b(), longName = baz( @@ -3935,7 +3978,7 @@ ruleTester.run("indent", rule, { 'bar' ); `, - unIndent` + unIndent` const foo = a.b(), longName = baz( @@ -3943,7 +3986,7 @@ ruleTester.run("indent", rule, { 'bar' ); `, - unIndent` + unIndent` const foo = a.b(), longName = baz( @@ -3951,50 +3994,50 @@ ruleTester.run("indent", rule, { 'bar' ); `, - unIndent` + unIndent` const foo = a.b(), longName = ('fff'); `, - unIndent` + unIndent` const foo = a.b(), longName = ('fff'); `, - unIndent` + unIndent` const foo = a.b(), longName = ('fff'); `, - unIndent` + unIndent` const foo = a.b(), longName = ('fff'); `, - unIndent` + unIndent` const foo = a.b(), longName = ( 'fff' ); `, - unIndent` + unIndent` const foo = a.b(), longName = ( 'fff' ); `, - unIndent` + unIndent` const foo = a.b(), longName =( 'fff' ); `, - unIndent` + unIndent` const foo = a.b(), longName =( @@ -4002,22 +4045,23 @@ ruleTester.run("indent", rule, { ); `, + //---------------------------------------------------------------------- + // Ignore Unknown Nodes + //---------------------------------------------------------------------- - //---------------------------------------------------------------------- - // Ignore Unknown Nodes - //---------------------------------------------------------------------- - - { - code: unIndent` + { + code: unIndent` interface Foo { bar: string; baz: number; } `, - languageOptions: { parser: require(parser("unknown-nodes/interface")) } - }, - { - code: unIndent` + languageOptions: { + parser: require(parser("unknown-nodes/interface")), + }, + }, + { + code: unIndent` namespace Foo { const bar = 3, baz = 2; @@ -4027,10 +4071,12 @@ ruleTester.run("indent", rule, { } } `, - languageOptions: { parser: require(parser("unknown-nodes/namespace-valid")) } - }, - { - code: unIndent` + languageOptions: { + parser: require(parser("unknown-nodes/namespace-valid")), + }, + }, + { + code: unIndent` abstract class Foo { public bar() { let aaa = 4, @@ -4044,10 +4090,12 @@ ruleTester.run("indent", rule, { } } `, - languageOptions: { parser: require(parser("unknown-nodes/abstract-class-valid")) } - }, - { - code: unIndent` + languageOptions: { + parser: require(parser("unknown-nodes/abstract-class-valid")), + }, + }, + { + code: unIndent` function foo() { function bar() { abstract class X { @@ -4060,10 +4108,14 @@ ruleTester.run("indent", rule, { } } `, - languageOptions: { parser: require(parser("unknown-nodes/functions-with-abstract-class-valid")) } - }, - { - code: unIndent` + languageOptions: { + parser: require( + parser("unknown-nodes/functions-with-abstract-class-valid"), + ), + }, + }, + { + code: unIndent` namespace Unknown { function foo() { function bar() { @@ -4078,27 +4130,43 @@ ruleTester.run("indent", rule, { } } `, - languageOptions: { parser: require(parser("unknown-nodes/namespace-with-functions-with-abstract-class-valid")) } - }, - { - code: unIndent` + languageOptions: { + parser: require( + parser( + "unknown-nodes/namespace-with-functions-with-abstract-class-valid", + ), + ), + }, + }, + { + code: unIndent` type httpMethod = 'GET' | 'POST' | 'PUT'; `, - options: [2, { VariableDeclarator: 0 }], - languageOptions: { parser: require(parser("unknown-nodes/variable-declarator-type-indent-two-spaces")) } - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 0 }], + languageOptions: { + parser: require( + parser( + "unknown-nodes/variable-declarator-type-indent-two-spaces", + ), + ), + }, + }, + { + code: unIndent` type httpMethod = 'GET' | 'POST' | 'PUT'; `, - options: [2, { VariableDeclarator: 1 }], - languageOptions: { parser: require(parser("unknown-nodes/variable-declarator-type-no-indent")) } - }, - unIndent` + options: [2, { VariableDeclarator: 1 }], + languageOptions: { + parser: require( + parser("unknown-nodes/variable-declarator-type-no-indent"), + ), + }, + }, + unIndent` foo(\`foo \`, { ok: true @@ -4107,7 +4175,7 @@ ruleTester.run("indent", rule, { ok: false }) `, - unIndent` + unIndent` foo(tag\`foo \`, { ok: true @@ -4118,8 +4186,8 @@ ruleTester.run("indent", rule, { ) `, - // https://github.com/eslint/eslint/issues/8815 - unIndent` + // https://github.com/eslint/eslint/issues/8815 + unIndent` async function test() { const { foo, @@ -4131,7 +4199,7 @@ ruleTester.run("indent", rule, { ); } `, - unIndent` + unIndent` function* test() { const { foo, @@ -4143,14 +4211,14 @@ ruleTester.run("indent", rule, { ); } `, - unIndent` + unIndent` ({ a: b } = +foo( bar )); `, - unIndent` + unIndent` const { foo, bar, @@ -4160,7 +4228,7 @@ ruleTester.run("indent", rule, { 3, ); `, - unIndent` + unIndent` const { foo, bar, @@ -4169,34 +4237,34 @@ ruleTester.run("indent", rule, { ); `, - //---------------------------------------------------------------------- - // JSX tests - // https://github.com/eslint/eslint/issues/8425 - // Some of the following tests are adapted from the tests in eslint-plugin-react. - // License: https://github.com/yannickcr/eslint-plugin-react/blob/7ca9841f22d599f447a27ef5b2a97def9229d6c8/LICENSE - //---------------------------------------------------------------------- + //---------------------------------------------------------------------- + // JSX tests + // https://github.com/eslint/eslint/issues/8425 + // Some of the following tests are adapted from the tests in eslint-plugin-react. + // License: https://github.com/yannickcr/eslint-plugin-react/blob/7ca9841f22d599f447a27ef5b2a97def9229d6c8/LICENSE + //---------------------------------------------------------------------- - ";", - unIndent` + ';', + unIndent` ; `, - "var foo = ;", - unIndent` + 'var foo = ;', + unIndent` var foo = ; `, - unIndent` + unIndent` var foo = (); `, - unIndent` + unIndent` var foo = ( ); `, - unIndent` + unIndent` < Foo a="b" c="d" />; `, - unIndent` + unIndent` ; `, - unIndent` + unIndent` < Foo a="b" c="d"/>; `, - "bar;", - unIndent` + 'bar;', + unIndent` bar ; `, - unIndent` + unIndent` bar ; `, - unIndent` + unIndent` bar ; `, - unIndent` + unIndent` < a href="foo"> bar ; `, - unIndent` + unIndent` bar ; `, - unIndent` + unIndent` bar ; `, - unIndent` + unIndent` var foo = baz ; `, - unIndent` + unIndent` var foo = baz ; `, - unIndent` + unIndent` var foo = baz ; `, - unIndent` + unIndent` var foo = < a href="bar"> baz ; `, - unIndent` + unIndent` var foo = baz ; `, - unIndent` + unIndent` var foo = baz `, - unIndent` + unIndent` var foo = ( baz ); `, - unIndent` + unIndent` var foo = ( baz ); `, - unIndent` + unIndent` var foo = ( baz ); `, - unIndent` + unIndent` var foo = ( @@ -4327,21 +4395,21 @@ ruleTester.run("indent", rule, { ); `, - "var foo = baz;", - unIndent` + 'var foo = baz;', + unIndent` { } `, - unIndent` + unIndent` { foo } `, - unIndent` + unIndent` function foo() { return ( @@ -4357,57 +4425,57 @@ ruleTester.run("indent", rule, { ); } `, - "", - unIndent` + "", + unIndent` `, - { - code: unIndent` + { + code: unIndent` `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` `, - options: [0] - }, - { - code: unIndent` + options: [0], + }, + { + code: unIndent` \t `, - options: ["tab"] - }, - { - code: unIndent` + options: ["tab"], + }, + { + code: unIndent` function App() { return ; } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` function App() { return ( ); } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` function App() { return ( @@ -4416,10 +4484,10 @@ ruleTester.run("indent", rule, { ); } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` it( (
@@ -4428,10 +4496,10 @@ ruleTester.run("indent", rule, { ) ) `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` it( (
@@ -4440,20 +4508,20 @@ ruleTester.run("indent", rule, {
) ) `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` (
) `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` { head.title &&

@@ -4461,10 +4529,10 @@ ruleTester.run("indent", rule, {

} `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` { head.title &&

@@ -4472,10 +4540,10 @@ ruleTester.run("indent", rule, {

} `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` { head.title && (

@@ -4483,10 +4551,10 @@ ruleTester.run("indent", rule, {

) } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` { head.title && (

@@ -4495,18 +4563,18 @@ ruleTester.run("indent", rule, { ) } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` [
,
] `, - options: [2] - }, - unIndent` + options: [2], + }, + unIndent`
{ [ @@ -4516,7 +4584,7 @@ ruleTester.run("indent", rule, { }
`, - unIndent` + unIndent`
{foo && [ @@ -4526,7 +4594,7 @@ ruleTester.run("indent", rule, { }
`, - unIndent` + unIndent`
bar
bar @@ -4534,23 +4602,23 @@ ruleTester.run("indent", rule, { bar
`, - unIndent` + unIndent` foo ? : `, - unIndent` + unIndent` foo ? : `, - unIndent` + unIndent` foo ? : `, - unIndent` + unIndent`
{!foo ? `, - { - code: unIndent` + { + code: unIndent` {condition ? `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` {condition ? `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` function foo() { {condition ? @@ -4602,67 +4670,59 @@ ruleTester.run("indent", rule, { } `, - options: [2] - }, - unIndent` + options: [2], + }, + unIndent` `, - { - code: unIndent` + { + code: unIndent` `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` `, - options: [0] - }, - { - code: unIndent` - - `, - options: ["tab"] - }, - unIndent` + options: [0], + }, + unIndent` `, - unIndent` + unIndent` `, - { - code: unIndent` + { + code: unIndent` `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` var x = function() { return } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` var x = `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` @@ -4695,10 +4755,10 @@ ruleTester.run("indent", rule, { /> `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` @@ -4709,61 +4769,61 @@ ruleTester.run("indent", rule, { />} `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` `, - options: ["tab"] - }, - { - code: unIndent` + options: ["tab"], + }, + { + code: unIndent` `, - options: ["tab"] - }, - { - code: unIndent` + options: ["tab"], + }, + { + code: unIndent` `, - options: ["tab"] - }, - { - code: unIndent` + options: ["tab"], + }, + { + code: unIndent` var x = `, - options: ["tab"] - }, - unIndent` + options: ["tab"], + }, + unIndent` `, - unIndent` + unIndent`
unrelated{ foo }
`, - unIndent` + unIndent`
unrelated{ foo }
`, - unIndent` + unIndent` < foo .bar @@ -4776,35 +4836,35 @@ ruleTester.run("indent", rule, { baz > `, - unIndent` + unIndent` < input type= "number" /> `, - unIndent` + unIndent` < input type= {'number'} /> `, - unIndent` + unIndent` < input type ="number" /> `, - unIndent` + unIndent` foo ? ( bar ) : ( baz ) `, - unIndent` + unIndent` foo ? (
@@ -4813,7 +4873,7 @@ ruleTester.run("indent", rule, {
) `, - unIndent` + unIndent`
{ /* foo */ @@ -4821,128 +4881,128 @@ ruleTester.run("indent", rule, {
`, - /* - * JSX Fragments - * https://github.com/eslint/eslint/issues/12208 - */ - unIndent` + /* + * JSX Fragments + * https://github.com/eslint/eslint/issues/12208 + */ + unIndent` <>
`, - unIndent` + unIndent` < > `, - unIndent` + unIndent` <> < /> `, - unIndent` + unIndent` <> `, - unIndent` + unIndent` < > `, - unIndent` + unIndent` < > < /> `, - unIndent` + unIndent` < // Comment > `, - unIndent` + unIndent` < // Comment > `, - unIndent` + unIndent` < // Comment > `, - unIndent` + unIndent` <> < // Comment /> `, - unIndent` + unIndent` <> < // Comment /> `, - unIndent` + unIndent` <> < // Comment /> `, - unIndent` + unIndent` <> `, - unIndent` + unIndent` <> `, - unIndent` + unIndent` <> `, - unIndent` + unIndent` < /* Comment */ > `, - unIndent` + unIndent` < /* Comment */ > `, - unIndent` + unIndent` < /* Comment */ > `, - unIndent` + unIndent` < /* * Comment @@ -4951,7 +5011,7 @@ ruleTester.run("indent", rule, { `, - unIndent` + unIndent` < /* * Comment @@ -4960,64 +5020,64 @@ ruleTester.run("indent", rule, { `, - unIndent` + unIndent` <> < /* Comment */ /> `, - unIndent` + unIndent` <> < /* Comment */ /> `, - unIndent` + unIndent` <> < /* Comment */ /> `, - unIndent` + unIndent` <> < /* Comment */ /> `, - unIndent` + unIndent` <> < /* Comment */ /> `, - unIndent` + unIndent` <> `, - unIndent` + unIndent` <> `, - unIndent` + unIndent` <> `, - unIndent` + unIndent` <> `, - unIndent` + unIndent` <> `, - // https://github.com/eslint/eslint/issues/8832 - unIndent` + // https://github.com/eslint/eslint/issues/8832 + unIndent`
{ ( @@ -5035,7 +5095,7 @@ ruleTester.run("indent", rule, { }
`, - unIndent` + unIndent` function A() { return (
@@ -5049,76 +5109,76 @@ ruleTester.run("indent", rule, { ); } `, - unIndent` + unIndent`
foo
bar
`, - unIndent` + unIndent` Foo bar 
baz qux. `, - unIndent` + unIndent`
`, - unIndent` + unIndent`
`, - { - code: unIndent` + { + code: unIndent` a(b , c ) `, - options: [2, { CallExpression: { arguments: "off" } }] - }, - { - code: unIndent` + options: [2, { CallExpression: { arguments: "off" } }], + }, + { + code: unIndent` a( new B({ c, }) ); `, - options: [2, { CallExpression: { arguments: "off" } }] - }, - { - code: unIndent` + options: [2, { CallExpression: { arguments: "off" } }], + }, + { + code: unIndent` foo ? bar : baz `, - options: [4, { ignoredNodes: ["ConditionalExpression"] }] - }, - { - code: unIndent` + options: [4, { ignoredNodes: ["ConditionalExpression"] }], + }, + { + code: unIndent` class Foo { foo() { bar(); } } `, - options: [4, { ignoredNodes: ["ClassBody"] }] - }, - { - code: unIndent` + options: [4, { ignoredNodes: ["ClassBody"] }], + }, + { + code: unIndent` class Foo { foo() { bar(); } } `, - options: [4, { ignoredNodes: ["ClassBody", "BlockStatement"] }] - }, - { - code: unIndent` + options: [4, { ignoredNodes: ["ClassBody", "BlockStatement"] }], + }, + { + code: unIndent` foo({ bar: 1 }, @@ -5129,17 +5189,20 @@ ruleTester.run("indent", rule, { qux: 3 }) `, - options: [4, { ignoredNodes: ["CallExpression > ObjectExpression"] }] - }, - { - code: unIndent` + options: [ + 4, + { ignoredNodes: ["CallExpression > ObjectExpression"] }, + ], + }, + { + code: unIndent` foo .bar `, - options: [4, { ignoredNodes: ["MemberExpression"] }] - }, - { - code: unIndent` + options: [4, { ignoredNodes: ["MemberExpression"] }], + }, + { + code: unIndent` $(function() { foo(); @@ -5147,48 +5210,60 @@ ruleTester.run("indent", rule, { }); `, - options: [4, { - ignoredNodes: ["Program > ExpressionStatement > CallExpression[callee.name='$'] > FunctionExpression > BlockStatement"] - }] - }, - { - code: unIndent` + options: [ + 4, + { + ignoredNodes: [ + "Program > ExpressionStatement > CallExpression[callee.name='$'] > FunctionExpression > BlockStatement", + ], + }, + ], + }, + { + code: unIndent` `, - options: [4, { ignoredNodes: ["JSXOpeningElement"] }] - }, - { - code: unIndent` + options: [4, { ignoredNodes: ["JSXOpeningElement"] }], + }, + { + code: unIndent` foo && `, - options: [4, { ignoredNodes: ["JSXElement", "JSXOpeningElement"] }] - }, - { - code: unIndent` + options: [4, { ignoredNodes: ["JSXElement", "JSXOpeningElement"] }], + }, + { + code: unIndent` (function($) { $(function() { foo; }); }()) `, - options: [4, { ignoredNodes: ["ExpressionStatement > CallExpression > FunctionExpression.callee > BlockStatement"] }] - }, - { - code: unIndent` + options: [ + 4, + { + ignoredNodes: [ + "ExpressionStatement > CallExpression > FunctionExpression.callee > BlockStatement", + ], + }, + ], + }, + { + code: unIndent` const value = ( condition ? valueIfTrue : valueIfFalse ); `, - options: [4, { ignoredNodes: ["ConditionalExpression"] }] - }, - { - code: unIndent` + options: [4, { ignoredNodes: ["ConditionalExpression"] }], + }, + { + code: unIndent` var a = 0, b = 0, c = 0; export default foo( a, @@ -5197,19 +5272,26 @@ ruleTester.run("indent", rule, { } ) `, - options: [4, { ignoredNodes: ["ExportDefaultDeclaration > CallExpression > ObjectExpression"] }], - languageOptions: { ecmaVersion: 6, sourceType: "module" } - }, - { - code: unIndent` + options: [ + 4, + { + ignoredNodes: [ + "ExportDefaultDeclaration > CallExpression > ObjectExpression", + ], + }, + ], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + }, + { + code: unIndent` foobar = baz ? qux : boop `, - options: [4, { ignoredNodes: ["ConditionalExpression"] }] - }, - { - code: unIndent` + options: [4, { ignoredNodes: ["ConditionalExpression"] }], + }, + { + code: unIndent` \` SELECT \${ @@ -5217,46 +5299,49 @@ ruleTester.run("indent", rule, { } FROM THE_DATABASE \` `, - options: [4, { ignoredNodes: ["TemplateLiteral"] }] - }, - { - code: unIndent` + options: [4, { ignoredNodes: ["TemplateLiteral"] }], + }, + { + code: unIndent` Text `, - options: [4, { ignoredNodes: ["JSXOpeningElement"] }] - }, - { - code: unIndent` + options: [4, { ignoredNodes: ["JSXOpeningElement"] }], + }, + { + code: unIndent` { \tvar x = 1, \t y = 2; } `, - options: ["tab"] - }, - { - code: unIndent` + options: ["tab"], + }, + { + code: unIndent` var x = 1, y = 2; var z; `, - options: ["tab", { ignoredNodes: ["VariableDeclarator"] }] - }, - { - code: unIndent` + options: ["tab", { ignoredNodes: ["VariableDeclarator"] }], + }, + { + code: unIndent` [ foo(), bar ] `, - options: ["tab", { ArrayExpression: "first", ignoredNodes: ["CallExpression"] }] - }, - { - code: unIndent` + options: [ + "tab", + { ArrayExpression: "first", ignoredNodes: ["CallExpression"] }, + ], + }, + { + code: unIndent` if (foo) { doSomething(); @@ -5264,10 +5349,10 @@ ruleTester.run("indent", rule, { doSomethingElse(); } `, - options: [4, { ignoreComments: true }] - }, - { - code: unIndent` + options: [4, { ignoreComments: true }], + }, + { + code: unIndent` if (foo) { doSomething(); @@ -5275,9 +5360,9 @@ ruleTester.run("indent", rule, { doSomethingElse(); } `, - options: [4, { ignoreComments: true }] - }, - unIndent` + options: [4, { ignoreComments: true }], + }, + unIndent` const obj = { foo () { return condition ? // comment @@ -5287,34 +5372,34 @@ ruleTester.run("indent", rule, { } `, - //---------------------------------------------------------------------- - // Comment alignment tests - //---------------------------------------------------------------------- - unIndent` + //---------------------------------------------------------------------- + // Comment alignment tests + //---------------------------------------------------------------------- + unIndent` if (foo) { // Comment can align with code immediately above even if "incorrect" alignment doSomething(); } `, - unIndent` + unIndent` if (foo) { doSomething(); // Comment can align with code immediately below even if "incorrect" alignment } `, - unIndent` + unIndent` if (foo) { // Comment can be in correct alignment even if not aligned with code above/below } `, - unIndent` + unIndent` if (foo) { // Comment can be in correct alignment even if gaps between (and not aligned with) code above/below } `, - unIndent` + unIndent` [{ foo }, @@ -5325,7 +5410,7 @@ ruleTester.run("indent", rule, { bar }]; `, - unIndent` + unIndent` [{ foo }, @@ -5336,75 +5421,75 @@ ruleTester.run("indent", rule, { bar }]; `, - unIndent` + unIndent` let foo // comment ;(async () => {})() `, - unIndent` + unIndent` let foo // comment ;(async () => {})() `, - unIndent` + unIndent` let foo // comment ;(async () => {})() `, - unIndent` + unIndent` let foo // comment ;(async () => {})() `, - unIndent` + unIndent` let foo /* comment */; (async () => {})() `, - unIndent` + unIndent` let foo /* comment */; (async () => {})() `, - unIndent` + unIndent` let foo /* comment */; (async () => {})() `, - unIndent` + unIndent` let foo /* comment */; (async () => {})() `, - unIndent` + unIndent` let foo /* comment */; (async () => {})() `, - unIndent` + unIndent` let foo /* comment */; (async () => {})() `, - unIndent` + unIndent` // comment ;(async () => {})() `, - unIndent` + unIndent` // comment ;(async () => {})() `, - unIndent` + unIndent` { let foo @@ -5413,27 +5498,27 @@ ruleTester.run("indent", rule, { ;(async () => {})() } `, - unIndent` + unIndent` { let foo // comment ;(async () => {})() } `, - unIndent` + unIndent` { // comment ;(async () => {})() } `, - unIndent` + unIndent` { // comment ;(async () => {})() } `, - unIndent` + unIndent` const foo = 1 const bar = foo @@ -5441,27 +5526,27 @@ ruleTester.run("indent", rule, { ;[1, 2, 3].forEach(() => {}) `, - unIndent` + unIndent` const foo = 1 const bar = foo /* comment */ ;[1, 2, 3].forEach(() => {}) `, - unIndent` + unIndent` const foo = 1 const bar = foo /* comment */ ;[1, 2, 3].forEach(() => {}) `, - unIndent` + unIndent` const foo = 1 const bar = foo /* comment */ ;[1, 2, 3].forEach(() => {}) `, - unIndent` + unIndent` const foo = 1 const bar = foo @@ -5469,49 +5554,49 @@ ruleTester.run("indent", rule, { [1, 2, 3].forEach(() => {}) `, - unIndent` + unIndent` const foo = 1 const bar = foo /* comment */; [1, 2, 3].forEach(() => {}) `, - unIndent` + unIndent` const foo = 1 const bar = foo /* comment */; [1, 2, 3].forEach(() => {}) `, - unIndent` + unIndent` const foo = 1 const bar = foo /* comment */; [1, 2, 3].forEach(() => {}) `, - unIndent` + unIndent` const foo = 1 const bar = foo /* comment */; [1, 2, 3].forEach(() => {}) `, - unIndent` + unIndent` const foo = 1 const bar = foo /* comment */; [1, 2, 3].forEach(() => {}) `, - unIndent` + unIndent` /* comment */ ;[1, 2, 3].forEach(() => {}) `, - unIndent` + unIndent` /* comment */ ;[1, 2, 3].forEach(() => {}) `, - unIndent` + unIndent` { const foo = 1 const bar = foo @@ -5521,7 +5606,7 @@ ruleTester.run("indent", rule, { ;[1, 2, 3].forEach(() => {}) } `, - unIndent` + unIndent` { const foo = 1 const bar = foo @@ -5529,35 +5614,35 @@ ruleTester.run("indent", rule, { ;[1, 2, 3].forEach(() => {}) } `, - unIndent` + unIndent` { /* comment */ ;[1, 2, 3].forEach(() => {}) } `, - unIndent` + unIndent` { /* comment */ ;[1, 2, 3].forEach(() => {}) } `, - // import expressions - { - code: unIndent` + // import expressions + { + code: unIndent` import( // before source // after ) `, - languageOptions: { ecmaVersion: 2020 } - }, + languageOptions: { ecmaVersion: 2020 }, + }, - // https://github.com/eslint/eslint/issues/12122 - { - code: unIndent` + // https://github.com/eslint/eslint/issues/12122 + { + code: unIndent` foo(() => { tag\` multiline @@ -5568,10 +5653,10 @@ ruleTester.run("indent", rule, { }); }); `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` { tag\` multiline @@ -5583,10 +5668,10 @@ ruleTester.run("indent", rule, { }); } `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` foo(() => { tagOne\` multiline @@ -5606,10 +5691,10 @@ ruleTester.run("indent", rule, { }); }); `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` { tagOne\` \${a} \${b} @@ -5629,10 +5714,10 @@ ruleTester.run("indent", rule, { }); }; `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` tagOne\`multiline \${a} \${b} template @@ -5649,10 +5734,10 @@ ruleTester.run("indent", rule, { }); }); `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` tagOne\`multiline template literal @@ -5666,36 +5751,36 @@ ruleTester.run("indent", rule, { }) }); `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` foo.bar\` template literal \`(() => { baz(); }) `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` foo.bar.baz\` template literal \`(() => { baz(); }) `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` foo .bar\` template literal \`(() => { baz(); }) `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` foo .bar .baz\` template @@ -5703,30 +5788,30 @@ ruleTester.run("indent", rule, { baz(); }) `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` foo.bar\` \${a} \${b} \`(() => { baz(); }) `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` foo.bar1.bar2\` \${a} \${b} \`(() => { baz(); }) `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` foo .bar1 .bar2\` @@ -5735,10 +5820,10 @@ ruleTester.run("indent", rule, { baz(); }) `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` foo .bar\` \${a} \${b} @@ -5746,10 +5831,10 @@ ruleTester.run("indent", rule, { baz(); }) `, - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` foo .test\` \${a} \${b} @@ -5757,11 +5842,11 @@ ruleTester.run("indent", rule, { baz(); }) `, - options: [4, { MemberExpression: 0 }], - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + options: [4, { MemberExpression: 0 }], + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` foo .test\` \${a} \${b} @@ -5769,38 +5854,50 @@ ruleTester.run("indent", rule, { baz(); }) `, - options: [4, { MemberExpression: 2 }], - languageOptions: { ecmaVersion: 2015 } - }, - { - code: unIndent` + options: [4, { MemberExpression: 2 }], + languageOptions: { ecmaVersion: 2015 }, + }, + { + code: unIndent` const foo = async (arg1, arg2) => { return arg1 + arg2; } `, - options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }] - }, - { - code: unIndent` + options: [ + 2, + { + FunctionDeclaration: { parameters: "first" }, + FunctionExpression: { parameters: "first" }, + }, + ], + }, + { + code: unIndent` const foo = async /* some comments */(arg1, arg2) => { return arg1 + arg2; } `, - options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }] - }, - { - code: unIndent` + options: [ + 2, + { + FunctionDeclaration: { parameters: "first" }, + FunctionExpression: { parameters: "first" }, + }, + ], + }, + { + code: unIndent` const a = async b => {} `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` const foo = (arg1, arg2) => async (arr1, arr2) => @@ -5808,71 +5905,107 @@ ruleTester.run("indent", rule, { return arg1 + arg2; } `, - options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }] - }, - { - code: unIndent` + options: [ + 2, + { + FunctionDeclaration: { parameters: "first" }, + FunctionExpression: { parameters: "first" }, + }, + ], + }, + { + code: unIndent` const foo = async (arg1, arg2) => { return arg1 + arg2; } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` const foo = async /*comments*/(arg1, arg2) => { return arg1 + arg2; } `, - options: [2] - }, - { - code: unIndent` + options: [2], + }, + { + code: unIndent` const foo = async (arg1, arg2) => { return arg1 + arg2; } `, - options: [2, { FunctionDeclaration: { parameters: 4 }, FunctionExpression: { parameters: 4 } }] - }, - { - code: unIndent` + options: [ + 2, + { + FunctionDeclaration: { parameters: 4 }, + FunctionExpression: { parameters: 4 }, + }, + ], + }, + { + code: unIndent` const foo = (arg1, arg2) => { return arg1 + arg2; } `, - options: [2, { FunctionDeclaration: { parameters: 4 }, FunctionExpression: { parameters: 4 } }] - }, - { - code: unIndent` + options: [ + 2, + { + FunctionDeclaration: { parameters: 4 }, + FunctionExpression: { parameters: 4 }, + }, + ], + }, + { + code: unIndent` async function fn(ar1, ar2){} `, - options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }] - }, - { - code: unIndent` + options: [ + 2, + { + FunctionDeclaration: { parameters: "first" }, + FunctionExpression: { parameters: "first" }, + }, + ], + }, + { + code: unIndent` async function /* some comments */ fn(ar1, ar2){} `, - options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }] - }, - { - code: unIndent` + options: [ + 2, + { + FunctionDeclaration: { parameters: "first" }, + FunctionExpression: { parameters: "first" }, + }, + ], + }, + { + code: unIndent` async /* some comments */ function fn(ar1, ar2){} `, - options: [2, { FunctionDeclaration: { parameters: "first" }, FunctionExpression: { parameters: "first" } }] - }, - { - code: unIndent` + options: [ + 2, + { + FunctionDeclaration: { parameters: "first" }, + FunctionExpression: { parameters: "first" }, + }, + ], + }, + { + code: unIndent` class C { static { foo(); @@ -5880,11 +6013,11 @@ ruleTester.run("indent", rule, { } } `, - options: [2], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [2], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { foo(); @@ -5892,11 +6025,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { foo(); @@ -5904,11 +6037,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4, { StaticBlock: { body: 2 } }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4, { StaticBlock: { body: 2 } }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { foo(); @@ -5916,11 +6049,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4, { StaticBlock: { body: 0 } }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4, { StaticBlock: { body: 0 } }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { \tstatic { \t\tfoo(); @@ -5928,11 +6061,11 @@ ruleTester.run("indent", rule, { \t} } `, - options: ["tab"], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: ["tab"], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { \tstatic { \t\t\tfoo(); @@ -5940,11 +6073,11 @@ ruleTester.run("indent", rule, { \t} } `, - options: ["tab", { StaticBlock: { body: 2 } }], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: ["tab", { StaticBlock: { body: 2 } }], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { @@ -5953,11 +6086,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { var x, @@ -5965,11 +6098,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { @@ -5978,11 +6111,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { if (foo) { @@ -5991,11 +6124,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { { @@ -6004,11 +6137,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static {} @@ -6020,11 +6153,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { static { @@ -6037,11 +6170,11 @@ ruleTester.run("indent", rule, { } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { x = 1; @@ -6054,11 +6187,11 @@ ruleTester.run("indent", rule, { } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { method1(param) { @@ -6075,11 +6208,11 @@ ruleTester.run("indent", rule, { } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` function f() { class C { static { @@ -6089,11 +6222,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4], - languageOptions: { ecmaVersion: 2022 } - }, - { - code: unIndent` + options: [4], + languageOptions: { ecmaVersion: 2022 }, + }, + { + code: unIndent` class C { method() { foo; @@ -6103,106 +6236,109 @@ ruleTester.run("indent", rule, { } } `, - options: [4, { FunctionExpression: { body: 2 }, StaticBlock: { body: 2 } }], - languageOptions: { ecmaVersion: 2022 } - }, + options: [ + 4, + { FunctionExpression: { body: 2 }, StaticBlock: { body: 2 } }, + ], + languageOptions: { ecmaVersion: 2022 }, + }, - // https://github.com/eslint/eslint/issues/15930 - { - code: unIndent` + // https://github.com/eslint/eslint/issues/15930 + { + code: unIndent` if (2 > 1) \tconsole.log('a') ;[1, 2, 3].forEach(x=>console.log(x)) `, - options: ["tab"] - }, - { - code: unIndent` + options: ["tab"], + }, + { + code: unIndent` if (2 > 1) console.log('a') ;[1, 2, 3].forEach(x=>console.log(x)) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) bar(); baz() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) bar() ;baz() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) bar(); baz(); `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) bar() ; baz() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) bar() ;baz() qux() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) bar() ;else baz() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) bar() else baz() ;qux() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) if (bar) baz() ;qux() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) bar() else if (baz) qux() ;quux() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) if (bar) baz() @@ -6210,170 +6346,170 @@ ruleTester.run("indent", rule, { qux() ;quux() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) bar() ; baz() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) ; baz() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) ;baz() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo); else baz() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) ; else baz() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` if (foo) ;else baz() `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` do foo(); while (bar) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` do foo() ;while (bar) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` do foo(); while (bar) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` do foo() ;while (bar) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` do; while (foo) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` do ; while (foo) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` do ;while (foo) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` while (2 > 1) console.log('a') ;[1, 2, 3].forEach(x=>console.log(x)) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` for (;;) console.log('a') ;[1, 2, 3].forEach(x=>console.log(x)) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` for (a in b) console.log('a') ;[1, 2, 3].forEach(x=>console.log(x)) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` for (a of b) console.log('a') ;[1, 2, 3].forEach(x=>console.log(x)) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` with (a) console.log(b) ;[1, 2, 3].forEach(x=>console.log(x)) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` label: for (a of b) console.log('a') ;[1, 2, 3].forEach(x=>console.log(x)) `, - options: [4] - }, - { - code: unIndent` + options: [4], + }, + { + code: unIndent` label: for (a of b) console.log('a') ;[1, 2, 3].forEach(x=>console.log(x)) `, - options: [4] - }, + options: [4], + }, - // https://github.com/eslint/eslint/issues/17316 - { - code: unIndent` + // https://github.com/eslint/eslint/issues/17316 + { + code: unIndent` if (foo) \tif (bar) doSomething(); \telse doSomething(); @@ -6381,9 +6517,9 @@ ruleTester.run("indent", rule, { \tif (bar) doSomething(); \telse doSomething(); `, - options: ["tab"] - }, - unIndent` + options: ["tab"], + }, + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6391,7 +6527,7 @@ ruleTester.run("indent", rule, { if (bar) doSomething(); else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6400,7 +6536,7 @@ ruleTester.run("indent", rule, { doSomething(); else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6409,7 +6545,7 @@ ruleTester.run("indent", rule, { else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6419,14 +6555,14 @@ ruleTester.run("indent", rule, { else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); else if (bar) doSomething(); else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6434,7 +6570,7 @@ ruleTester.run("indent", rule, { doSomething(); else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6442,7 +6578,7 @@ ruleTester.run("indent", rule, { else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6451,7 +6587,7 @@ ruleTester.run("indent", rule, { else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6464,7 +6600,7 @@ ruleTester.run("indent", rule, { else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6476,14 +6612,14 @@ ruleTester.run("indent", rule, { else doSomething(); else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); else if (foo) doSomething(); else doSomething(); `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6491,7 +6627,7 @@ ruleTester.run("indent", rule, { doSomething(); } `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6500,7 +6636,7 @@ ruleTester.run("indent", rule, { doSomething(); } `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6509,7 +6645,7 @@ ruleTester.run("indent", rule, { doSomething(); } `, - unIndent` + unIndent` if (foo) if (bar) doSomething(); else doSomething(); @@ -6518,44 +6654,48 @@ ruleTester.run("indent", rule, { { doSomething(); } - ` - ], + `, + ], - invalid: [ - { - code: unIndent` + invalid: [ + { + code: unIndent` var a = b; if (a) { b(); } `, - output: unIndent` + output: unIndent` var a = b; if (a) { b(); } `, - options: [2], - errors: expectedErrors([[3, 2, 0, "Identifier"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([[3, 2, 0, "Identifier"]]), + }, + { + code: unIndent` require('http').request({hostname: 'localhost', port: 80}, function(res) { res.end(); }); `, - output: unIndent` + output: unIndent` require('http').request({hostname: 'localhost', port: 80}, function(res) { res.end(); }); `, - options: [2], - errors: expectedErrors([[2, 2, 18, "Identifier"], [3, 2, 4, "Identifier"], [4, 0, 2, "Punctuator"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [2, 2, 18, "Identifier"], + [3, 2, 4, "Identifier"], + [4, 0, 2, "Punctuator"], + ]), + }, + { + code: unIndent` if (array.some(function(){ return true; })) { @@ -6564,7 +6704,7 @@ ruleTester.run("indent", rule, { c++; // <- } `, - output: unIndent` + output: unIndent` if (array.some(function(){ return true; })) { @@ -6573,161 +6713,177 @@ ruleTester.run("indent", rule, { c++; // <- } `, - options: [2], - errors: expectedErrors([[4, 2, 0, "Identifier"], [6, 2, 4, "Identifier"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [4, 2, 0, "Identifier"], + [6, 2, 4, "Identifier"], + ]), + }, + { + code: unIndent` if (a){ \tb=c; \t\tc=d; e=f; } `, - output: unIndent` + output: unIndent` if (a){ \tb=c; \tc=d; \te=f; } `, - options: ["tab"], - errors: expectedErrors("tab", [[3, 1, 2, "Identifier"], [4, 1, 0, "Identifier"]]) - }, - { - code: unIndent` + options: ["tab"], + errors: expectedErrors("tab", [ + [3, 1, 2, "Identifier"], + [4, 1, 0, "Identifier"], + ]), + }, + { + code: unIndent` if (a){ b=c; c=d; e=f; } `, - output: unIndent` + output: unIndent` if (a){ b=c; c=d; e=f; } `, - options: [4], - errors: expectedErrors([[3, 4, 6, "Identifier"], [4, 4, 1, "Identifier"]]) - }, - { - code: fixture, - output: fixedFixture, - options: [2, { SwitchCase: 1, MemberExpression: 1, CallExpression: { arguments: "off" } }], - errors: expectedErrors([ - [5, 2, 4, "Keyword"], - [6, 2, 0, "Line"], - [10, 4, 6, "Punctuator"], - [11, 2, 4, "Punctuator"], - - [15, 4, 2, "Identifier"], - [16, 2, 4, "Punctuator"], - [23, 2, 4, "Punctuator"], - [29, 2, 4, "Keyword"], - [30, 4, 6, "Identifier"], - [36, 4, 6, "Identifier"], - [38, 2, 4, "Punctuator"], - [39, 4, 2, "Identifier"], - [40, 2, 0, "Punctuator"], - [54, 2, 4, "Punctuator"], - [114, 4, 2, "Keyword"], - [120, 4, 6, "Keyword"], - [124, 4, 2, "Keyword"], - [134, 4, 6, "Keyword"], - [138, 2, 3, "Punctuator"], - [139, 2, 3, "Punctuator"], - [143, 4, 0, "Identifier"], - [144, 6, 2, "Punctuator"], - [145, 6, 2, "Punctuator"], - [151, 4, 6, "Identifier"], - [152, 6, 8, "Punctuator"], - [153, 6, 8, "Punctuator"], - [159, 4, 2, "Identifier"], - [161, 4, 6, "Identifier"], - [175, 2, 0, "Identifier"], - [177, 2, 4, "Identifier"], - [189, 2, 0, "Keyword"], - [192, 6, 18, "Identifier"], - [193, 6, 4, "Identifier"], - [195, 6, 8, "Identifier"], - [228, 5, 4, "Identifier"], - [231, 3, 2, "Punctuator"], - [245, 0, 2, "Punctuator"], - [248, 0, 2, "Punctuator"], - [304, 4, 6, "Identifier"], - [306, 4, 8, "Identifier"], - [307, 2, 4, "Punctuator"], - [308, 2, 4, "Identifier"], - [311, 4, 6, "Identifier"], - [312, 4, 6, "Identifier"], - [313, 4, 6, "Identifier"], - [314, 2, 4, "Punctuator"], - [315, 2, 4, "Identifier"], - [318, 4, 6, "Identifier"], - [319, 4, 6, "Identifier"], - [320, 4, 6, "Identifier"], - [321, 2, 4, "Punctuator"], - [322, 2, 4, "Identifier"], - [326, 2, 1, "Numeric"], - [327, 2, 1, "Numeric"], - [328, 2, 1, "Numeric"], - [329, 2, 1, "Numeric"], - [330, 2, 1, "Numeric"], - [331, 2, 1, "Numeric"], - [332, 2, 1, "Numeric"], - [333, 2, 1, "Numeric"], - [334, 2, 1, "Numeric"], - [335, 2, 1, "Numeric"], - [340, 2, 4, "Identifier"], - [341, 2, 0, "Identifier"], - [344, 2, 4, "Identifier"], - [345, 2, 0, "Identifier"], - [348, 2, 4, "Identifier"], - [349, 2, 0, "Identifier"], - [355, 2, 0, "Identifier"], - [357, 2, 4, "Identifier"], - [361, 4, 6, "Identifier"], - [362, 2, 4, "Punctuator"], - [363, 2, 4, "Identifier"], - [368, 2, 0, "Keyword"], - [370, 2, 4, "Keyword"], - [374, 4, 6, "Keyword"], - [376, 4, 2, "Keyword"], - [383, 2, 0, "Identifier"], - [385, 2, 4, "Identifier"], - [390, 2, 0, "Identifier"], - [392, 2, 4, "Identifier"], - [409, 2, 0, "Identifier"], - [410, 2, 4, "Identifier"], - [416, 2, 0, "Identifier"], - [417, 2, 4, "Identifier"], - [418, 0, 4, "Punctuator"], - [422, 2, 4, "Identifier"], - [423, 2, 0, "Identifier"], - [427, 2, 6, "Identifier"], - [428, 2, 8, "Identifier"], - [429, 2, 4, "Identifier"], - [430, 0, 4, "Punctuator"], - [433, 2, 4, "Identifier"], - [434, 0, 4, "Punctuator"], - [437, 2, 0, "Identifier"], - [438, 0, 4, "Punctuator"], - [442, 2, 4, "Identifier"], - [443, 2, 4, "Identifier"], - [444, 0, 2, "Punctuator"], - [451, 2, 0, "Identifier"], - [453, 2, 4, "Identifier"], - [499, 6, 8, "Punctuator"], - [500, 8, 6, "Identifier"], - [504, 4, 6, "Punctuator"], - [505, 6, 8, "Identifier"], - [506, 4, 8, "Punctuator"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([ + [3, 4, 6, "Identifier"], + [4, 4, 1, "Identifier"], + ]), + }, + { + code: fixture, + output: fixedFixture, + options: [ + 2, + { + SwitchCase: 1, + MemberExpression: 1, + CallExpression: { arguments: "off" }, + }, + ], + errors: expectedErrors([ + [5, 2, 4, "Keyword"], + [6, 2, 0, "Line"], + [10, 4, 6, "Punctuator"], + [11, 2, 4, "Punctuator"], + + [15, 4, 2, "Identifier"], + [16, 2, 4, "Punctuator"], + [23, 2, 4, "Punctuator"], + [29, 2, 4, "Keyword"], + [30, 4, 6, "Identifier"], + [36, 4, 6, "Identifier"], + [38, 2, 4, "Punctuator"], + [39, 4, 2, "Identifier"], + [40, 2, 0, "Punctuator"], + [54, 2, 4, "Punctuator"], + [114, 4, 2, "Keyword"], + [120, 4, 6, "Keyword"], + [124, 4, 2, "Keyword"], + [134, 4, 6, "Keyword"], + [138, 2, 3, "Punctuator"], + [139, 2, 3, "Punctuator"], + [143, 4, 0, "Identifier"], + [144, 6, 2, "Punctuator"], + [145, 6, 2, "Punctuator"], + [151, 4, 6, "Identifier"], + [152, 6, 8, "Punctuator"], + [153, 6, 8, "Punctuator"], + [159, 4, 2, "Identifier"], + [161, 4, 6, "Identifier"], + [175, 2, 0, "Identifier"], + [177, 2, 4, "Identifier"], + [189, 2, 0, "Keyword"], + [192, 6, 18, "Identifier"], + [193, 6, 4, "Identifier"], + [195, 6, 8, "Identifier"], + [228, 5, 4, "Identifier"], + [231, 3, 2, "Punctuator"], + [245, 0, 2, "Punctuator"], + [248, 0, 2, "Punctuator"], + [304, 4, 6, "Identifier"], + [306, 4, 8, "Identifier"], + [307, 2, 4, "Punctuator"], + [308, 2, 4, "Identifier"], + [311, 4, 6, "Identifier"], + [312, 4, 6, "Identifier"], + [313, 4, 6, "Identifier"], + [314, 2, 4, "Punctuator"], + [315, 2, 4, "Identifier"], + [318, 4, 6, "Identifier"], + [319, 4, 6, "Identifier"], + [320, 4, 6, "Identifier"], + [321, 2, 4, "Punctuator"], + [322, 2, 4, "Identifier"], + [326, 2, 1, "Numeric"], + [327, 2, 1, "Numeric"], + [328, 2, 1, "Numeric"], + [329, 2, 1, "Numeric"], + [330, 2, 1, "Numeric"], + [331, 2, 1, "Numeric"], + [332, 2, 1, "Numeric"], + [333, 2, 1, "Numeric"], + [334, 2, 1, "Numeric"], + [335, 2, 1, "Numeric"], + [340, 2, 4, "Identifier"], + [341, 2, 0, "Identifier"], + [344, 2, 4, "Identifier"], + [345, 2, 0, "Identifier"], + [348, 2, 4, "Identifier"], + [349, 2, 0, "Identifier"], + [355, 2, 0, "Identifier"], + [357, 2, 4, "Identifier"], + [361, 4, 6, "Identifier"], + [362, 2, 4, "Punctuator"], + [363, 2, 4, "Identifier"], + [368, 2, 0, "Keyword"], + [370, 2, 4, "Keyword"], + [374, 4, 6, "Keyword"], + [376, 4, 2, "Keyword"], + [383, 2, 0, "Identifier"], + [385, 2, 4, "Identifier"], + [390, 2, 0, "Identifier"], + [392, 2, 4, "Identifier"], + [409, 2, 0, "Identifier"], + [410, 2, 4, "Identifier"], + [416, 2, 0, "Identifier"], + [417, 2, 4, "Identifier"], + [418, 0, 4, "Punctuator"], + [422, 2, 4, "Identifier"], + [423, 2, 0, "Identifier"], + [427, 2, 6, "Identifier"], + [428, 2, 8, "Identifier"], + [429, 2, 4, "Identifier"], + [430, 0, 4, "Punctuator"], + [433, 2, 4, "Identifier"], + [434, 0, 4, "Punctuator"], + [437, 2, 0, "Identifier"], + [438, 0, 4, "Punctuator"], + [442, 2, 4, "Identifier"], + [443, 2, 4, "Identifier"], + [444, 0, 2, "Punctuator"], + [451, 2, 0, "Identifier"], + [453, 2, 4, "Identifier"], + [499, 6, 8, "Punctuator"], + [500, 8, 6, "Identifier"], + [504, 4, 6, "Punctuator"], + [505, 6, 8, "Identifier"], + [506, 4, 8, "Punctuator"], + ]), + }, + { + code: unIndent` switch(value){ case "1": a(); @@ -6740,7 +6896,7 @@ ruleTester.run("indent", rule, { break; } `, - output: unIndent` + output: unIndent` switch(value){ case "1": a(); @@ -6753,29 +6909,35 @@ ruleTester.run("indent", rule, { break; } `, - options: [4, { SwitchCase: 1 }], - errors: expectedErrors([[4, 8, 4, "Keyword"], [7, 8, 4, "Keyword"]]) - }, - { - code: unIndent` + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [4, 8, 4, "Keyword"], + [7, 8, 4, "Keyword"], + ]), + }, + { + code: unIndent` var x = 0 && { a: 1, b: 2 }; `, - output: unIndent` + output: unIndent` var x = 0 && { a: 1, b: 2 }; `, - options: [4], - errors: expectedErrors([[3, 8, 7, "Identifier"], [4, 8, 10, "Identifier"]]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([ + [3, 8, 7, "Identifier"], + [4, 8, 10, "Identifier"], + ]), + }, + { + code: unIndent` switch(value){ case "1": a(); @@ -6787,7 +6949,7 @@ ruleTester.run("indent", rule, { break; } `, - output: unIndent` + output: unIndent` switch(value){ case "1": a(); @@ -6799,11 +6961,11 @@ ruleTester.run("indent", rule, { break; } `, - options: [4, { SwitchCase: 1 }], - errors: expectedErrors([9, 8, 4, "Keyword"]) - }, - { - code: unIndent` + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([9, 8, 4, "Keyword"]), + }, + { + code: unIndent` switch(value){ case "1": case "2": @@ -6823,7 +6985,7 @@ ruleTester.run("indent", rule, { break; } `, - output: unIndent` + output: unIndent` switch(value){ case "1": case "2": @@ -6843,11 +7005,15 @@ ruleTester.run("indent", rule, { break; } `, - options: [4, { SwitchCase: 1 }], - errors: expectedErrors([[11, 8, 4, "Keyword"], [14, 8, 4, "Keyword"], [17, 8, 4, "Keyword"]]) - }, - { - code: unIndent` + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [11, 8, 4, "Keyword"], + [14, 8, 4, "Keyword"], + [17, 8, 4, "Keyword"], + ]), + }, + { + code: unIndent` switch(value){ case "1": a(); @@ -6858,7 +7024,7 @@ ruleTester.run("indent", rule, { break; } `, - output: unIndent` + output: unIndent` switch(value){ case "1": a(); @@ -6869,33 +7035,33 @@ ruleTester.run("indent", rule, { break; } `, - options: [4], - errors: expectedErrors([ - [3, 4, 8, "Identifier"], - [4, 4, 8, "Keyword"], - [5, 0, 4, "Keyword"], - [6, 4, 8, "Keyword"], - [7, 0, 4, "Keyword"], - [8, 4, 8, "Keyword"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([ + [3, 4, 8, "Identifier"], + [4, 4, 8, "Keyword"], + [5, 0, 4, "Keyword"], + [6, 4, 8, "Keyword"], + [7, 0, 4, "Keyword"], + [8, 4, 8, "Keyword"], + ]), + }, + { + code: unIndent` var obj = {foo: 1, bar: 2}; with (obj) { console.log(foo + bar); } `, - output: unIndent` + output: unIndent` var obj = {foo: 1, bar: 2}; with (obj) { console.log(foo + bar); } `, - errors: expectedErrors([3, 4, 0, "Identifier"]) - }, - { - code: unIndent` + errors: expectedErrors([3, 4, 0, "Identifier"]), + }, + { + code: unIndent` switch (a) { case '1': b(); @@ -6905,7 +7071,7 @@ ruleTester.run("indent", rule, { break; } `, - output: unIndent` + output: unIndent` switch (a) { case '1': b(); @@ -6915,87 +7081,81 @@ ruleTester.run("indent", rule, { break; } `, - options: [4, { SwitchCase: 1 }], - errors: expectedErrors([ - [2, 4, 0, "Keyword"], - [3, 8, 0, "Identifier"], - [4, 8, 0, "Keyword"], - [5, 4, 0, "Keyword"], - [6, 8, 0, "Identifier"], - [7, 8, 0, "Keyword"] - ]) - }, - { - code: unIndent` + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [2, 4, 0, "Keyword"], + [3, 8, 0, "Identifier"], + [4, 8, 0, "Keyword"], + [5, 4, 0, "Keyword"], + [6, 8, 0, "Identifier"], + [7, 8, 0, "Keyword"], + ]), + }, + { + code: unIndent` var foo = function(){ foo .bar } `, - output: unIndent` + output: unIndent` var foo = function(){ foo .bar } `, - options: [4, { MemberExpression: 1 }], - errors: expectedErrors( - [3, 8, 10, "Punctuator"] - ) - }, - { - code: unIndent` + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([3, 8, 10, "Punctuator"]), + }, + { + code: unIndent` ( foo .bar ) `, - output: unIndent` + output: unIndent` ( foo .bar ) `, - errors: expectedErrors([3, 8, 4, "Punctuator"]) - }, - { - code: unIndent` + errors: expectedErrors([3, 8, 4, "Punctuator"]), + }, + { + code: unIndent` var foo = function(){ foo .bar } `, - output: unIndent` + output: unIndent` var foo = function(){ foo .bar } `, - options: [4, { MemberExpression: 2 }], - errors: expectedErrors( - [3, 12, 13, "Punctuator"] - ) - }, - { - code: unIndent` + options: [4, { MemberExpression: 2 }], + errors: expectedErrors([3, 12, 13, "Punctuator"]), + }, + { + code: unIndent` var foo = () => { foo .bar } `, - output: unIndent` + output: unIndent` var foo = () => { foo .bar } `, - options: [4, { MemberExpression: 2 }], - errors: expectedErrors( - [3, 12, 13, "Punctuator"] - ) - }, - { - code: unIndent` + options: [4, { MemberExpression: 2 }], + errors: expectedErrors([3, 12, 13, "Punctuator"]), + }, + { + code: unIndent` TestClass.prototype.method = function () { return Promise.resolve(3) .then(function (x) { @@ -7003,7 +7163,7 @@ ruleTester.run("indent", rule, { }); }; `, - output: unIndent` + output: unIndent` TestClass.prototype.method = function () { return Promise.resolve(3) .then(function (x) { @@ -7011,25 +7171,23 @@ ruleTester.run("indent", rule, { }); }; `, - options: [2, { MemberExpression: 1 }], - errors: expectedErrors([3, 4, 6, "Punctuator"]) - }, - { - code: unIndent` + options: [2, { MemberExpression: 1 }], + errors: expectedErrors([3, 4, 6, "Punctuator"]), + }, + { + code: unIndent` while (a) b(); `, - output: unIndent` + output: unIndent` while (a) b(); `, - options: [4], - errors: expectedErrors([ - [2, 4, 0, "Identifier"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([[2, 4, 0, "Identifier"]]), + }, + { + code: unIndent` lmn = [{ a: 1 }, @@ -7040,7 +7198,7 @@ ruleTester.run("indent", rule, { x: 2 }]; `, - output: unIndent` + output: unIndent` lmn = [{ a: 1 }, @@ -7051,140 +7209,138 @@ ruleTester.run("indent", rule, { x: 2 }]; `, - errors: expectedErrors([ - [2, 4, 8, "Identifier"], - [3, 0, 4, "Punctuator"], - [4, 0, 4, "Punctuator"], - [5, 4, 8, "Identifier"], - [6, 0, 4, "Punctuator"], - [7, 0, 4, "Punctuator"], - [8, 4, 8, "Identifier"] - ]) - }, - { - code: unIndent` + errors: expectedErrors([ + [2, 4, 8, "Identifier"], + [3, 0, 4, "Punctuator"], + [4, 0, 4, "Punctuator"], + [5, 4, 8, "Identifier"], + [6, 0, 4, "Punctuator"], + [7, 0, 4, "Punctuator"], + [8, 4, 8, "Identifier"], + ]), + }, + { + code: unIndent` for (var foo = 1; foo < 10; foo++) {} `, - output: unIndent` + output: unIndent` for (var foo = 1; foo < 10; foo++) {} `, - errors: expectedErrors([[2, 4, 0, "Identifier"], [3, 4, 0, "Identifier"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [2, 4, 0, "Identifier"], + [3, 4, 0, "Identifier"], + ]), + }, + { + code: unIndent` for ( var foo = 1; foo < 10; foo++ ) {} `, - output: unIndent` + output: unIndent` for ( var foo = 1; foo < 10; foo++ ) {} `, - errors: expectedErrors([[2, 4, 0, "Keyword"], [3, 4, 0, "Identifier"], [4, 4, 0, "Identifier"], [5, 0, 4, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [2, 4, 0, "Keyword"], + [3, 4, 0, "Identifier"], + [4, 4, 0, "Identifier"], + [5, 0, 4, "Punctuator"], + ]), + }, + { + code: unIndent` for (;;) b(); `, - output: unIndent` + output: unIndent` for (;;) b(); `, - options: [4], - errors: expectedErrors([ - [2, 4, 0, "Identifier"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([[2, 4, 0, "Identifier"]]), + }, + { + code: unIndent` for (a in x) b(); `, - output: unIndent` + output: unIndent` for (a in x) b(); `, - options: [4], - errors: expectedErrors([ - [2, 4, 0, "Identifier"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([[2, 4, 0, "Identifier"]]), + }, + { + code: unIndent` do b(); while(true) `, - output: unIndent` + output: unIndent` do b(); while(true) `, - options: [4], - errors: expectedErrors([ - [2, 4, 0, "Identifier"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([[2, 4, 0, "Identifier"]]), + }, + { + code: unIndent` with(a) b(); `, - output: unIndent` + output: unIndent` with(a) b(); `, - options: [4], - errors: expectedErrors([ - [2, 4, 0, "Identifier"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([[2, 4, 0, "Identifier"]]), + }, + { + code: unIndent` if(true) b(); `, - output: unIndent` + output: unIndent` if(true) b(); `, - options: [4], - errors: expectedErrors([ - [2, 4, 0, "Identifier"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([[2, 4, 0, "Identifier"]]), + }, + { + code: unIndent` var test = { a: 1, b: 2 }; `, - output: unIndent` + output: unIndent` var test = { a: 1, b: 2 }; `, - options: [2], - errors: expectedErrors([ - [2, 2, 6, "Identifier"], - [3, 2, 4, "Identifier"], - [4, 0, 4, "Punctuator"] - ]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [2, 2, 6, "Identifier"], + [3, 2, 4, "Identifier"], + [4, 0, 4, "Punctuator"], + ]), + }, + { + code: unIndent` var a = function() { a++; b++; @@ -7192,7 +7348,7 @@ ruleTester.run("indent", rule, { }, b; `, - output: unIndent` + output: unIndent` var a = function() { a++; b++; @@ -7200,206 +7356,210 @@ ruleTester.run("indent", rule, { }, b; `, - options: [4], - errors: expectedErrors([ - [2, 8, 6, "Identifier"], - [3, 8, 4, "Identifier"], - [4, 8, 10, "Identifier"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([ + [2, 8, 6, "Identifier"], + [3, 8, 4, "Identifier"], + [4, 8, 10, "Identifier"], + ]), + }, + { + code: unIndent` var a = 1, b = 2, c = 3; `, - output: unIndent` + output: unIndent` var a = 1, b = 2, c = 3; `, - options: [4], - errors: expectedErrors([ - [2, 4, 0, "Identifier"], - [3, 4, 0, "Identifier"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([ + [2, 4, 0, "Identifier"], + [3, 4, 0, "Identifier"], + ]), + }, + { + code: unIndent` [a, b, c].forEach((index) => { index; }); `, - output: unIndent` + output: unIndent` [a, b, c].forEach((index) => { index; }); `, - options: [4], - errors: expectedErrors([ - [3, 4, 8, "Identifier"], - [4, 0, 4, "Punctuator"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([ + [3, 4, 8, "Identifier"], + [4, 0, 4, "Punctuator"], + ]), + }, + { + code: unIndent` [a, b, c].forEach(function(index){ return index; }); `, - output: unIndent` + output: unIndent` [a, b, c].forEach(function(index){ return index; }); `, - options: [4], - errors: expectedErrors([ - [2, 4, 0, "Identifier"], - [3, 4, 2, "Keyword"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([ + [2, 4, 0, "Identifier"], + [3, 4, 2, "Keyword"], + ]), + }, + { + code: unIndent` [a, b, c].forEach(function(index){ return index; }); `, - output: unIndent` + output: unIndent` [a, b, c].forEach(function(index){ return index; }); `, - options: [4], - errors: expectedErrors([ - [2, 4, 2, "Keyword"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([[2, 4, 2, "Keyword"]]), + }, + { + code: unIndent` (foo) .bar([ baz ]); `, - output: unIndent` + output: unIndent` (foo) .bar([ baz ]); `, - options: [4, { MemberExpression: 1 }], - errors: expectedErrors([[3, 8, 4, "Identifier"], [4, 4, 0, "Punctuator"]]) - }, - { - code: unIndent` + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([ + [3, 8, 4, "Identifier"], + [4, 4, 0, "Punctuator"], + ]), + }, + { + code: unIndent` var x = ['a', 'b', 'c' ]; `, - output: unIndent` + output: unIndent` var x = ['a', 'b', 'c' ]; `, - options: [4], - errors: expectedErrors([ - [2, 4, 9, "String"], - [3, 4, 9, "String"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([ + [2, 4, 9, "String"], + [3, 4, 9, "String"], + ]), + }, + { + code: unIndent` var x = [ 'a', 'b', 'c' ]; `, - output: unIndent` + output: unIndent` var x = [ 'a', 'b', 'c' ]; `, - options: [4], - errors: expectedErrors([ - [2, 4, 9, "String"], - [3, 4, 9, "String"], - [4, 4, 9, "String"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([ + [2, 4, 9, "String"], + [3, 4, 9, "String"], + [4, 4, 9, "String"], + ]), + }, + { + code: unIndent` var x = [ 'a', 'b', 'c', 'd']; `, - output: unIndent` + output: unIndent` var x = [ 'a', 'b', 'c', 'd']; `, - options: [4], - errors: expectedErrors([ - [2, 4, 9, "String"], - [3, 4, 9, "String"], - [4, 4, 9, "String"], - [5, 4, 0, "String"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([ + [2, 4, 9, "String"], + [3, 4, 9, "String"], + [4, 4, 9, "String"], + [5, 4, 0, "String"], + ]), + }, + { + code: unIndent` var x = [ 'a', 'b', 'c' ]; `, - output: unIndent` + output: unIndent` var x = [ 'a', 'b', 'c' ]; `, - options: [4], - errors: expectedErrors([ - [2, 4, 9, "String"], - [3, 4, 9, "String"], - [4, 4, 9, "String"], - [5, 0, 2, "Punctuator"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([ + [2, 4, 9, "String"], + [3, 4, 9, "String"], + [4, 4, 9, "String"], + [5, 0, 2, "Punctuator"], + ]), + }, + { + code: unIndent` [[ ], function( foo ) {} ] `, - output: unIndent` + output: unIndent` [[ ], function( foo ) {} ] `, - errors: expectedErrors([[3, 4, 8, "Identifier"], [4, 0, 4, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [3, 4, 8, "Identifier"], + [4, 0, 4, "Punctuator"], + ]), + }, + { + code: unIndent` define([ 'foo' ], function( @@ -7409,7 +7569,7 @@ ruleTester.run("indent", rule, { } ) `, - output: unIndent` + output: unIndent` define([ 'foo' ], function( @@ -7419,27 +7579,30 @@ ruleTester.run("indent", rule, { } ) `, - errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [4, 4, 8, "Identifier"], + [5, 0, 4, "Punctuator"], + ]), + }, + { + code: unIndent` while (1 < 2) console.log('foo') console.log('bar') `, - output: unIndent` + output: unIndent` while (1 < 2) console.log('foo') console.log('bar') `, - options: [2], - errors: expectedErrors([ - [2, 2, 0, "Identifier"], - [3, 0, 2, "Identifier"] - ]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [2, 2, 0, "Identifier"], + [3, 0, 2, "Identifier"], + ]), + }, + { + code: unIndent` function salutation () { switch (1) { case 0: return console.log('hi') @@ -7447,7 +7610,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` function salutation () { switch (1) { case 0: return console.log('hi') @@ -7455,27 +7618,23 @@ ruleTester.run("indent", rule, { } } `, - options: [2, { SwitchCase: 1 }], - errors: expectedErrors([ - [3, 4, 2, "Keyword"] - ]) - }, - { - code: unIndent` + options: [2, { SwitchCase: 1 }], + errors: expectedErrors([[3, 4, 2, "Keyword"]]), + }, + { + code: unIndent` var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth, height, rotate; `, - output: unIndent` + output: unIndent` var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth, height, rotate; `, - options: [2, { SwitchCase: 1 }], - errors: expectedErrors([ - [2, 2, 0, "Identifier"] - ]) - }, - { - code: unIndent` + options: [2, { SwitchCase: 1 }], + errors: expectedErrors([[2, 2, 0, "Identifier"]]), + }, + { + code: unIndent` switch (a) { case '1': b(); @@ -7485,7 +7644,7 @@ ruleTester.run("indent", rule, { break; } `, - output: unIndent` + output: unIndent` switch (a) { case '1': b(); @@ -7495,201 +7654,189 @@ ruleTester.run("indent", rule, { break; } `, - options: [4, { SwitchCase: 2 }], - errors: expectedErrors([ - [2, 8, 0, "Keyword"], - [3, 12, 0, "Identifier"], - [4, 12, 0, "Keyword"], - [5, 8, 0, "Keyword"], - [6, 12, 0, "Identifier"], - [7, 12, 0, "Keyword"] - ]) - }, - { - code: unIndent` + options: [4, { SwitchCase: 2 }], + errors: expectedErrors([ + [2, 8, 0, "Keyword"], + [3, 12, 0, "Identifier"], + [4, 12, 0, "Keyword"], + [5, 8, 0, "Keyword"], + [6, 12, 0, "Identifier"], + [7, 12, 0, "Keyword"], + ]), + }, + { + code: unIndent` var geometry, rotate; `, - output: unIndent` + output: unIndent` var geometry, rotate; `, - options: [2, { VariableDeclarator: 1 }], - errors: expectedErrors([ - [2, 2, 0, "Identifier"] - ]) - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 1 }], + errors: expectedErrors([[2, 2, 0, "Identifier"]]), + }, + { + code: unIndent` var geometry, rotate; `, - output: unIndent` + output: unIndent` var geometry, rotate; `, - options: [2, { VariableDeclarator: 2 }], - errors: expectedErrors([ - [2, 4, 2, "Identifier"] - ]) - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2 }], + errors: expectedErrors([[2, 4, 2, "Identifier"]]), + }, + { + code: unIndent` var geometry, \trotate; `, - output: unIndent` + output: unIndent` var geometry, \t\trotate; `, - options: ["tab", { VariableDeclarator: 2 }], - errors: expectedErrors("tab", [ - [2, 2, 1, "Identifier"] - ]) - }, - { - code: unIndent` + options: ["tab", { VariableDeclarator: 2 }], + errors: expectedErrors("tab", [[2, 2, 1, "Identifier"]]), + }, + { + code: unIndent` let geometry, rotate; `, - output: unIndent` + output: unIndent` let geometry, rotate; `, - options: [2, { VariableDeclarator: 2 }], - errors: expectedErrors([ - [2, 4, 2, "Identifier"] - ]) - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2 }], + errors: expectedErrors([[2, 4, 2, "Identifier"]]), + }, + { + code: unIndent` let foo = 'foo', bar = bar; const a = 'a', b = 'b'; `, - output: unIndent` + output: unIndent` let foo = 'foo', bar = bar; const a = 'a', b = 'b'; `, - options: [2, { VariableDeclarator: "first" }], - errors: expectedErrors([ - [2, 4, 2, "Identifier"], - [4, 6, 2, "Identifier"] - ]) - }, - { - code: unIndent` + options: [2, { VariableDeclarator: "first" }], + errors: expectedErrors([ + [2, 4, 2, "Identifier"], + [4, 6, 2, "Identifier"], + ]), + }, + { + code: unIndent` var foo = 'foo', bar = bar; `, - output: unIndent` + output: unIndent` var foo = 'foo', bar = bar; `, - options: [2, { VariableDeclarator: { var: "first" } }], - errors: expectedErrors([ - [2, 4, 2, "Identifier"] - ]) - }, - { - code: unIndent` + options: [2, { VariableDeclarator: { var: "first" } }], + errors: expectedErrors([[2, 4, 2, "Identifier"]]), + }, + { + code: unIndent` if(true) if (true) if (true) console.log(val); `, - output: unIndent` + output: unIndent` if(true) if (true) if (true) console.log(val); `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - errors: expectedErrors([ - [4, 6, 4, "Identifier"] - ]) - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([[4, 6, 4, "Identifier"]]), + }, + { + code: unIndent` var a = { a: 1, b: 2 } `, - output: unIndent` + output: unIndent` var a = { a: 1, b: 2 } `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - errors: expectedErrors([ - [2, 2, 4, "Identifier"], - [3, 2, 4, "Identifier"] - ]) - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 2, 4, "Identifier"], + [3, 2, 4, "Identifier"], + ]), + }, + { + code: unIndent` var a = [ a, b ] `, - output: unIndent` + output: unIndent` var a = [ a, b ] `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - errors: expectedErrors([ - [2, 2, 4, "Identifier"], - [3, 2, 4, "Identifier"] - ]) - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 2, 4, "Identifier"], + [3, 2, 4, "Identifier"], + ]), + }, + { + code: unIndent` let a = [ a, b ] `, - output: unIndent` + output: unIndent` let a = [ a, b ] `, - options: [2, { VariableDeclarator: { let: 2 }, SwitchCase: 1 }], - errors: expectedErrors([ - [2, 2, 4, "Identifier"], - [3, 2, 4, "Identifier"] - ]) - }, - { - code: unIndent` + options: [2, { VariableDeclarator: { let: 2 }, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 2, 4, "Identifier"], + [3, 2, 4, "Identifier"], + ]), + }, + { + code: unIndent` var a = new Test({ a: 1 }), b = 4; `, - output: unIndent` + output: unIndent` var a = new Test({ a: 1 }), b = 4; `, - options: [4], - errors: expectedErrors([ - [2, 8, 6, "Identifier"], - [3, 4, 2, "Punctuator"] - ]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([ + [2, 8, 6, "Identifier"], + [3, 4, 2, "Punctuator"], + ]), + }, + { + code: unIndent` var a = new Test({ a: 1 }), @@ -7699,7 +7846,7 @@ ruleTester.run("indent", rule, { }), d = 4; `, - output: unIndent` + output: unIndent` var a = new Test({ a: 1 }), @@ -7709,15 +7856,15 @@ ruleTester.run("indent", rule, { }), d = 4; `, - options: [2, { VariableDeclarator: { var: 2 } }], - errors: expectedErrors([ - [6, 4, 6, "Identifier"], - [7, 2, 4, "Punctuator"], - [8, 2, 4, "Identifier"] - ]) - }, - { - code: unIndent` + options: [2, { VariableDeclarator: { var: 2 } }], + errors: expectedErrors([ + [6, 4, 6, "Identifier"], + [7, 2, 4, "Punctuator"], + [8, 2, 4, "Identifier"], + ]), + }, + { + code: unIndent` var abc = 5, c = 2, xyz = @@ -7726,7 +7873,7 @@ ruleTester.run("indent", rule, { b: 2 }; `, - output: unIndent` + output: unIndent` var abc = 5, c = 2, xyz = @@ -7735,29 +7882,29 @@ ruleTester.run("indent", rule, { b: 2 }; `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - errors: expectedErrors([6, 6, 7, "Identifier"]) - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([6, 6, 7, "Identifier"]), + }, + { + code: unIndent` var abc = { a: 1, b: 2 }; `, - output: unIndent` + output: unIndent` var abc = { a: 1, b: 2 }; `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - errors: expectedErrors([4, 7, 8, "Identifier"]) - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([4, 7, 8, "Identifier"]), + }, + { + code: unIndent` var foo = { bar: 1, baz: { @@ -7766,7 +7913,7 @@ ruleTester.run("indent", rule, { }, bar = 1; `, - output: unIndent` + output: unIndent` var foo = { bar: 1, baz: { @@ -7775,78 +7922,80 @@ ruleTester.run("indent", rule, { }, bar = 1; `, - options: [2], - errors: expectedErrors([[4, 6, 8, "Identifier"], [5, 4, 6, "Punctuator"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [4, 6, 8, "Identifier"], + [5, 4, 6, "Punctuator"], + ]), + }, + { + code: unIndent` var path = require('path') , crypto = require('crypto') ; `, - output: unIndent` + output: unIndent` var path = require('path') , crypto = require('crypto') ; `, - options: [2], - errors: expectedErrors([ - [2, 2, 1, "Punctuator"] - ]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([[2, 2, 1, "Punctuator"]]), + }, + { + code: unIndent` var a = 1 ,b = 2 ; `, - output: unIndent` + output: unIndent` var a = 1 ,b = 2 ; `, - errors: expectedErrors([ - [2, 4, 3, "Punctuator"] - ]) - }, - { - code: unIndent` + errors: expectedErrors([[2, 4, 3, "Punctuator"]]), + }, + { + code: unIndent` class A{ constructor(){} a(){} get b(){} } `, - output: unIndent` + output: unIndent` class A{ constructor(){} a(){} get b(){} } `, - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], - errors: expectedErrors([[2, 4, 2, "Identifier"]]) - }, - { - code: unIndent` + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + errors: expectedErrors([[2, 4, 2, "Identifier"]]), + }, + { + code: unIndent` var A = class { constructor(){} a(){} get b(){} }; `, - output: unIndent` + output: unIndent` var A = class { constructor(){} a(){} get b(){} }; `, - options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], - errors: expectedErrors([[2, 4, 2, "Identifier"], [4, 4, 2, "Identifier"]]) - }, - { - code: unIndent` + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 4, 2, "Identifier"], + [4, 4, 2, "Identifier"], + ]), + }, + { + code: unIndent` var a = 1, B = class { constructor(){} @@ -7854,7 +8003,7 @@ ruleTester.run("indent", rule, { get b(){} }; `, - output: unIndent` + output: unIndent` var a = 1, B = class { constructor(){} @@ -7862,11 +8011,11 @@ ruleTester.run("indent", rule, { get b(){} }; `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - errors: expectedErrors([[3, 6, 4, "Identifier"]]) - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([[3, 6, 4, "Identifier"]]), + }, + { + code: unIndent` { if(a){ foo(); @@ -7876,7 +8025,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` { if(a){ foo(); @@ -7886,11 +8035,11 @@ ruleTester.run("indent", rule, { } } `, - options: [4], - errors: expectedErrors([[5, 4, 2, "Keyword"]]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([[5, 4, 2, "Keyword"]]), + }, + { + code: unIndent` { if(a){ foo(); @@ -7900,7 +8049,7 @@ ruleTester.run("indent", rule, { } `, - output: unIndent` + output: unIndent` { if(a){ foo(); @@ -7910,11 +8059,11 @@ ruleTester.run("indent", rule, { } `, - options: [4], - errors: expectedErrors([[5, 4, 2, "Keyword"]]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([[5, 4, 2, "Keyword"]]), + }, + { + code: unIndent` { if(a) foo(); @@ -7922,7 +8071,7 @@ ruleTester.run("indent", rule, { bar(); } `, - output: unIndent` + output: unIndent` { if(a) foo(); @@ -7930,362 +8079,387 @@ ruleTester.run("indent", rule, { bar(); } `, - options: [4], - errors: expectedErrors([[4, 4, 2, "Keyword"]]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([[4, 4, 2, "Keyword"]]), + }, + { + code: unIndent` (function(){ function foo(x) { return x + 1; } })(); `, - output: unIndent` + output: unIndent` (function(){ function foo(x) { return x + 1; } })(); `, - options: [2, { outerIIFEBody: 0 }], - errors: expectedErrors([[2, 0, 2, "Keyword"], [3, 2, 4, "Keyword"], [4, 0, 2, "Punctuator"]]) - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([ + [2, 0, 2, "Keyword"], + [3, 2, 4, "Keyword"], + [4, 0, 2, "Punctuator"], + ]), + }, + { + code: unIndent` (function(){ function foo(x) { return x + 1; } })(); `, - output: unIndent` + output: unIndent` (function(){ function foo(x) { return x + 1; } })(); `, - options: [4, { outerIIFEBody: 2 }], - errors: expectedErrors([[2, 8, 4, "Keyword"], [3, 12, 8, "Keyword"], [4, 8, 4, "Punctuator"]]) - }, - { - code: unIndent` + options: [4, { outerIIFEBody: 2 }], + errors: expectedErrors([ + [2, 8, 4, "Keyword"], + [3, 12, 8, "Keyword"], + [4, 8, 4, "Punctuator"], + ]), + }, + { + code: unIndent` if(data) { console.log('hi'); } `, - output: unIndent` + output: unIndent` if(data) { console.log('hi'); } `, - options: [2, { outerIIFEBody: 0 }], - errors: expectedErrors([[2, 2, 0, "Identifier"]]) - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([[2, 2, 0, "Identifier"]]), + }, + { + code: unIndent` var ns = function(){ function fooVar(x) { return x + 1; } }(x); `, - output: unIndent` + output: unIndent` var ns = function(){ function fooVar(x) { return x + 1; } }(x); `, - options: [4, { outerIIFEBody: 2 }], - errors: expectedErrors([[2, 8, 4, "Keyword"], [3, 12, 8, "Keyword"], [4, 8, 4, "Punctuator"]]) - }, - { - code: unIndent` + options: [4, { outerIIFEBody: 2 }], + errors: expectedErrors([ + [2, 8, 4, "Keyword"], + [3, 12, 8, "Keyword"], + [4, 8, 4, "Punctuator"], + ]), + }, + { + code: unIndent` var obj = { foo: function() { return true; }() }; `, - output: unIndent` + output: unIndent` var obj = { foo: function() { return true; }() }; `, - options: [2, { outerIIFEBody: 0 }], - errors: expectedErrors([[3, 4, 2, "Keyword"]]) - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([[3, 4, 2, "Keyword"]]), + }, + { + code: unIndent` typeof function() { function fooVar(x) { return x + 1; } }(); `, - output: unIndent` + output: unIndent` typeof function() { function fooVar(x) { return x + 1; } }(); `, - options: [2, { outerIIFEBody: 2 }], - errors: expectedErrors([[2, 2, 4, "Keyword"], [3, 4, 6, "Keyword"], [4, 2, 4, "Punctuator"]]) - }, - { - code: unIndent` + options: [2, { outerIIFEBody: 2 }], + errors: expectedErrors([ + [2, 2, 4, "Keyword"], + [3, 4, 6, "Keyword"], + [4, 2, 4, "Punctuator"], + ]), + }, + { + code: unIndent` { \t!function(x) { \t\t\t\treturn x + 1; \t}() }; `, - output: unIndent` + output: unIndent` { \t!function(x) { \t\treturn x + 1; \t}() }; `, - options: ["tab", { outerIIFEBody: 3 }], - errors: expectedErrors("tab", [[3, 2, 4, "Keyword"]]) - }, - { - code: unIndent` + options: ["tab", { outerIIFEBody: 3 }], + errors: expectedErrors("tab", [[3, 2, 4, "Keyword"]]), + }, + { + code: unIndent` (function(){ function foo(x) { return x + 1; } })(); `, - output: unIndent` + output: unIndent` (function(){ function foo(x) { return x + 1; } })(); `, - options: [4, { outerIIFEBody: "off" }], - errors: expectedErrors([[3, 8, 4, "Keyword"]]) - }, - { - code: unIndent` + options: [4, { outerIIFEBody: "off" }], + errors: expectedErrors([[3, 8, 4, "Keyword"]]), + }, + { + code: unIndent` (function(){ function foo(x) { return x + 1; } })(); `, - output: unIndent` + output: unIndent` (function(){ function foo(x) { return x + 1; } })(); `, - options: [4, { outerIIFEBody: "off" }], - errors: expectedErrors([[3, 4, 0, "Keyword"]]) - }, - { - code: unIndent` + options: [4, { outerIIFEBody: "off" }], + errors: expectedErrors([[3, 4, 0, "Keyword"]]), + }, + { + code: unIndent` (() => { function foo(x) { return x + 1; } })(); `, - output: unIndent` + output: unIndent` (() => { function foo(x) { return x + 1; } })(); `, - options: [4, { outerIIFEBody: "off" }], - errors: expectedErrors([[3, 8, 4, "Keyword"]]) - }, - { - code: unIndent` + options: [4, { outerIIFEBody: "off" }], + errors: expectedErrors([[3, 8, 4, "Keyword"]]), + }, + { + code: unIndent` (() => { function foo(x) { return x + 1; } })(); `, - output: unIndent` + output: unIndent` (() => { function foo(x) { return x + 1; } })(); `, - options: [4, { outerIIFEBody: "off" }], - errors: expectedErrors([[3, 4, 0, "Keyword"]]) - }, - { - code: unIndent` + options: [4, { outerIIFEBody: "off" }], + errors: expectedErrors([[3, 4, 0, "Keyword"]]), + }, + { + code: unIndent` Buffer .toString() `, - output: unIndent` + output: unIndent` Buffer .toString() `, - options: [4, { MemberExpression: 1 }], - errors: expectedErrors([[2, 4, 0, "Punctuator"]]) - }, - { - code: unIndent` + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[2, 4, 0, "Punctuator"]]), + }, + { + code: unIndent` Buffer .indexOf('a') .toString() `, - output: unIndent` + output: unIndent` Buffer .indexOf('a') .toString() `, - options: [4, { MemberExpression: 1 }], - errors: expectedErrors([[3, 4, 0, "Punctuator"]]) - }, - { - code: unIndent` + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[3, 4, 0, "Punctuator"]]), + }, + { + code: unIndent` Buffer. length `, - output: unIndent` + output: unIndent` Buffer. length `, - options: [4, { MemberExpression: 1 }], - errors: expectedErrors([[2, 4, 0, "Identifier"]]) - }, - { - code: unIndent` + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[2, 4, 0, "Identifier"]]), + }, + { + code: unIndent` Buffer. \t\tlength `, - output: unIndent` + output: unIndent` Buffer. \tlength `, - options: ["tab", { MemberExpression: 1 }], - errors: expectedErrors("tab", [[2, 1, 2, "Identifier"]]) - }, - { - code: unIndent` + options: ["tab", { MemberExpression: 1 }], + errors: expectedErrors("tab", [[2, 1, 2, "Identifier"]]), + }, + { + code: unIndent` Buffer .foo .bar `, - output: unIndent` + output: unIndent` Buffer .foo .bar `, - options: [2, { MemberExpression: 2 }], - errors: expectedErrors([[2, 4, 2, "Punctuator"], [3, 4, 2, "Punctuator"]]) - }, - { - code: unIndent` + options: [2, { MemberExpression: 2 }], + errors: expectedErrors([ + [2, 4, 2, "Punctuator"], + [3, 4, 2, "Punctuator"], + ]), + }, + { + code: unIndent` function foo() { new .target } `, - output: unIndent` + output: unIndent` function foo() { new .target } `, - errors: expectedErrors([3, 8, 4, "Punctuator"]) - }, - { - code: unIndent` + errors: expectedErrors([3, 8, 4, "Punctuator"]), + }, + { + code: unIndent` function foo() { new. target } `, - output: unIndent` + output: unIndent` function foo() { new. target } `, - errors: expectedErrors([3, 8, 4, "Identifier"]) - }, - { - - // Indentation with multiple else statements: https://github.com/eslint/eslint/issues/6956 + errors: expectedErrors([3, 8, 4, "Identifier"]), + }, + { + // Indentation with multiple else statements: https://github.com/eslint/eslint/issues/6956 - code: unIndent` + code: unIndent` if (foo) bar(); else if (baz) foobar(); else if (qux) qux(); `, - output: unIndent` + output: unIndent` if (foo) bar(); else if (baz) foobar(); else if (qux) qux(); `, - options: [2], - errors: expectedErrors([3, 0, 2, "Keyword"]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([3, 0, 2, "Keyword"]), + }, + { + code: unIndent` if (foo) bar(); else if (baz) foobar(); else qux(); `, - output: unIndent` + output: unIndent` if (foo) bar(); else if (baz) foobar(); else qux(); `, - options: [2], - errors: expectedErrors([3, 0, 2, "Keyword"]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([3, 0, 2, "Keyword"]), + }, + { + code: unIndent` foo(); if (baz) foobar(); else qux(); `, - output: unIndent` + output: unIndent` foo(); if (baz) foobar(); else qux(); `, - options: [2], - errors: expectedErrors([[2, 0, 2, "Keyword"], [3, 0, 2, "Keyword"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [2, 0, 2, "Keyword"], + [3, 0, 2, "Keyword"], + ]), + }, + { + code: unIndent` if (foo) bar(); else if (baz) foobar(); else if (bip) { qux(); } `, - output: unIndent` + output: unIndent` if (foo) bar(); else if (baz) foobar(); else if (bip) { qux(); } `, - options: [2], - errors: expectedErrors([[3, 0, 5, "Keyword"], [4, 2, 7, "Identifier"], [5, 0, 5, "Punctuator"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [3, 0, 5, "Keyword"], + [4, 2, 7, "Identifier"], + [5, 0, 5, "Punctuator"], + ]), + }, + { + code: unIndent` if (foo) bar(); else if (baz) { foobar(); @@ -8293,7 +8467,7 @@ ruleTester.run("indent", rule, { qux(); } `, - output: unIndent` + output: unIndent` if (foo) bar(); else if (baz) { foobar(); @@ -8301,113 +8475,142 @@ ruleTester.run("indent", rule, { qux(); } `, - options: [2], - errors: expectedErrors([[3, 2, 4, "Identifier"], [4, 0, 5, "Punctuator"], [5, 2, 7, "Identifier"], [6, 0, 5, "Punctuator"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [3, 2, 4, "Identifier"], + [4, 0, 5, "Punctuator"], + [5, 2, 7, "Identifier"], + [6, 0, 5, "Punctuator"], + ]), + }, + { + code: unIndent` function foo(aaa, bbb, ccc, ddd) { bar(); } `, - output: unIndent` + output: unIndent` function foo(aaa, bbb, ccc, ddd) { bar(); } `, - options: [2, { FunctionDeclaration: { parameters: 1, body: 2 } }], - errors: expectedErrors([[2, 2, 4, "Identifier"], [3, 4, 6, "Identifier"]]) - }, - { - code: unIndent` + options: [2, { FunctionDeclaration: { parameters: 1, body: 2 } }], + errors: expectedErrors([ + [2, 2, 4, "Identifier"], + [3, 4, 6, "Identifier"], + ]), + }, + { + code: unIndent` function foo(aaa, bbb, ccc, ddd) { bar(); } `, - output: unIndent` + output: unIndent` function foo(aaa, bbb, ccc, ddd) { bar(); } `, - options: [2, { FunctionDeclaration: { parameters: 3, body: 1 } }], - errors: expectedErrors([[2, 6, 2, "Identifier"], [3, 2, 0, "Identifier"]]) - }, - { - code: unIndent` + options: [2, { FunctionDeclaration: { parameters: 3, body: 1 } }], + errors: expectedErrors([ + [2, 6, 2, "Identifier"], + [3, 2, 0, "Identifier"], + ]), + }, + { + code: unIndent` function foo(aaa, bbb, ccc) { bar(); } `, - output: unIndent` + output: unIndent` function foo(aaa, bbb, ccc) { bar(); } `, - options: [4, { FunctionDeclaration: { parameters: 1, body: 3 } }], - errors: expectedErrors([[2, 4, 8, "Identifier"], [3, 4, 2, "Identifier"], [4, 12, 6, "Identifier"]]) - }, - { - code: unIndent` + options: [4, { FunctionDeclaration: { parameters: 1, body: 3 } }], + errors: expectedErrors([ + [2, 4, 8, "Identifier"], + [3, 4, 2, "Identifier"], + [4, 12, 6, "Identifier"], + ]), + }, + { + code: unIndent` function foo(aaa, bbb, ccc, ddd, eee, fff) { bar(); } `, - output: unIndent` + output: unIndent` function foo(aaa, bbb, ccc, ddd, eee, fff) { bar(); } `, - options: [2, { FunctionDeclaration: { parameters: "first", body: 1 } }], - errors: expectedErrors([[2, 13, 2, "Identifier"], [3, 13, 19, "Identifier"], [4, 2, 3, "Identifier"]]) - }, - { - code: unIndent` + options: [ + 2, + { FunctionDeclaration: { parameters: "first", body: 1 } }, + ], + errors: expectedErrors([ + [2, 13, 2, "Identifier"], + [3, 13, 19, "Identifier"], + [4, 2, 3, "Identifier"], + ]), + }, + { + code: unIndent` function foo(aaa, bbb) { bar(); } `, - output: unIndent` + output: unIndent` function foo(aaa, bbb) { bar(); } `, - options: [2, { FunctionDeclaration: { body: 3 } }], - errors: expectedErrors([3, 6, 0, "Identifier"]) - }, - { - code: unIndent` + options: [2, { FunctionDeclaration: { body: 3 } }], + errors: expectedErrors([3, 6, 0, "Identifier"]), + }, + { + code: unIndent` function foo( aaa, bbb) { bar(); } `, - output: unIndent` + output: unIndent` function foo( aaa, bbb) { bar(); } `, - options: [2, { FunctionDeclaration: { parameters: "first", body: 2 } }], - errors: expectedErrors([[2, 2, 0, "Identifier"], [3, 2, 4, "Identifier"], [4, 4, 0, "Identifier"]]) - }, - { - code: unIndent` + options: [ + 2, + { FunctionDeclaration: { parameters: "first", body: 2 } }, + ], + errors: expectedErrors([ + [2, 2, 0, "Identifier"], + [3, 2, 4, "Identifier"], + [4, 4, 0, "Identifier"], + ]), + }, + { + code: unIndent` var foo = function(aaa, bbb, ccc, @@ -8415,7 +8618,7 @@ ruleTester.run("indent", rule, { bar(); } `, - output: unIndent` + output: unIndent` var foo = function(aaa, bbb, ccc, @@ -8423,127 +8626,152 @@ ruleTester.run("indent", rule, { bar(); } `, - options: [2, { FunctionExpression: { parameters: 2, body: 0 } }], - errors: expectedErrors([[2, 4, 2, "Identifier"], [4, 4, 6, "Identifier"], [5, 0, 2, "Identifier"]]) - }, - { - code: unIndent` + options: [2, { FunctionExpression: { parameters: 2, body: 0 } }], + errors: expectedErrors([ + [2, 4, 2, "Identifier"], + [4, 4, 6, "Identifier"], + [5, 0, 2, "Identifier"], + ]), + }, + { + code: unIndent` var foo = function(aaa, bbb, ccc) { bar(); } `, - output: unIndent` + output: unIndent` var foo = function(aaa, bbb, ccc) { bar(); } `, - options: [2, { FunctionExpression: { parameters: 1, body: 10 } }], - errors: expectedErrors([[2, 2, 3, "Identifier"], [3, 2, 1, "Identifier"], [4, 20, 2, "Identifier"]]) - }, - { - code: unIndent` + options: [2, { FunctionExpression: { parameters: 1, body: 10 } }], + errors: expectedErrors([ + [2, 2, 3, "Identifier"], + [3, 2, 1, "Identifier"], + [4, 20, 2, "Identifier"], + ]), + }, + { + code: unIndent` var foo = function(aaa, bbb, ccc, ddd, eee, fff) { bar(); } `, - output: unIndent` + output: unIndent` var foo = function(aaa, bbb, ccc, ddd, eee, fff) { bar(); } `, - options: [4, { FunctionExpression: { parameters: "first", body: 1 } }], - errors: expectedErrors([[2, 19, 2, "Identifier"], [3, 19, 24, "Identifier"], [4, 4, 8, "Identifier"]]) - }, - { - code: unIndent` + options: [ + 4, + { FunctionExpression: { parameters: "first", body: 1 } }, + ], + errors: expectedErrors([ + [2, 19, 2, "Identifier"], + [3, 19, 24, "Identifier"], + [4, 4, 8, "Identifier"], + ]), + }, + { + code: unIndent` var foo = function( aaa, bbb, ccc, ddd, eee) { bar(); } `, - output: unIndent` + output: unIndent` var foo = function( aaa, bbb, ccc, ddd, eee) { bar(); } `, - options: [2, { FunctionExpression: { parameters: "first", body: 3 } }], - errors: expectedErrors([[2, 2, 0, "Identifier"], [3, 2, 4, "Identifier"], [4, 6, 2, "Identifier"]]) - }, - { - code: unIndent` + options: [ + 2, + { FunctionExpression: { parameters: "first", body: 3 } }, + ], + errors: expectedErrors([ + [2, 2, 0, "Identifier"], + [3, 2, 4, "Identifier"], + [4, 6, 2, "Identifier"], + ]), + }, + { + code: unIndent` var foo = bar; \t\t\tvar baz = qux; `, - output: unIndent` + output: unIndent` var foo = bar; var baz = qux; `, - options: [2], - errors: expectedErrors([2, "0 spaces", "3 tabs", "Keyword"]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([2, "0 spaces", "3 tabs", "Keyword"]), + }, + { + code: unIndent` function foo() { \tbar(); baz(); qux(); } `, - output: unIndent` + output: unIndent` function foo() { \tbar(); \tbaz(); \tqux(); } `, - options: ["tab"], - errors: expectedErrors("tab", [[3, "1 tab", "2 spaces", "Identifier"], [4, "1 tab", "14 spaces", "Identifier"]]) - }, - { - code: unIndent` + options: ["tab"], + errors: expectedErrors("tab", [ + [3, "1 tab", "2 spaces", "Identifier"], + [4, "1 tab", "14 spaces", "Identifier"], + ]), + }, + { + code: unIndent` function foo() { bar(); \t\t} `, - output: unIndent` + output: unIndent` function foo() { bar(); } `, - options: [2], - errors: expectedErrors([[3, "0 spaces", "2 tabs", "Punctuator"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([[3, "0 spaces", "2 tabs", "Punctuator"]]), + }, + { + code: unIndent` function foo() { function bar() { baz(); } } `, - output: unIndent` + output: unIndent` function foo() { function bar() { baz(); } } `, - options: [2, { FunctionDeclaration: { body: 1 } }], - errors: expectedErrors([3, 4, 8, "Identifier"]) - }, - { - code: unIndent` + options: [2, { FunctionDeclaration: { body: 1 } }], + errors: expectedErrors([3, 4, 8, "Identifier"]), + }, + { + code: unIndent` function foo() { function bar(baz, qux) { @@ -8551,7 +8779,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` function foo() { function bar(baz, qux) { @@ -8559,11 +8787,11 @@ ruleTester.run("indent", rule, { } } `, - options: [2, { FunctionDeclaration: { body: 1, parameters: 2 } }], - errors: expectedErrors([3, 6, 4, "Identifier"]) - }, - { - code: unIndent` + options: [2, { FunctionDeclaration: { body: 1, parameters: 2 } }], + errors: expectedErrors([3, 6, 4, "Identifier"]), + }, + { + code: unIndent` function foo() { var bar = function(baz, qux) { @@ -8571,7 +8799,7 @@ ruleTester.run("indent", rule, { }; } `, - output: unIndent` + output: unIndent` function foo() { var bar = function(baz, qux) { @@ -8579,29 +8807,35 @@ ruleTester.run("indent", rule, { }; } `, - options: [2, { FunctionExpression: { parameters: 3 } }], - errors: expectedErrors([3, 8, 10, "Identifier"]) - }, - { - code: unIndent` + options: [2, { FunctionExpression: { parameters: 3 } }], + errors: expectedErrors([3, 8, 10, "Identifier"]), + }, + { + code: unIndent` foo.bar( baz, qux, function() { qux; } ); `, - output: unIndent` + output: unIndent` foo.bar( baz, qux, function() { qux; } ); `, - options: [2, { FunctionExpression: { body: 3 }, CallExpression: { arguments: 3 } }], - errors: expectedErrors([3, 12, 8, "Identifier"]) - }, - { - code: unIndent` + options: [ + 2, + { + FunctionExpression: { body: 3 }, + CallExpression: { arguments: 3 }, + }, + ], + errors: expectedErrors([3, 12, 8, "Identifier"]), + }, + { + code: unIndent` { try { } @@ -8611,7 +8845,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` { try { } @@ -8621,66 +8855,66 @@ ruleTester.run("indent", rule, { } } `, - errors: expectedErrors([ - [4, 4, 0, "Keyword"], - [6, 4, 0, "Keyword"] - ]) - }, - { - code: unIndent` + errors: expectedErrors([ + [4, 4, 0, "Keyword"], + [6, 4, 0, "Keyword"], + ]), + }, + { + code: unIndent` { do { } while (true) } `, - output: unIndent` + output: unIndent` { do { } while (true) } `, - errors: expectedErrors([4, 4, 0, "Keyword"]) - }, - { - code: unIndent` + errors: expectedErrors([4, 4, 0, "Keyword"]), + }, + { + code: unIndent` function foo() { return ( 1 ) } `, - output: unIndent` + output: unIndent` function foo() { return ( 1 ) } `, - options: [2], - errors: expectedErrors([[4, 2, 4, "Punctuator"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([[4, 2, 4, "Punctuator"]]), + }, + { + code: unIndent` function foo() { return ( 1 ); } `, - output: unIndent` + output: unIndent` function foo() { return ( 1 ); } `, - options: [2], - errors: expectedErrors([[4, 2, 4, "Punctuator"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([[4, 2, 4, "Punctuator"]]), + }, + { + code: unIndent` function test(){ switch(length){ case 1: return function(a){ @@ -8689,7 +8923,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` function test(){ switch(length){ case 1: return function(a){ @@ -8698,217 +8932,240 @@ ruleTester.run("indent", rule, { } } `, - options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], - errors: expectedErrors([[4, 6, 4, "Keyword"]]) - }, - { - code: unIndent` + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([[4, 6, 4, "Keyword"]]), + }, + { + code: unIndent` function foo() { return 1 } `, - output: unIndent` + output: unIndent` function foo() { return 1 } `, - options: [2], - errors: expectedErrors([[2, 2, 3, "Keyword"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([[2, 2, 3, "Keyword"]]), + }, + { + code: unIndent` foo( bar, baz, qux); `, - output: unIndent` + output: unIndent` foo( bar, baz, qux); `, - options: [2, { CallExpression: { arguments: 1 } }], - errors: expectedErrors([[2, 2, 0, "Identifier"], [4, 2, 4, "Identifier"]]) - }, - { - code: unIndent` + options: [2, { CallExpression: { arguments: 1 } }], + errors: expectedErrors([ + [2, 2, 0, "Identifier"], + [4, 2, 4, "Identifier"], + ]), + }, + { + code: unIndent` foo( \tbar, \tbaz); `, - output: unIndent` + output: unIndent` foo( bar, baz); `, - options: [2, { CallExpression: { arguments: 2 } }], - errors: expectedErrors([[2, "4 spaces", "1 tab", "Identifier"], [3, "4 spaces", "1 tab", "Identifier"]]) - }, - { - code: unIndent` + options: [2, { CallExpression: { arguments: 2 } }], + errors: expectedErrors([ + [2, "4 spaces", "1 tab", "Identifier"], + [3, "4 spaces", "1 tab", "Identifier"], + ]), + }, + { + code: unIndent` foo(bar, \t\tbaz, \t\tqux); `, - output: unIndent` + output: unIndent` foo(bar, \tbaz, \tqux); `, - options: ["tab", { CallExpression: { arguments: 1 } }], - errors: expectedErrors("tab", [[2, 1, 2, "Identifier"], [3, 1, 2, "Identifier"]]) - }, - { - code: unIndent` + options: ["tab", { CallExpression: { arguments: 1 } }], + errors: expectedErrors("tab", [ + [2, 1, 2, "Identifier"], + [3, 1, 2, "Identifier"], + ]), + }, + { + code: unIndent` foo(bar, baz, qux); `, - output: unIndent` + output: unIndent` foo(bar, baz, qux); `, - options: [2, { CallExpression: { arguments: "first" } }], - errors: expectedErrors([2, 4, 9, "Identifier"]) - }, - { - code: unIndent` + options: [2, { CallExpression: { arguments: "first" } }], + errors: expectedErrors([2, 4, 9, "Identifier"]), + }, + { + code: unIndent` foo( bar, baz); `, - output: unIndent` + output: unIndent` foo( bar, baz); `, - options: [2, { CallExpression: { arguments: "first" } }], - errors: expectedErrors([[2, 2, 10, "Identifier"], [3, 2, 4, "Identifier"]]) - }, - { - code: unIndent` + options: [2, { CallExpression: { arguments: "first" } }], + errors: expectedErrors([ + [2, 2, 10, "Identifier"], + [3, 2, 4, "Identifier"], + ]), + }, + { + code: unIndent` foo(bar, 1 + 2, !baz, new Car('!') ); `, - output: unIndent` + output: unIndent` foo(bar, 1 + 2, !baz, new Car('!') ); `, - options: [2, { CallExpression: { arguments: 3 } }], - errors: expectedErrors([[2, 6, 2, "Numeric"], [3, 6, 14, "Punctuator"], [4, 6, 8, "Keyword"]]) - }, - - // https://github.com/eslint/eslint/issues/7573 - { - code: unIndent` + options: [2, { CallExpression: { arguments: 3 } }], + errors: expectedErrors([ + [2, 6, 2, "Numeric"], + [3, 6, 14, "Punctuator"], + [4, 6, 8, "Keyword"], + ]), + }, + + // https://github.com/eslint/eslint/issues/7573 + { + code: unIndent` return ( foo ); `, - output: unIndent` + output: unIndent` return ( foo ); `, - languageOptions: { parserOptions: { ecmaFeatures: { globalReturn: true } } }, - errors: expectedErrors([3, 0, 4, "Punctuator"]) - }, - { - code: unIndent` + languageOptions: { + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + errors: expectedErrors([3, 0, 4, "Punctuator"]), + }, + { + code: unIndent` return ( foo ) `, - output: unIndent` + output: unIndent` return ( foo ) `, - languageOptions: { parserOptions: { ecmaFeatures: { globalReturn: true } } }, - errors: expectedErrors([3, 0, 4, "Punctuator"]) - }, + languageOptions: { + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + errors: expectedErrors([3, 0, 4, "Punctuator"]), + }, - // https://github.com/eslint/eslint/issues/7604 - { - code: unIndent` + // https://github.com/eslint/eslint/issues/7604 + { + code: unIndent` if (foo) { /* comment */bar(); } `, - output: unIndent` + output: unIndent` if (foo) { /* comment */bar(); } `, - errors: expectedErrors([2, 4, 8, "Block"]) - }, - { - code: unIndent` + errors: expectedErrors([2, 4, 8, "Block"]), + }, + { + code: unIndent` foo('bar', /** comment */{ ok: true }); `, - output: unIndent` + output: unIndent` foo('bar', /** comment */{ ok: true }); `, - errors: expectedErrors([2, 4, 8, "Block"]) - }, - { - code: unIndent` + errors: expectedErrors([2, 4, 8, "Block"]), + }, + { + code: unIndent` foo( (bar) ); `, - output: unIndent` + output: unIndent` foo( (bar) ); `, - options: [4, { CallExpression: { arguments: 1 } }], - errors: expectedErrors([2, 4, 0, "Punctuator"]) - }, - { - code: unIndent` + options: [4, { CallExpression: { arguments: 1 } }], + errors: expectedErrors([2, 4, 0, "Punctuator"]), + }, + { + code: unIndent` (( foo )) `, - output: unIndent` + output: unIndent` (( foo )) `, - options: [4], - errors: expectedErrors([2, 4, 0, "Identifier"]) - }, + options: [4], + errors: expectedErrors([2, 4, 0, "Identifier"]), + }, - // ternary expressions (https://github.com/eslint/eslint/issues/7420) - { - code: unIndent` + // ternary expressions (https://github.com/eslint/eslint/issues/7420) + { + code: unIndent` foo ? bar : baz `, - output: unIndent` + output: unIndent` foo ? bar : baz `, - options: [2], - errors: expectedErrors([[2, 2, 0, "Punctuator"], [3, 2, 4, "Punctuator"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [2, 2, 0, "Punctuator"], + [3, 2, 4, "Punctuator"], + ]), + }, + { + code: unIndent` [ foo ? bar : @@ -8916,7 +9173,7 @@ ruleTester.run("indent", rule, { qux ] `, - output: unIndent` + output: unIndent` [ foo ? bar : @@ -8924,10 +9181,10 @@ ruleTester.run("indent", rule, { qux ] `, - errors: expectedErrors([5, 4, 8, "Identifier"]) - }, - { - code: unIndent` + errors: expectedErrors([5, 4, 8, "Identifier"]), + }, + { + code: unIndent` condition ? () => { return true @@ -8940,7 +9197,7 @@ ruleTester.run("indent", rule, { return false } `, - output: unIndent` + output: unIndent` condition ? () => { return true @@ -8953,22 +9210,22 @@ ruleTester.run("indent", rule, { return false } `, - options: [2, { offsetTernaryExpressions: true }], - errors: expectedErrors([ - [2, 2, 0, "Punctuator"], - [3, 6, 0, "Keyword"], - [4, 4, 0, "Punctuator"], - [5, 2, 0, "Punctuator"], - [6, 4, 0, "Punctuator"], - [7, 8, 0, "Keyword"], - [8, 6, 0, "Punctuator"], - [9, 4, 0, "Punctuator"], - [10, 8, 0, "Keyword"], - [11, 6, 0, "Punctuator"] - ]) - }, - { - code: unIndent` + options: [2, { offsetTernaryExpressions: true }], + errors: expectedErrors([ + [2, 2, 0, "Punctuator"], + [3, 6, 0, "Keyword"], + [4, 4, 0, "Punctuator"], + [5, 2, 0, "Punctuator"], + [6, 4, 0, "Punctuator"], + [7, 8, 0, "Keyword"], + [8, 6, 0, "Punctuator"], + [9, 4, 0, "Punctuator"], + [10, 8, 0, "Keyword"], + [11, 6, 0, "Punctuator"], + ]), + }, + { + code: unIndent` condition ? () => { return true @@ -8981,7 +9238,7 @@ ruleTester.run("indent", rule, { return false } `, - output: unIndent` + output: unIndent` condition ? () => { return true @@ -8994,27 +9251,26 @@ ruleTester.run("indent", rule, { return false } `, - options: [2, { offsetTernaryExpressions: false }], - errors: expectedErrors([ - [2, 2, 0, "Punctuator"], - [3, 4, 0, "Keyword"], - [4, 2, 0, "Punctuator"], - [5, 2, 0, "Punctuator"], - [6, 4, 0, "Punctuator"], - [7, 6, 0, "Keyword"], - [8, 4, 0, "Punctuator"], - [9, 4, 0, "Punctuator"], - [10, 6, 0, "Keyword"], - [11, 4, 0, "Punctuator"] - ]) - }, - { - - /* - * Checking comments: - * https://github.com/eslint/eslint/issues/6571 - */ - code: unIndent` + options: [2, { offsetTernaryExpressions: false }], + errors: expectedErrors([ + [2, 2, 0, "Punctuator"], + [3, 4, 0, "Keyword"], + [4, 2, 0, "Punctuator"], + [5, 2, 0, "Punctuator"], + [6, 4, 0, "Punctuator"], + [7, 6, 0, "Keyword"], + [8, 4, 0, "Punctuator"], + [9, 4, 0, "Punctuator"], + [10, 6, 0, "Keyword"], + [11, 4, 0, "Punctuator"], + ]), + }, + { + /* + * Checking comments: + * https://github.com/eslint/eslint/issues/6571 + */ + code: unIndent` foo(); // comment /* multiline @@ -9022,7 +9278,7 @@ ruleTester.run("indent", rule, { bar(); // trailing comment `, - output: unIndent` + output: unIndent` foo(); // comment /* multiline @@ -9030,56 +9286,59 @@ ruleTester.run("indent", rule, { bar(); // trailing comment `, - options: [2], - errors: expectedErrors([[2, 0, 2, "Line"], [3, 0, 4, "Block"], [6, 0, 1, "Line"]]) - }, - { - code: " // comment", - output: "// comment", - errors: expectedErrors([1, 0, 2, "Line"]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [2, 0, 2, "Line"], + [3, 0, 4, "Block"], + [6, 0, 1, "Line"], + ]), + }, + { + code: " // comment", + output: "// comment", + errors: expectedErrors([1, 0, 2, "Line"]), + }, + { + code: unIndent` foo // comment `, - output: unIndent` + output: unIndent` foo // comment `, - errors: expectedErrors([2, 0, 2, "Line"]) - }, - { - code: unIndent` + errors: expectedErrors([2, 0, 2, "Line"]), + }, + { + code: unIndent` // comment foo `, - output: unIndent` + output: unIndent` // comment foo `, - errors: expectedErrors([1, 0, 2, "Line"]) - }, - { - code: unIndent` + errors: expectedErrors([1, 0, 2, "Line"]), + }, + { + code: unIndent` [ // no elements ] `, - output: unIndent` + output: unIndent` [ // no elements ] `, - errors: expectedErrors([2, 4, 8, "Line"]) - }, - { - - /* - * Destructuring assignments: - * https://github.com/eslint/eslint/issues/6813 - */ - code: unIndent` + errors: expectedErrors([2, 4, 8, "Line"]), + }, + { + /* + * Destructuring assignments: + * https://github.com/eslint/eslint/issues/6813 + */ + code: unIndent` var { foo, bar, @@ -9087,7 +9346,7 @@ ruleTester.run("indent", rule, { foobar: baz = foobar } = qux; `, - output: unIndent` + output: unIndent` var { foo, bar, @@ -9095,121 +9354,142 @@ ruleTester.run("indent", rule, { foobar: baz = foobar } = qux; `, - options: [2], - errors: expectedErrors([[2, 2, 0, "Identifier"], [4, 2, 4, "Identifier"], [5, 2, 6, "Identifier"], [6, 0, 2, "Punctuator"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [2, 2, 0, "Identifier"], + [4, 2, 4, "Identifier"], + [5, 2, 6, "Identifier"], + [6, 0, 2, "Punctuator"], + ]), + }, + { + code: unIndent` const { a } = { a: 1 } `, - output: unIndent` + output: unIndent` const { a } = { a: 1 } `, - options: [2], - errors: expectedErrors([[4, 2, 4, "Identifier"], [5, 0, 2, "Punctuator"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [4, 2, 4, "Identifier"], + [5, 0, 2, "Punctuator"], + ]), + }, + { + code: unIndent` var foo = [ bar, baz ] `, - output: unIndent` + output: unIndent` var foo = [ bar, baz ] `, - errors: expectedErrors([[2, 4, 11, "Identifier"], [3, 4, 2, "Identifier"], [4, 0, 10, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [2, 4, 11, "Identifier"], + [3, 4, 2, "Identifier"], + [4, 0, 10, "Punctuator"], + ]), + }, + { + code: unIndent` var foo = [bar, baz, qux ] `, - output: unIndent` + output: unIndent` var foo = [bar, baz, qux ] `, - errors: expectedErrors([2, 4, 0, "Identifier"]) - }, - { - code: unIndent` + errors: expectedErrors([2, 4, 0, "Identifier"]), + }, + { + code: unIndent` var foo = [bar, baz, qux ] `, - output: unIndent` + output: unIndent` var foo = [bar, baz, qux ] `, - options: [2, { ArrayExpression: 0 }], - errors: expectedErrors([[2, 0, 2, "Identifier"], [3, 0, 2, "Identifier"]]) - }, - { - code: unIndent` + options: [2, { ArrayExpression: 0 }], + errors: expectedErrors([ + [2, 0, 2, "Identifier"], + [3, 0, 2, "Identifier"], + ]), + }, + { + code: unIndent` var foo = [bar, baz, qux ] `, - output: unIndent` + output: unIndent` var foo = [bar, baz, qux ] `, - options: [2, { ArrayExpression: 8 }], - errors: expectedErrors([[2, 16, 2, "Identifier"], [3, 16, 2, "Identifier"]]) - }, - { - code: unIndent` + options: [2, { ArrayExpression: 8 }], + errors: expectedErrors([ + [2, 16, 2, "Identifier"], + [3, 16, 2, "Identifier"], + ]), + }, + { + code: unIndent` var foo = [bar, baz, qux ] `, - output: unIndent` + output: unIndent` var foo = [bar, baz, qux ] `, - options: [2, { ArrayExpression: "first" }], - errors: expectedErrors([[2, 11, 4, "Identifier"], [3, 11, 4, "Identifier"]]) - }, - { - code: unIndent` + options: [2, { ArrayExpression: "first" }], + errors: expectedErrors([ + [2, 11, 4, "Identifier"], + [3, 11, 4, "Identifier"], + ]), + }, + { + code: unIndent` var foo = [bar, baz, qux ] `, - output: unIndent` + output: unIndent` var foo = [bar, baz, qux ] `, - options: [2, { ArrayExpression: "first" }], - errors: expectedErrors([2, 11, 4, "Identifier"]) - }, - { - code: unIndent` + options: [2, { ArrayExpression: "first" }], + errors: expectedErrors([2, 11, 4, "Identifier"]), + }, + { + code: unIndent` var foo = [ { bar: 1, baz: 2 }, @@ -9217,7 +9497,7 @@ ruleTester.run("indent", rule, { qux: 4 } ] `, - output: unIndent` + output: unIndent` var foo = [ { bar: 1, baz: 2 }, @@ -9225,57 +9505,67 @@ ruleTester.run("indent", rule, { qux: 4 } ] `, - options: [4, { ArrayExpression: 2, ObjectExpression: "first" }], - errors: expectedErrors([[3, 10, 12, "Identifier"], [5, 10, 12, "Identifier"]]) - }, - { - code: unIndent` + options: [4, { ArrayExpression: 2, ObjectExpression: "first" }], + errors: expectedErrors([ + [3, 10, 12, "Identifier"], + [5, 10, 12, "Identifier"], + ]), + }, + { + code: unIndent` var foo = { bar: 1, baz: 2 }; `, - output: unIndent` + output: unIndent` var foo = { bar: 1, baz: 2 }; `, - options: [2, { ObjectExpression: 0 }], - errors: expectedErrors([[2, 0, 2, "Identifier"], [3, 0, 2, "Identifier"]]) - }, - { - code: unIndent` + options: [2, { ObjectExpression: 0 }], + errors: expectedErrors([ + [2, 0, 2, "Identifier"], + [3, 0, 2, "Identifier"], + ]), + }, + { + code: unIndent` var quux = { foo: 1, bar: 2, baz: 3 } `, - output: unIndent` + output: unIndent` var quux = { foo: 1, bar: 2, baz: 3 } `, - options: [2, { ObjectExpression: "first" }], - errors: expectedErrors([2, 13, 0, "Identifier"]) - }, - { - code: unIndent` + options: [2, { ObjectExpression: "first" }], + errors: expectedErrors([2, 13, 0, "Identifier"]), + }, + { + code: unIndent` function foo() { [ foo ] } `, - output: unIndent` + output: unIndent` function foo() { [ foo ] } `, - options: [2, { ArrayExpression: 4 }], - errors: expectedErrors([[2, 2, 4, "Punctuator"], [3, 10, 12, "Identifier"], [4, 2, 4, "Punctuator"]]) - }, - { - code: unIndent` + options: [2, { ArrayExpression: 4 }], + errors: expectedErrors([ + [2, 2, 4, "Punctuator"], + [3, 10, 12, "Identifier"], + [4, 2, 4, "Punctuator"], + ]), + }, + { + code: unIndent` var [ foo, bar, @@ -9283,7 +9573,7 @@ ruleTester.run("indent", rule, { foobar = baz ] = qux; `, - output: unIndent` + output: unIndent` var [ foo, bar, @@ -9291,63 +9581,71 @@ ruleTester.run("indent", rule, { foobar = baz ] = qux; `, - options: [2], - errors: expectedErrors([[2, 2, 0, "Identifier"], [4, 2, 4, "Identifier"], [5, 2, 6, "Identifier"], [6, 0, 2, "Punctuator"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [2, 2, 0, "Identifier"], + [4, 2, 4, "Identifier"], + [5, 2, 6, "Identifier"], + [6, 0, 2, "Punctuator"], + ]), + }, + { + code: unIndent` import { foo, bar, baz } from 'qux'; `, - output: unIndent` + output: unIndent` import { foo, bar, baz } from 'qux'; `, - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: expectedErrors([[2, 4, 0, "Identifier"], [3, 4, 2, "Identifier"]]) - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: expectedErrors([ + [2, 4, 0, "Identifier"], + [3, 4, 2, "Identifier"], + ]), + }, + { + code: unIndent` import { foo, bar, baz, } from 'qux'; `, - output: unIndent` + output: unIndent` import { foo, bar, baz, } from 'qux'; `, - options: [4, { ImportDeclaration: "first" }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: expectedErrors([[3, 9, 10, "Identifier"]]) - }, - { - code: unIndent` + options: [4, { ImportDeclaration: "first" }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: expectedErrors([[3, 9, 10, "Identifier"]]), + }, + { + code: unIndent` import { foo, bar, baz, } from 'qux'; `, - output: unIndent` + output: unIndent` import { foo, bar, baz, } from 'qux'; `, - options: [2, { ImportDeclaration: 2 }], - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: expectedErrors([[3, 4, 5, "Identifier"]]) - }, - { - code: unIndent` + options: [2, { ImportDeclaration: 2 }], + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: expectedErrors([[3, 4, 5, "Identifier"]]), + }, + { + code: unIndent` var foo = 0, bar = 0, baz = 0; export { foo, @@ -9355,7 +9653,7 @@ ruleTester.run("indent", rule, { baz }; `, - output: unIndent` + output: unIndent` var foo = 0, bar = 0, baz = 0; export { foo, @@ -9363,11 +9661,14 @@ ruleTester.run("indent", rule, { baz }; `, - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: expectedErrors([[3, 4, 0, "Identifier"], [4, 4, 2, "Identifier"]]) - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: expectedErrors([ + [3, 4, 0, "Identifier"], + [4, 4, 2, "Identifier"], + ]), + }, + { + code: unIndent` var foo = 0, bar = 0, baz = 0; export { foo, @@ -9375,7 +9676,7 @@ ruleTester.run("indent", rule, { baz } from 'qux'; `, - output: unIndent` + output: unIndent` var foo = 0, bar = 0, baz = 0; export { foo, @@ -9383,219 +9684,247 @@ ruleTester.run("indent", rule, { baz } from 'qux'; `, - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: expectedErrors([[3, 4, 0, "Identifier"], [4, 4, 2, "Identifier"]]) - }, - { - - // https://github.com/eslint/eslint/issues/7233 - code: unIndent` + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: expectedErrors([ + [3, 4, 0, "Identifier"], + [4, 4, 2, "Identifier"], + ]), + }, + { + // https://github.com/eslint/eslint/issues/7233 + code: unIndent` var folder = filePath .foo() .bar; `, - output: unIndent` + output: unIndent` var folder = filePath .foo() .bar; `, - options: [2, { MemberExpression: 2 }], - errors: expectedErrors([[2, 4, 2, "Punctuator"], [3, 4, 6, "Punctuator"]]) - }, - { - code: unIndent` + options: [2, { MemberExpression: 2 }], + errors: expectedErrors([ + [2, 4, 2, "Punctuator"], + [3, 4, 6, "Punctuator"], + ]), + }, + { + code: unIndent` for (const foo of bar) baz(); `, - output: unIndent` + output: unIndent` for (const foo of bar) baz(); `, - options: [2], - errors: expectedErrors([2, 2, 4, "Identifier"]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([2, 2, 4, "Identifier"]), + }, + { + code: unIndent` var x = () => 5; `, - output: unIndent` + output: unIndent` var x = () => 5; `, - options: [2], - errors: expectedErrors([2, 2, 4, "Numeric"]) - }, - { - - // BinaryExpressions with parens - code: unIndent` + options: [2], + errors: expectedErrors([2, 2, 4, "Numeric"]), + }, + { + // BinaryExpressions with parens + code: unIndent` foo && ( bar ) `, - output: unIndent` + output: unIndent` foo && ( bar ) `, - options: [4], - errors: expectedErrors([2, 4, 8, "Identifier"]) - }, - { - code: unIndent` + options: [4], + errors: expectedErrors([2, 4, 8, "Identifier"]), + }, + { + code: unIndent` foo && !bar( ) `, - output: unIndent` + output: unIndent` foo && !bar( ) `, - errors: expectedErrors([3, 4, 0, "Punctuator"]) - }, - { - code: unIndent` + errors: expectedErrors([3, 4, 0, "Punctuator"]), + }, + { + code: unIndent` foo && ![].map(() => { bar(); }) `, - output: unIndent` + output: unIndent` foo && ![].map(() => { bar(); }) `, - errors: expectedErrors([[3, 8, 4, "Identifier"], [4, 4, 0, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [3, 8, 4, "Identifier"], + [4, 4, 0, "Punctuator"], + ]), + }, + { + code: unIndent` [ ] || [ ] `, - output: unIndent` + output: unIndent` [ ] || [ ] `, - errors: expectedErrors([3, 0, 4, "Punctuator"]) - }, - { - code: unIndent` + errors: expectedErrors([3, 0, 4, "Punctuator"]), + }, + { + code: unIndent` foo || ( bar ) `, - output: unIndent` + output: unIndent` foo || ( bar ) `, - errors: expectedErrors([[3, 12, 16, "Identifier"], [4, 8, 12, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [3, 12, 16, "Identifier"], + [4, 8, 12, "Punctuator"], + ]), + }, + { + code: unIndent` 1 + ( 1 ) `, - output: unIndent` + output: unIndent` 1 + ( 1 ) `, - errors: expectedErrors([[3, 4, 8, "Numeric"], [4, 0, 4, "Punctuator"]]) - }, + errors: expectedErrors([ + [3, 4, 8, "Numeric"], + [4, 0, 4, "Punctuator"], + ]), + }, - // Template curlies - { - code: unIndent` + // Template curlies + { + code: unIndent` \`foo\${ bar}\` `, - output: unIndent` + output: unIndent` \`foo\${ bar}\` `, - options: [2], - errors: expectedErrors([2, 2, 0, "Identifier"]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([2, 2, 0, "Identifier"]), + }, + { + code: unIndent` \`foo\${ \`bar\${ baz}\`}\` `, - output: unIndent` + output: unIndent` \`foo\${ \`bar\${ baz}\`}\` `, - options: [2], - errors: expectedErrors([[2, 2, 4, "Template"], [3, 4, 0, "Identifier"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [2, 2, 4, "Template"], + [3, 4, 0, "Identifier"], + ]), + }, + { + code: unIndent` \`foo\${ \`bar\${ baz }\` }\` `, - output: unIndent` + output: unIndent` \`foo\${ \`bar\${ baz }\` }\` `, - options: [2], - errors: expectedErrors([[2, 2, 4, "Template"], [3, 4, 2, "Identifier"], [4, 2, 4, "Template"], [5, 0, 2, "Template"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [2, 2, 4, "Template"], + [3, 4, 2, "Identifier"], + [4, 2, 4, "Template"], + [5, 0, 2, "Template"], + ]), + }, + { + code: unIndent` \`foo\${ ( bar ) }\` `, - output: unIndent` + output: unIndent` \`foo\${ ( bar ) }\` `, - options: [2], - errors: expectedErrors([[2, 2, 0, "Punctuator"], [3, 4, 2, "Identifier"], [4, 2, 0, "Punctuator"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [2, 2, 0, "Punctuator"], + [3, 4, 2, "Identifier"], + [4, 2, 0, "Punctuator"], + ]), + }, + { + code: unIndent` function foo() { \`foo\${bar}baz\${ qux}foo\${ bar}baz\` } `, - output: unIndent` + output: unIndent` function foo() { \`foo\${bar}baz\${ qux}foo\${ bar}baz\` } `, - errors: expectedErrors([[3, 8, 0, "Identifier"], [4, 8, 2, "Identifier"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [3, 8, 0, "Identifier"], + [4, 8, 2, "Identifier"], + ]), + }, + { + code: unIndent` function foo() { const template = \`the indentation of a curly element in a \${ @@ -9603,7 +9932,7 @@ ruleTester.run("indent", rule, { } node is checked.\`; } `, - output: unIndent` + output: unIndent` function foo() { const template = \`the indentation of a curly element in a \${ @@ -9611,10 +9940,13 @@ ruleTester.run("indent", rule, { } node is checked.\`; } `, - errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Template"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [4, 4, 8, "Identifier"], + [5, 0, 4, "Template"], + ]), + }, + { + code: unIndent` function foo() { const template = \`this time the closing curly is at the end of the line \${ @@ -9622,7 +9954,7 @@ ruleTester.run("indent", rule, { so the spaces before this line aren't removed.\`; } `, - output: unIndent` + output: unIndent` function foo() { const template = \`this time the closing curly is at the end of the line \${ @@ -9630,56 +9962,56 @@ ruleTester.run("indent", rule, { so the spaces before this line aren't removed.\`; } `, - errors: expectedErrors([4, 4, 12, "Identifier"]) - }, - { - - /* - * https://github.com/eslint/eslint/issues/1801 - * Note: This issue also mentioned checking the indentation for the 2 below. However, - * this is intentionally ignored because everyone seems to have a different idea of how - * BinaryExpressions should be indented. - */ - code: unIndent` + errors: expectedErrors([4, 4, 12, "Identifier"]), + }, + { + /* + * https://github.com/eslint/eslint/issues/1801 + * Note: This issue also mentioned checking the indentation for the 2 below. However, + * this is intentionally ignored because everyone seems to have a different idea of how + * BinaryExpressions should be indented. + */ + code: unIndent` if (true) { a = ( 1 + 2); } `, - output: unIndent` + output: unIndent` if (true) { a = ( 1 + 2); } `, - errors: expectedErrors([3, 8, 0, "Numeric"]) - }, - { - - // https://github.com/eslint/eslint/issues/3737 - code: unIndent` + errors: expectedErrors([3, 8, 0, "Numeric"]), + }, + { + // https://github.com/eslint/eslint/issues/3737 + code: unIndent` if (true) { for (;;) { b(); } } `, - output: unIndent` + output: unIndent` if (true) { for (;;) { b(); } } `, - options: [2], - errors: expectedErrors([[2, 2, 4, "Keyword"], [3, 4, 6, "Identifier"]]) - }, - { - - // https://github.com/eslint/eslint/issues/6670 - code: unIndent` + options: [2], + errors: expectedErrors([ + [2, 2, 4, "Keyword"], + [3, 4, 6, "Identifier"], + ]), + }, + { + // https://github.com/eslint/eslint/issues/6670 + code: unIndent` function f() { return asyncCall() .then( @@ -9692,7 +10024,7 @@ ruleTester.run("indent", rule, { ); } `, - output: unIndent` + output: unIndent` function f() { return asyncCall() .then( @@ -9705,82 +10037,98 @@ ruleTester.run("indent", rule, { ); } `, - options: [4, { MemberExpression: 1, CallExpression: { arguments: 1 } }], - errors: expectedErrors([ - [3, 8, 4, "Punctuator"], - [4, 12, 15, "String"], - [5, 12, 14, "Punctuator"], - [6, 16, 14, "Numeric"], - [7, 16, 9, "Numeric"], - [8, 16, 35, "Numeric"], - [9, 12, 22, "Punctuator"], - [10, 8, 0, "Punctuator"], - [11, 0, 1, "Punctuator"] - ]) - }, - - // https://github.com/eslint/eslint/issues/7242 - { - code: unIndent` + options: [ + 4, + { MemberExpression: 1, CallExpression: { arguments: 1 } }, + ], + errors: expectedErrors([ + [3, 8, 4, "Punctuator"], + [4, 12, 15, "String"], + [5, 12, 14, "Punctuator"], + [6, 16, 14, "Numeric"], + [7, 16, 9, "Numeric"], + [8, 16, 35, "Numeric"], + [9, 12, 22, "Punctuator"], + [10, 8, 0, "Punctuator"], + [11, 0, 1, "Punctuator"], + ]), + }, + + // https://github.com/eslint/eslint/issues/7242 + { + code: unIndent` var x = [ [1], [2] ] `, - output: unIndent` + output: unIndent` var x = [ [1], [2] ] `, - errors: expectedErrors([[2, 4, 6, "Punctuator"], [3, 4, 2, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [2, 4, 6, "Punctuator"], + [3, 4, 2, "Punctuator"], + ]), + }, + { + code: unIndent` var y = [ {a: 1}, {b: 2} ] `, - output: unIndent` + output: unIndent` var y = [ {a: 1}, {b: 2} ] `, - errors: expectedErrors([[2, 4, 6, "Punctuator"], [3, 4, 2, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [2, 4, 6, "Punctuator"], + [3, 4, 2, "Punctuator"], + ]), + }, + { + code: unIndent` echo = spawn('cmd.exe', ['foo', 'bar', 'baz']); `, - output: unIndent` + output: unIndent` echo = spawn('cmd.exe', ['foo', 'bar', 'baz']); `, - options: [2, { ArrayExpression: "first", CallExpression: { arguments: "first" } }], - errors: expectedErrors([[2, 13, 12, "Punctuator"], [3, 14, 13, "String"]]) - }, - { - - // https://github.com/eslint/eslint/issues/7522 - code: unIndent` + options: [ + 2, + { + ArrayExpression: "first", + CallExpression: { arguments: "first" }, + }, + ], + errors: expectedErrors([ + [2, 13, 12, "Punctuator"], + [3, 14, 13, "String"], + ]), + }, + { + // https://github.com/eslint/eslint/issues/7522 + code: unIndent` foo( ) `, - output: unIndent` + output: unIndent` foo( ) `, - errors: expectedErrors([2, 0, 2, "Punctuator"]) - }, - { - - // https://github.com/eslint/eslint/issues/7616 - code: unIndent` + errors: expectedErrors([2, 0, 2, "Punctuator"]), + }, + { + // https://github.com/eslint/eslint/issues/7616 + code: unIndent` foo( bar, { @@ -9788,7 +10136,7 @@ ruleTester.run("indent", rule, { } ) `, - output: unIndent` + output: unIndent` foo( bar, { @@ -9796,16 +10144,16 @@ ruleTester.run("indent", rule, { } ) `, - options: [4, { CallExpression: { arguments: "first" } }], - errors: expectedErrors([[2, 4, 8, "Identifier"]]) - }, - { - code: " new Foo", - output: "new Foo", - errors: expectedErrors([1, 0, 2, "Keyword"]) - }, - { - code: unIndent` + options: [4, { CallExpression: { arguments: "first" } }], + errors: expectedErrors([[2, 4, 8, "Identifier"]]), + }, + { + code: " new Foo", + output: "new Foo", + errors: expectedErrors([1, 0, 2, "Keyword"]), + }, + { + code: unIndent` var foo = 0, bar = 0, baz = 0; export { foo, @@ -9813,7 +10161,7 @@ ruleTester.run("indent", rule, { baz } `, - output: unIndent` + output: unIndent` var foo = 0, bar = 0, baz = 0; export { foo, @@ -9821,183 +10169,187 @@ ruleTester.run("indent", rule, { baz } `, - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: expectedErrors([[3, 4, 0, "Identifier"], [4, 4, 8, "Identifier"], [5, 4, 2, "Identifier"]]) - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: expectedErrors([ + [3, 4, 0, "Identifier"], + [4, 4, 8, "Identifier"], + [5, 4, 2, "Identifier"], + ]), + }, + { + code: unIndent` foo ? bar : baz `, - output: unIndent` + output: unIndent` foo ? bar : baz `, - options: [4, { flatTernaryExpressions: true }], - errors: expectedErrors([3, 4, 0, "Punctuator"]) - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 0, "Punctuator"]), + }, + { + code: unIndent` foo ? bar : baz `, - output: unIndent` + output: unIndent` foo ? bar : baz `, - options: [4, { flatTernaryExpressions: true }], - errors: expectedErrors([3, 4, 0, "Identifier"]) - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 0, "Identifier"]), + }, + { + code: unIndent` foo ? bar : baz `, - output: unIndent` + output: unIndent` foo ? bar : baz `, - options: [4, { flatTernaryExpressions: true }], - errors: expectedErrors([3, 4, 2, "Punctuator"]) - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 2, "Punctuator"]), + }, + { + code: unIndent` foo ? bar : baz `, - output: unIndent` + output: unIndent` foo ? bar : baz `, - options: [4, { flatTernaryExpressions: true }], - errors: expectedErrors([3, 4, 0, "Identifier"]) - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 0, "Identifier"]), + }, + { + code: unIndent` foo ? bar : baz ? qux : foobar ? boop : beep `, - output: unIndent` + output: unIndent` foo ? bar : baz ? qux : foobar ? boop : beep `, - options: [4, { flatTernaryExpressions: true }], - errors: expectedErrors([ - [3, 4, 8, "Punctuator"], - [4, 4, 12, "Punctuator"] - ]) - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 4, 8, "Punctuator"], + [4, 4, 12, "Punctuator"], + ]), + }, + { + code: unIndent` foo ? bar : baz ? qux : foobar ? boop : beep `, - output: unIndent` + output: unIndent` foo ? bar : baz ? qux : foobar ? boop : beep `, - options: [4, { flatTernaryExpressions: true }], - errors: expectedErrors([ - [3, 4, 8, "Identifier"], - [4, 4, 12, "Identifier"] - ]) - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 4, 8, "Identifier"], + [4, 4, 12, "Identifier"], + ]), + }, + { + code: unIndent` var a = foo ? bar : baz ? qux : foobar ? boop : /*else*/ beep `, - output: unIndent` + output: unIndent` var a = foo ? bar : baz ? qux : foobar ? boop : /*else*/ beep `, - options: [4, { flatTernaryExpressions: true }], - errors: expectedErrors([ - [3, 4, 6, "Identifier"], - [4, 4, 2, "Identifier"] - ]) - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 4, 6, "Identifier"], + [4, 4, 2, "Identifier"], + ]), + }, + { + code: unIndent` var a = foo ? bar : baz `, - output: unIndent` + output: unIndent` var a = foo ? bar : baz `, - options: [4, { flatTernaryExpressions: true }], - errors: expectedErrors([ - [3, 8, 4, "Punctuator"], - [4, 8, 4, "Punctuator"] - ]) - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 8, 4, "Punctuator"], + [4, 8, 4, "Punctuator"], + ]), + }, + { + code: unIndent` foo ? bar : baz ? qux : foobar ? boop : beep `, - output: unIndent` + output: unIndent` foo ? bar : baz ? qux : foobar ? boop : beep `, - options: [4, { flatTernaryExpressions: false }], - errors: expectedErrors([ - [3, 8, 4, "Punctuator"], - [4, 12, 4, "Punctuator"] - ]) - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [3, 8, 4, "Punctuator"], + [4, 12, 4, "Punctuator"], + ]), + }, + { + code: unIndent` foo ? bar : baz ? qux : foobar ? boop : beep `, - output: unIndent` + output: unIndent` foo ? bar : baz ? qux : foobar ? boop : beep `, - options: [4, { flatTernaryExpressions: false }], - errors: expectedErrors([ - [3, 8, 4, "Identifier"], - [4, 12, 4, "Identifier"] - ]) - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [3, 8, 4, "Identifier"], + [4, 12, 4, "Identifier"], + ]), + }, + { + code: unIndent` foo ? bar : baz @@ -10006,7 +10358,7 @@ ruleTester.run("indent", rule, { ? boop : beep `, - output: unIndent` + output: unIndent` foo ? bar : baz @@ -10015,16 +10367,16 @@ ruleTester.run("indent", rule, { ? boop : beep `, - options: [4, { flatTernaryExpressions: false }], - errors: expectedErrors([ - [4, 8, 4, "Punctuator"], - [5, 8, 4, "Punctuator"], - [6, 12, 4, "Punctuator"], - [7, 12, 4, "Punctuator"] - ]) - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [4, 8, 4, "Punctuator"], + [5, 8, 4, "Punctuator"], + [6, 12, 4, "Punctuator"], + [7, 12, 4, "Punctuator"], + ]), + }, + { + code: unIndent` foo ? bar : baz ? @@ -10033,7 +10385,7 @@ ruleTester.run("indent", rule, { boop : beep `, - output: unIndent` + output: unIndent` foo ? bar : baz ? @@ -10042,289 +10394,325 @@ ruleTester.run("indent", rule, { boop : beep `, - options: [4, { flatTernaryExpressions: false }], - errors: expectedErrors([ - [4, 8, 4, "Identifier"], - [5, 8, 4, "Identifier"], - [6, 12, 4, "Identifier"], - [7, 12, 4, "Identifier"] - ]) - }, - { - code: unIndent` + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [4, 8, 4, "Identifier"], + [5, 8, 4, "Identifier"], + [6, 12, 4, "Identifier"], + [7, 12, 4, "Identifier"], + ]), + }, + { + code: unIndent` foo.bar('baz', function(err) { qux; }); `, - output: unIndent` + output: unIndent` foo.bar('baz', function(err) { qux; }); `, - options: [2, { CallExpression: { arguments: "first" } }], - errors: expectedErrors([2, 2, 10, "Identifier"]) - }, - { - code: unIndent` + options: [2, { CallExpression: { arguments: "first" } }], + errors: expectedErrors([2, 2, 10, "Identifier"]), + }, + { + code: unIndent` foo.bar(function() { cookies; }).baz(function() { cookies; }); `, - output: unIndent` + output: unIndent` foo.bar(function() { cookies; }).baz(function() { cookies; }); `, - options: [2, { MemberExpression: 1 }], - errors: expectedErrors([[4, 2, 4, "Identifier"], [5, 0, 2, "Punctuator"]]) - }, - { - code: unIndent` + options: [2, { MemberExpression: 1 }], + errors: expectedErrors([ + [4, 2, 4, "Identifier"], + [5, 0, 2, "Punctuator"], + ]), + }, + { + code: unIndent` foo.bar().baz(function() { cookies; }).qux(function() { cookies; }); `, - output: unIndent` + output: unIndent` foo.bar().baz(function() { cookies; }).qux(function() { cookies; }); `, - options: [2, { MemberExpression: 1 }], - errors: expectedErrors([[4, 2, 4, "Identifier"], [5, 0, 2, "Punctuator"]]) - }, - { - code: unIndent` + options: [2, { MemberExpression: 1 }], + errors: expectedErrors([ + [4, 2, 4, "Identifier"], + [5, 0, 2, "Punctuator"], + ]), + }, + { + code: unIndent` [ foo, bar ].forEach(function() { baz; }) `, - output: unIndent` + output: unIndent` [ foo, bar ].forEach(function() { baz; }) `, - options: [2, { ArrayExpression: "first", MemberExpression: 1 }], - errors: expectedErrors([[3, 2, 4, "Identifier"], [4, 0, 2, "Punctuator"]]) - }, - { - code: unIndent` + options: [2, { ArrayExpression: "first", MemberExpression: 1 }], + errors: expectedErrors([ + [3, 2, 4, "Identifier"], + [4, 0, 2, "Punctuator"], + ]), + }, + { + code: unIndent` foo[ bar ]; `, - output: unIndent` + output: unIndent` foo[ bar ]; `, - options: [4, { MemberExpression: 1 }], - errors: expectedErrors([3, 0, 4, "Punctuator"]) - }, - { - code: unIndent` + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([3, 0, 4, "Punctuator"]), + }, + { + code: unIndent` foo({ bar: 1, baz: 2 }) `, - output: unIndent` + output: unIndent` foo({ bar: 1, baz: 2 }) `, - options: [4, { ObjectExpression: "first" }], - errors: expectedErrors([[2, 4, 0, "Identifier"], [3, 4, 0, "Identifier"]]) - }, - { - code: unIndent` + options: [4, { ObjectExpression: "first" }], + errors: expectedErrors([ + [2, 4, 0, "Identifier"], + [3, 4, 0, "Identifier"], + ]), + }, + { + code: unIndent` foo( bar, baz, qux); `, - output: unIndent` + output: unIndent` foo( bar, baz, qux); `, - options: [2, { CallExpression: { arguments: "first" } }], - errors: expectedErrors([[2, 2, 24, "Identifier"], [3, 2, 24, "Identifier"]]) - }, - { - code: unIndent` + options: [2, { CallExpression: { arguments: "first" } }], + errors: expectedErrors([ + [2, 2, 24, "Identifier"], + [3, 2, 24, "Identifier"], + ]), + }, + { + code: unIndent` if (foo) bar() ; [1, 2, 3].map(baz) `, - output: unIndent` + output: unIndent` if (foo) bar() ; [1, 2, 3].map(baz) `, - errors: expectedErrors([3, 0, 4, "Punctuator"]) - }, - { - code: unIndent` + errors: expectedErrors([3, 0, 4, "Punctuator"]), + }, + { + code: unIndent` if (foo) ; `, - output: unIndent` + output: unIndent` if (foo) ; `, - errors: expectedErrors([2, 4, 0, "Punctuator"]) - }, - { - code: unIndent` + errors: expectedErrors([2, 4, 0, "Punctuator"]), + }, + { + code: unIndent` import {foo} from 'bar'; `, - output: unIndent` + output: unIndent` import {foo} from 'bar'; `, - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: expectedErrors([2, 4, 0, "Identifier"]) - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: expectedErrors([2, 4, 0, "Identifier"]), + }, + { + code: unIndent` export {foo} from 'bar'; `, - output: unIndent` + output: unIndent` export {foo} from 'bar'; `, - languageOptions: { ecmaVersion: 6, sourceType: "module" }, - errors: expectedErrors([2, 4, 0, "Identifier"]) - }, - { - code: unIndent` + languageOptions: { ecmaVersion: 6, sourceType: "module" }, + errors: expectedErrors([2, 4, 0, "Identifier"]), + }, + { + code: unIndent` ( a ) => b => { c } `, - output: unIndent` + output: unIndent` ( a ) => b => { c } `, - errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [4, 4, 8, "Identifier"], + [5, 0, 4, "Punctuator"], + ]), + }, + { + code: unIndent` ( a ) => b => c => d => { e } `, - output: unIndent` + output: unIndent` ( a ) => b => c => d => { e } `, - errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [4, 4, 8, "Identifier"], + [5, 0, 4, "Punctuator"], + ]), + }, + { + code: unIndent` if ( foo ) bar( baz ); `, - output: unIndent` + output: unIndent` if ( foo ) bar( baz ); `, - errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [4, 4, 8, "Identifier"], + [5, 0, 4, "Punctuator"], + ]), + }, + { + code: unIndent` ( foo )( bar ) `, - output: unIndent` + output: unIndent` ( foo )( bar ) `, - errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [4, 4, 8, "Identifier"], + [5, 0, 4, "Punctuator"], + ]), + }, + { + code: unIndent` (() => foo )( bar ) `, - output: unIndent` + output: unIndent` (() => foo )( bar ) `, - errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [4, 4, 8, "Identifier"], + [5, 0, 4, "Punctuator"], + ]), + }, + { + code: unIndent` (() => { foo(); })( bar ) `, - output: unIndent` + output: unIndent` (() => { foo(); })( bar ) `, - errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [4, 4, 8, "Identifier"], + [5, 0, 4, "Punctuator"], + ]), + }, + { + code: unIndent` foo. bar. baz `, - output: unIndent` + output: unIndent` foo. bar. baz `, - errors: expectedErrors([[2, 4, 2, "Identifier"], [3, 4, 6, "Identifier"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [2, 4, 2, "Identifier"], + [3, 4, 6, "Identifier"], + ]), + }, + { + code: unIndent` const foo = a.b(), longName = (baz( @@ -10332,7 +10720,7 @@ ruleTester.run("indent", rule, { 'bar' )); `, - output: unIndent` + output: unIndent` const foo = a.b(), longName = (baz( @@ -10340,10 +10728,14 @@ ruleTester.run("indent", rule, { 'bar' )); `, - errors: expectedErrors([[4, 8, 12, "String"], [5, 8, 12, "String"], [6, 4, 8, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [4, 8, 12, "String"], + [5, 8, 12, "String"], + [6, 4, 8, "Punctuator"], + ]), + }, + { + code: unIndent` const foo = a.b(), longName = (baz( @@ -10351,7 +10743,7 @@ ruleTester.run("indent", rule, { 'bar' )); `, - output: unIndent` + output: unIndent` const foo = a.b(), longName = (baz( @@ -10359,10 +10751,14 @@ ruleTester.run("indent", rule, { 'bar' )); `, - errors: expectedErrors([[4, 8, 12, "String"], [5, 8, 12, "String"], [6, 4, 8, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([ + [4, 8, 12, "String"], + [5, 8, 12, "String"], + [6, 4, 8, "Punctuator"], + ]), + }, + { + code: unIndent` const foo = a.b(), longName =baz( @@ -10370,7 +10766,7 @@ ruleTester.run("indent", rule, { 'bar' ); `, - output: unIndent` + output: unIndent` const foo = a.b(), longName =baz( @@ -10378,32 +10774,32 @@ ruleTester.run("indent", rule, { 'bar' ); `, - errors: expectedErrors([[6, 8, 4, "Punctuator"]]) - }, - { - code: unIndent` + errors: expectedErrors([[6, 8, 4, "Punctuator"]]), + }, + { + code: unIndent` const foo = a.b(), longName =( 'fff' ); `, - output: unIndent` + output: unIndent` const foo = a.b(), longName =( 'fff' ); `, - errors: expectedErrors([[4, 12, 8, "String"]]) - }, + errors: expectedErrors([[4, 12, 8, "String"]]), + }, - //---------------------------------------------------------------------- - // Ignore Unknown Nodes - //---------------------------------------------------------------------- + //---------------------------------------------------------------------- + // Ignore Unknown Nodes + //---------------------------------------------------------------------- - { - code: unIndent` + { + code: unIndent` namespace Foo { const bar = 3, baz = 2; @@ -10413,7 +10809,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` namespace Foo { const bar = 3, baz = 2; @@ -10423,11 +10819,16 @@ ruleTester.run("indent", rule, { } } `, - languageOptions: { parser: require(parser("unknown-nodes/namespace-invalid")) }, - errors: expectedErrors([[3, 8, 4, "Identifier"], [6, 8, 4, "Keyword"]]) - }, - { - code: unIndent` + languageOptions: { + parser: require(parser("unknown-nodes/namespace-invalid")), + }, + errors: expectedErrors([ + [3, 8, 4, "Identifier"], + [6, 8, 4, "Keyword"], + ]), + }, + { + code: unIndent` abstract class Foo { public bar() { let aaa = 4, @@ -10441,7 +10842,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` abstract class Foo { public bar() { let aaa = 4, @@ -10455,11 +10856,17 @@ ruleTester.run("indent", rule, { } } `, - languageOptions: { parser: require(parser("unknown-nodes/abstract-class-invalid")) }, - errors: expectedErrors([[4, 12, 8, "Identifier"], [7, 12, 8, "Identifier"], [10, 8, 4, "Identifier"]]) - }, - { - code: unIndent` + languageOptions: { + parser: require(parser("unknown-nodes/abstract-class-invalid")), + }, + errors: expectedErrors([ + [4, 12, 8, "Identifier"], + [7, 12, 8, "Identifier"], + [10, 8, 4, "Identifier"], + ]), + }, + { + code: unIndent` function foo() { function bar() { abstract class X { @@ -10472,7 +10879,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` function foo() { function bar() { abstract class X { @@ -10485,17 +10892,23 @@ ruleTester.run("indent", rule, { } } `, - languageOptions: { parser: require(parser("unknown-nodes/functions-with-abstract-class-invalid")) }, - errors: expectedErrors([ - [4, 12, 8, "Keyword"], - [5, 16, 8, "Keyword"], - [6, 20, 8, "Identifier"], - [7, 16, 8, "Punctuator"], - [8, 12, 8, "Punctuator"] - ]) - }, - { - code: unIndent` + languageOptions: { + parser: require( + parser( + "unknown-nodes/functions-with-abstract-class-invalid", + ), + ), + }, + errors: expectedErrors([ + [4, 12, 8, "Keyword"], + [5, 16, 8, "Keyword"], + [6, 20, 8, "Identifier"], + [7, 16, 8, "Punctuator"], + [8, 12, 8, "Punctuator"], + ]), + }, + { + code: unIndent` namespace Unknown { function foo() { function bar() { @@ -10510,7 +10923,7 @@ ruleTester.run("indent", rule, { } } `, - output: unIndent` + output: unIndent` namespace Unknown { function foo() { function bar() { @@ -10525,98 +10938,104 @@ ruleTester.run("indent", rule, { } } `, - languageOptions: { parser: require(parser("unknown-nodes/namespace-with-functions-with-abstract-class-invalid")) }, - errors: expectedErrors([ - [3, 8, 4, "Keyword"], - [7, 24, 20, "Identifier"] - ]) - }, - - //---------------------------------------------------------------------- - // JSX tests - // Some of the following tests are adapted from the tests in eslint-plugin-react. - // License: https://github.com/yannickcr/eslint-plugin-react/blob/7ca9841f22d599f447a27ef5b2a97def9229d6c8/LICENSE - //---------------------------------------------------------------------- - - { - code: unIndent` + languageOptions: { + parser: require( + parser( + "unknown-nodes/namespace-with-functions-with-abstract-class-invalid", + ), + ), + }, + errors: expectedErrors([ + [3, 8, 4, "Keyword"], + [7, 24, 20, "Identifier"], + ]), + }, + + //---------------------------------------------------------------------- + // JSX tests + // Some of the following tests are adapted from the tests in eslint-plugin-react. + // License: https://github.com/yannickcr/eslint-plugin-react/blob/7ca9841f22d599f447a27ef5b2a97def9229d6c8/LICENSE + //---------------------------------------------------------------------- + + { + code: unIndent` `, - output: unIndent` + output: unIndent` `, - errors: expectedErrors([2, 4, 2, "Punctuator"]) - }, - { - code: unIndent` + errors: expectedErrors([2, 4, 2, "Punctuator"]), + }, + { + code: unIndent` `, - output: unIndent` + output: unIndent` `, - options: [2], - errors: expectedErrors([2, 2, 4, "Punctuator"]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([2, 2, 4, "Punctuator"]), + }, + { + code: unIndent` `, - output: unIndent` + output: unIndent` \t `, - options: ["tab"], - errors: expectedErrors([2, "1 tab", "4 spaces", "Punctuator"]) - }, - { - code: unIndent` + options: ["tab"], + errors: expectedErrors([2, "1 tab", "4 spaces", "Punctuator"]), + }, + { + code: unIndent` function App() { return ; } `, - output: unIndent` + output: unIndent` function App() { return ; } `, - options: [2], - errors: expectedErrors([4, 2, 9, "Punctuator"]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([4, 2, 9, "Punctuator"]), + }, + { + code: unIndent` function App() { return ( ); } `, - output: unIndent` + output: unIndent` function App() { return ( ); } `, - options: [2], - errors: expectedErrors([4, 2, 4, "Punctuator"]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([4, 2, 4, "Punctuator"]), + }, + { + code: unIndent` function App() { return ( @@ -10625,7 +11044,7 @@ ruleTester.run("indent", rule, { ); } `, - output: unIndent` + output: unIndent` function App() { return ( @@ -10634,24 +11053,28 @@ ruleTester.run("indent", rule, { ); } `, - options: [2], - errors: expectedErrors([[3, 4, 0, "Punctuator"], [4, 6, 2, "Punctuator"], [5, 4, 0, "Punctuator"]]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([ + [3, 4, 0, "Punctuator"], + [4, 6, 2, "Punctuator"], + [5, 4, 0, "Punctuator"], + ]), + }, + { + code: unIndent` {test} `, - output: unIndent` + output: unIndent` {test} `, - errors: expectedErrors([2, 4, 1, "Punctuator"]) - }, - { - code: unIndent` + errors: expectedErrors([2, 4, 1, "Punctuator"]), + }, + { + code: unIndent` {options.map((option, index) => ( `, - output: unIndent` + output: unIndent` {options.map((option, index) => ( `, - errors: expectedErrors([4, 12, 11, "Punctuator"]) - }, - { - code: unIndent` + errors: expectedErrors([4, 12, 11, "Punctuator"]), + }, + { + code: unIndent` [
,
] `, - output: unIndent` + output: unIndent` [
,
] `, - options: [2], - errors: expectedErrors([3, 2, 4, "Punctuator"]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([3, 2, 4, "Punctuator"]), + }, + { + code: unIndent` `, - output: unIndent` + output: unIndent` \t `, - options: ["tab"], - errors: expectedErrors([3, "1 tab", "1 space", "Punctuator"]) - }, - { - - /* - * Multiline ternary - * (colon at the end of the first expression) - */ - code: unIndent` + options: ["tab"], + errors: expectedErrors([3, "1 tab", "1 space", "Punctuator"]), + }, + { + /* + * Multiline ternary + * (colon at the end of the first expression) + */ + code: unIndent` foo ? : `, - output: unIndent` + output: unIndent` foo ? : `, - errors: expectedErrors([3, 4, 0, "Punctuator"]) - }, - { - - /* - * Multiline ternary - * (colon on its own line) - */ - code: unIndent` + errors: expectedErrors([3, 4, 0, "Punctuator"]), + }, + { + /* + * Multiline ternary + * (colon on its own line) + */ + code: unIndent` foo ? : `, - output: unIndent` + output: unIndent` foo ? : `, - errors: expectedErrors([[3, 4, 0, "Punctuator"], [4, 4, 0, "Punctuator"]]) - }, - { - - /* - * Multiline ternary - * (colon at the end of the first expression, parenthesized first expression) - */ - code: unIndent` + errors: expectedErrors([ + [3, 4, 0, "Punctuator"], + [4, 4, 0, "Punctuator"], + ]), + }, + { + /* + * Multiline ternary + * (colon at the end of the first expression, parenthesized first expression) + */ + code: unIndent` foo ? ( ) : `, - output: unIndent` + output: unIndent` foo ? ( ) : `, - errors: expectedErrors([4, 4, 0, "Punctuator"]) - }, - { - code: unIndent` + errors: expectedErrors([4, 4, 0, "Punctuator"]), + }, + { + code: unIndent` `, - output: unIndent` + output: unIndent` `, - errors: expectedErrors([2, 4, 2, "JSXIdentifier"]) - }, - { - code: unIndent` + errors: expectedErrors([2, 4, 2, "JSXIdentifier"]), + }, + { + code: unIndent` `, - output: unIndent` + output: unIndent` `, - options: [2], - errors: expectedErrors([3, 0, 2, "Punctuator"]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([3, 0, 2, "Punctuator"]), + }, + { + code: unIndent` `, - output: unIndent` + output: unIndent` `, - options: [2], - errors: expectedErrors([3, 0, 2, "Punctuator"]) - }, - { - code: unIndent` + options: [2], + errors: expectedErrors([3, 0, 2, "Punctuator"]), + }, + { + code: unIndent` const Button = function(props) { return (